커스텀 프러시저 블록 만들기

커스텀 프러시저 블록을 만들려면 다음 작업이 필요합니다.

  1. 사용 절차 페이지에 설명된 대로 @blockly/block-shareable-procedures 플러그인을 설치합니다.
  2. 개요 페이지에 설명된 대로 JSON 직렬화 시스템을 사용합니다.

작업공간에 데이터 모델 추가

프로시져 정의와 프로시져 호출자 블록은 모두 프로시져의 서명 (이름, 매개변수, 반환)을 정의하는 지원 데이터 모델을 참조합니다. 이를 통해 애플리케이션을 더 유연하게 설계할 수 있습니다 (예: 절차를 하나의 작업공간에서 정의하고 다른 작업공간에서 참조하도록 허용할 수 있음).

즉, 블록이 작동하려면 프러시저 데이터 모델을 작업공간에 추가해야 합니다. 이를 수행하는 방법에는 여러 가지가 있습니다 (예: 맞춤 UI).

@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.

    // Insertion markers reference the model of the original block.
    if (this.isInsertionMarker()) return;
    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. 프로시져 블록을 복사하여 붙여넣을 때 블록은 복제될 수 있도록 프로시져 모델의 전체 상태를 직렬화해야 합니다.

이 두 가지 모두 saveExtraStateloadExtraState를 통해 처리됩니다. 커스텀 프러시저 블록은 JSON 직렬화 시스템을 사용할 때만 지원되므로 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();
      }
    }
  }
};

원하는 경우 절차 모델을 수정합니다.

사용자가 수술 모델을 수정할 수 있는 기능을 추가할 수도 있습니다. insertParameter, deleteParameter 또는 setReturnTypes 메서드를 호출하면 doProcedureUpdate를 통해 블록이 자동으로 렌더링되도록 트리거합니다.

프로시져 모델을 수정하기 위해 UI를 만드는 옵션에는 뮤테이터 (내장 프로시저 블록에서 사용하는 뮤테이터), 클릭 핸들러가 있는 이미지 필드, 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);