Criar blocos de procedimentos personalizados

A criação de blocos de procedimentos personalizados exige que você:

  1. Instale o plug-in @blockly/block-shareable-procedures, conforme descrito na página de procedimentos de uso.
  2. Use o sistema de serialização JSON, conforme explicado na página de visão geral.

Adicionar modelos de dados ao espaço de trabalho

Os blocos de definição e de autor da chamada de procedimento se referem a um modelo de dados de apoio que define a assinatura do procedimento (nome, parâmetros e retorno). Isso permite mais flexibilidade na forma como você projeta o aplicativo. Por exemplo, é possível permitir que os procedimentos sejam definidos em um espaço de trabalho e referenciados em outro.

Isso significa que você precisará adicionar os modelos de dados de procedimentos ao espaço de trabalho para que seus blocos funcionem. Há várias maneiras de fazer isso (por exemplo, IUs personalizadas).

O @blockly/block-shareable-procedures faz isso com blocos de definição de procedimento criando dinamicamente os modelos de dados de apoio quando instanciados no espaço de trabalho. Para implementar isso, crie o modelo em init e exclua-o em 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());
  }
}

Retornar informações sobre os blocos

Seus blocos de definição e chamada de procedimento precisam implementar os métodos getProcedureModel, isProcedureDef e getVarModels. Esses são os ganchos que o código do Blockly usa para receber informações sobre os blocos de procedimento.

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 [];
  },
};

Acionar a nova renderização em atualizações

Seus blocos de definição e chamada de procedimento precisam implementar o método doProcedureUpdate. Esse é o gancho que os modelos de dados chamam para dizer aos blocos de procedimento que serão renderizados novamente.

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

Adicionar serialização personalizada

A serialização de blocos de procedimento precisa realizar duas tarefas distintas.

  1. Ao carregar pelo JSON, seus blocos precisarão pegar uma referência ao modelo de dados de apoio, porque os blocos e modelos são serializados separadamente.
  2. Ao copiar e colar um bloco de procedimento, ele precisará serializar todo o estado do modelo de procedimento para que possa ser replicado.

Essas duas coisas são processadas usando saveExtraState e loadExtraState. Os blocos de procedimento personalizado são compatíveis apenas com o uso do sistema de serialização JSON. Portanto, só precisamos definir ganchos de serialização 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();
      }
    }
  }
};

Modificar o modelo do procedimento opcionalmente

Também é possível permitir que os usuários modifiquem o modelo do procedimento. Chamar os métodos insertParameter, deleteParameter ou setReturnTypes vai acionar automaticamente os blocos para serem renderizados novamente (via doProcedureUpdate).

As opções para criar interfaces para modificar o modelo de procedimento incluem o uso de mutadores (que os blocos de procedimentos integrados usam), campos de imagem com gerenciadores de cliques, algo completamente externo ao Blockly etc.

Adicionar blocos à caixa de ferramentas

A categoria de procedimentos dinâmicos integrados do Blockly é específica dos blocos de procedimentos integrados. Portanto, para acessar seus blocos, você precisa definir sua própria categoria dinâmica personalizada e adicioná-la à sua caixa de ferramentas.

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