Crea bloques de procedimiento personalizados

Para crear bloques de procedimientos personalizados, debes hacer lo siguiente:

  1. Instala el complemento @blockly/block-shareable-procedures, como se describe en la página de uso de procedimientos.
  2. Usa el sistema de serialización JSON, como se explica en la página de descripción general.

Agrega modelos de datos al lugar de trabajo

Tanto la definición del procedimiento como los bloques de llamada del procedimiento hacen referencia a un modelo de datos de copia de seguridad que define la firma del procedimiento (nombre, parámetros y retorno). Esto permite una mayor flexibilidad en el diseño de la aplicación (p.ej., puedes permitir que se definan los procedimientos en un lugar de trabajo y se haga referencia a ellos en otro).

Esto significa que deberás agregar los modelos de datos de procedimiento al lugar de trabajo para que funcionen tus bloques. Puedes hacerlo de muchas maneras (p.ej., IU personalizadas).

Los @blockly/block-shareable-procedures hacen que los bloques de definición de procedimiento crean de forma dinámica sus modelos de datos de copia de seguridad cuando se crean instancias de ellos en el lugar de trabajo. Para implementarlo tú, crea el modelo en init y bórralo en destroy.

import {ObservableProcedureModel} from '@blockly/block-shareable-procedures';

Blockly.Blocks['my_procedure_def'] = {
  init: function() {
    this.model = new ObservableProcedureModel('default name');
    this.workspace.getProcedureMap().add(model);
    // etc...
  }

  destroy: function() {
    // Optionally:
    // Destroy the model when the definition block is deleted.
    this.workpace.getProcedureMap().delete(model.getId());
  }
}

Muestra información sobre los bloques

La definición del procedimiento y los bloques de llamada a procedimiento deben implementar los métodos getProcedureModel, isProcedureDef y getVarModels. Estos son los hooks que usa el código de Blockly para obtener información sobre tus bloques de procedimiento.

Blockly.Blocks['my_procedure_def'] = {
  getProcedureModel() {
    return this.model;
  },

  isProcedureDef() {
    return true;
  },

  getVarModels() {
    // If your procedure references variables
    // then you should return those models here.
    return [];
  },
};

Blockly.Blocks['my_procedure_call'] = {
  getProcedureModel() {
    return this.model;
  },

  isProcedureDef() {
    return false;
  },

  getVarModels() {
    // If your procedure references variables
    // then you should return those models here.
    return [];
  },
};

Cómo activar la repetición de renderización en actualizaciones

La definición del procedimiento y los bloques de llamadas a procedimientos deben implementar el método doProcedureUpdate. Este es el hook que los modelos de datos llaman para indicarle a los bloques de procedimiento que se vuelvan a renderizar.

Blockly.Blocks['my_procedure_def'] = {
  doProcedureUpdate() {
    this.setFieldValue('NAME', this.model.getName());
    this.setFieldValue(
        'PARAMS',
        this.model.getParameters()
            .map((p) => p.getName())
            .join(','));
    this.setFieldValue(
        'RETURN', this.model.getReturnTypes().join(',');
  }
};

Blockly.Blocks['my_procedure_call'] = {
  doProcedureUpdate() {
    // Similar to the def block above...
  }
};

Agregar serialización personalizada

La serialización de los bloques de procedimientos debe cumplir dos funciones.

  1. Cuando realices cargas desde JSON, tus bloques deberán tomar una referencia a su modelo de datos de copia de seguridad, ya que los bloques y los modelos se serializan por separado.
  2. Cuando copias y pegas un bloque de procedimiento, el bloque deberá serializar todo el estado de su modelo de procedimiento para que se pueda replicar o duplicar.

Ambas cosas se controlan a través de saveExtraState y loadExtraState. Ten en cuenta que los bloques de procedimiento personalizados solo se admiten cuando se usa el sistema de serialización JSON, por lo que solo necesitamos definir los hooks de serialización JSON.

import {
    ObservableProcedureModel,
    ObservableParameterModel,
    isProcedureBlock
} from '@blockly/block-shareable-procedures';

Blockly.Blocks['my_procedure_def'] = {
  saveExtraState() {
    return {
      'procedureId': this.model.getId(),

      // These properties are only necessary for pasting.
      'name': this.model.getName(),
      'parameters': this.model.getParameters().map((p) => {
        return {name: p.getName(), p.getId()};
      }),
      'returnTypes': this.model.getReturnTypes(),
    };
  },

  loadExtraState(state) {
    const id = state['procedureId']
    const map = this.workspace.getProcedureMap();

    // Grab a reference to the existing procedure model.
    if (this.model.getId() != id && map.has(id) &&
        (this.isInsertionMarker || this.noBlockHasClaimedModel_(id))) {
      // Delete the existing model (created in init).
      this.workspace.getProcedureMap().delete(model.getId());
      // Grab a reference to the new model.
      this.model = this.workspace.getProcedureMap()
          .get(state['procedureId']);
      this.doProcedureUpdate();
      return;
    }

    // There is no existing procedure model (we are likely pasting), so
    // generate it from JSON.
    this.model
        .setName(state['name'])
        .setReturnTypes(state['returnTypes']);
    for (const [i, param] of state['parameters'].entries()) {
      this.model.insertParameter(
          i,
          new ObservableParameterModel(
              this.workspace, param['name'], param['id']));
    }
  },

  // We don't want to reference a model that some other procedure definition
  // is already referencing.
  noBlockHasClaimedModel_(procedureId) {
    const model =
      this.workspace.getProcedureMap().get(procedureId);
    return this.workspace.getAllBlocks(false).every(
      (block) =>
        !isProcedureBlock(block) ||
        !block.isProcedureDef() ||
        block.getProcedureModel() !== model);
  },
};

Blockly.Blocks['my_procedure_call'] = {
  saveExtraState() {
    return {
      'procedureId': this.model.getId(),
    };
  },

  loadExtraState(state) {
    // Delete our existing model (created in init).
    this.workspace.getProcedureMap().delete(model.getId());
    // Grab a reference to the new model.
    this.model = this.workspace.getProcedureMap()
        .get(state['procedureId']);
    if (this.model) this.doProcedureUpdate();
  },

  // Handle pasting after the procedure definition has been deleted.
  onchange(event) {
    if (event.type === Blockly.Events.BLOCK_CREATE &&
        event.blockId === this.id) {
      if(!this.model) { // Our procedure definition doesn't exist =(
        this.dispose();
      }
    }
  }
};

De manera opcional, puedes modificar el modelo o la firma del procedimiento.

También puedes agregar la capacidad para que los usuarios modifiquen el modelo o la firma del procedimiento. Si llamas a los métodos insertParameter, deleteParameter o setReturnTypes, se activarán automáticamente tus bloques para que se vuelvan a renderizar (a través de doProcedureUpdate).

Las opciones para crear IU que modifiquen el modelo de procedimiento incluyen el uso de mutadores (que usan los bloques de procedimiento integrados), campos de imagen con controladores de clics, algo completamente externo a Blockly, etcétera.

Agrega bloques a la caja de herramientas

La categoría integrada de procedimiento dinámico de Blockly es específica de sus bloques integrados de procedimientos. Por lo tanto, para poder acceder a tus bloques, deberás definir tu propia categoría dinámica personalizada y agregarla a tu caja de herramientas.

const proceduresFlyoutCallback = function(workspace) {
  const blockList = [];
  blockList.push({
    'kind': 'block',
    'type': 'my_procedure_def',
  });
  for (const model of
        workspace.getProcedureMap().getProcedures()) {
    blockList.push({
      'kind': 'block',
      'type': 'my_procedure_call',
      'extraState': {
        'procedureId': model.getId(),
      },
    });
  }
  return blockList;
};

myWorkspace.registerToolboxCategoryCallback(
    'MY_PROCEDURES', proceduresFlyoutCallback);