Adobe XD APIで選択しているオブジェクトを操作する

こちらの記事はAdobe XD advent Calendar5日目の穴埋め記事です。穴埋め記事シリーズでは作る上でちょっとした壁になりそうなポイントを思いついた順で解説していこうと思っています。

今回は、英語ドキュメントの[The main.js file](https://adobexdplatform.com/plugin-docs/reference/structure/handlers.html)、および、[selection](https://adobexdplatform.com/plugin-docs/reference/selection.html)を元に説明や使ってみた所見を加えています。

JavaScriptとHTMLの知識があれば作れるXDプラグインですが、それってなぁに？というかたは[公式ブログ](https://blogs.adobe.com/japan/web-the-new-adobe-xd-ecosystem-supercharging-design-workflows-with-third-party-plugins/)をご覧ください。現在はまだできることが限られていますが、かなりのコントロールが自分たちの手で作れるAPIをとおして行えるようになるようです。

## はじめに

対象となる人：

* Hello,Worldも終わったし、これからXDのプラグインを作ってみようという人
* プラグインがどんな仕組みで動いているのか気になる人

### プラグインはどうやって呼び出される？
ちょっとだけXDのプラグインの動きを復習しましょう。XDのプラグインは、メニューのプラグインから選択されて実行されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/10363/89628787-cf58-16ed-5dbb-ec3d46e4cc9e.png)

実行されるのは`manifest.json`に書いている`"commandId": `で指定した関数。実際の振る舞いは、main.jsで記述します。その際にXDから渡される引数の一つが今回のテーマです。

### プラグインがタッチポイントで呼び出されたときに受け取れる引数は2つ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/10363/ea477250-28f6-8d3b-42ba-94d9dcb4b47e.png)

メニューから呼び出されたXDプラグインの関数が必ず受け取るのが2つの引数`selection`と`documentRoot`です。
後者の`documentRoot`は、開いているファイルのルートを示します。例えば、ドキュメント上のすべてのオブジェクトにアクセスするようなプラグインはこちらを使いますが本ブログでは触れません。

では、selectionは何ができてどう使っていくのかを見てみましょう。

### 選択中のオブジェクトを操作しよう

ユーザーが選択しているオブジェクトはすべて`selection.items`にリストで入ってきます。

```JavaScript:選択している数を示すサンプルコード（１）
function getLengthOfSelection(selection) {
 console.log("選択したオブジェクトの数は" + seletion.items.length + "です。");
}
```

選択されてない場合には、`selection.items`は要素数が0の配列となります。リストの順番はShiftキーを押しながらクリックするようにバラバラの選択を行った場合、あとからクリックをした場合にはその順番が反映されて入ってきます。※XDのAPIでリストになっているものは順番が取れるようです。

選択しているオブジェクトの種類は`instanceof`で判定が可能です。

```JavaScript:オブジェクトの種類を判定するサンプルコード
const {Artboard,Text,Rectangle,Ellipse} = require("scenegraph");
function checkSelectionItem(selection) {
  for(let i=0; i<selection.items.length; i++) {
    let item = selection.items[i];
    let itemType = "何かのオブジェクト";
    if (item instanceof Artboard) {
      itemType = "アートボード";
    } else if (item instanceof Text) {
      itemType = "テキスト";
    } else if (item instanceof Rectangle) {
      itemType = "長方形";
    } if (item instanceof Ellipse) {
      itemType = "楕円形";
    } 
    console.log(i + " : " + itemType);
  }
}
```

過去に作った[テキストオブジェクトを選択してそのテキストをクリップボードにコピーするプラグイン](https://medium.com/@risayuguchi/xd-plugin-direct-text-copy-53e1e2295de3)にテキストオブジェクトを判別しているコードがありますので参考にどうぞ！

なお、ロックされたオブジェクトは含まれないので、ロックされたオブジェクトも必要な場合は`selection.itemsIncludingLocked`を使用しましょう。

#### 選択中のオブジェクトを操作するときは存在判定をしっかりする

XDのプラグインの審査はそこまでうるさくありませんが、ガイドラインとしてエラーはユーザーが認識できるように伝えようというものがあります。プラグインの使い方や使用条件は使う側からするとわかりにくいことも多いので、選択してないと機能しないプラグインでは処理を開始する前に必要な選択が行われるか判定して、選択されていなければユーザーに知らせてあげるほうが親切です。

```JavaScript:存在を判定
function mainFunction(selection) {
  if(selection.itemsIncludingLocked.length==0) {
    console.log("何も選択されていません");
    return;
  }
  if(selection.hasArtwork) {
    console.log("描画オブジェクトやグループが選択されています");
    return;
  }
  if(selection.hasArtboard) {
    console.log("アートボードが選択されています");
    return;
  }
}
```

### オブジェクトを選択状態にしよう

selection.itemsは読み書きができるプロパティなので、特定のオブジェクトを選択するようなスクリプトも組めます。

```JavaScript:選択状態にするサンプル
//使うクラスを先に設定
const {Rectangle, Color} = require("scenegraph");
function selectSomeObject(selection) {
  //長方形を作成
  let rect = new Rectangle();
  rect.width = 100;
  rect.height = 30;
  rect.fill = new Color("#333");
  // ドキュメント上に追加
  selection.insertionPoint.addChild(rect);
  rect.placeInParentCoordinates({x:0, y:0});
  //選択する
  selection.items = [rect];
}
```

選択解除なら、`selection.items = [];`とすればよいです。

## まとめ

私達がなにかしたいときはだいたい選択したオブジェクトが存在します。つまり、selectionを操るだけでちょっとした作業効率アップのスクリプトを作ることもできるはずです！
急にUIなどを考えずにまずはUIのないプラグインを作って試してみてください。

### 付録：selection

selectionのプロパティのざっくり説明です。きっちり知りたい方はAPIドキュメント(英語)をご覧ください。

| プロパティ/メソッド       | 読み取り専用か | 説明 |
|:----------------- |:----------------: |:----------------- |
| .items |N|選択したオブジェクトが入る順序を持つ配列|
| .itemsIncludingLocked |N| ロックされたオブジェクトを含んだ選択中のオブジェクトが入る配列 |
| .hasArtwork | Y | true/false。hasArtboardsがtrueのときは必ずfalseになります。これはXDがアートボードとアートワークを同時に選択できない挙動によるものだと思われます。 |
| .hasArtboards | Y | true/false。hasArtworkがtrueのときは必ずfalseが入ります。trueのときitemsにはアートボードが入ります |
| .editContext | Y | 編集状態であれば編集中のオブジェクトが取れます。編集状態でないときはdocumentRoot |
| .insertionParent | Y | 現在の挿入位置のとなるオブジェクト。選択中もしくはフォーカスされているアートボードがあればそのアートボードが入ります |
| .focusedArtboard | Y | フォーカスされたアートボード | 

楽しいXDプラグインライフを！

※穴埋めコンテンツとして、次の題材を候補に上げています。希望がありましたらコメントください。

* AdobeのデザインシステムSpectrumとXDプラグイン
* ダイアログを使った入力制御
* (あとは考え中)