Menus contextuels

Un menu contextuel contient une liste d'actions qu'un utilisateur peut effectuer sur un composant, tel qu'un espace de travail, un bloc ou un commentaire d'espace de travail. Le menu contextuel s'affiche en réponse à un clic droit ou à un appui prolongé sur un appareil tactile. Si vous utilisez le plug-in @blockly/keyboard-navigation, il est également affiché avec un raccourci clavier, qui est défini par défaut sur Ctrl+Enter sous Windows ou Command+Enter sous Mac.

Menu contextuel par défaut d'un bloc

Les menus contextuels sont un bon endroit pour ajouter des actions que l'utilisateur effectue rarement, comme le téléchargement d'une capture d'écran. Si vous pensez qu'une action sera utilisée plus fréquemment, vous pouvez créer un moyen plus facile à découvrir pour l'invoquer.

Les menus contextuels sont compatibles avec les espaces de travail, les blocs, les commentaires d'espace de travail, les bulles et les connexions. Vous pouvez également les implémenter sur vos propres composants personnalisés. Blockly fournit des menus contextuels standards que vous pouvez personnaliser. Vous pouvez également personnaliser les menus contextuels des espaces de travail et des blocs, individuellement pour chaque espace de travail ou chaque bloc.

Fonctionnement des menus contextuels

Blockly dispose d'un registre contenant des modèles pour tous les éléments de menu possibles. Chaque modèle décrit comment construire un seul élément dans un menu contextuel. Lorsque l'utilisateur appelle un menu contextuel sur un composant, le composant :

  1. Demande au registre de construire un tableau d'éléments de menu qui s'appliquent au composant. Le registre demande à chaque modèle s'il s'applique au composant et, le cas échéant, ajoute un élément de menu correspondant au tableau.

  2. Si le composant est un espace de travail ou un bloc, vérifie si l'espace de travail ou le bloc spécifique sur lequel le menu a été appelé dispose d'une fonction de personnalisation du menu contextuel. Si c'est le cas, il transmet le tableau à la fonction, qui peut ajouter, supprimer ou modifier des éléments du tableau.

  3. Affiche le menu contextuel à l'aide du tableau (éventuellement modifié) des éléments du menu contextuel.

Blockly définit un ensemble standard de modèles pour les menus contextuels des espaces de travail, des blocs et des commentaires d'espace de travail. Il précharge les modèles pour les espaces de travail et les blocs dans le registre. Si vous souhaitez utiliser les modèles pour les commentaires dans l'espace de travail, vous devez les charger vous-même dans le registre.

Pour savoir comment ajouter, supprimer et modifier des modèles dans le registre, consultez Personnaliser le registre.

Champ d'application

Les menus contextuels sont implémentés par différents types de composants, y compris les espaces de travail, les commentaires d'espace de travail, les connexions, les blocs, les bulles et vos propres composants personnalisés. Les menus contextuels de chacun de ces types de composants peuvent contenir différents éléments, et les éléments peuvent se comporter différemment selon le type de composant. Le système de menu contextuel doit donc savoir sur quel composant il a été appelé.

Pour résoudre ce problème, le registre utilise un objet Scope. Le composant sur lequel le menu contextuel a été appelé est stocké dans la propriété focusedNode en tant qu'objet qui implémente IFocusableNode. (IFocusableNode est implémenté par tous les composants sur lesquels les utilisateurs peuvent se concentrer, y compris ceux qui implémentent des menus contextuels. Pour en savoir plus, consultez Système de focus.)

L'objet Scope est transmis à plusieurs fonctions d'un modèle. Dans n'importe quelle fonction qui obtient un objet Scope, vous pouvez décider quoi faire en fonction du type d'objet dans la propriété focusedNode. Par exemple, vous pouvez vérifier si le composant est un bloc avec :

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

L'objet Scope possède d'autres propriétés facultatives dont l'utilisation n'est plus recommandée, mais qui peuvent toujours être définies :

  • block n'est défini que si le composant dont le menu est affiché est un BlockSvg.
  • workspace n'est défini que si le composant est un WorkspaceSvg.
  • comment n'est défini que si le composant est un RenderedWorkspaceComment.

Ces propriétés ne couvrent pas tous les types de composants pouvant comporter un menu contextuel. Il est donc préférable d'utiliser la propriété focusedNode.

Type RegistryItem

Les modèles sont de type ContextMenuRegistry.RegistryItem et contiennent les propriétés suivantes. Notez que les propriétés preconditionFn, displayText et callback s'excluent mutuellement avec la propriété separator.

ID

La propriété id doit être une chaîne unique qui indique ce que fait votre élément de menu contextuel.

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

Fonction de préconditionnement

Vous pouvez utiliser preconditionFn pour limiter le moment et la manière dont un élément de menu contextuel doit être affiché.

Il doit renvoyer l'une des chaînes suivantes : 'enabled', 'disabled' ou 'hidden'.

Valeur Description Image
'enabled' Indique que l'élément est actif. Option activée
'disabled' Indique que l'élément n'est pas actif. Option désactivée
'hidden' Masque l'élément.

preconditionFn reçoit également un Scope que vous pouvez utiliser pour déterminer le type de composant sur lequel le menu a été ouvert et l'état de ce composant.

Par exemple, vous pouvez souhaiter qu'un élément n'apparaisse que pour les blocs et uniquement lorsque ces blocs sont dans un état particulier :

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';
  },
  // ...
}

Texte à afficher

displayText est ce qui doit être affiché à l'utilisateur dans l'élément de menu. Le texte à afficher peut être une chaîne, du code HTML ou une fonction qui renvoie une chaîne ou du code HTML.

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

Si vous souhaitez afficher une traduction à partir de Blockly.Msg, vous devez utiliser une fonction. Si vous essayez d'attribuer la valeur directement, il est possible que les messages ne soient pas chargés et que vous obteniez une valeur de undefined à la place.

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

Si vous utilisez une fonction, une valeur Scope lui est également transmise. Vous pouvez l'utiliser pour ajouter des informations sur l'élément à votre texte à afficher.

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 '';
  },
  // ...
}

Poids

weight détermine l'ordre dans lequel les éléments du menu contextuel sont affichés. Les valeurs les plus positives sont affichées plus bas dans la liste que les valeurs moins positives. (Vous pouvez imaginer que les éléments avec des pondérations plus élevées sont "plus lourds" et qu'ils coulent au fond.)

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

Les pondérations des éléments de menu contextuel intégrés sont classées par ordre croissant, en commençant par 1 et en augmentant de 1.

Fonction de rappel

La propriété callback est une fonction qui exécute l'action de votre élément de menu contextuel. Plusieurs paramètres lui sont transmis :

  • scope : objet Scope qui fournit une référence au composant dont le menu est ouvert.
  • menuOpenEvent : Event qui a déclenché l'ouverture du menu contextuel. Il peut s'agir d'un PointerEvent ou d'un KeyboardEvent, selon la façon dont l'utilisateur a ouvert le menu.
  • menuSelectEvent : Event qui a sélectionné cet élément de menu contextuel spécifique dans le menu. Il peut s'agir d'un PointerEvent ou d'un KeyboardEvent, selon la façon dont l'utilisateur a sélectionné l'élément.
  • location : Coordinate dans les coordonnées en pixels où le menu a été ouvert. Cela vous permet, par exemple, de créer un bloc à l'emplacement du clic.
const collapseTemplate = {
  // ...
  callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      scope.focusedNode.collapse();
    }
  },
}

Vous pouvez utiliser scope pour concevoir des modèles qui fonctionnent différemment selon le composant dans lequel ils ont été ouverts :

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);
    }
  }
}

Séparateur

La propriété separator dessine une ligne dans le menu contextuel.

Les modèles avec la propriété separator ne peuvent pas avoir de propriétés preconditionFn, displayText ou callback, et ne peuvent être définis que par la propriété scopeType. Cette dernière restriction signifie qu'ils ne peuvent être utilisés que dans les menus contextuels des espaces de travail, des blocs et des commentaires d'espace de travail.

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

Vous avez besoin d'un modèle différent pour chaque séparateur de votre menu contextuel. Utilisez la propriété weight pour positionner chaque séparateur.

Type de champ d'application

La propriété scopeType est obsolète. Auparavant, il était utilisé pour déterminer si un élément de menu devait être affiché dans un menu contextuel pour un bloc, un commentaire d'espace de travail ou un espace de travail. Étant donné que les menus contextuels peuvent être ouverts sur d'autres composants, la propriété scopeType est trop restrictive. Vous devez plutôt utiliser preconditionFn pour afficher ou masquer votre option pour les composants correspondants.

Si vous disposez de modèles de menu contextuel existants qui utilisent scopeType, Blockly continuera à afficher l'élément uniquement pour le composant approprié.

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

Personnaliser le registre

Vous pouvez ajouter, supprimer ou modifier des modèles dans le registre. Vous trouverez les modèles par défaut dans contextmenu_items.ts.

Ajouter un modèle

Vous pouvez ajouter un modèle au registre en l'enregistrant. Vous devez le faire une seule fois au chargement de la page. Cela peut se produire avant ou après l'injection de votre espace de travail.

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

Supprimer un modèle

Vous pouvez supprimer un modèle du registre en le désenregistrant par ID.

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

Modifier un modèle

Vous pouvez modifier un modèle existant en l'obtenant à partir du registre, puis en le modifiant sur place.

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

Désactiver les menus contextuels de bloc

Par défaut, les blocs disposent d'un menu contextuel qui permet aux utilisateurs d'ajouter des commentaires ou de dupliquer des blocs, par exemple.

Vous pouvez désactiver le menu contextuel d'un bloc individuel en procédant comme suit :

block.contextMenu = false;

Dans la définition JSON d'un type de bloc, utilisez la clé enableContextMenu :

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

Personnaliser les menus contextuels par type de bloc ou par espace de travail

Une fois que Blockly a généré un tableau d'éléments de menu contextuel, vous pouvez le personnaliser pour des blocs ou des espaces de travail individuels. Pour ce faire, définissez BlockSvg.customContextMenu ou WorkspaceSvg.configureContextMenu sur une fonction qui modifie le tableau sur place.

Les objets du tableau transmis aux blocs sont de type ContextMenuOption ou implémentent l'interface LegacyContextMenuOption. Les objets transmis aux espaces de travail sont de type ContextMenuOption. Blockly utilise les propriétés suivantes de ces objets :

  • text : texte à afficher.
  • enabled : si la valeur est false, affichez l'élément avec du texte gris.
  • callback : fonction à appeler lorsque l'utilisateur clique sur l'élément.
  • separator : l'élément est un séparateur. S'exclut mutuellement avec les trois autres propriétés.

Consultez la documentation de référence pour les types de propriétés et les signatures de fonctions.

Par exemple, voici une fonction qui ajoute un élément Hello, World! au menu contextuel d'un espace de travail :

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);
}

Afficher un menu contextuel sur un objet personnalisé

Pour afficher des menus contextuels pour les composants personnalisés, procédez comme suit :

  1. Implémentez IFocusableNode ou étendez une classe qui implémente IFocusableNode. Cette interface est utilisée dans le système de menu contextuel pour identifier votre composant. Il permet également aux utilisateurs d'accéder à votre composant à l'aide du plug-in de navigation au clavier.
  2. Implémentez IContextMenu, qui contient la fonction showContextMenu. Cette fonction récupère les éléments du menu contextuel à partir du registre, calcule l'emplacement à l'écran où afficher le menu et, enfin, affiche le menu s'il y a des éléments à afficher.

    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. Ajoutez un gestionnaire d'événements qui appelle showContextMenu lorsque l'utilisateur effectue un clic droit sur votre composant. Notez que le plug-in de navigation au clavier fournit un gestionnaire d'événements qui appelle showContextMenu lorsque l'utilisateur appuie sur Ctrl+Enter (Windows) ou Command+Enter (Mac).

  4. Ajoutez des modèles au registre pour les éléments de votre menu contextuel.