Tworzenie niestandardowych bloków procedury

Tworzenie niestandardowych bloków procedur wymaga:

  1. Zainstaluj wtyczkę @blockly/block-shareable-procedures zgodnie z opisem na stronie z procedurami.
  2. korzystać z systemu serializacji JSON zgodnie z opisem na stronie przeglądu;

Dodawanie modeli danych do obszaru roboczego

Zarówno bloki definicji procedury, jak i bloków wywołujących procedury odwołują się do bazowego modelu danych, który określa podpis procedury (nazwę, parametry i zwrot). Zwiększa to elastyczność projektowania aplikacji (np. możesz zezwolić na definiowanie procedur w jednym obszarze roboczym i odwoływanie się do nich w innym).

Oznacza to, że aby bloki działały, musisz dodać do obszaru roboczego modele danych procedur. Możesz to zrobić na wiele sposobów (np. korzystając z niestandardowego interfejsu).

@blockly/block-shareable-procedures. bloki definicji procedury dynamicznie tworzą zapasowe modele danych, gdy są tworzone w obszarze roboczym. Aby wdrożyć go samodzielnie, musisz utworzyć model w init, a potem usunąć go w usłudze 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());
  }
}

Zwróć informacje o blokach

Bloki wywołań procedury i definicji procedury muszą implementować metody getProcedureModel, isProcedureDef i getVarModels. To są punkty zaczepienia używane przez kod Blockly do pobierania informacji o blokach procedur.

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

Aktywuj ponowne renderowanie przy aktualizacjach

Bloki wywołań procedury i definicji procedury muszą implementować metodę doProcedureUpdate. Jest to punkt zaczepienia wywoływany przez modele danych, który informuje bloki procedur o ponownym wyrenderowaniu.

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

Dodaj niestandardową serializację

Serializacja bloków procedur musi spełniać 2 osobne zadania.

  1. Podczas wczytywania z formatu JSON bloki muszą mieć odniesienie do zapasowego modelu danych, ponieważ bloki i modele są zserializowane oddzielnie.
  2. Podczas kopiowania i wklejania bloku procedury blok musi zserilizować cały stan swojego modelu procedury, aby można było go zreplikować/duplikować.

Obie te czynności są obsługiwane przez saveExtraState i loadExtraState. Zwróć uwagę, że niestandardowe bloki procedur są obsługiwane tylko w przypadku korzystania z systemu serializacji JSON, więc musimy zdefiniować tylko punkty zaczepienia serializacji 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();
      }
    }
  }
};

Opcjonalnie zmodyfikuj model/podpis procedury

Możesz też dodać możliwość modyfikowania modelu lub podpisu procedury przez użytkowników. Wywołanie metody insertParameter, deleteParameter lub setReturnTypes spowoduje automatyczne ponowne wyrenderowanie bloków (za pomocą doProcedureUpdate).

Opcje tworzenia interfejsów użytkownika w celu modyfikacji modelu procedury to m.in. użycie mutatorów (używanych przez wbudowaną procedurę), pól obrazów z modułami obsługi kliknięć czy czegoś całkowicie zewnętrznego w stosunku do Blockly.

Dodawanie bloków do zestawu narzędzi

Wbudowana kategoria dynamicznych procedur Blockly jest charakterystyczna dla bloków procedur wbudowanych w Blockly. Aby mieć dostęp do blokad, musisz zdefiniować własną niestandardową kategorię dynamiczną i dodać ją do swojego zestawu narzędzi.

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