Crea bloques de procedimiento personalizados

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

  1. Instala el complemento @blockly/block-shareable-procedures, como se describe en la página sobre el 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 tu aplicación (p.ej., puedes permitir que los procedimientos se definan 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 los bloques. Existen muchas formas de hacerlo (p.ej., IU personalizadas).

Para ello, @blockly/block-shareable-procedures hace que los bloques de definición de procedimiento creen 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 por tu cuenta, 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.

    // Insertion markers reference the model of the original block.
    if (this.isInsertionMarker()) return;
    this.workpace.getProcedureMap().delete(model.getId());
  }
}

Muestra información sobre los bloques

La definición del procedimiento y los bloques de llamadas a los procedimientos 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 [];
  },
};

Activa la repetición de renderizaciones durante las actualizaciones

La definición del procedimiento y los bloques de llamadas a los procedimientos deben implementar el método doProcedureUpdate. Este es el hook que llaman los modelos de datos para indicarles 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 procedimiento debe cumplir con dos objetivos distintos.

  1. Cuando realices una carga desde JSON, tus bloques deberán obtener una referencia a su modelo de datos de copia de seguridad, ya que los bloques y los modelos se serializan por separado.
  2. Cuando se copia y pega un bloque de procedimiento, el bloque deberá serializar todo el estado de su modelo de procedimiento para que pueda replicarse.

Ambas opciones se controlan a través de saveExtraState y loadExtraState. Ten en cuenta que los bloques de procedimiento personalizado 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'] = {
  // When doFullSerialization is true, we should serialize the full state of
  // the model.
  saveExtraState(doFullSerialization) {
    const state = Object.create(null);
    state['procedureId']: this.model.getId();

    if (doFullSerialization) {
      state['name'] = this.model.getName();
      state['parameters'] = this.model.getParameters().map((p) => {
        return {name: p.getName(), p.getId()};
      });
      state['returnTypes'] = this.model.getReturnTypes();

      // Flag for deserialization.
      state['createNewModel'] = true;
    }

    return state;
  },

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

    if (map.has(id) && !state['createNewModel']) {
      // Delete the existing model (created in init).
      map.delete(this.model.getId());
      // Grab a reference to the model we're supposed to reference.
      this.model = map.get(id);
      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']));
    }
    this.doProcedureUpdate();
  },
};

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 forma opcional, modifica el modelo de procedimiento

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

Las opciones para crear IUs para modificar 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 procedimientos dinámicos de Blockly es específica de sus bloques integrados de procedimientos. Por lo tanto, para 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);