Контекстные меню

Контекстное меню содержит список действий, которые пользователь может выполнить с компонентом, таким как рабочая область, блок или комментарий к рабочей области. Контекстное меню отображается при щелчке правой кнопкой мыши или длительном нажатии на сенсорном устройстве. При использовании плагина @blockly/keyboard-navigation оно также отображается с помощью сочетания клавиш, которое по умолчанию — Ctrl+Enter в Windows или Command+Enter на Mac.

Контекстное меню по умолчанию для блока

Контекстные меню — хорошее место для добавления действий, которые пользователь выполняет нечасто, например, загрузки скриншота. Если вы считаете, что действие будет использоваться чаще, возможно, стоит создать более заметный способ его вызова.

Контекстные меню поддерживаются рабочими областями, блоками, комментариями к ним, всплывающими подсказками и связями. Вы также можете реализовать их в собственных компонентах . 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 , который содержит следующие свойства. Обратите внимание, что свойства preconditionFn , displayText и callback являются взаимоисключающими со свойством separator .

ИДЕНТИФИКАТОР

Свойство 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 не могут иметь свойства preconditionFn , displayText или callback и могут быть ограничены только свойством 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);

Удалить шаблон

Вы можете удалить шаблон из реестра, отменив его регистрацию по идентификатору.

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 : элемент является разделителем. Взаимоисключающий с тремя другими свойствами.

Типы свойств и сигнатуры функций см. в справочной документации.

Например, вот функция, которая добавляет элемент 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 при щелчке правой кнопкой мыши по компоненту. Обратите внимание, что плагин навигации с помощью клавиатуры предоставляет обработчик событий, который вызывает showContextMenu при нажатии пользователем Ctrl+Enter (Windows) или Command+Enter (Mac).

  4. Добавьте в реестр шаблоны для пунктов контекстного меню.