UNPKG

plot-plan-designer

Version:

Design Editor Tools with React.js + ant.design + fabric.js

299 lines (298 loc) 14.9 kB
import React, { Component } from 'react'; import i18n from 'i18next'; import { message, Popconfirm } from 'antd'; import Container from '../common/Container'; import WorkflowTitle from './WorkflowTitle'; import Canvas from '../canvas/Canvas'; import WorkflowItems from './WorkflowItems'; import WorkflowConfigurations from './WorkflowConfigurations'; import WorkflowNodeConfigurations from './WorkflowNodeConfigurations'; import WorkflowToolbar from './WorkflowToolbar'; import Nodes from './node'; import Links from './link'; import { getEllipsis, getNode } from './configuration/NodeConfiguration'; import { OUT_PORT_TYPE } from './constant/constants'; import NodeConfigurationError from './error/NodeConfigurationError'; import CommonButton from '../common/CommonButton'; class WorkflowEditor extends Component { constructor() { super(...arguments); this.state = { loading: true, zoomRatio: 1, workflow: {}, selectedItem: null, descriptors: {}, editing: false, }; this.canvasHandlers = { onZoom: (zoom) => { this.setState({ zoomRatio: zoom, }); }, onAdd: (target) => { if (target.type === 'activeSelection') { this.canvasHandlers.onSelect(null); return; } if (target.superType === 'node') { this.canvasRef.handler.select(target); } }, onSelect: (target) => { this.nodeConfigurationRef.props.form.validateFields((err) => { if (this.state.selectedItem) { if (err || (this.state.selectedItem.errors && this.state.selectedItem.errors.length)) { this.state.selectedItem.setErrors(true); } else { this.state.selectedItem.setErrors(false); } } }); if (target && target.id && target.id !== 'workarea' && target.type !== 'activeSelection' && target.superType !== 'link' && target.superType !== 'port') { this.setState({ selectedItem: target, }); return; } this.setState({ selectedItem: null, }, () => { this.canvasRef.handler.nodeHandler.deselect(); }); }, onRemove: () => { if (!this.state.editing) { this.changeEditing(true); } }, onModified: () => { if (!this.state.editing) { this.changeEditing(true); } }, }; this.handlers = { onImport: (files) => { if (files) { this.showLoading(); const reader = new FileReader(); reader.onload = (e) => { const result = JSON.parse(e.target.result); this.setState({ workflow: result, }); this.canvasRef.handler.clear(); const nodes = result.nodes.map((node) => { return Object.assign(Object.assign({}, node), { type: getNode(node.nodeClazz), left: node.properties ? node.properties.left : 0, top: node.properties ? node.properties.top : 0 }); }); const links = result.links.map((link) => { return { fromNodeId: link.fromNode, fromPortId: link.fromPort, toNodeId: link.toNode, type: 'curvedLink', superType: 'link', left: link.properties ? link.properties.left : 0, top: link.properties ? link.properties.top : 0, }; }); const objects = nodes.concat(links); const { viewportTransform } = result.properties; if (viewportTransform) { this.canvasRef.canvas.setViewportTransform(viewportTransform); } this.canvasRef.handler.importJSON(objects, () => { this.hideLoading(); this.canvasRef.canvas.setZoom(this.state.zoomRatio); }); }; reader.readAsText(files[0]); } }, onUpload: () => { const inputEl = document.createElement('input'); inputEl.accept = '.json'; inputEl.type = 'file'; inputEl.hidden = true; inputEl.onchange = (e) => { this.handlers.onImport(e.target.files); }; document.body.appendChild(inputEl); // required for firefox inputEl.click(); inputEl.remove(); }, onDownload: () => { this.showLoading(); const workflow = this.handlers.exportJsonCode(); if (workflow) { const anchorEl = document.createElement('a'); anchorEl.href = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(workflow, null, '\t'))}`; anchorEl.download = `${workflow.name}.json`; document.body.appendChild(anchorEl); // required for firefox anchorEl.click(); anchorEl.remove(); this.hideLoading(); } }, exportJsonCode: () => { const workflow = Object.assign({}, this.state.workflow); const nodes = []; const links = []; try { this.canvasRef.handler.exportJSON().forEach((obj) => { if (obj.superType === 'node') { if (obj.errors) { throw new NodeConfigurationError(i18n.t('workflow.validate-fields-error'), obj.id, obj.name); } const node = { id: obj.id, name: obj.name, description: obj.description, nodeClazz: obj.nodeClazz, configuration: obj.configuration, properties: { left: obj.left || 0, top: obj.top || 0, icon: obj.icon, }, }; nodes.push(node); } else if (obj.superType === 'link') { const link = { fromNode: obj.fromNode.id, fromPort: obj.fromPort.id, toNode: obj.toNode.id, properties: { left: obj.left || 0, top: obj.top || 0, }, }; links.push(link); } }); workflow.nodes = nodes; workflow.links = links; const properties = { viewportTransform: this.canvasRef.canvas.viewportTransform, }; workflow.properties = properties; return workflow; } catch (error) { console.error(`[ERROR] ${this.constructor.name} exportJsonCode()`, error); this.canvasRef.handler.selectById(error.nodeId); message.error(error.message); this.hideLoading(); } }, onChange: (selectedItem, changedValues, allValues) => { if (!this.state.editing) { this.changeEditing(true); } if (changedValues.workflow) { const workflow = Object.assign({}, this.state.workflow, changedValues.workflow); this.setState({ workflow, }); } else { setTimeout(() => { const configurationList = Object.keys(allValues.configuration).map((key) => `configuration.${key}`); const errors = this.nodeConfigurationRef.props.form.getFieldsError(configurationList.concat(['name'])); if (Object.values(errors.configuration).filter((error) => error).length || errors.name) { selectedItem.setErrors(true); } else { selectedItem.setErrors(false); } this.canvasRef.canvas.renderAll(); }, 0); const configuration = Object.assign({}, selectedItem.configuration, changedValues.configuration); this.canvasRef.handler.setObject({ configuration, name: allValues.name, description: allValues.description, }); selectedItem.label.set({ text: getEllipsis(allValues.name, 18), }); if (selectedItem.descriptor.outPortType === OUT_PORT_TYPE.DYNAMIC) { this.canvasRef.handler.portHandler.recreate(selectedItem); } } }, }; this.showLoading = () => { this.setState({ loading: true, }); }; this.hideLoading = () => { this.setState({ loading: false, }); }; this.changeEditing = (editing) => { this.setState({ editing, }); }; } componentDidMount() { import('./Descriptors.json').then((descriptors) => { this.setState({ descriptors, }, () => { this.hideLoading(); }); }); } render() { const { zoomRatio, workflow, selectedItem, descriptors, loading, editing } = this.state; const { onChange, onDownload, onUpload } = this.handlers; const { onZoom, onAdd, onSelect, onRemove, onModified } = this.canvasHandlers; const nodes = Nodes(descriptors); const action = (React.createElement(React.Fragment, null, React.createElement(CommonButton, { className: "rde-action-btn", shape: "circle", icon: "file-download", disabled: !editing, tooltipTitle: i18n.t('action.download'), onClick: onDownload, tooltipPlacement: "bottomRight" }), editing ? (React.createElement(Popconfirm, { title: i18n.t('workflow.workflow-editing-confirm'), okText: i18n.t('action.ok'), cancelText: i18n.t('action.cancel'), onConfirm: onUpload, placement: "bottomRight" }, React.createElement(CommonButton, { className: "rde-action-btn", shape: "circle", icon: "file-upload", tooltipTitle: i18n.t('action.upload'), tooltipPlacement: "bottomRight" }))) : (React.createElement(CommonButton, { className: "rde-action-btn", shape: "circle", icon: "file-upload", tooltipTitle: i18n.t('action.upload'), tooltipPlacement: "bottomRight", onClick: onUpload })))); const titleContent = (React.createElement(React.Fragment, null, React.createElement("span", null, i18n.t('workflow.workflow-editor')), React.createElement("span", { style: { width: 40, textAlign: 'center' } }, "/"), React.createElement("span", { style: { color: workflow.enabled ? '#49a9ee' : 'rgba(0, 0, 0, 0.65)' } }, workflow.name))); const title = React.createElement(WorkflowTitle, { title: titleContent, action: action }); const content = (React.createElement("div", { className: "rde-editor" }, React.createElement(WorkflowItems, { canvasRef: this.canvasRef, descriptors: descriptors }), React.createElement("div", { ref: (c) => { this.container = c; }, className: "rde-editor-canvas" }, React.createElement(Canvas, { ref: (c) => { this.canvasRef = c; }, className: "rde-canvas", fabricObjects: Object.assign(Object.assign({}, nodes), Links), workareaOption: { width: 0, height: 0, }, gridOption: { enabled: true, grid: 20, snapToGrid: true }, activeSelectionOption: { hasControls: false, hasBorders: false, perPixelTargetFind: true, }, minZoom: 50, maxZoom: 150, onZoom: onZoom, onSelect: onSelect, onAdd: onAdd, onRemove: onRemove, onModified: onModified, keyEvent: { move: false, transaction: true, clipboard: true } }), React.createElement("div", { className: "rde-editor-properties", style: { display: selectedItem ? 'block' : 'none' } }, React.createElement(WorkflowNodeConfigurations, { wrappedComponentRef: (c) => { this.nodeConfigurationRef = c; }, workflow: workflow, selectedItem: selectedItem, canvasRef: this.canvasRef, descriptors: descriptors, onChange: onChange })), React.createElement("div", { className: "rde-editor-toolbar" }, React.createElement(WorkflowToolbar, { canvasRef: this.canvasRef, zoomRatio: zoomRatio }))), React.createElement(WorkflowConfigurations, { workflow: workflow, selectedItem: selectedItem, canvasRef: this.canvasRef, onChange: onChange }))); return React.createElement(Container, { title: title, content: content, loading: loading, className: "" }); } } export default WorkflowEditor;