インターフェース
エディタ(Editor.tsx)は、4 つの領域を持つ 1 枚のフルスクリーンシェルです。ヘッダー、左ツールバー、中央のキャンバスビューポート、右サイドバーで構成されます。シェルが保持するのは汎用的な状態だけです。ドキュメント(undo/redo 履歴を含む)、カメラ、現在の選択、そしてどのツールがアクティブか、という状態です。ドメイン固有の要素はすべて、拡張レジストリが公開するデータからレンダリングされます。そのためこのページでは、レジストリが登録済みになった状態での UI の挙動を説明します。
ヘッダー
components/ は最上部のバーです。左から右へ、次の要素が並びます。
アプリ名。
ズーム操作のまとまり。ズームアウト / ズームインボタン(25% 刻み)、現在のズーム率(物理的な mm ではなく「フィット」スケールに対する相対値)、そしてパネル全体が収まるようにカメラを再センタリング・再スケールする Fit ボタン。
undo / redo ボタン。undo/redo できる対象がないときは無効化されます。
キーボードショートカットのダイアログを開く ? ボタン。
現在のドキュメントを発注可能なパネル設定 JSON としてダウンロードする ⬇ JSON ボタン(後述の ダウンロードトリガー を参照)。
左ツールバー
components/ は完全にデータ駆動です。ツールレジストリの各エントリを登録順に 1 つずつボタンとしてレンダリングし、続いて区切り線を挟んで 追加アクションレジストリの各エントリを 1 つずつボタンにします。ツールボタンをクリックすると ctx.setActiveTool(tool.id) が、追加アクションのボタンをクリックすると action.run(ctx) が呼ばれます。どちらのリストも手作業で管理するものではありません。tools/ や add-actions/ に新しいファイルを置くだけで、自動的にここへ現れます。組み込みのツール一式については ツール を参照してください。
キャンバスビューポートとルーラー
components/ は、単一の <canvas> を包む、純粋に表示だけを担うラッパーです。containerRef(ResizeObserver で計測し、リサイズ時にカメラが再フィットできるようにする)を公開し、ポインタイベントを Editor シェルへ転送します。<canvas> 自体には touch-action: none が指定されており、タッチデバイスでドラッグがスクロールやピンチのジェスチャーとして横取りされないようにしています。実際の描画はすべて Editor の再描画エフェクトで行われます。レンダリングとカメラ を参照してください。
ビューポートは、固定された ルーラーフレーム(components/)の内側に収まります。上辺には 20px のミリメートルルーラーの帯、左辺にはそれと対になる帯、そして両者が交わる左上には小さな mm のコーナーボックスが置かれます。これらの帯は、カメラがパンやズームをするたびに目盛りとラベルを描き直しますが、レイアウト上は決して動きません。動くのはキャンバスの内容だけで、帯自体はその下で静止しています。目盛りの計算については レンダリングとカメラ → mm ルーラー を参照してください。
右サイドバー
components/ は、折りたたみ可能なカードパネル を縦に積み重ねたものです。上の 4 つはスクロールする内側の列を共有し(overflow-y-auto overscroll-contain なので、リストが下端まで来ても背後のキャンバスがスクロールすることはありません)、Help パネルはその下に、スクロールしないフッターとして固定されます。上のスタックがあふれても常に見えたままです。
各パネルは CollapsibleSection(components/)です。枠線付きのカードで、そのヘッダーがボタンになっており、本体の開閉を切り替えます(ボタンに aria-expanded、▾/▸ のインジケーター付き)。開閉の状態はセッション単位で、意図的に永続化されません。並べ替えやアニメーションもありません。
Panel — 実在する Eurorack の HP サイズ(
@zpd/coreのPANEL_SIZES)を並べた<select>。各項目は{hp}HP — {widthMm}×{heightMm}mmとラベル付けされます。変更するとドキュメントに新しいpanelHpがコミットされ、カメラが再フィットされます。Palette (fixed) — パネルの 3 色(黒/ソルダーマスク、金/露出した銅、白/シルクスクリーン)を示す読み取り専用の凡例。このリストはユーザーの好みではなく物理的な PCB の仕上げを反映しているため、固定されています。
Layers —
components/。レイヤースタックを視覚的に表示します(リストの一番上 = スタックの最上位 = 配列の最後のインデックス)。各行では、選択(クリック)、名前変更(名前をダブルクリック)、並べ替え(▲/▼)、表示/非表示(👁)、削除(✕)ができます。これらはすべてlayer- list. tsx @zpd/coreの layer-ops を経由するため、順序や不変性のルールが 1 か所にまとまっています。Properties —
components/。選択中のレイヤーの型に登録された インスペクタ をレンダリングします。何も選択されていないときはプレースホルダーを表示します。カードのタイトルには、選択中のレイヤーの型も表示されます(例: Properties — shape)。inspector- host. tsx Help(フッター) —
components/。サイドバーの一番下に固定され、初期状態では折りたたまれています。現在 アクティブな ツールを説明します。ツール名、キーボードショートカットのバッジ、そしてツールのhelp- panel. tsx descriptionテキストです(ツール と 拡張アーキテクチャ を参照)。表示はactiveToolIdに追従するため、Space を押している間の一時的なパンのオーバーライドで内容が変わることはありません。
アプリ全体でのテキスト選択
エディタシェルのルート要素には select-none が指定されているため、クロムやキャンバスのどこをクリック&ドラッグしても、ネイティブのテキスト選択が始まることはありません。これにより、レイヤーの移動・パン・ペンパスの描画といったドラッグのジェスチャーが、操作の途中で UI のラベルを誤ってハイライトしてしまうのを防ぎます。編集用のフィールドは select-text で明示的に選択を有効化しているため(レイヤー名の変更用入力、インスペクタの数値/テキストフィールド、テキストインスペクタの <textarea>)、その中身は通常どおり選択・編集できます。
ダウンロードトリガー
ヘッダーの ⬇ JSON ボタンは download.ts の downloadPanelConfig(ctx.doc) を呼び出します。この関数は次の処理を行います。
@zpd/coreのserializePanelConfig(製造側のリーダーが期待する正規の形)でドキュメントをシリアライズし、JSON.stringifyします。この処理は DOM に依存しない純粋なpanelConfigJson()ヘルパーとして切り出されており、実際のBlobやアンカーなしで出力文字列そのものをユニットテストできます。文字列を
Blobに包んでオブジェクト URL を作成し、一時的な<a download>のクリックによってzpd-panel-{panelHp}hp.jsonという名前でブラウザのダウンロードを発火させます。オブジェクト URL の破棄は即座にではなく、遅延した tick(
setTimeout(..., 0))で行います。クリックと同じ tick で破棄すると、ブラウザによっては blob を読み終える前にダウンロードが中断されることがあるためです。
これはあくまでエディタ側のトリガーです。エクスポートされる JSON の形と、それが下流でどう利用されるかについては、エクスポートのセクションで解説します。
Note
このセクション全体で参照しているドキュメントモデル、undo/redo 履歴、レイヤーの型(DocState、Layer、PATTERN_GENERATORS、serializePanelConfig など)は @zpd/core と @zpd/patterns にあり、ドキュメントモデルのセクションで解説します。