커스텀 절차 블록 만들기

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

  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.
    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'] = {
  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를 통해 블록이 자동으로 다시 렌더링됩니다.

프러시저 모델을 수정하기 위해 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);