当前位置: 首页 > article >正文

Blockly 二次封装

基础功能封装

对 Blockly 进行二次封装,提供如下方法:

  • loadCustomBlocks: 用来动态加载自定义 block
  • buildContainer: 确保 workspace 的 container 和 toolbox 被创建
  • initSpace:在指定位置创建一个画布
  • clearSpace: 清除画布上的内容
  • loadSpace: 将画布存档加载到指定的 workspace 中去
  • saveSpace:保存画布内容

1. 创建 Blcokly 文件 – @/externals/blockly.js

// @/externals/blockly.js

import Blockly from 'blockly'; // version = "blockly": "8",
// import { javascriptGenerator, Order } from 'blockly/javascript';

/**
 * 用来动态加载自定义 block
 */
export const loadCustomBlocks = () => {
  Blockly.Blocks['my_block'] = {
    init: function () {
      this.appendDummyInput()
        .appendField("My Block");
      this.setPreviousStatement(true, null);
      this.setNextStatement(true, null);
      this.setColour(230);
      this.setTooltip('');
      this.setHelpUrl('');
    }
  };

  Blockly.JavaScript['my_block'] = function (block) {
    console.log('block:', block)
    var code = 'console.log("Hello, World!");\n';
    return code;
  };

  Blockly.Blocks['my_custom_block'] = {
    init: function () {
      this.appendDummyInput()
        .appendField("自定义块");
      this.setPreviousStatement(true, null);
      this.setNextStatement(true, null);
      this.setColour(230);
      this.setTooltip('');
      this.setHelpUrl('');
    }
  };

  Blockly.Blocks['combined_block'] = {
    init: function () {
      this.appendValueInput("TEXT_INPUT")
        .setCheck(null)
        .appendField("输入文本:");
      this.appendDummyInput()
        .appendField(new Blockly.FieldDropdown([["选项1", "1"], ["选项2", "2"]]), "DROPDOWN");
      this.setPreviousStatement(true, null);
      this.setNextStatement(true, null);
      this.setColour(230);
      this.setTooltip('111');
      this.setHelpUrl('');
    }
  };

  Blockly.defineBlocksWithJsonArray([
    {
      "type": "my_custom_block",
      "message0": "自定义块",
      "previousStatement": null,
      "nextStatement": null,
      "colour": 230,
      "tooltip": "",
      "helpUrl": ""
    },
    {
      "type": "combined_block",
      "message0": "输入文本: %1 选择选项: %2",
      "args0": [
        {
          "type": "input_value",
          "name": "TEXT_INPUT"
        },
        {
          "type": "field_dropdown",
          "name": "DROPDOWN",
          "options": [["选项1", "1"], ["选项2", "2"]]
        }
      ],
      "previousStatement": null,
      "nextStatement": null,
      "colour": 230,
      "tooltip": "",
      "helpUrl": ""
    }
  ]);

  const C_Block = {
    init: function () {
      this.appendValueInput('VAR1')
        .setCheck('Number');
      this.setInputsInline(false)
      this.setNextStatement(true, null);
      this.setTooltip('这是自定义的模块');
      this.setHelpUrl('这只是一个测试用例');
      this.setColour(255);
    }
  };
  Blockly.Blocks['C_Block'] = C_Block;
}

/**
 * 确保 workspace 的 container 和 toolbox 被创建
 * @param {*} root 新建 toolbox 和 container 插入锚点
 */
export const buildContainer = (containerId = 'blocklyDiv', toolboxId = 'toolbox') => {
  let toolbox = document.getElementById(toolboxId);
  if (toolbox) {
    toolbox.innerHTML = null;
  } else {
    toolbox = document.createElement('xml');
    toolbox.id = toolboxId;
    toolbox.style = "display: none;";
  }
  toolbox.innerHTML = `
          <category name="custom" colour="#230">
            <block type="my_custom_block"></block>
            <block type="combined_block"></block>
            <block type="C_Block"></block>
            <block type="my_block"></block>
          </category>
          <category name="Logic" colour="#5b80a5"> 
          <block type="controls_if"></block> 
          <block type="logic_compare"> 
            <field name="OP">EQ</field> 
          </block> 
          <block type="logic_operation"> 
            <field name="OP">AND</field> 
          </block> 
          <block type="logic_negate"></block> 
          <block type="logic_boolean"> 
            <field name="BOOL">TRUE</field> 
          </block> 
          <block type="logic_null"></block> 
          <block type="logic_ternary"></block> 
        </category> 
        <category name="Loops" colour="#5ba55b"> 
          <block type="controls_repeat_ext"> 
            <value name="TIMES"> 
              <shadow type="math_number"> 
                <field name="NUM">10</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="controls_whileUntil"> 
            <field name="MODE">WHILE</field> 
          </block> 
          <block type="controls_for"> 
            <field name="VAR" id="W4T~@+9%MGU@6@Bmao1)">i</field> 
            <value name="FROM"> 
              <shadow type="math_number"> 
                <field name="NUM">1</field> 
              </shadow> 
            </value> 
            <value name="TO"> 
              <shadow type="math_number"> 
                <field name="NUM">10</field> 
              </shadow> 
            </value> 
            <value name="BY"> 
              <shadow type="math_number"> 
                <field name="NUM">1</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="controls_forEach"> 
            <field name="VAR" id="8Ic*U$XC+I3a7UGQ5NCS">j</field> 
          </block> 
          <block type="controls_flow_statements"> 
            <field name="FLOW">BREAK</field> 
          </block> 
        </category> 
        <category name="Math" colour="#5b67a5"> 
          <block type="math_number"> 
            <field name="NUM">0</field> 
          </block> 
          <block type="math_arithmetic"> 
            <field name="OP">ADD</field> 
            <value name="A"> 
              <shadow type="math_number"> 
                <field name="NUM">1</field> 
              </shadow> 
            </value> 
            <value name="B"> 
              <shadow type="math_number"> 
                <field name="NUM">1</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_single"> 
            <field name="OP">ROOT</field> 
            <value name="NUM"> 
              <shadow type="math_number"> 
                <field name="NUM">9</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_trig"> 
            <field name="OP">SIN</field> 
            <value name="NUM"> 
              <shadow type="math_number"> 
                <field name="NUM">45</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_constant"> 
            <field name="CONSTANT">PI</field> 
          </block> 
          <block type="math_number_property"> 
            <mutation divisor_input="false"></mutation> 
            <field name="PROPERTY">EVEN</field> 
            <value name="NUMBER_TO_CHECK"> 
              <shadow type="math_number"> 
                <field name="NUM">0</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_round"> 
            <field name="OP">ROUND</field> 
            <value name="NUM"> 
              <shadow type="math_number"> 
                <field name="NUM">3.1</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_on_list"> 
            <mutation op="SUM"></mutation> 
            <field name="OP">SUM</field> 
          </block> 
          <block type="math_modulo"> 
            <value name="DIVIDEND"> 
              <shadow type="math_number"> 
                <field name="NUM">64</field> 
              </shadow> 
            </value> 
            <value name="DIVISOR"> 
              <shadow type="math_number"> 
                <field name="NUM">10</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_constrain"> 
            <value name="VALUE"> 
              <shadow type="math_number"> 
                <field name="NUM">50</field> 
              </shadow> 
            </value> 
            <value name="LOW"> 
              <shadow type="math_number"> 
                <field name="NUM">1</field> 
              </shadow> 
            </value> 
            <value name="HIGH"> 
              <shadow type="math_number"> 
                <field name="NUM">100</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_random_int"> 
            <value name="FROM"> 
              <shadow type="math_number"> 
                <field name="NUM">1</field> 
              </shadow> 
            </value> 
            <value name="TO"> 
              <shadow type="math_number"> 
                <field name="NUM">100</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="math_random_float"></block> 
        </category> 
        <category name="Text" colour="#5ba58c"> 
          <block type="text"> 
            <field name="TEXT"></field> 
          </block> 
          <block type="text_join"> 
            <mutation items="2"></mutation> 
          </block> 
          <block type="text_append"> 
            <field name="VAR" id="t=e01]I(kFE;GF^|L[xE">item</field> 
            <value name="TEXT"> 
              <shadow type="text"> 
                <field name="TEXT"></field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_length"> 
            <value name="VALUE"> 
              <shadow type="text"> 
                <field name="TEXT">abc</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_isEmpty"> 
            <value name="VALUE"> 
              <shadow type="text"> 
                <field name="TEXT"></field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_indexOf"> 
            <field name="END">FIRST</field> 
            <value name="VALUE"> 
              <block type="variables_get"> 
                <field name="VAR" id="]i+*soCH@O.?{:Ho$po">text</field> 
              </block> 
            </value> 
            <value name="FIND"> 
              <shadow type="text"> 
                <field name="TEXT">abc</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_charAt"> 
            <mutation at="true"></mutation> 
            <field name="WHERE">FROM_START</field> 
            <value name="VALUE"> 
              <block type="variables_get"> 
                <field name="VAR" id="]i+*soCH@O.?{:Ho$po">text</field> 
              </block> 
            </value> 
          </block> 
          <block type="text_getSubstring"> 
            <mutation at1="true" at2="true"></mutation> 
            <field name="WHERE1">FROM_START</field> 
            <field name="WHERE2">FROM_START</field> 
            <value name="STRING"> 
              <block type="variables_get"> 
                <field name="VAR" id="]i+*soCH@O.?{:Ho$po">text</field> 
              </block> 
            </value> 
          </block> 
          <block type="text_changeCase"> 
            <field name="CASE">UPPERCASE</field> 
            <value name="TEXT"> 
              <shadow type="text"> 
                <field name="TEXT">abc</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_trim"> 
            <field name="MODE">BOTH</field> 
            <value name="TEXT"> 
              <shadow type="text"> 
                <field name="TEXT">abc</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_print"> 
            <value name="TEXT"> 
              <shadow type="text"> 
                <field name="TEXT">abc</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="text_prompt_ext"> 
            <mutation type="TEXT"></mutation> 
            <field name="TYPE">TEXT</field> 
            <value name="TEXT"> 
              <shadow type="text"> 
                <field name="TEXT">abc</field> 
              </shadow> 
            </value> 
          </block> 
        </category> 
        <category name="Lists" colour="#745ba5"> 
          <block type="lists_create_with"> 
            <mutation items="0"></mutation> 
          </block> 
          <block type="lists_create_with"> 
            <mutation items="3"></mutation> 
          </block> 
          <block type="lists_repeat"> 
            <value name="NUM"> 
              <shadow type="math_number"> 
                <field name="NUM">5</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="lists_length"></block> 
          <block type="lists_isEmpty"></block> 
          <block type="lists_indexOf"> 
            <field name="END">FIRST</field> 
            <value name="VALUE"> 
              <block type="variables_get"> 
                <field name="VAR" id="dB-;(eN*HU{H);~5[4WK">list</field> 
              </block> 
            </value> 
          </block> 
          <block type="lists_getIndex"> 
            <mutation statement="false" at="true"></mutation> 
            <field name="MODE">GET</field> 
            <field name="WHERE">FROM_START</field> 
            <value name="VALUE"> 
              <block type="variables_get"> 
                <field name="VAR" id="dB-;(eN*HU{H);~5[4WK">list</field> 
              </block> 
            </value> 
          </block> 
          <block type="lists_setIndex"> 
            <mutation at="true"></mutation> 
            <field name="MODE">SET</field> 
            <field name="WHERE">FROM_START</field> 
            <value name="LIST"> 
              <block type="variables_get"> 
                <field name="VAR" id="dB-;(eN*HU{H);~5[4WK">list</field> 
              </block> 
            </value> 
          </block> 
          <block type="lists_getSublist"> 
            <mutation at1="true" at2="true"></mutation> 
            <field name="WHERE1">FROM_START</field> 
            <field name="WHERE2">FROM_START</field> 
            <value name="LIST"> 
              <block type="variables_get"> 
                <field name="VAR" id="dB-;(eN*HU{H);~5[4WK">list</field> 
              </block> 
            </value> 
          </block> 
          <block type="lists_split"> 
            <mutation mode="SPLIT"></mutation> 
            <field name="MODE">SPLIT</field> 
            <value name="DELIM"> 
              <shadow type="text"> 
                <field name="TEXT">,</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="lists_sort"> 
            <field name="TYPE">NUMERIC</field> 
            <field name="DIRECTION">1</field> 
          </block> 
        </category> 
        <category name="Colour" colour="#a5745b"> 
          <block type="colour_picker"> 
            <field name="COLOUR">#ff0000</field> 
          </block> 
          <block type="colour_random"></block> 
          <block type="colour_rgb"> 
            <value name="RED"> 
              <shadow type="math_number"> 
                <field name="NUM">100</field> 
              </shadow> 
            </value> 
            <value name="GREEN"> 
              <shadow type="math_number"> 
                <field name="NUM">50</field> 
              </shadow> 
            </value> 
            <value name="BLUE"> 
              <shadow type="math_number"> 
                <field name="NUM">0</field> 
              </shadow> 
            </value> 
          </block> 
          <block type="colour_blend"> 
            <value name="COLOUR1"> 
              <shadow type="colour_picker"> 
                <field name="COLOUR">#ff0000</field> 
              </shadow> 
            </value> 
            <value name="COLOUR2"> 
              <shadow type="colour_picker"> 
                <field name="COLOUR">#3333ff</field> 
              </shadow> 
            </value> 
            <value name="RATIO"> 
              <shadow type="math_number"> 
                <field name="NUM">0.5</field> 
              </shadow> 
            </value> 
          </block> 
        </category> 
        <sep></sep> 
        <category name="Variables" colour="#a55b80" custom="VARIABLE"></category> 
        <category name="Functions" colour="#995ba5" custom="PROCEDURE"></category>
        `
  // document.body.insertBefore(toolbox, root);
  document.body.appendChild(toolbox);

  let container = document.getElementById(containerId);

  if (container) {
    container.innerHTML = null;
  } else {
    container = document.createElement('div');
    container.id = containerId;
    container.style = "height: 100vh; width: 100%;";
    document.body.appendChild(container);
    // document.body.insertBefore(container, root);
  }
}

/**
 * 在指定位置创建一个画布
 * @param {string} containerId container id 默认值为 'blocklyDiv'
 * @param {string} toolboxId toolbox id 默认值为 'toolbox'
 * @returns workspace 绘图空间/画布
 */
export const initSpace = (containerId = 'blocklyDiv', toolboxId = 'toolbox') => {
  // 将 workspace 注入到 id 为 blocklyDiv 的容器中
  const workspace = Blockly.inject(containerId, {
    toolbox: document.getElementById(toolboxId) // 指定 toolbox xml 格式
  })

  workspace.registerToolboxCategoryCallback('MY_CATEGORY', function () {
    return [
      { kind: 'block', type: 'my_block' }
    ];
  });
  // 返回 workspace
  return workspace;
}

/**
 * 清除画布上的内容
 * @param {*} workspace 清除目标
 */
export const clearSpace = (workspace) => {
  // 清空工作区的内容
  workspace.clear();
}

/**
 * 将画布存档加载到指定的 workspace 中去
 * @param {*} workspace 目标画布
 * @param {string} xmlText 加载内容
 */
export const loadSpace = (workspace, xmlText) => {
  xmlText ??= `<xml xmlns="https://developers.google.com/blockly/xml"><block type="text_print" id="%nkw(EK0gme?jpRyFt6c" x="417" y="120"><value name="TEXT"><shadow type="text" id="f]JRs#?(A]75a8|~b~}c"><field name="TEXT">abc</field></shadow></value></block></xml>`;
  // 需要将保存的 xmlText 转为 xml dom 对象
  const xml = Blockly.Xml.textToDom(xmlText);
  // 回显数据
  Blockly.Xml.domToWorkspace(xml, workspace);
}

/**
 * 保存画布内容
 * @param {*} workspace 
 * @returns code: 生成出来的代码; xml:工作区目前编辑的 xml dom 对象; xmlText: xml dom 对象转 text
 */
export const saveSpace = (workspace) => {
  // code: 生成出来的代码
  // xml:工作区目前编辑的 xml dom 对象
  // xmlText: xml dom 对象转 text
  const code = Blockly.JavaScript.workspaceToCode(workspace);
  const xml = Blockly.Xml.workspaceToDom(workspace);
  const xmlText = Blockly.Xml.domToText(xml);

  return {
    code,
    xml,
    xmlText,
  }
}

2. Vue2 项目中使用示例

  <div 
    id="blocklyDiv2" 
    style="height: 480px; width: 100%; border: none; overflow: hidden; margin-top: 24px;"
  ></div>
import {
  loadCustomBlocks,
  buildContainer,
  initSpace,
  clearSpace,
  loadSpace,
} from "@/externals/blockly.js";
import * as Blockly from "@/externals/blockly.js";

window.blc = Blockly;

export default {
  mounted(){
    loadCustomBlocks()
    buildContainer('blocklyDiv2', 'toolbox')
    const workspace = window.workspace = initSpace('blocklyDiv2', 'toolbox')
    clearSpace(workspace);
    loadSpace(workspace)
  },
}

上面的代码很容易封装成一个 mixin 如果在 vue3 中,那么自然可以封装成一个 hook 就像下面的 React 示例一样。

3. React 项目中使用示例

创建一个 hook:

import { useEffect } from "react";
import {
  loadCustomBlocks,
  buildContainer,
  initSpace,
  clearSpace,
  loadSpace,
} from "./utils/blockly";
import * as Blockly from "./utils/blockly";

window.blc = Blockly;

export const useBlockly = (blockId = 'blocklyDiv', toolboxId = 'toolbox') => {
  useEffect(() => {
    loadCustomBlocks()
    buildContainer(blockId, toolboxId)
    const workspace = window.workspace = initSpace(blockId, toolboxId)
    clearSpace(workspace);
    loadSpace(workspace)

    return () => {
      document.getElementById(blockId)?.remove();
      document.getElementById(toolboxId)?.remove();
    }
  }, [])
}

在相应的视图组件中使用这个 hook:

// App.js
import './App.css';
import {useBlockly} from "./hooks";

function App() {
  useBlockly();

  return (
    <div className="App">
      <div id="blocklyDiv" style={{height: "100vh", width: "100%"}}></div>
    </div>
  );
}

export default App;

http://www.kler.cn/a/473094.html

相关文章:

  • 基于html5实现音乐录音播放动画源码
  • 第四、五章补充:线代本质合集(B站:小崔说数)
  • 数据结构(1~10)
  • 爬虫学习记录
  • Electron快速入门——跨平台桌面端应用开发框架
  • window CMD大全
  • 沁恒CH32V208GBU6定时器:开启定时器读取RSSI并且定时器单位为广播间隔单位一样;自动重装载定时器与关闭定时器
  • Elasticsearch(三)
  • 解决SSH连接时遇到的“远程主机身份验证已更改 (WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!)”警告
  • 八万字Java面试高频题目汇总(冲刺春招!)
  • Lua语言的函数实现
  • Objective-C语言的文件操作
  • wireshark抓包工具新手使用教程
  • .NET Core + Kafka 开发指南
  • MySQL 数据库分片技术指南
  • 数据库中锁与ETL的故障排除和性能优化
  • 【微服务】8、分布式事务 ( XA 和 AT )
  • Perl语言的文件操作
  • DeviceNet转Profinet网关如何革新污水处理行业!
  • tomcat12启动流程源码分析
  • adb使用及常用命令
  • JavaEE之定时器及自我实现
  • 闲谭SpringBoot--ShardingSphere分库分表探究
  • spring mvc源码学习笔记之一
  • Java高频面试之SE-10
  • 单片机-外部中断