コンテキスト メニュー

コンテキスト メニューには、ワークスペース、ブロック、ワークスペースのコメントなどのコンポーネントに対してユーザーが実行できるアクションのリストが含まれています。コンテキスト メニューは、右クリックまたはタッチデバイスでの長押しに応じて表示されます。@blockly/keyboard-navigation プラグインを使用している場合は、キーボード ショートカットも表示されます。デフォルトでは、Windows の場合は Ctrl+Enter、Mac の場合は Command+Enter です。

ブロックのデフォルトのコンテキスト メニュー

コンテキスト メニューは、スクリーンショットのダウンロードなど、ユーザーが頻繁に行わない操作を追加するのに適しています。アクションがより一般的に使用されると思われる場合は、アクションを呼び出すためのより見つけやすい方法を作成することをおすすめします。

コンテキスト メニューは、ワークスペース、ブロック、ワークスペースのコメント、バブル、接続でサポートされています。独自のカスタム コンポーネントに実装することもできます。Blockly には、カスタマイズ可能な標準のコンテキスト メニューが用意されています。ワークスペースとブロックのコンテキスト メニューは、ワークスペース単位またはブロック単位でカスタマイズすることもできます。

コンテキスト メニューの仕組み

Blockly には、考えられるすべてのメニュー項目のテンプレートを含むレジストリがあります。各テンプレートは、コンテキスト メニューの単一のアイテムを構築する方法を記述します。ユーザーがコンポーネントのコンテキスト メニューを呼び出すと、コンポーネントは次のようになります。

  1. コンポーネントに適用されるメニュー項目の配列を構築するようレジストリに要求します。レジストリは各テンプレートにコンポーネントに適用されるかどうかを問い合わせ、適用される場合は対応するメニュー項目を配列に追加します。

  2. コンポーネントがワークスペースまたはブロックの場合、メニューが呼び出された特定のワークスペースまたはブロックにコンテキスト メニューをカスタマイズする関数があるかどうかを確認します。その場合、配列は関数に渡され、関数の要素を追加、削除、変更できます。

  3. (変更された可能性のある)コンテキスト メニュー項目の配列を使用してコンテキスト メニューを表示します。

Blockly は、ワークスペース、ブロック、ワークスペース コメントのコンテキスト メニューの標準テンプレート セットを定義します。ワークスペースとブロックのテンプレートをレジストリにプリロードします。ワークスペース コメントのテンプレートを使用する場合は、自分でレジストリに読み込む必要があります。

レジストリでテンプレートを追加、削除、変更する方法については、レジストリをカスタマイズするをご覧ください。

範囲

コンテキスト メニューは、ワークスペース、ワークスペースのコメント、接続、ブロック、バブル、独自のカスタム コンポーネントなど、さまざまなタイプのコンポーネントによって実装されます。これらのコンポーネント タイプのコンテキスト メニューには、異なる項目が含まれる場合があります。また、コンポーネント タイプによって項目の動作が異なる場合があります。そのため、コンテキスト メニュー システムは、どのコンポーネントで呼び出されたかを知る必要があります。

この問題を解決するために、レジストリは Scope オブジェクトを使用します。コンテキスト メニューが呼び出されたコンポーネントは、focusedNode プロパティに IFocusableNode を実装するオブジェクトとして格納されます。(IFocusableNode は、コンテキスト メニューを実装するコンポーネントを含め、ユーザーがフォーカスできるすべてのコンポーネントによって実装されます。詳しくは、フォーカス システムをご覧ください)。

Scope オブジェクトは、テンプレート内の複数の関数に渡されます。Scope オブジェクトを取得する関数では、focusedNode プロパティのオブジェクトの型に基づいて処理を決定できます。たとえば、コンポーネントがブロックかどうかを確認するには、次のコマンドを実行します。

if (scope.focusedNode instanceof Blockly.BlockSvg) {
  // do something with the block
}

Scope オブジェクトには、使用が推奨されなくなったものの、設定は可能な他の省略可能なプロパティがあります。

  • block は、メニューが表示されるコンポーネントが BlockSvg の場合にのみ設定されます。
  • workspace は、コンポーネントが WorkspaceSvg の場合にのみ設定されます。
  • comment は、コンポーネントが RenderedWorkspaceComment の場合にのみ設定されます。

これらのプロパティは、コンテキスト メニューを持つ可能性のあるすべてのタイプのコンポーネントを網羅しているわけではないため、focusedNode プロパティを使用することをおすすめします。

RegistryItem 型

テンプレートの型は ContextMenuRegistry.RegistryItem で、次のプロパティが含まれています。preconditionFndisplayTextcallback の各プロパティは、separator プロパティと相互に排他的です。

ID

id プロパティは、コンテキスト メニュー項目の機能を説明する一意の文字列である必要があります。

const collapseTemplate = {
  id: 'collapseBlock',
  // ...
};

前提条件関数

preconditionFn を使用して、コンテキスト メニュー項目を表示するタイミングと方法を制限できます。

文字列のセット('enabled''disabled''hidden')のいずれかを返す必要があります。

説明 画像
有効 アイテムがアクティブであることを示します。 有効なオプション
無効 アイテムがアクティブでないことを示します。 無効なオプション
非表示 アイテムを非表示にします。

preconditionFn には Scope も渡されます。これを使用して、メニューが開かれたコンポーネントのタイプと、そのコンポーネントの状態を特定できます。

たとえば、アイテムがブロックに対してのみ表示され、それらのブロックが特定の状態にある場合にのみ表示されるようにしたい場合があります。

const collapseTemplate = {
  // ...
  preconditionFn: (scope) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      if (!scope.focusedNode.isCollapsed()) {
        // The component is a block and it is not already collapsed
        return 'enabled';
      } else {
        // The block is already collapsed
        return 'disabled';
      }
    }
    // The component is not a block
    return 'hidden';
  },
  // ...
}

表示テキスト

displayText は、メニュー アイテムの一部としてユーザーに表示されるものです。表示テキストには、文字列、HTML、または文字列または HTML を返す関数を指定できます。

const collapseTemplate = {
  // ...
  displayText: 'Collapse block',
  // ...
};

Blockly.Msg からの翻訳を表示する場合は、関数を使用する必要があります。値を直接割り当てようとすると、メッセージが読み込まれず、代わりに undefined の値が返されることがあります。

const collapseTemplate = {
  // ...
  displayText: () => Blockly.Msg['MY_COLLAPSE_BLOCK_TEXT'],
  // ...
};

関数を使用する場合は、Scope 値も渡されます。これを使用して、要素に関する情報を表示テキストに追加できます。

const collapseTemplate = {
  // ...
  displayText: (scope) => {
    if (scope.focusedNode instanceof Blockly.Block) {
      return `Collapse ${scope.focusedNode.type} block`;
    }
    // Shouldn't be possible, as our preconditionFn only shows this item for blocks
    return '';
  },
  // ...
}

重量

weight は、コンテキスト メニュー項目が表示される順序を決定します。値が正であるほど、リストの下に表示されます。(重みの高いアイテムは「重い」ため、下部に沈むと考えることができます)。

const collapseTemplate = {
  // ...
  weight: 10,
  // ...
}

組み込みのコンテキスト メニュー項目の重みは、1 から始まり 1 ずつ増加する昇順で指定します。

コールバック関数

callback プロパティは、コンテキスト メニュー項目のアクションを実行する関数です。この関数には、いくつかのパラメータが渡されます。

  • scope: メニューが開いているコンポーネントへの参照を提供する Scope オブジェクト。
  • menuOpenEvent: コンテキスト メニューを開くトリガーとなった Event。これは、ユーザーがメニューを開いた方法に応じて PointerEvent または KeyboardEvent になることがあります。
  • menuSelectEvent: メニューからこの特定のコンテキスト メニュー項目を選択した Event。ユーザーが項目を選択した方法に応じて、PointerEvent または KeyboardEvent になります。
  • location: メニューが開かれたピクセル座標Coordinate。たとえば、クリックした場所に新しいブロックを作成できます。
const collapseTemplate = {
  // ...
  callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      scope.focusedNode.collapse();
    }
  },
}

scope を使用すると、開かれたコンポーネントに応じて動作が異なるテンプレートを設計できます。

const collapseTemplate = {
  // ...
  callback: (scope) => {
    if (scope.focusedNode instance of Blockly.BlockSvg) {
      // On a block, collapse just the block.
      const block = scope.focusedNode;
      block.collapse();
    } else if (scope.focusedNode instanceof Blockly.WorkspaceSvg) {
      // On a workspace, collapse all the blocks.
      let workspace = scope.focusedNode;
      collapseAllBlocks(workspace);
    }
  }
}

区切り文字

separator プロパティは、コンテキスト メニューに線を描画します。

separator プロパティを持つテンプレートには、preconditionFndisplayTextcallback プロパティを含めることはできません。また、scopeType プロパティでのみスコープ設定できます。後者の制限により、ワークスペース、ブロック、ワークスペースのコメントのコンテキスト メニューでのみ使用できます。

const separatorAfterCollapseBlockTemplate = {
  id: 'separatorAfterCollapseBlock',
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
  weight: 11, // Between the weights of the two items you want to separate.
  separator: true,
};

コンテキスト メニューの区切り文字ごとに異なるテンプレートが必要です。weight プロパティを使用して、各セパレータを配置します。

スコープのタイプ

scopeType プロパティは非推奨となっています。以前は、ブロック、ワークスペース コメント、ワークスペースのコンテキスト メニューにメニュー項目を表示するかどうかを判断するために使用されていました。コンテキスト メニューは他のコンポーネントでも開くことができるため、scopeType プロパティは制限が厳しすぎます。代わりに、preconditionFn を使用して、対応するコンポーネントのオプションを表示または非表示にする必要があります。

scopeType を使用する既存のコンテキスト メニュー テンプレートがある場合、Blockly は引き続き適切なコンポーネントに対してのみアイテムを表示します。

const collapseTemplate = {
  // ...
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
  // ...
};

レジストリをカスタマイズする

レジストリでテンプレートを追加、削除、変更できます。デフォルトのテンプレートは contextmenu_items.ts にあります。

テンプレートを追加する

テンプレートを登録することで、レジストリにテンプレートを追加できます。これは、ページの読み込み時に 1 回行う必要があります。このエラーは、ワークスペースを挿入する前または後に発生する可能性があります。

const collapseTemplate = { /* properties from above */ };
Blockly.ContextMenuRegistry.registry.register(collapseTemplate);

テンプレートを削除する

テンプレートをレジストリから削除するには、ID で登録解除します。

Blockly.ContextMenuRegistry.registry.unregister('someID');

テンプレートを変更する

既存のテンプレートを変更するには、レジストリからテンプレートを取得し、その場で変更します。

const template = Blockly.ContextMenuRegistry.registry.getItem('someID');
template?.displayText = 'some other display text';

ブロックのコンテキスト メニューを無効にする

デフォルトでは、ブロックにはコンテキスト メニューがあり、ユーザーはブロック コメントの追加やブロックの複製などの操作を行うことができます。

個々のブロックのコンテキスト メニューを無効にするには、次の操作を行います。

block.contextMenu = false;

ブロックタイプの JSON 定義で、enableContextMenu キーを使用します。

{
  // ...,
  "enableContextMenu": false,
}

ブロックタイプまたはワークスペースごとにコンテキスト メニューをカスタマイズする

Blockly でコンテキスト メニュー項目の配列が生成された後、個々のブロックまたはワークスペースに合わせてカスタマイズできます。これを行うには、BlockSvg.customContextMenu または WorkspaceSvg.configureContextMenu を、配列をその場で変更する関数に設定します。

ブロックに渡される配列内のオブジェクトは、ContextMenuOption 型であるか、LegacyContextMenuOption インターフェースを実装します。ワークスペースに渡されるオブジェクトの型は ContextMenuOption です。Blockly は、これらのオブジェクトから次のプロパティを使用します。

  • text: 表示テキスト。
  • enabled: false の場合、アイテムをグレーのテキストで表示します。
  • callback: アイテムがクリックされたときに呼び出される関数。
  • separator: このアイテムは区切り文字です。他の 3 つのプロパティとは相互に排他的です。

プロパティの型と関数シグネチャについては、リファレンス ドキュメントをご覧ください。

たとえば、ワークスペースのコンテキスト メニューに Hello, World! 項目を追加する関数は次のようになります。

workspace.configureContextMenu = function (menuOptions, e) {
  const item = {
    text: 'Hello, World!',
    enabled: true,
    callback: function () {
      alert('Hello, World!');
    },
  };
  // Add the item to the end of the context menu.
  menuOptions.push(item);
}

カスタム オブジェクトのコンテキスト メニューを表示する

カスタム コンポーネントのコンテキスト メニューを表示するには、次の手順を行います。

  1. IFocusableNode を実装するか、IFocusableNode を実装するクラスを拡張します。このインターフェースは、コンテキスト メニュー システムでコンポーネントを識別するために使用されます。また、ユーザーがキーボード ナビゲーション プラグインを使用してコンポーネントに移動することもできます。
  2. showContextMenu 関数を含む IContextMenu を実装します。この関数は、レジストリからコンテキスト メニュー項目を取得し、メニューを表示する画面上の位置を計算して、表示する項目がある場合はメニューを表示します。

    const MyBubble implements IFocusableNode, IContextMenu {
      ...
      showContextMenu(menuOpenEvent) {
        // Get the items from the context menu registry
        const scope = {focusedNode: this};
        const items = Blockly.ContextMenuRegistry.registry.getContextMenuOptions(scope, menuOpenEvent);
    
        // Return early if there are no items available
        if (!items.length) return;
    
        // Show the menu at the same location on screen as this component
        // The location is in pixel coordinates, so translate from workspace coordinates
        const location = Blockly.utils.svgMath.wsToScreenCoordinates(new Coordinate(this.x, this.y));
    
        // Show the context menu
        Blockly.ContextMenu.show(menuOpenEvent, items, this.workspace.RTL, this.workspace, location);
      }
    }
    
  3. ユーザーがコンポーネントを右クリックしたときに showContextMenu を呼び出すイベント ハンドラを追加します。キーボード ナビゲーション プラグインは、ユーザーが Ctrl+Enter(Windows)または Command+Enter(Mac)を押したときに showContextMenu を呼び出すイベント ハンドラを提供します。

  4. コンテキスト メニュー項目のレジストリにテンプレートを追加します。