Menu contestuali

Un menu contestuale contiene un elenco di azioni che un utente può eseguire su un componente come uno spazio di lavoro, un blocco o un commento dello spazio di lavoro. Il menu contestuale viene visualizzato in risposta a un clic con il tasto destro del mouse o a una pressione prolungata su un dispositivo touch. Se utilizzi il plug-in @blockly/keyboard-navigation, viene visualizzato anche con una scorciatoia da tastiera, che per impostazione predefinita è Ctrl+Enter su Windows o Command+Enter su Mac.

Il menu contestuale predefinito per un blocco

I menu contestuali sono un buon posto per aggiungere azioni che l'utente esegue di rado, ad esempio il download di uno screenshot. Se ritieni che un'azione verrà utilizzata più spesso, potresti creare un modo più semplice per invocarla.

I menu contestuali sono supportati da spazi di lavoro, blocchi, commenti dello spazio di lavoro, bolle e connessioni. Puoi anche implementarli nei tuoi componenti personalizzati. Blockly fornisce menu contestuali standard che puoi personalizzare. Puoi anche personalizzare i menu contestuali di spazi di lavoro e blocchi in base allo spazio di lavoro o al blocco.

Come funzionano i menu contestuali

Blockly ha un registro che contiene modelli per tutte le possibili voci di menu. Ogni modello descrive come creare un singolo elemento in un menu contestuale. Quando l'utente richiama un menu contestuale su un componente, quest'ultimo:

  1. Chiede al registro di creare un array di voci di menu che si applicano al componente. Il registro chiede a ogni modello se si applica al componente e, in caso affermativo, aggiunge una voce di menu corrispondente all'array.

  2. Se il componente è uno spazio di lavoro o un blocco, verifica se lo spazio di lavoro o il blocco specifico su cui è stato richiamato il menu ha una funzione per personalizzare il menu contestuale. In questo caso, passa l'array alla funzione, che può aggiungere, eliminare o modificare gli elementi dell'array.

  3. Visualizza il menu contestuale utilizzando l'array (eventualmente modificato) di voci del menu contestuale.

Blockly definisce un insieme standard di modelli per i menu contestuali per gli spazi di lavoro, i blocchi e i commenti dello spazio di lavoro. Precarica i modelli per gli spazi di lavoro e i blocchi nel registro. Se vuoi utilizzare i modelli per i commenti di Workspace, devi caricarli nel registro personalmente.

Per informazioni su come aggiungere, eliminare e modificare i modelli nel registro, vedi Personalizzare il registro.

Ambito

I menu contestuali vengono implementati da diversi tipi di componenti, tra cui spazi di lavoro, commenti dello spazio di lavoro, connessioni, blocchi, bolle e i tuoi componenti personalizzati. I menu contestuali per ciascuno di questi tipi di componenti possono contenere elementi diversi e gli elementi possono comportarsi in modo diverso in base al tipo di componente. Pertanto, il sistema di menu contestuale deve sapere su quale componente è stato richiamato.

Per risolvere questo problema, il registro utilizza un oggetto Scope. Il componente su cui è stato richiamato il menu contestuale viene memorizzato nella proprietà focusedNode come oggetto che implementa IFocusableNode. (IFocusableNode è implementato da tutti i componenti su cui gli utenti possono concentrarsi, inclusi quelli che implementano i menu contestuali. Per ulteriori informazioni, consulta la sezione Sistema di messa a fuoco.)

L'oggetto Scope viene passato a diverse funzioni in un modello. In qualsiasi funzione che riceve un oggetto Scope, puoi decidere cosa fare in base al tipo di oggetto nella proprietà focusedNode. Ad esempio, puoi verificare se il componente è un blocco con:

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

L'oggetto Scope ha altre proprietà facoltative il cui utilizzo non è più consigliato, ma che possono comunque essere impostate:

  • block viene impostato solo se il componente il cui menu viene visualizzato è un BlockSvg.
  • workspace è impostato solo se il componente è un WorkspaceSvg.
  • comment è impostato solo se il componente è un RenderedWorkspaceComment.

Queste proprietà non coprono tutti i tipi di componenti che potrebbero avere un menu contestuale, quindi è preferibile utilizzare la proprietà focusedNode.

Tipo RegistryItem

I modelli hanno il tipo ContextMenuRegistry.RegistryItem, che contiene le seguenti proprietà. Tieni presente che le proprietà preconditionFn, displayText e callback si escludono a vicenda con la proprietà separator.

ID

La proprietà id deve essere una stringa univoca che indica la funzione della voce del menu contestuale.

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

Funzione di precondizionamento

Puoi utilizzare preconditionFn per limitare quando e come deve essere visualizzata una voce del menu contestuale.

Deve restituire una delle seguenti stringhe: 'enabled', 'disabled' o 'hidden'.

Valore Descrizione Immagine
'enabled' Indica che l'elemento è attivo. Un'opzione attivata
'disabled' Indica che l'elemento non è attivo. Un'opzione disattivata
'hidden' Nasconde l'elemento.

A preconditionFn viene passato anche un Scope che puoi utilizzare per determinare il tipo di componente su cui è stato aperto il menu e lo stato di quel componente.

Ad esempio, potresti voler visualizzare un elemento solo per i blocchi e solo quando questi blocchi si trovano in un determinato stato:

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

Testo visualizzato

Il displayText è ciò che deve essere mostrato all'utente come parte della voce di menu. Il testo visualizzato può essere una stringa, un codice HTML o una funzione che restituisce una stringa o un codice HTML.

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

Se vuoi visualizzare una traduzione da Blockly.Msg, devi utilizzare una funzione. Se provi ad assegnare il valore direttamente, i messaggi potrebbero non essere caricati e riceverai invece il valore undefined.

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

Se utilizzi una funzione, viene passato anche un valore Scope. Puoi utilizzare questo attributo per aggiungere informazioni sull'elemento al testo visualizzato.

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

Peso

weight determina l'ordine di visualizzazione delle voci del menu contestuale. I valori più positivi vengono visualizzati più in basso nell'elenco rispetto a quelli meno positivi. Puoi immaginare che gli elementi con pesi più elevati siano "più pesanti" e quindi affondino sul fondo.

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

I pesi per le voci del menu contestuale integrato sono in ordine crescente a partire da 1 e aumentano di 1.

Funzione di callback

La proprietà callback è una funzione che esegue l'azione della voce di menu contestuale. Vengono passati diversi parametri:

  • scope: un oggetto Scope che fornisce un riferimento al componente il cui menu è aperto.
  • menuOpenEvent: il Event che ha attivato l'apertura del menu contestuale. Potrebbe essere un PointerEvent o un KeyboardEvent, a seconda di come l'utente ha aperto il menu.
  • menuSelectEvent: il Event che ha selezionato questa particolare voce del menu contestuale dal menu. Potrebbe trattarsi di un PointerEvent o di un KeyboardEvent, a seconda di come l'utente ha selezionato l'elemento.
  • location: Le Coordinate in coordinate pixel in cui è stato aperto il menu. In questo modo, ad esempio, puoi creare un nuovo blocco nella posizione del clic.
const collapseTemplate = {
  // ...
  callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      scope.focusedNode.collapse();
    }
  },
}

Puoi utilizzare scope per progettare modelli che funzionano in modo diverso a seconda del componente su cui sono stati aperti:

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

Separatore

La proprietà separator traccia una linea nel menu contestuale.

I modelli con la proprietà separator non possono avere le proprietà preconditionFn, displayText o callback e possono essere limitati solo con la proprietà scopeType. Quest'ultima limitazione significa che possono essere utilizzati solo nei menu contestuali per spazi di lavoro, blocchi e commenti dello spazio di lavoro.

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

Per ogni separatore nel menu contestuale è necessario un modello diverso. Utilizza la proprietà weight per posizionare ogni separatore.

Tipo di ambito

La proprietà scopeType è stata ritirata. In precedenza, veniva utilizzato per determinare se una voce di menu doveva essere visualizzata in un menu contestuale per un blocco, un commento di un workspace o un workspace. Poiché i menu contestuali possono essere aperti su altri componenti, la proprietà scopeType è troppo restrittiva. Devi invece utilizzare preconditionFn per mostrare o nascondere l'opzione per i componenti corrispondenti.

Se hai modelli di menu contestuale esistenti che utilizzano scopeType, Blockly continuerà a mostrare l'elemento solo per il componente appropriato.

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

Personalizzare il registro

Puoi aggiungere, eliminare o modificare i modelli nel registro. Puoi trovare i modelli predefiniti in contextmenu_items.ts.

Aggiungere un modello

Puoi aggiungere un modello al registro registrandolo. Devi farlo una volta al caricamento della pagina. Può verificarsi prima o dopo l'inserimento dello spazio di lavoro.

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

Eliminare un modello

Puoi rimuovere un modello dal registro annullandone la registrazione in base all'ID.

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

Modificare un modello

Puoi modificare un modello esistente recuperandolo dal registro e poi modificandolo sul posto.

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

Disattiva i menu contestuali di blocco

Per impostazione predefinita, i blocchi hanno un menu contestuale che consente agli utenti di aggiungere commenti ai blocchi o duplicarli.

Per disattivare il menu contestuale di un singolo blocco:

block.contextMenu = false;

Nella definizione JSON di un tipo di blocco, utilizza la chiave enableContextMenu:

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

Personalizzare i menu contestuali per tipo di blocco o workspace

Dopo che Blockly ha generato un array di voci di menu contestuale, puoi personalizzarlo per singoli blocchi o spazi di lavoro. Per farlo, imposta BlockSvg.customContextMenu o WorkspaceSvg.configureContextMenu su una funzione che modifica l'array sul posto.

Gli oggetti nell'array passato ai blocchi hanno il tipo ContextMenuOption o implementano l'interfaccia LegacyContextMenuOption. Gli oggetti passati agli spazi di lavoro hanno il tipo ContextMenuOption. Blockly utilizza le seguenti proprietà di questi oggetti:

  • text: il testo visualizzato.
  • enabled: Se false, visualizza l'elemento con testo grigio.
  • callback: La funzione da chiamare quando viene fatto clic sull'elemento.
  • separator: l'elemento è un separatore. Si esclude a vicenda con le altre tre proprietà.

Consulta la documentazione di riferimento per i tipi di proprietà e le firme delle funzioni.

Ad esempio, ecco una funzione che aggiunge un elemento Hello, World! al menu contestuale di uno spazio di lavoro:

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

Mostrare un menu contestuale su un oggetto personalizzato

Per visualizzare i menu contestuali per i componenti personalizzati, segui questi passaggi:

  1. Implementa IFocusableNode o estendi una classe che implementa IFocusableNode. Questa interfaccia viene utilizzata nel sistema di menu contestuale per identificare il componente. Consente inoltre agli utenti di navigare nel componente utilizzando il plug-in di navigazione da tastiera.
  2. Implementa IContextMenu, che contiene la funzione showContextMenu. Questa funzione recupera le voci del menu contestuale dal registro, calcola la posizione sullo schermo in cui mostrare il menu e infine lo mostra se sono presenti voci da visualizzare.

    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. Aggiungi un gestore di eventi che chiama showContextMenu quando l'utente fa clic con il tasto destro del mouse sul componente. Tieni presente che il plug-in di navigazione da tastiera fornisce un gestore di eventi che chiama showContextMenu quando l'utente preme Ctrl+Enter (Windows) o Command+Enter (Mac).

  4. Aggiungi modelli al registro per le voci del menu contestuale.