🏝️

Slack の絵文字を FigJam のステッカーにする

2022/12/23に公開

こちらはFigma 開発アドベントカレンダー21日目の記事です。

FigJam で会議をしている時
「あ、Slack のあの絵文字押してぇ...!!」ってなることありませんか?私はあります。

というわけで Slack の絵文字を FigJam のステッカーとして読み込むための Widget を作ってみました。

Express でサーバー作る

Figma Widget からでも API リクエストは別に飛ばせるのですが、 Origin が null になってしまうせいか CORS エラーになってしまいました。

Access to fetch at 'https://slack.com/api/emoji.list' from origin 'null' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

とりあえず今回は自分のローカル環境で動けばいいので、express でサーバーを立てて、そこから Slack API を叩き、Figma からはその express サーバからリクエストを送る方式を取ります。

まずはインストール。

npm install express

そして Hello World

const express = require("express");
const app = express();
const port = 3003;

// CORS ノーガード
const allowCrossDomain = function (req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE");
  res.header(
    "Access-Control-Allow-Headers",
    "Content-Type, Authorization, access_token"
  );

  // intercept OPTIONS method
  if ("OPTIONS" === req.method) {
    res.send(200);
  } else {
    next();
  }
};
app.use(allowCrossDomain);

app.get("/", async (req, res) => {
	// ここに Slack API を叩く処理を書いていくぜ。
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

Slack API から絵文字のデータとってくる

次に Slack の API を叩く手筈を整えます。
まずはトークンを手に入れます。

ここから絵文字を抜き出したいワークスペースに対してアプリを作成してなんやかんや頑張ってトークンをゲットしてください。また、 emoji:read の権限も追加してください。
https://api.slack.com/apps

次に Slack API クライアントをインストールします。

npm install @slack/web-api

そしてこれを使うとこんな感じで簡単にゲットできます。

const { WebClient } = require("@slack/web-api");
const token = "xoxb-で始まるトークン";

app.get("/", async (req, res) => {
  const client = new WebClient(token);
  const response = await client.emoji.list();
  
  // 中身は {[key as 絵文字の名前]: "絵文字のソース"} というフォーマットです。
  res.send(response.emoji);
});

Widget を作る

いよいよ Figma 側です。
本当は API が楽だったのですが、Figma API は Write 系の処理ができないっぽいので Widget を作ります。

まずは npm init @figma/widget してプロジェクトを作ります。

https://github.com/figma/create-widget

そして manifest ファイルを読み込み npm run dev でビルドするとこのような無機質な Widget が登場します。

そして code.tsx を開いて下記のような感じで書き換えます。
内容としては先ほど作った API からデータを取ってきて、ひたすらその画像をコンポーネントにしています。

const { widget } = figma;
const {
  AutoLayout,
  Ellipse,
  Frame,
  Image,
  Rectangle,
  SVG,
  Text,
  useSyncedState,
} = widget;

function Widget() {
  const createImage = async (
    emojiName: string,
    src: string,
    rowIndex: number,
    colIndex: number
  ) => {
    const imageNode = await figma.createNodeFromJSXAsync(
      <Image src={src} width={32} height={32} />
    );
    const component = figma.createComponent();
    component.name = emojiName;
    component.appendChild(imageNode);

    component.x = 32 * colIndex;
    component.y = 32 * rowIndex;
  };

  const onClick = () => {
    fetch("http://localhost:3003", {
      method: "GET",
    })
      .then((response) => response.text())
      .then((text) => {
        const emojis = JSON.parse(text);
        const emojiKeys = Object.keys(emojis);
        const chunkSize = 10;
        const chunks = [];
        const intervalMsec = 2000;

        for (let i = 0; i < emojiKeys.length; i += chunkSize) {
          chunks.push(
            emojiKeys
              .slice(i, i + chunkSize)
              .map((key) => ({ key, src: emojis[key] }))
          );
        }

        chunks.forEach((chunk, rowIndex) => {
          setTimeout(() => {
            chunk.forEach((emoji, colIndex) => {
              createImage(emoji.key, emoji.src, rowIndex, colIndex);
            });
          }, intervalMsec * rowIndex);
        });
      })
      .catch((e) => {
        console.log(e);
      });
  };

  return (
    <AutoLayout
      direction="horizontal"
      horizontalAlignItems="center"
      verticalAlignItems="center"
      height="hug-contents"
      padding={8}
      fill="#FFFFFF"
      cornerRadius={8}
      spacing={12}
      onClick={onClick}
    >
      <Text fontSize={32} horizontalAlignText="center">
        Click Me
      </Text>
    </AutoLayout>
  );
}
widget.register(Widget);

ポイントは chunk に分けて、インターバルを設けて実行しているところです。
なぜそうしているかというと、スタンプが多いと処理がとんでもなく重くなり、Figma をクラッシュさせてしまうからです。時間はかかってしまいますが負荷をかけないようにしていきましょう。

https://twitter.com/sekikazu01/status/1605933624255287301?s=20&t=m0V72mv_0MtVlrN-hueXpg

これが無事終了すると絵文字の山ができあがります。
ちなみにスタンプの数が多いとめっちゃファイル重くなります。当然ですね。

https://twitter.com/sekikazu01/status/1605945209665179649?s=20&t=OvPPD22D8NW4iLY_5V_tUw

ライブラリとして Publish して FigJam から使えるようにする

できたらライブラリとして Publish します!!

そして FigJam の Stickers の Add your own からそのライブラリを作れば準備万端です!!

ちなみにスタンプが多いと初期のロードはエグいもっさりしますが、その後は簡単に検索できてる気がします。

ぜひお気に入りの Slack スタンプたちを FigJam に持ち込んで会議を盛り上げてください!

Discussion