npm package: @ng-blockly/custom
Description: 为当前项目做的个性化定制,依赖于@ng-blockly/**blockly包**
Author: Xiaoxiang.Chen
Latest version: 0.0.16

# @ng-blockly/custom
为当前项目做的个性化定制,依赖于@ng-blockly/**blockly包**

例如：修改卡槽形状、修改toolbox样式、toolbox目录下的block为异步加载、下拉列表的背景色与sourceBlock的颜色相同、自定义支持模糊查询的单选/多选下拉列表、自定义的逻辑判断表达式、自定义主题 等等

### 安装

Install from npm repository:

```
npm install @ng-blockly/blockly --save && npm install @ng-blockly/custom --save  
or you can: ng add @ng-blockly/custom, this will install @ng-blockly/custom, @ng-blockly/blockly and generate a custom blockly template.
```

只需要在AppModule中导入NgCustomBlocklyModule一次。
推荐使用自定义主题，或者使用内置的主题，new DarkTheme().DarkTheme
```typescript
public config: NgBlocklyConfig = {
  theme: new DarkTheme().DarkTheme,
  scrollbars: true, // 工作区域可滚动
  trashcan: true, // 显示或隐藏垃圾桶
  media: '/assets/blockly/media/', // blockly媒体路径---默认路径访问不到，需要翻墙
};
```

样式覆盖
```scss
.blocklyToolboxDiv {
  background-color: #eee;
}

.blocklyHidden {
  display: none !important;
}
.blocklyTreeRow {
  height: 40px;
  display: flex;
  align-items: center;
  margin-bottom: 0;
  padding-right: 16px;
}
// 目录展开
.app-blockly-expanded {
  i {
    transform: rotateX(180deg);
    transition: all .3s;
  }
}
// 目录闭合
.app-blockly-collapsed {
  i {
    transform: rotateX(0deg);
    transition: all .3s;
  }
}
.app-blockly-selected {
  i {
    color: #fff;
  }
}
// flyout背景色
.blocklyFlyoutBackground {
  fill: #4b4949!important;
}
// flyout中label的颜色
.blocklyFlyoutLabelText {
  fill: #fff;
}
// mutator图标的背景色
.blocklyIconShape {
  fill: #aaa;
}
.blocklyDropDownDiv {
  .goog-menuitem {
    color: #fff;
    &:hover {
      background: #4b4949;
      opacity: .4;
    }
  }
  .goog-menuitem-content {
    color: unset;
  }
}

// 修改浏览器滚动条默认样式
::-webkit-scrollbar {
  width: 0.25rem;
  height: 0.25rem;
  // background-image: linear-gradient(135deg, #1DE9B6 0%, rgba(8, 196, 219, 0.5) 72%, rgba(0, 182, 234, 0.3) 100%);
}
::-webkit-scrollbar-track {
  border-radius: 0;
}
::-webkit-scrollbar-thumb {
  border-radius: 0;
  background: #ccc;
  transition: all .2s;
  border-radius: 0.25rem;
}
::-webkit-scrollbar-thumb:hover {
  background-color: rgba(95, 95, 95, 0.7);
}

// json格式化展示
.string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }

:focus {
  outline-width: 0;
}
// blockly ul
.app-blockly-ul {
  display: flex;
  height: 100%;
  padding-inline-start: 0;
  margin-bottom: 0;
  background: #fff;
  overflow-x: scroll;
  overflow-y: hidden;
  &::-webkit-scrollbar {
    width: 0;
    height: 0;
  }
}
.app-blockly-li {
  display: flex;
  height: 100%;
  flex: 0 0 auto;
  list-style-type:none;
  background: #fafafa;
  color: rgba(0,0,0,.65);
  border-left: 1px solid #e8e8e8;
  border-right: 1px solid #e8e8e8;
  margin-right: 4px;
  &:last-child {
    margin-right: 0;
    border: none;
  }
  input {
    height: 100%;
    background: none;
    border: none;
    width: .75em;
    &:focus {
      outline-width: 0;
    }
  }
}
```

### 异步加载目录
即异步请求目录的数据，再将其转化为blockly需要的xml结构，转化为xml过程中可以给目录指定{commonUrl: string, activeUrl: string},分别代表目录在未选中，和选中下显示的图标。
将目录的code值写入xml字符串中，以便点击目录时，可以获取到code值，从而根据code值发送异步请求。
```typescript
jsonToXml(categoryes: any[] = []) {
  for (let i = 0, len = categoryes.length ; i < len; i++) {
    if (categoryes[i].name) {
      (Blockly.tree.BaseNode.prototype.categoriesInObject || {})[categoryes[i].name] = {
        commonUrl: categoryes[i].commonUrl,
        activeUrl: categoryes[i].activeUrl
      };
    }
    this.categoriesInString += `<category code="${categoryes[i].code}" name="${categoryes[i].name}" colour="${categoryes[i].color || generateColor()}">`;
    const children = categoryes[i].tagClassChildren || [];
    if (children.length > 0) {
      this.jsonToXml(children);
    }
    this.categoriesInString += '</category>';
  }
}
```

### toolbox目录下的block为异步加载
自行实现Blockly.Toolbox.prototype.loadVariable方法，该方法在点击目录中某一项时触发。
```typescript
@ViewChild(NgBlocklyComponent, { static: true }) workspace: NgBlocklyComponent;

Blockly.Toolbox.prototype.loadVariable = (node) => {
  const categoryColour = node.hexColour;
  const categoryName = node.content_;
  const categoryCode = node.code;
  if (categoryCode && node.blocks.length === 0) {
    // 异步加载blocks(此处为伪代码，思路是发起异步请求，拿到结果后，生成flyout中的xml表示，然后调用refreshSelection)
    .......
      const treeControl = this.workspace.workspace.getToolbox().tree_; // 每次调用renderTree都会生成新的TreeControl
      const preSelectedItem = treeControl.getSelectedItem();
      if (rsp[0]) {
        const xml = Blockly.Xml.textToDom(`<xml>
        ...........
        </xml>`);
        preSelectedItem.blocks.push(...xml.children);
        this.workspace.workspace.getToolbox().refreshSelection();
      }
  }
};
```

### AndOrBlock BlocklySelfAddMutator
![sparkles](./assets/imgs/and-or-block.jpg)
```typescript
new AndOrBlock('logic_block_self_add', null, new BlocklySelfAddMutator('blockly_self_add_mutator', ['block_self_boolean']))
```

## ValuesDropDownBlock SelfSelectorField
支持模糊查询及单选/多选模式的下拉列表
![sparkles](./assets/imgs/value-dropdown-block.jpg)
```typescript
new ValuesDropDownBlock(dropdownBlockType, null, null, extensionName, code, categoryColour);
Blockly.Extensions.register(extensionName,
  function() {
    this.getInput('INPUT')
      **SelfSelectorField的第一个参数是下拉列表的值[['男', '1'], ['女', '0']],第二个参数为当前选中的值，第四个参数为是否是多选模式**
      .appendField(new Blockly.SelfSelectorField(labels, fieldValue, this, false), 'NAME');
  });
```

## memory recovery
组件卸载时，一定要注销使用过的自定义的filed或者extensions，以免内存泄漏
```typescript
@ViewChild(NgBlocklyComponent, { static: true }) workspace: NgBlocklyComponent;
...
...
Blockly.Extensions.unregister('blockly_self_add_mutator');
Blockly.fieldRegistry.unregister('self-selector');
Blockly.tree.BaseNode.prototype.categoriesInObject = null;
Blockly.utils.IdGenerator.nextId_ = 0;
this.workspace.workspace.dispose();
```
## 联系我
目前，该项目不是开源的，如果有问题可以联系我，xiaoxiang930601@163.com