Criar blocos de procedimentos personalizados

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

  1. Instale o arquivo @blockly/block-shareable-procedures conforme descrito no guia Como usar procedimentos página.
  2. Use o sistema de serialização JSON, conforme explicado na visão geral .
.

Adicionar modelos de dados ao espaço de trabalho

Tanto a definição de procedimento quanto os blocos de autor da chamada de procedimento referenciam um dado de apoio que define a assinatura do procedimento (nome, parâmetros e retornar). Isso permite mais flexibilidade na forma como você projeta seu aplicativo (por exemplo, você pode permitir que os procedimentos sejam definidos em um espaço de trabalho e referenciados em outra).

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

O @blockly/block-shareable-procedures faz isso ter blocos de definição de procedimento para criar dinamicamente seus modelos de dados de apoio quando eles são instanciados no espaço de trabalho. Para implementar isso, você 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

A definição do procedimento e os blocos de chamada de procedimento precisam implementar a métodos getProcedureModel, isProcedureDef e getVarModels. Estes são os hooks que o código do Blockly usa para conseguir informações sobre os blocos do 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 nova renderização em atualizações

A definição do procedimento e os blocos de chamada de procedimento precisam implementar a doProcedureUpdate. Esse é o gancho que os modelos de dados chamam para informar e blocos de procedimento para se renderizarem 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 para blocos de procedimento precisa fazer duas coisas separadas.

  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, o bloco terá que serializar todo o estado do seu modelo de procedimento para que ele possa ser replicado.

Essas duas coisas são processadas usando saveExtraState e loadExtraState. Vale lembrar que os blocos de procedimentos personalizados só são compatíveis com o uso do por isso, só precisamos definir hooks 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();
      }
    }
  }
};

Opcionalmente, modifique o modelo de procedimento

Também é possível adicionar a capacidade de os usuários modificarem o modelo de procedimento. Ligando insertParameter, deleteParameter ou setReturnTypes métodos acionará automaticamente a nova renderização dos seus bloqueios (via doProcedureUpdate).

Opções para criar interfaces para modificar o modelo de procedimento incluem o uso de mutators (que o uso de blocos de procedimento integrado), campos de imagem com manipuladores de cliques, algo completamente externo ao Blockly etc.

Adicionar blocos à caixa de ferramentas

A categoria de procedimento dinâmico integrado do Blockly é específica para o blocos de procedimento superior. Para acessar seus blocos, você precisa definir seu próprio modelo dinâmico personalizado categoria e adicione-a na 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);