自分用にCustom8 JSMacro KeyPadを作りました

自分用にCustom8 JSMacro KeyPadというお気楽ツールを作りました

ボタン毎にのJSが登録できてマクロボタン的に実行する画面です。
evalで実行してるだけです。

ElgadoのStream DECKが格好良いので真似したくてしょぼい画面を作りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35574/d540f4c9-2d58-e9a3-0855-b856817bcde9.png)

１．ツールの画面

Stream DECKと全然違いますが…オマージュということで、JSのマクロ登録するとこんな感じです。

![Screenshot 2024-08-30 09.41.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35574/1a3d4bbd-8002-f61e-ecd3-97602788d0a6.png)

２．ソースコード

ソースコードというかcodepenに作ったのでそちらでソースもご確認ください。
https://codepen.io/sf-os/pen/YzoOKed

<p class="codepen" data-height="463.60906982421875" data-default-tab="result" data-slug-hash="YzoOKed" data-pen-title="Custom8 JSMacro KeyPad" data-user="sf-os" style="height: 463.60906982421875px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sf-os/pen/YzoOKed">
  Custom8 JSMacro KeyPad</a> by sf-os (<a href="https://codepen.io/sf-os">@sf-os</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

※少しCSS追加したバージョンもあります。  https://codepen.io/sf-os/pen/VwJVeBj

３．登録できるマクロの例

ボタン番号選んでボタン名とJSを登録するだけです。localStoregeに保存されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35574/32736249-a549-a535-66c9-45acfdfa1bc0.png)

* Hello World

```javascript
alert("hello world");
```

* 台風情報HPを開く

```javascript
var result = window.confirm('気象庁台風情報を開きますか');
if (result) {window.open('https://www.jma.go.jp/bosai/map.html#6/33.978/140.682/&elem=root&typhoon=all&contents=typhoon')};
```

* Yahoo NewsのRSS取得

```javascript
// div要素の作成
const yahooNewsDiv = document.createElement('div');
yahooNewsDiv.id = 'yahoonews';
document.body.appendChild(yahooNewsDiv);

// RSSフィードのURL
const rssUrls = [
  'https://news.yahoo.co.jp/rss/topics/top-picks.xml',
  'https://news.yahoo.co.jp/rss/topics/domestic.xml',
  'https://news.yahoo.co.jp/rss/topics/world.xml'
];

// RSSフィードを取得して表示する関数
async function fetchAndDisplayRss(url) {
  try {
    const response = await fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(url)}`);
    const data = await response.json();
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(data.contents, 'text/xml');
    
    const channel = xmlDoc.querySelector('channel');
    const title = channel.querySelector('title').textContent;
    const items = channel.querySelectorAll('item');
    
    let html = `<h2>${title}</h2><ul>`;
    
    items.forEach((item, index) => {
      if (index < 5) {
        const itemTitle = item.querySelector('title').textContent;
        const itemLink = item.querySelector('link').textContent;
        const itemPubDate = new Date(item.querySelector('pubDate').textContent);
        html += `
          <li>
            <a href="${itemLink}" target="_blank">${itemTitle}</a>
            <br>
            <small>公開日時: ${itemPubDate.toLocaleString('ja-JP')}</small>
          </li>
        `;
      }
    });
    
    html += '</ul>';
    yahooNewsDiv.innerHTML += html;
  } catch (error) {
    console.error('エラーが発生しました:', error);
    yahooNewsDiv.innerHTML += `<p>RSSフィードの取得に失敗しました: ${url}</p>`;
  }
}

// 全てのRSSフィードを取得して表示
async function fetchAllRssFeeds() {
  for (const url of rssUrls) {
    await fetchAndDisplayRss(url);
    // 各リクエスト間に3秒の遅延を追加
    await new Promise(resolve => setTimeout(resolve, 3000));
  }
}

// スクリプトの実行
fetchAllRssFeeds();
```

* シンプルメモ

```javascript
function createMemoModal() {
  // 既存のモーダルがあれば削除
  const existingModal = document.getElementById('memoModal');
  if (existingModal) {
    existingModal.remove();
  }

  // モーダルの作成
  const modal = document.createElement('dialog');
  modal.id = 'memoModal';
  modal.style.backgroundColor = '#888';
  modal.style.padding = '20px';
  modal.style.borderRadius = '5px';

  // タイトルの作成
  const title = document.createElement('h2');
  title.textContent = 'シンプル・メモ';
  title.style.marginTop = '0';
  title.style.marginBottom = '15px';
  title.style.color = 'white';

  // テキストエリアの作成
  const textarea = document.createElement('textarea');
  textarea.id = 'memoText';
  textarea.rows = 10;
  textarea.cols = 50;
  textarea.style.width = '100%';
  textarea.style.marginBottom = '15px';

  // 保存＆終了ボタンの作成
  const saveButton = document.createElement('button');
  saveButton.textContent = '保存＆終了';
  saveButton.style.padding = '5px 10px';
  saveButton.onclick = function() {
    const memoContent = textarea.value;
    localStorage.setItem('memosave20240831-1', memoContent);
    modal.close();
  };

  // モーダルに要素を追加
  modal.appendChild(title);
  modal.appendChild(textarea);
  modal.appendChild(saveButton);

  // bodyにモーダルを追加
  document.body.appendChild(modal);

  // ローカルストレージから値を取得して表示
  const savedMemo = localStorage.getItem('memosave20240831-1');
  if (savedMemo) {
    textarea.value = savedMemo;
  }

  // モーダルを表示
  modal.showModal();
};

createMemoModal();
```

* 時計

```javascript
// div要素を作成
const clockDiv = document.createElement('div');
clockDiv.id = 'clock';
document.body.appendChild(clockDiv);

// 時計を更新する関数
function updateClock() {
    const now = new Date();
    const days = ['日', '月', '火', '水', '木', '金', '土'];
    
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const date = String(now.getDate()).padStart(2, '0');
    const day = days[now.getDay()];
    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    const seconds = String(now.getSeconds()).padStart(2, '0');

    const timeString = `${year}年${month}月${date}日 (${day}) ${hours}:${minutes}:${seconds}`;
    
    document.getElementById('clock').textContent = timeString;
}

// 1秒ごとに時計を更新
setInterval(updateClock, 1000);

// 初回の更新を即時実行
updateClock();

```

* 地図

```javascript
let leafletPromise = null;

function loadLeaflet() {
  if (leafletPromise) {
    return leafletPromise;
  }

  leafletPromise = new Promise((resolve, reject) => {
    if (window.L) {
      resolve(window.L);
      return;
    }

    const linkElement = document.createElement('link');
    linkElement.rel = 'stylesheet';
    linkElement.href = 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.css';
    document.head.appendChild(linkElement);

    const scriptElement = document.createElement('script');
    scriptElement.src = 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.js';
    scriptElement.onload = () => resolve(window.L);
    scriptElement.onerror = () => reject(new Error('Leafletスクリプトの読み込みに失敗しました。'));
    document.body.appendChild(scriptElement);
  });

  return leafletPromise;
}

function createLeafletMap(containerId, lat, lon, zoom) {
  return loadLeaflet().then(L => {
    let container = document.getElementById(containerId);
    if (!container) {
      container = document.createElement('div');
      container.id = containerId;
      container.style.width = '100%';
      container.style.height = '400px';
      document.body.appendChild(container);
    }

    const map = L.map(container).setView([lat, lon], zoom);

    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    L.marker([lat, lon]).addTo(map)
      .bindPopup('ここが指定された位置です')
      .openPopup();

    return map;
  });
}

// 使用例
createLeafletMap('map-container-1', 35.6586, 139.7454, 15)
  .then(map => console.log('地図1が作成されました'));




```


４．おわりに

しょぼいけど、ランチャー的に使えそうなので記事にしました。
CSS得意なひとならもっとかっこよくできそうです。

以上