npm package: @ng-blockly/blockly
Description: An angular wrapper for google's blockly.
Author: xiaoxiang930601
Latest version: 0.0.16

# @ng-blockly/blockly

对可视化编程语言blockly做的一个angular的简单封装

### 安装

Install from npm repository:
```
npm install @ng-blockly/blockly --save
```

Add the blockly scripts and assets in `angular.json`

```json
"assets": [
  {
    "glob": "**/*",
    "input": "./node_modules/@ng-blockly/blockly/scripts/",
    "output": "/assets/"
  }
]

"scripts": [
  "node_modules/@ng-blockly/blockly/scripts/blockly/blockly_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/blocks_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/python_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/javascript_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/php_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/dart_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/lua_compressed.js",
  "node_modules/@ng-blockly/blockly/scripts/blockly/msg/js/zh-hans.js"
]
```

Or you can use:
```
ng add @ng-blockly/blockly
```
in this way, angular.json will be changed automatically like above.

### 例子
Example app.module.ts
```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { NgBlocklyModule } from '@ng-blockly/blockly';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    NgBlocklyModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}
```
'Example app.component.ts'
```typescript
import {NgBlocklyConfig } from '@ng-blockly/blockly';

export class AppComponent {
    
    public config: NgBlocklyConfig = {
        toolbox: '<xml id="toolbox" style="display: none">' +
                    '<block type="controls_if"></block>' +
                    '<block type="controls_repeat_ext"></block>' +
                    '<block type="logic_compare"></block>' +
                    '<block type="math_number"></block>' +
                    '<block type="math_arithmetic"></block>' +
                    '<block type="text"></block>' +
                    '<block type="text_print"></block>' +
                 '</xml>',
        scrollbars: true,
        trashcan: true,
        media: '/assets/blockly/media/'
    };
}
```
Example app.component.ts
```html
<ng-blockly [config]="config"></ng-blockly>

```
### Styling
```scss
ng-blockly {
  position: absolute;
  width: 100%;
  height: 100%;
}
```

### 配置 
Default Blockly Configuration (see https://developers.google.com/blockly/guides/get-started/web#configuration)
```typescript
export class NgBlocklyConfig {
    collapse?: boolean; // Allows blocks to be collapsed or expanded. Defaults to true if the toolbox has categories, false otherwise.
    comments?: boolean; // Allows blocks to have comments. Defaults to true if the toolbox has categories, false otherwise.
    css?: boolean; // If false, don't inject CSS (providing CSS becomes the document's responsibility). Defaults to true.
    disable?: boolean; // If false, don't inject CSS (providing CSS becomes the document's responsibility). Defaults to true.
    grid?: {
      spacing: number,
      length: number,
      colour: string,
      snap: boolean
    };
    horizontalLayout?: boolean; // If true toolbox is horizontal, if false toolbox is vertical. Defaults to false.
    maxBlocks?: number; // 	Maximum number of blocks that may be created. Useful for student exercises. Defaults to Infinity.
    maxInstances?: object; // Map from block types to maximum number of blocks of that type that may be created. Undeclared types default to Infinity.
    media?: string; // Path from page (or frame) to the Blockly media directory. Defaults to "https://blockly-demo.appspot.com/static/media/"
    oneBasedIndex?: boolean; // If true list and string operations should index from 1, if false index from 0. Defaults to true.
    readOnly?: boolean; // If true, prevent the user from editing. Supresses the toolbox and trashcan. Defaults to false.
    rtl?: boolean; // If true, mirror the editor (for Arabic or Hebrew locales). Defaults to false.
    scrollbars?: boolean; // Sets whether the workspace is scrollable or not. Defaults to true if the toolbox has categories, false otherwise
    sounds?: boolean; // If false, don't play sounds (e.g. click and delete). Defaults to true.
    theme?: any; // Defaults to classic theme if no theme is provided. (https://developers.google.com/blockly/guides/configure/web/themes)
    toolbox?: string; // Tree structure of categories and blocks available to the user
    toolboxPosition?: string; // If "start" toolbox is on top (if horizontal) or left (if vertical and LTR) or right (if vertical and RTL). If "end" toolbox is on opposite side. Defaults to "start".
    trashcan?: boolean; // Displays or hides the trashcan. Defaults to true if the toolbox has categories, false otherwise.
    maxTrashcanContents?: number; // Maximum number of deleted items that will appear in the trashcan flyout. '0' disables the feature. Defaults to '32'.
    zoom?: {
      controls: boolean,
      wheel: boolean,
      startScale: number,
      maxScale: number,
      minScale: number,
      scaleSpeed: number
    };

}
```
Blockly Generator 配置
```typescript
export class NgBlocklyGeneratorConfig {
    dart?: boolean;
    javascript?: boolean;
    lua?: boolean;
    php?: boolean;
    python?: boolean;
    xml?: boolean
}

```
### 生成代码
```typescript
import { Component } from '@angular/core';
import { NgBlocklyConfig, NgBlocklyGeneratorConfig } from '@ng-blockly/blockly';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent {

    public config: ngBlocklyConfig = {
        toolbox: '<xml id="toolbox" style="display: none">' +
            '<block type="controls_if"></block>' +
            '<block type="controls_repeat_ext"></block>' +
            '<block type="logic_compare"></block>' +
            '<block type="math_number"></block>' +
            '<block type="math_arithmetic"></block>' +
            '<block type="text"></block>' +
            '<block type="text_print"></block>' +
            '</xml>',
        scrollbars: true,
        trashcan: true
    };


    public generatorConfig: NgBlocklyGeneratorConfig = {
        dart: true,
        javascript: true,
        lua: true,
        php: true,
        python: true,
        xml: true
    };

    onCode(code: string) {
        console.log(code);
    }
}

```
```html
<ng-blockly [config]="config" [generatorConfig]="generatorConfig" (javascriptCode)="onCode($event)" (pythonCode)="onCode($event)"></ng-blockly>

```
### Import/Export Blockly Project
```
    @ViewChild(NgBlocklyComponent) workspace;

    // 返回当前workspace的xml
    this.workspace.toXml();

    // 从xml复原到workspace (会先清空workspace)
    this.workspace.fromXml(xml);

    // 从xml复原到workspace(不会先清空workspace)
    this.workspace.appendFromXml(xml);
```

### Blockly Toolbox
XML-Definition
```typescript
public config: NgBlocklyConfig = {
        toolbox: '<xml id="toolbox" style="display: none">' +
            '<block type="controls_if"></block>' +
            '<block type="controls_repeat_ext"></block>' +
            '<block type="logic_compare"></block>' +
            '<block type="math_number"></block>' +
            '<block type="math_arithmetic"></block>' +
            '<block type="text"></block>' +
            '<block type="text_print"></block>' +
            '</xml>',
        scrollbars: true,
        trashcan: true
    };
```

Toolbox Generator
```typescript
    public customBlocks: CustomBlock[] = [
        new TestBlock('test', null, null),
        new DeviceBlock('device', null, null)
    ];


    constructor(ngToolboxBuilder: NgToolboxBuilderService) {
        ngToolboxBuilder.nodes = [
                   new Category(this.customBlocks, '#FF00FF', 'Test', null),
                   LOGIC_CATEGORY,
                   LOOP_CATEGORY,
                   MATH_CATEGORY,
                   TEXT_CATEGORY,
                   new Separator(), //Add Separator
                   LISTS_CATEGORY,
                   COLOUR_CATEGORY,
                   VARIABLES_CATEGORY,
                   FUNCTIONS_CATEGORY
        ];
        this.config.toolbox = ngToolboxBuilder.build();
    }
```
```html
    # do not forget to add your customblocks
   <ng-blockly [config]="config" [customBlocks]="customBlocks" [generatorConfig]="generatorConfig"  (javascriptCode)="onCode($event)"></ng-blockly>
```

### 自定义 Block
```typescript
declare var Blockly: any;

export class TestBlock extends CustomBlock {


    constructor(type: string, block: any, blockMutator: BlockMutator, ...args: any[]) {
        super(type, block, blockMutator, ...args);
        this.class = TestBlock;
    }

    defineBlock() {
        this.block.appendDummyInput()
            .appendField(this.type)
            .appendField(new Blockly.FieldImage('assets/testblock.png', 50, 50, '*'));
            .appendField(new Blockly.FieldImage(this.args[0], 50, 50, '*'));
        this.block.setOutput(true, 'Input');
        this.block.setColour(30);
        this.block.setTooltip('');
        this.block.setHelpUrl('');
    }

    toXML() {
        return '<block type="test"></block>';
    }


    onChange(changeEvent: any) {
        console.log(changeEvent);
    }
}
```



### 主题
Customized theme can be specified in the `theme` option for `ngBlocklyConfig`, and the format of the block style and category style is stated in the [Themes](https://developers.google.com/blockly/guides/configure/web/themes).

Sample theme class (Using Google Blockly [Classic theme](https://github.com/google/blockly/blob/master/core/theme/classic.js)):
```typescript
export const blockStyles: BlockStyles = {
  logic_blocks: {
    colourPrimary: '210',
  },
};

export const categoryStyles: CategoryStyles = {
  logic_category: {
    colour: '210',
  },
}

export const ClassicTheme: Theme = new Theme (
  blockStyles,
  categoryStyles
)
```

When you have specified the theme used in Blockly workspace, you need to declare the corresponding block style/category style in block/category definition. Noted that once you have defined the theme option in `ngBlocklyConfig`, then you need to manage color scheme of all blocks and categories.

Block Styling
```typescript
{
  "type": "controls_if",
  "style": "logic_blocks", // Specify the block style to apply
}
```

Category Styling
```xml
<!-- Specify the category style to apply -->
<category name="Logic" categorystyle="logic_category">
</category>
```

Corresponding NgBlocklyConfig
```typescript
config: NgBlocklyConfig = {
    theme: ClassicTheme.createBlocklyTheme(),
  };
```

### 自定义
前往查看@ng-blockly/custom https://www.npmjs.com/package/@ng-blockly/custom

### 联系我
目前，该项目不是开源的，如果有问题可以联系我，xiaoxiang930601@163.com