Создание пользовательских блоков процедур

Для создания пользовательских блоков процедур необходимо:

  1. Установите плагин @blockly/block-shareable-procedures , как описано на странице использования процедур .
  2. Используйте систему сериализации JSON, как описано на обзорной странице .

Добавьте модели данных в рабочую область

Как определение процедуры, так и блоки вызова процедуры ссылаются на резервную модель данных, которая определяет сигнатуру процедуры (имя, параметры и возвращаемый результат). Это обеспечивает большую гибкость при разработке приложения (например, вы можете разрешить определять процедуры в одном рабочем пространстве и ссылаться на них в другом).

Это означает, что вам нужно будет добавить модели данных процедур в рабочую область, чтобы ваши блоки работали. Есть много способов сделать это (например, с помощью пользовательских интерфейсов).

@blockly/block-shareable-procedures позволяет блокам определения процедур динамически создавать свои модели данных при их создании в рабочей области. Чтобы реализовать это самостоятельно, вы создаете модель в init и удаляете ее в 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());
  }
}

Возвращать информацию о блоках

Блоки определения процедуры и вызова процедур должны реализовывать методы getProcedureModel , isProcedureDef и getVarModels . Это хуки, которые код Blockly использует для получения информации о ваших блоках процедур.

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

Запускать повторный рендеринг при обновлениях

В определении процедуры и блоках вызова процедур необходимо реализовать метод doProcedureUpdate . Это ловушка, которую вызывают модели данных, чтобы сообщить вашим блокам процедур о необходимости повторной визуализации.

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

Добавить пользовательскую сериализацию

Сериализация блоков процедур должна выполнять две разные задачи.

  1. При загрузке из JSON вашим блокам необходимо будет получить ссылку на поддерживающую их модель данных, поскольку блоки и модели сериализуются отдельно.
  2. При копировании и вставке блока процедуры блоку необходимо будет сериализовать все состояние своей модели процедуры, чтобы его можно было реплицировать/дублировать.

Обе эти вещи обрабатываются с помощью saveExtraState и loadExtraState . Еще раз обратите внимание, что пользовательские блоки процедур поддерживаются только при использовании системы сериализации JSON, поэтому нам нужно только определить перехватчики сериализации 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();
      }
    }
  }
};

При необходимости измените модель/подпись процедуры.

Вы также можете добавить пользователям возможность изменять модель/подпись процедуры. Вызов методов insertParameter , deleteParameter или setReturnTypes автоматически запустит повторную визуализацию ваших блоков (через doProcedureUpdate ).

Варианты создания пользовательских интерфейсов для изменения модели процедуры включают использование мутаторов (которые используют встроенные блоки процедур), полей изображений с обработчиками кликов, чего-то совершенно внешнего для Blockly и т. д.

Добавьте блоки в панель инструментов

Категория встроенных динамических процедур Blockly специфична для блоков встроенных процедур Blockly. Поэтому, чтобы иметь доступ к своим блокам, вам нужно будет определить свою собственную динамическую категорию и добавить ее в свой набор инструментов .

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