內容選單

內容選單包含使用者可對元件執行的動作清單,例如工作區、區塊或工作區註解。在觸控裝置上按一下滑鼠右鍵或長按時,系統會顯示內容選單。如果您使用 @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 物件還有其他選用屬性,但我們不再建議使用,不過您仍可設定:

  • 只有在顯示選單的元件是 BlockSvg 時,才會設定 block
  • 只有在元件為 WorkspaceSvg 時,才會設定 workspace
  • 只有在元件為 RenderedWorkspaceComment 時,才會設定 comment

這些屬性並未涵蓋所有可能含有內容選單的元件類型,因此建議使用 focusedNode 屬性。

RegistryItem 類型

範本的類型為 ContextMenuRegistry.RegistryItem,包含下列屬性。請注意,preconditionFndisplayTextcallback 屬性與 separator 屬性互斥。

ID

id 屬性應為不重複的字串,指出內容選單項目執行的動作。

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

先決條件函式

您可以使用 preconditionFn 限制內容選單項目的顯示時間和方式。

這個方法應傳回一組字串中的其中一個:'enabled''disabled''hidden'

說明 圖片
'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 屬性是執行內容選單項目動作的函式。系統會傳遞多個參數:

  • scopeScope 物件,提供開啟選單的元件參照。
  • menuOpenEvent:觸發開啟內容選單的 Event。這可能是 PointerEventKeyboardEvent,取決於使用者開啟選單的方式。
  • menuSelectEvent:從選單中選取這個特定內容選單項目的 Event。視使用者選取項目的方式而定,這可能是 PointerEventKeyboardEvent
  • 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 中找到預設範本。

新增範本

註冊範本即可將其新增至登錄檔。您應該在網頁載入時執行一次這項操作。您可以在注入工作區前後執行這項操作。

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.customContextMenuWorkspaceSvg.configureContextMenu 設為會就地修改陣列的函式。

傳遞至區塊的陣列中的物件具有 ContextMenuOption 型別,或實作 LegacyContextMenuOption 介面。傳遞至工作區的物件類型為 ContextMenuOption。Blockly 會使用這些物件的下列屬性:

  • text:顯示文字。
  • enabled:如果為 false,請以灰色文字顯示項目。
  • callback:點選項目時要呼叫的函式。
  • separator:該項目為分隔符。與其他三個屬性互斥。

如要瞭解屬性型別和函式簽章,請參閱參考說明文件。

舉例來說,以下函式會將 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. 實作 IContextMenu,其中包含 showContextMenu 函式。這個函式會從登錄檔取得內容選單項目,計算要在畫面上顯示選單的位置,最後顯示選單 (如有任何項目要顯示)。

    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. 將範本新增至登錄檔,以用於右鍵選單項目。