追加アクションとダイアログ
さらに 2 種類の自己登録型の拡張が、ツールバーを補完します。追加アクション(左ツールバー下部の「Add …」ボタン)と、それら(およびインスペクタ)が開くモーダル表面である ダイアログ です。どちらもそれぞれ packages/ と packages/ から検出されます(拡張アーキテクチャを参照)。
追加アクション
追加アクションは最もシンプルな拡張の種類です。
interface AddAction {
id: string;
label: string;
icon?: string;
run(ctx: ToolContext): void;
}components/ は、登録されたアクションごとに 1 つのボタンを、ツールボタンの下の区切り線の下に、登録順でレンダリングします。ボタンをクリックすると action.run(ctx) を呼ぶだけです。
| 追加アクション | File | 挙動 |
|---|---|---|
| 矩形を追加 | add- | パネルに対して相対的にサイズと位置を決めた新しい shape レイヤー(矩形)をコミットし、選択する。 |
| 楕円を追加 | add- | 上と同じで shape: 'ellipse'。 |
| パターンを追加… | add- | props なしで パターンピッカーダイアログを開く。パターンが選ばれると、ダイアログ自身がレイヤーを追加する。 |
| 画像を追加… | add- | 一時的な <input type="file"> を開き、共有ファイル分類器へ渡す。ラスタ内容はパネルに収めて image としてコミットし、ベクターサイズ上限内の SVG はベクター/マテリアルのインポートダイアログを開く。上限を超える SVG はラスタへフォールバックする。 |
矩形/楕円の追加はどちらも、初期ジオメトリを 0.1mm グリッドにスナップし、デフォルトの幅を min(20mm, panelWidth/2) に制限します。そのため、追加したばかりの形状が、置かれたパネルより大きくなることはありません。
ダイアログ
ダイアログは、登録された { id, component } のペアで、components/ によってレンダリングされます。これは Editor のルートに一度だけマウントされ、開閉を管理する小さな observable ストアを購読します。
interface DialogModule<P = unknown> {
id: string;
component: ComponentType<DialogProps<P>>;
}
interface DialogProps<P = unknown> {
props: P;
close(): void;
ctx: ToolContext;
}ストアは React の外(registry/ の openDialog / closeDialog)にあるため、React コンポーネントではないツールのポインタハンドラを含め、あらゆるものがダイアログを開けます。DialogHost は、あらゆるダイアログのモーダルクロムを一手に担う唯一の存在です。開いているダイアログのコンポーネントを role="dialog" aria-modal="true" のバックドロップ内にレンダリングし、バックドロップ(ただし伝播を止めるダイアログ本体は除く)のクリックに加えて、ページのどこからでも Escape を押すと閉じます。開いている間はフォーカスをダイアログ内に閉じ込め、開いたときは最初のフォーカス可能な要素にフォーカスし(検索ボックスや Cancel ボタンへ自分でフォーカスを移すダイアログコンポーネントには譲ります)、閉じたときはダイアログが開く前にフォーカスされていた要素へフォーカスを戻します。登録されるダイアログのコンポーネントはコンテンツだけを提供し、このクロムをダイアログごとに再実装することはありません。
組み込みダイアログ
| Dialog id | File | 開く場所 |
|---|---|---|
shortcut-panel | dialogs/ | ヘッダーの ? ボタン、または ? キー。 |
command-palette | dialogs/ | ⌘/Ctrl+Shift+K。 |
font-explorer | dialogs/ | テキストインスペクタの Browse Google Fonts… ボタン、またはコマンドパレットの Browse Google Fonts コマンド。 |
pattern-picker | dialogs/ | パターンインスペクタの Browse…、または パターンを追加… の追加アクション。 |
svg-import | dialogs/ | 画像を追加…、ドロップインポート、またはクリップボードペーストで選ばれた、ベクターサイズ上限内の SVG。上限を超える場合はラスタへフォールバックする。 |
trace | dialogs/ | 画像インスペクタの Convert to vector…。 |
confirm-dialog | components/ | 破壊的な操作の前に Confirm/Cancel のゲートが必要なあらゆるコードパス——例えば New panel と JSON インポートは、どちらもドキュメント全体を置き換えます。Wave-5 の拡張ではなくコア基盤で、dialogs/* の自動検出グロブではなく dialog-host.tsx からの直接インポートを通じて自己登録します。 |
ショートカットオーバーレイとコマンドパレット
キーボードショートカットのリファレンスとコマンドパレットは、どちらも同じコマンドレジストリから ライブに 生成されます——古い手作業で維持していた静的なショートカットテーブルはもうありません。? で開くオーバーレイは今では検索可能でカテゴリごとにグループ化され、⌘/Ctrl+Shift+K は、Align や New Panel、Import/Download JSON のような chordless(パレット専用)のコマンドも実行できる、ファジー検索のコマンドパレットを開きます。完全な契約についてはショートカットとコマンドパレットを参照してください。
サイドバーの Help パネルは、このショートカットオーバーレイを置き換えるのではなく 補完 します。Help パネルが説明するのは、いま現在アクティブな 1 つのツール(そのツールごとの description)だけであるのに対し、ショートカットオーバーレイは、アプリのキーボードショートカットを、いつでも検索できるリファレンスとして提供し続けます。ショートカットの一覧が見たいときはオーバーレイを、いま手にしているツールが何をするのか知りたいときは Help フッターに目をやる、という使い分けです。
Font Explorer ダイアログ
1,942 family すべての Google Fonts カタログを、検索・カテゴリフィルタ(フォントの subsets から導出される日本語フィルタを含む)・スター付けできるお気に入りとともに閲覧できます——テキストインスペクタのキュレート済みドロップダウンと並ぶ、フォントピッカーの 2 段目です。フォント → Google Fonts エクスプローラを参照してください。
パターンピッカーダイアログ
パターンのサムネイルをレスポンシブに並べたグリッドで、PATTERN_GENERATORS の各エントリ(パターン → 組み込みカタログを参照)ごとに 1 枚のカードを、renderPatternThumb で描画します。開き方は 2 通りあり、どちらも閉じる前に 1 回の ctx.commit()(1 つの undo エントリ)に解決されます。
{ layerId }を伴う場合(パターンインスペクタから) — カードをクリックすると、その既存のパターンレイヤーのpatternTypeを差し替え、paramsを新しいパターンのデフォルトにリセットします。props なしの場合(パターンを追加… の追加アクションから) — カードをクリックすると、Copper コンテナに真新しいパターンレイヤーを作成して選択します。
各サムネイルは useEffect ではなく useLayoutEffect で描画されるため、ブラウザの最初の描画フレームより前にサイズが決まり描かれます。そうでなければ、<canvas> がデフォルトの 300×150 のボックスを 1 フレームだけちらつかせ、グリッドが目に見えて飛んでしまいます。
カタログが数十パターンまで増えたため、このダイアログはもはやすべてのサムネイルを最初から描画しません。検索ボックス(開いたときに自動フォーカスされる。Font Explorer の検索と同じ慣習)が、各ジェネレータの name(安定した kebab id)または displayName に対する大文字小文字を区別しない部分一致でグリッドを絞り込みます。その下では、カードはページ単位で描画されます。最初は一致したカードのうち先頭約 24 枚だけがマウントされ、末尾のセンチネル要素を監視する 1 個の IntersectionObserver が、ユーザーがグリッドの下端近くまでスクロールすると次のページを読み込みます——これは、カードごとの visibility トリックではなく、Font Explorer がフォントカードに使っているのと同じページング+センチネルのパターンです(CSS の content-visibility では各カードの useLayoutEffect によるキャンバス描画を止められないため、実際には処理を遅延させられません)。検索ボックスに入力すると、絞り込んだ結果の 1 ページ目にページングがリセットされます。
トレースダイアログ
画像からベクターへのワークフローです。それが駆動するパイプライン全体については 画像のトレースを参照してください。概要としては、ソースの画像レイヤーをデコードし、ダウンスケールして SVG にトレースし(オプション変更後 250ms のデバウンス)、その SVG を <img> でプレビューし(決して dangerouslySetInnerHTML は使わないため、悪意あるトレース結果が埋め込みスクリプトを実行することはありません)、Apply で SVG を PathLayer に変換します。ソース画像は隠れたデザイン時の参照として Copper へ再挿入され、各トレースパスはパレット塗りに対応するマテリアルコンテナへ振り分けられ、最初のトレース後レイヤーが選択されます。これらすべてが 1 回の ctx.commit() です。
ダイアログで公開されるトレースオプション: 3-color palette トグル(固定のパネルパレットにそのまま量子化するか、自由な 2〜8 色に量子化するか)、min shape outline(このピクセルしきい値を下回る小さなトレース領域を除外する)、blur radius(トレース前にあらかじめブラーをかけ、ノイズの多いソースラスタを滑らかにする)。
Tip
パターンインスペクタと画像インスペクタはどちらも、対応するダイアログの登録(getDialog('pattern-picker') / getDialog('trace'))を確認し、まだ登録されていなければトリガーボタンを — 説明ツールチップ付きで — 無効化します。これにより、ダイアログより先にインスペクタを出荷しても、ボタンが壊れることはありません。