Creazione di blocchi di procedura personalizzati

La creazione di blocchi di procedure personalizzate richiede quanto segue:

  1. Installa il plug-in @blockly/block-shareable-procedures, come descritto nella pagina delle procedure di utilizzo.
  2. Utilizza il sistema di serializzazione JSON, come spiegato nella pagina della panoramica.

Aggiungi modelli dei dati all'area di lavoro

Sia la definizione della procedura che i blocchi dei chiamanti della procedura fanno riferimento a un modello di dati di supporto che definisce la firma della procedura (nome, parametri e ritorno). Questo consente una maggiore flessibilità nella progettazione dell'applicazione (ad es. è possibile definire le procedure in un'area di lavoro e farvi riferimento in un'altra).

Ciò significa che dovrai aggiungere i modelli dei dati di procedura all'area di lavoro affinché i blocchi funzionino. Puoi farlo in molti modi (ad es. con le interfacce utente personalizzate).

La @blockly/block-shareable-procedures esegue questa operazione grazie ai blocchi di definizione della procedura che creano dinamicamente i propri modelli dei dati di supporto quando vengono creati un'istanza nell'area di lavoro. Per implementarla personalmente, devi creare il modello in init ed eliminarlo in 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());
  }
}

Restituisci informazioni sui blocchi

La definizione di procedura e i blocchi di chiamata di procedura devono implementare i metodi getProcedureModel, isProcedureDef e getVarModels. Questi sono gli hook utilizzati dal codice di Blockly per ottenere informazioni sui blocchi di procedure.

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

Attiva il rendering con gli aggiornamenti

La definizione di procedura e i blocchi di chiamata di procedura devono implementare il metodo doProcedureUpdate. Si tratta dell'hook della chiamata dei modelli dati per indicare ai blocchi di procedura di eseguire nuovamente il rendering.

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

Aggiungi serializzazione personalizzata

La serializzazione per i blocchi di procedure deve eseguire due operazioni distinte.

  1. Durante il caricamento da JSON i blocchi dovranno acquisire un riferimento al modello dei dati di supporto, perché i blocchi e i modelli sono serializzati separatamente.
  2. Quando copi e incolli un blocco di procedura, il blocco dovrà serializzare l'intero stato del relativo modello di procedura, in modo che possa essere replicato/duplicato.

Entrambe queste operazioni vengono gestite tramite saveExtraState e loadExtraState. Ancora una volta, tieni presente che i blocchi di procedure personalizzate sono supportati solo quando si utilizza il sistema di serializzazione JSON, quindi dobbiamo definire solo gli hook di serializzazione 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();
      }
    }
  }
};

Facoltativamente, modifica il modello/la firma della procedura

Puoi anche aggiungere la possibilità per gli utenti di modificare il modello/la firma della procedura. La chiamata dei metodi insertParameter, deleteParameter o setReturnTypes attiverà automaticamente il rendering dei blocchi (tramite doProcedureUpdate).

Le opzioni per creare UI per modificare il modello di procedura includono l'utilizzo di mutatori (utilizzati dai blocchi di procedure integrati), campi immagine con gestori di clic, qualcosa di completamente esterno a Blockly e così via.

Aggiungi blocchi alla casella degli strumenti

La categoria di procedura dinamica integrata di Blockly è specifica per i blocchi di procedura integrati di Blockly. Per poter accedere ai blocchi, devi definire una categoria dinamica personalizzata e aggiungerla alla tua casella degli strumenti.

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