Menús contextuales

Un menú contextual contiene una lista de acciones que un usuario puede realizar en un componente, como un espacio de trabajo, un bloque o un comentario del espacio de trabajo. El menú contextual se muestra en respuesta a un clic con el botón derecho o a una presión prolongada en un dispositivo táctil. Si usas el complemento @blockly/keyboard-navigation, también se muestra con una combinación de teclas, que, de forma predeterminada, es Ctrl+Enter en Windows o Command+Enter en Mac.

El menú contextual predeterminado de un bloque

Los menús contextuales son un buen lugar para agregar acciones que el usuario realiza con poca frecuencia, como descargar una captura de pantalla. Si crees que una acción se usará con más frecuencia, tal vez quieras crear una forma más fácil de descubrir para invocarla.

Los menús contextuales son compatibles con los espacios de trabajo, los bloques, los comentarios del espacio de trabajo, las burbujas y las conexiones. También puedes implementarlos en tus propios componentes personalizados. Blockly proporciona menús contextuales estándar que puedes personalizar. También puedes personalizar los menús contextuales en los espacios de trabajo y los bloques por espacio de trabajo o por bloque.

Cómo funcionan los menús contextuales

Blockly tiene un registro que contiene plantillas para todos los elementos de menú posibles. Cada plantilla describe cómo construir un solo elemento en un menú contextual. Cuando el usuario invoca un menú contextual en un componente, este hace lo siguiente:

  1. Le solicita al constructo del registro un array de elementos de menú que se aplican al componente. El registro le pregunta a cada plantilla si se aplica al componente y, si es así, agrega un elemento de menú correspondiente al array.

  2. Si el componente es un espacio de trabajo o un bloque, verifica si el espacio de trabajo o el bloque específicos en los que se invocó el menú tienen una función para personalizar el menú contextual. Si es así, pasa el array a la función, que puede agregar, borrar o modificar elementos del array.

  3. Muestra el menú contextual con el array (posiblemente modificado) de elementos del menú contextual.

Blockly define un conjunto estándar de plantillas para los menús contextuales de los espacios de trabajo, los bloques y los comentarios del espacio de trabajo. Precarga las plantillas de los espacios de trabajo y los bloques en el registro. Si quieres usar las plantillas para los comentarios del espacio de trabajo, debes cargarlas en el registro por tu cuenta.

Para obtener información sobre cómo agregar, borrar y modificar plantillas en el registro, consulta Personaliza el registro.

Alcance

Los menús contextuales se implementan con diferentes tipos de componentes, incluidos los espacios de trabajo, los comentarios de espacios de trabajo, las conexiones, los bloques, las burbujas y tus propios componentes personalizados. Los menús contextuales de cada uno de estos tipos de componentes pueden contener diferentes elementos, y los elementos pueden comportarse de manera diferente según el tipo de componente. Por lo tanto, el sistema de menú contextual debe saber en qué componente se invocó.

Para abordar este problema, el registro usa un objeto Scope. El componente en el que se invocó el menú contextual se almacena en la propiedad focusedNode como un objeto que implementa IFocusableNode. (IFocusableNode se implementa en todos los componentes en los que los usuarios pueden enfocar, incluidos los que implementan menús contextuales. Para obtener más información, consulta Sistema de enfoque.

El objeto Scope se pasa a varias de las funciones de una plantilla. En cualquier función que obtenga un objeto Scope, puedes decidir qué hacer según el tipo del objeto en la propiedad focusedNode. Por ejemplo, puedes verificar si el componente es un bloque con el siguiente código:

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

El objeto Scope tiene otras propiedades opcionales que ya no se recomiendan para su uso, pero que aún se pueden establecer:

  • block solo se establece si el componente cuyo menú se muestra es un BlockSvg.
  • workspace solo se establece si el componente es un WorkspaceSvg.
  • comment solo se establece si el componente es un RenderedWorkspaceComment.

Estas propiedades no abarcan todos los tipos de componentes que pueden tener un menú contextual, por lo que debes usar la propiedad focusedNode.

El tipo RegistryItem

Las plantillas tienen el tipo ContextMenuRegistry.RegistryItem, que contiene las siguientes propiedades. Ten en cuenta que las propiedades preconditionFn, displayText y callback son mutuamente excluyentes con la propiedad separator.

ID

La propiedad id debe ser una cadena única que indique lo que hace el elemento de menú contextual.

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

Función de condición previa

Puedes usar preconditionFn para restringir cuándo y cómo se debe mostrar un elemento del menú contextual.

Debe devolver una de las siguientes cadenas: 'enabled', 'disabled' o 'hidden'.

Valor Descripción Imagen
'enabled' Indica que el elemento está activo. Una opción habilitada
'disabled' Indica que el elemento no está activo. Una opción inhabilitada
'hidden' Oculta el elemento.

También se pasa un Scope a preconditionFn, que puedes usar para determinar qué tipo de componente abrió el menú y el estado de ese componente.

Por ejemplo, es posible que desees que un elemento solo aparezca para los bloques y solo cuando esos bloques se encuentren en un estado en particular:

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

Texto visible

El displayText es lo que se debe mostrar al usuario como parte del elemento de menú. El texto visible puede ser una cadena, HTML o una función que muestre una cadena o HTML.

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

Si quieres mostrar una traducción de Blockly.Msg, debes usar una función. Si intentas asignar el valor directamente, es posible que no se carguen los mensajes y, en su lugar, obtendrás un valor de undefined.

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

Si usas una función, también se le pasa un valor Scope. Puedes usarlo para agregar información sobre el elemento al texto visible.

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

El weight determina el orden en que se muestran los elementos del menú contextual. Los valores más positivos se muestran más abajo en la lista que los valores menos positivos. (Puedes imaginar que los elementos con pesos más altos son "más pesados", por lo que se hunden hasta el fondo).

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

Los pesos de los elementos integrados del menú contextual se ordenan de forma creciente a partir de 1 y aumentan de a 1.

Función de devolución de llamada

La propiedad callback es una función que realiza la acción del elemento de menú contextual. Se le pasan varios parámetros:

  • scope: Es un objeto Scope que proporciona una referencia al componente cuyo menú está abierto.
  • menuOpenEvent: Es el Event que activó la apertura del menú contextual. Puede ser PointerEvent o KeyboardEvent, según cómo el usuario abrió el menú.
  • menuSelectEvent: Es el Event que seleccionó este elemento de menú contextual en particular del menú. Puede ser PointerEvent o KeyboardEvent, según cómo el usuario haya seleccionado el elemento.
  • location: Es el Coordinate en coordenadas de píxeles donde se abrió el menú. Esto te permite, por ejemplo, crear un nuevo bloque en la ubicación del clic.
const collapseTemplate = {
  // ...
  callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      scope.focusedNode.collapse();
    }
  },
}

Puedes usar scope para diseñar plantillas que funcionen de manera diferente según el componente en el que se abrieron:

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

Separador

La propiedad separator dibuja una línea en el menú contextual.

Las plantillas con la propiedad separator no pueden tener propiedades preconditionFn, displayText ni callback, y solo se pueden definir con la propiedad scopeType. Esta última restricción significa que solo se pueden usar en los menús contextuales de los espacios de trabajo, los bloques y los comentarios del espacio de trabajo.

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

Necesitas una plantilla diferente para cada separador del menú contextual. Usa la propiedad weight para colocar cada separador.

Tipo de permiso

La propiedad scopeType está obsoleta. Anteriormente, se usaba para determinar si se debía mostrar un elemento de menú en un menú contextual para un bloque, un comentario del espacio de trabajo o un espacio de trabajo. Dado que los menús contextuales se pueden abrir en otros componentes, la propiedad scopeType es demasiado restrictiva. En su lugar, debes usar preconditionFn para mostrar u ocultar tu opción para los componentes correspondientes.

Si tienes plantillas de menú contextual existentes que usan scopeType, Blockly seguirá mostrando el elemento solo para el componente adecuado.

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

Personaliza el registro

Puedes agregar, borrar o modificar plantillas en el registro. Puedes encontrar las plantillas predeterminadas en contextmenu_items.ts.

Cómo agregar una plantilla

Puedes agregar una plantilla al registro si la registras. Debes hacerlo una vez cuando se cargue la página. Puede ocurrir antes o después de que insertes tu espacio de trabajo.

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

Borra una plantilla

Puedes quitar una plantilla del registro si la anulas por ID.

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

Cómo modificar una plantilla

Puedes modificar una plantilla existente. Para ello, obtén la plantilla del registro y, luego, modifícala en el lugar.

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

Inhabilita los menús contextuales de bloqueo

De forma predeterminada, los bloques tienen un menú contextual que permite a los usuarios realizar acciones como agregar comentarios o duplicar bloques.

Puedes inhabilitar el menú contextual de un bloque individual de la siguiente manera:

block.contextMenu = false;

En la definición JSON de un tipo de bloque, usa la clave enableContextMenu:

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

Personaliza los menús contextuales por tipo de bloque o espacio de trabajo

Después de que Blockly genera un array de elementos del menú contextual, puedes personalizarlo para bloques o espacios de trabajo individuales. Para ello, establece BlockSvg.customContextMenu o WorkspaceSvg.configureContextMenu en una función que modifique el array en su lugar.

Los objetos del array que se pasan a los bloques tienen el tipo ContextMenuOption o implementan la interfaz LegacyContextMenuOption. Los objetos que se pasan a los espacios de trabajo tienen el tipo ContextMenuOption. Blockly usa las siguientes propiedades de estos objetos:

  • text: Es el texto visible.
  • enabled: Si es false, muestra el elemento con texto gris.
  • callback: Es la función a la que se llamará cuando se haga clic en el elemento.
  • separator: El elemento es un separador. Es mutuamente excluyente con las otras tres propiedades.

Consulta la documentación de referencia para conocer los tipos de propiedades y las firmas de funciones.

Por ejemplo, esta es una función que agrega un elemento Hello, World! al menú contextual de un espacio de trabajo:

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

Cómo mostrar un menú contextual en un objeto personalizado

Para que aparezcan menús contextuales en los componentes personalizados, sigue estos pasos:

  1. Implementa IFocusableNode o extiende una clase que implemente IFocusableNode. Esta interfaz se usa en el sistema de menú contextual para identificar tu componente. También permite que los usuarios naveguen a tu componente con el complemento de navegación por teclado.
  2. Implementa IContextMenu, que contiene la función showContextMenu. Esta función obtiene los elementos del menú contextual del registro, calcula la ubicación en la pantalla en la que se debe mostrar el menú y, finalmente, muestra el menú si hay elementos para mostrar.

    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. Agrega un controlador de eventos que llame a showContextMenu cuando el usuario haga clic con el botón derecho en tu componente. Ten en cuenta que el complemento de navegación con el teclado proporciona un controlador de eventos que llama a showContextMenu cuando el usuario presiona Ctrl+Enter (Windows) o Command+Enter (Mac).

  4. Agrega plantillas al registro para los elementos del menú contextual.