CloudEditor in Twaver HTML5 is rewritten in Angular2

言月
中文

CloudEditor in Twaver HTML5 is rewritten in Angular2

background

The business progress is pressing, so it took two days to rewrite Angular2 of twaver's CloudEditor to realize the introduction of twaver's initial view structure;

First acquaintance with twaver

Twaver is a commercial closed source drawing engine tool. Similar open source products include mxgraph, jointjs, raphael, etc.;

Reason for rewriting

  • advantage

    • Do not increase the introduction of tripartite parts, twaver already exists on the current train version of manageone, which can be used directly;
    • In line with business scenarios, twaver officially provides samples of currently developed application scenarios and the official samples are abundant;
    • Functional stability has been verified. Some products of the company have already used its functions to create more complex scenarios. After communication, it is the first time to judge that the secondary development is not a big problem;
    • Angular2 framework is compatible, twaver's technology stack uses native js to achieve seamless integration with the current Angular2 framework;
  • shortcoming

    • In the official demo, a large number of jquery libraries are used to operate dom, and the jqueryUI library implements UI components and styles. These additional tripartite functions need to be stripped and removed for the first introduction;
    • There is no source code, which is not conducive to debugging and troubleshooting;
    • Familiarity is low, no one in the current group knows twaver;

The main content of CloudEditor:

|-- CloudEditor
    |-- CloudEditor.html
    |-- css
    |   |-- bootstrap.min.css
    |   |-- jquery-ui-1.10.4.custom.min.css
    |   |-- jquery.ui.all.css
    |   |-- images
    |       |-- animated-overlay.gif
    |-- images
    |   |-- cent32os_s.png
    |   |-- zoomReset.png
    |-- js
        |-- AccordionPane.js
        |-- category.js
        |-- editor.js
        |-- GridNetwork.js
        |-- images.js
        |-- jquery-ui-1.10.4.custom.js
        |-- jquery.js

The main guidelines for rewriting:

  • The output files are implemented in Typescript language, and type declaration files are added;
  • Strip the operation of directly operating dom, that is, remove the jquery library;
  • Rewrite the long grammar in twaver, ES6 grammar transformation;

Left tree menu

The left tree menu in CloudEditor is mainly a list of accordion effects. Its implementation is to use the file AccordionPanel.js. Its content is to dynamically generate the content of the right panel using dynamic splicing dom; we use the template feature of Angular and rewrite it as The Angular component menu removes all the inefficient operations of the original JS operation dom.

AccorditonPanel analysis

// 这里声明了一个editor命名空间下的函数变量AccordionPane
editor.AccordionPane = function() {
 this.init();
};
// 内部方法基本都是为了生成左树菜单结构,如下方法
createView: function() {
    var rootView = $('<div id="accordion-resizer" class="ui-widget-content"></div>');
    this.mainPane = $('<div id="accordion"></div>');
    this.setCategories(categoryJson.categories);
    rootView.append(this.mainPane);
    return rootView[0];
},
  // 生成菜单标题
  initCategoryTitle: function(title) {
    var titleDiv = $('<h3>' + title + '</h3>');
    this.mainPane.append(titleDiv);
  },
  // 生成菜单内容
  initCategoryContent: function(datas) {
    var contentDiv = $('<ul class="mn-accordion"></ul>');
    for (var i = 0; i < datas.length; i++) {
      var data = datas[i];
      contentDiv.append(this.initItemDiv(data));
    }
    this.mainPane.append(contentDiv);
  },
  // 生成菜单项
  initItemDiv: function(data) {
    var icon = data.icon;
    var itemDiv = $('<li class="item-li"></li>');
    var img = $('<img src=' + icon + '></img>');
    img.attr('title', data.tooltip);
    var label = $('<div class="item-label">' + data.label + '</div>');
    itemDiv.append(img);
    itemDiv.append(label);

    this.setDragTarget(img[0], data);
    return itemDiv;
  },

Rewrite structure using tiny components

<div id='left-tree-menu'>
  <tp-accordionlist [options]="menuData">
      <!--自定义面板内容-->
      <ng-template #content let-menuGroup let-i=index>
        <div *ngFor="let item of menuGroup.contents" [id]="item.label" class="item"
            [attr.data-type]="item.type" [attr.data-width]="item.width" [attr.data-height]="item.height"
            [attr.data-os]="item.os" [attr.data-bit]="item.bit" [attr.data-version]="item.version" 
            [title]="item.tooltip"> 
          <img [src]="item.icon" (dragstart)="dragStartMenuItem($event, item)"/>
          <div class="item-label">{{item.label}}</div>
        </div>
      </ng-template>
  </tp-accordionlist>
</div>

Component logic after rewriting

Mainly deal with the mapping relationship between the data model and the UI component model

import { Component, Input, OnInit } from '@angular/core';
import { TpAccordionlistOption } from '@cloud/tinyplus3';

@Component({
  selector: 'design-menu',
  templateUrl: './menu.component.html',
  styleUrls: ['./menu.component.less']
})
export class MenuComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }
  @Input() set inputMenuData(v) {
    setTimeout(() => {
      this.menuData = this.b2uMenuData(v.categories);
    });
  }
  menuData:TpAccordionlistOption[] = [];
  categories: any[];

  /**
   * 设置菜单项数据
   * @param categories 菜单数据列表
   */
  setCategories(categories) {
    this.categories = categories;
  }

  /**
   * 菜单项数据转换为UI组件数据
   * @param bData 菜单模型数据
   * @returns 手风琴UI组件数据
   */
  b2uMenuData(bData: Array<any>): Array<TpAccordionlistOption>{ 
    return bData.map((item, i) => {
      let tpAccordionlistOption: TpAccordionlistOption = {};
      tpAccordionlistOption.disabled = false;
      tpAccordionlistOption.headLabel = item.title;
      tpAccordionlistOption.open = !Boolean(i);
      tpAccordionlistOption.headClick = () => { };
      tpAccordionlistOption.contents = [...item.contents];
      tpAccordionlistOption.actionmenu = {
        items: []
      };
      return tpAccordionlistOption;
    });
  }
  /**
   * 拖拽菜单项功能
   * @param event 拖拽事件
   * @param data 拖拽数据
   */
  dragStartMenuItem(event, data) {
    data.draggable = true;
    event.dataTransfer.setData("Text", JSON.stringify(data));
  }
}

Draw the stage

The realization of the stage in CloudEditor is to use the file GridNetwork.js; the stage is realized by extending twaver.vector.Network

GridNetwork analysis

In this file, the core functions related to the stage are mainly implemented, such as drag and drop events, navigation pane, simple property panel, etc.

The refactoring of this file requires the addition of a large number of type declarations to ensure the normal use of ts type inference. In this part, I maintain the greatest restraint, try to avoid using any types, and add declarations for known types.

Missing type declaration

declare interface Window {
  twaver: any;
  GAP: number;
}
declare var GAP: number;
declare interface Document { 
  ALLOW_KEYBOARD_INPUT: any;
}
declare namespace _twaver { 
  export var html: any;
  export class math {
    static createMatrix(angle, x, y);
  }
}
declare namespace twaver { 
  export class Util { 
    static registerImage(name: string, obj: object);
    static isSharedLinks(host: any, element: any);
    static moveElements(selections, xoffset, yoffset, flag: boolean);
  }
  export class Element { 
    getLayerId();
    getImage();
    getHost();
    getLayerId();
    setClient(str, flag: boolean);
  }
  export class Node { 
    getImage();
  }
  export class ElementBox {
    getLayerBox(): twaver.LayerBox;
    add(node: twaver.Follower| twaver.Link);
    getUndoManager();
    addDataBoxChangeListener(fn: Function);
    addDataPropertyChangeListener(fn: Function);
    getSelectionModel();
  }
  export class SerializationSettings { 
    static getStyleType(propertyName);
    static getClientType(propertyName);
    static getPropertyType(propertyName);
  }
  export class Follower { 
    constructor(obj: any);
    setLayerId(id: string);
    setHost(host: any);
    setSize(w: boolean, h: boolean);
    setCenterLocation(location: any);
    setVisible(visible:boolean);
  }
  export class Property { }
  export class Link { 
    constructor(one, two);
    getClient(name: string);
    getFromNode();
    getToNode();
    setClient(attr, val);
    setStyle(attr, val);
  }
  export class Styles { 
    static setStyle(attr: string, val: any);
  }
  export class List extends Set { }
  export class Layer{ 
    constructor(name: string);
  }
  export class LayerBox { 
    add(box: twaver.Layer, num?: number);
  }
  export namespace controls { 
    export class PropertySheet { 
      constructor(box: twaver.ElementBox);
      getView(): HTMLElement;
      setEditable(editable: boolean);
      getPropertyBox();
    }
  }
  export namespace vector { 
    export class Overview { 
      constructor(obj: any);
      getView(): HTMLElement;
    }
    export class Network { 
      invalidateElementUIs();
      setMovableFunction(fn:Function);
      getSelectionModel();
      removeSelection();
      getElementBox(): twaver.ElementBox;
      setKeyboardRemoveEnabled(keyboardRemoveEnabled: boolean);
      setToolTipEnabled(toolTipEnable: boolean);
      setTransparentSelectionEnable(transparent: boolean);
      setMinZoom(zoom:number);
      setMaxZoom(zoom:number);
      getView();
      setVisibleFunction(fn: Function);
      getLabel(data: twaver.Link | { getName();});
      setLinkPathFunction(fn:Function);
      getInnerColor(data: twaver.Link);
      adjustBounds(obj: any);
      addPropertyChangeListener(fn: Function);
      getElementAt(e: Event | any): twaver.Element;
      setInteractions(option: any);
      getLogicalPoint(e: Event | any);
      getViewRect();
      setViewRect(x,y,w,h);
      setDefaultInteractions();
      getZoom();
      // 如下页面用到的私有属性,但在api中为声明
      __button;
      __startPoint;
      __resizeNode;
      __originSize;
      __resize;
      __createLink;
      __fromButton;
      __dragging;
      __currentPoint;
      __focusElement;
    }
  }
}

The rewritten stage.ts file (the unchanged code is omitted in this article)

export default class Stage extends twaver.vector.Network {
  constructor(editor) { 
    super();
    this.editor = editor;
    this.element = this.editor.element;
    twaver.Styles.setStyle('select.style', 'none');
    twaver.Styles.setStyle('link.type', 'orthogonal');
    twaver.Styles.setStyle('link.corner', 'none');
    twaver.Styles.setStyle('link.pattern', [8, 8]);
    this.init();
  }
  editor;
  element: HTMLElement;
  box: twaver.ElementBox;
  init() { 
    this.initListener();
  }
  initOverview () {
  }
  sheet;
  sheetBox;
  initPropertySheet () {
  }
  getSheetBox() { 
    return this.sheetBox;
  }
  infoNode;
  optionNode;
  linkNode;
  fourthNode;
  initListener() {
    _twaver.html.addEventListener('keydown', 'handle_keydown', this.getView(), this);
    _twaver.html.addEventListener('dragover', 'handle_dragover', this.getView(), this);
    _twaver.html.addEventListener('drop', 'handle_drop', this.getView(), this);
    _twaver.html.addEventListener('mousedown', 'handle_mousedown', this.getView(), this);
    _twaver.html.addEventListener('mousemove', 'handle_mousemove', this.getView(), this);
    _twaver.html.addEventListener('mouseup', 'handle_mouseup', this.getView(), this);
    //...
  }
  refreshButtonNodeLocation (node) {
    var rect = node.getRect();
    this.infoNode.setCenterLocation({ x: rect.x, y: rect.y });
    this.optionNode.setCenterLocation({ x: rect.x, y: rect.y + rect.height });
    this.linkNode.setCenterLocation({ x: rect.x + rect.width, y: rect.y });
    this.fourthNode.setCenterLocation({ x: rect.x + rect.width, y: rect.y + rect.height });
  }
  handle_mousedown(e) {
  }
  handle_mousemove(e) {
  }
  handle_mouseup(e) {
  }
  handle_keydown(e) {
  }
  //get element by mouse event, set lastElement as ImageShapeNode
  handle_dragover(e) {
  }
  handle_drop(e) {
  }
  _moveSelectionElements(type) {
  }
  isCurveLine () {
    return this._curveLine;
  }
  setCurveLine (value) {
    this._curveLine = value;
    this.invalidateElementUIs();
  }
  isShowLine () {
    return this._showLine;
  }
  setShowLine (value) {
    this._showLine = value;
    this.invalidateElementUIs();
  }
  isLineTip () {
    return this._lineTip;
  }
  setLineTip (value) {
    this._lineTip = value;
    this.invalidateElementUIs();
  }
  paintTop (g) {
  }
  paintBottom(g) {
  }
}

Main entrance controller

The entry controller in CloudEditor is implemented using editor.js. I have added the twaver.component.ts component to integrate into the angular project to guide the introduction and instantiation of the editor.

The first part of the twaver component file

Template section

<div id="toolbar">
  <button *ngFor="let toolItem of toolbarData" [id]="toolItem.id" [title]="toolItem.title">
    <img [src]="toolItem.src"/>
  </button>
</div>
<div class="main">
  <div class="editor-container">
    <design-menu [inputMenuData]="menuData"></design-menu>
    <div class="stage" id="stage">
    </div>
  </div>
</div>

Logical part

import { Component, OnInit, ElementRef, NgZone, AfterViewInit } from '@angular/core';
import * as twaver from "../../../lib/twaver.js";
import "./shapeDefined";
import TwaverEditor from "./twaver-editor";
import { menuData, toolbarData } from './editorData';
window.GAP = 10;
@Component({
  selector: 'design-twaver',
  templateUrl: './twaver.component.html',
  styleUrls: ['./twaver.component.less']
})
export class TwaverComponent implements OnInit, AfterViewInit {

  constructor(private element: ElementRef, private zone: NgZone) {
  }
  twaverEditor: TwaverEditor;
  menuData = {
    categories: []
  };
  toolbarData = toolbarData;
  ngOnInit(): void {
  }
  ngAfterViewInit() {
    this.twaverEditor = new TwaverEditor(this.element.nativeElement);
    this.menuData = menuData;
  }
}

The second part of the WaverEditor file

This file is the rewritten file of the main part of editor.js (the unchanged content is omitted, and only the structure is retained).

import Stage from './stage';
export default class TwaverEditor { 
  constructor(element) { 
    this.element = element;
    this.init()
  }
  element;
  stage: Stage;
  init() { 
    this.stage = new Stage(this);
    let stageDom = this.element.querySelector('#stage');
    stageDom.append(this.stage.getView());


    this.stage.initOverview();
    this.stage.initPropertySheet();
        
    this.adjustBounds();
    this.initProperties();
    // this.toolbar = new Toolbar();
    window.onresize = (e)  => {
      this.adjustBounds();
    };
  }
  adjustBounds() {
    let stageDom = this.element.querySelector('#stage');
    this.stage.adjustBounds({
      x: 0,
      y: 0,
      width: stageDom.clientWidth,
      height: stageDom.clientHeight
    });
  }
  initProperties() { 
  }
  isFullScreenSupported () {
  }
  toggleFullscreen() {
  }
  getAngle (p1, p2) {
  }
  fixNodeLocation (node) {
  }
  layerIndex = 0;
  addNode (box, obj, centerLocation, host) {
  }
  GAP = 10;
  fixLocation (location, viewRect?) {
  }
  fixSize (size) {
  }
  addStyleProperty (box, propertyName, category, name) {
    return this._addProperty(box, propertyName, category, name, 'style');
  }
  addClientProperty (box, propertyName, category, name) {
    return this._addProperty(box, propertyName, category, name, 'client');
  }
  addAccessorProperty (box, propertyName, category, name) {
    return this._addProperty(box, propertyName, category, name, 'accessor');
  }
  _addProperty (box, propertyName, category, name, proprtyType) {
  }
}

Output list

Realize the main output content:

  • The type declaration file required to implement Typescript, namely the twaver.d.ts file
  • Realize the function of the left tree menu, that is, the menu component file;
  • Realize the stage function of drawing operation, namely stage.ts file;
  • Realize the editor main controller, namely the TwaverEditor.ts file
|-- twaver
    |-- editorData.ts                  # 数据文件,包含左树列表数据
    |-- shapeDefined.ts                   # 图形绘制定义
    |-- stage.ts                       # 舞台类
    |-- twaver-editor.ts               # twaver主入口控制器
    |-- twaver.component.html        
    |-- twaver.component.less
    |-- twaver.component.ts               # twaver Angular 组件
    |-- twaver.module.ts               # twaver Module
    |-- menu                           # meun组件
        |-- menu.component.html
        |-- menu.component.less
        |-- menu.component.ts

Summarize

Rewriting CloudEditor is just the beginning of a journey. I hope this article can help you get a good start. You can smoothly understand some of the APIs and grammars in twaver.

阅读 249

从有技术广度到技术深度的转变,这样才能被自己迷恋

1.7k 声望
488 粉丝
0 条评论
你知道吗?

从有技术广度到技术深度的转变,这样才能被自己迷恋

1.7k 声望
488 粉丝
文章目录
宣传栏