UNPKG

plot-plan-designer

Version:

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

698 lines (697 loc) 32.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importStar(require("react")); const antd_1 = require("antd"); const debounce_1 = __importDefault(require("lodash/debounce")); const i18next_1 = __importDefault(require("i18next")); const ImageMapFooterToolbar_1 = __importDefault(require("./ImageMapFooterToolbar")); const ImageMapItems_1 = __importDefault(require("./ImageMapItems")); const ImageMapHeaderToolbar_1 = __importDefault(require("./ImageMapHeaderToolbar")); const ImageMapPreview_1 = __importDefault(require("./ImageMapPreview")); const ImageMapConfigurations_1 = __importDefault(require("./ImageMapConfigurations")); const SandBox_1 = __importDefault(require("../sandbox/SandBox")); require("../../libs/fontawesome-5.2.0/css/all.css"); require("../../styles/index.less"); const Container_1 = __importDefault(require("../common/Container")); const Canvas_1 = __importDefault(require("../canvas/Canvas")); const Descriptors_1 = require("./Descriptors"); const prop_types_1 = __importDefault(require("prop-types")); const FlowContext_1 = __importStar(require("../../contexts/FlowContext")); const PDFProcessor_1 = __importDefault(require("../common/PDFProcessor")); const utils_1 = require("../common/utils/utils"); const propertiesToInclude = [ 'id', 'name', 'locked', 'file', 'src', 'link', 'tooltip', 'animation', 'layout', 'workareaWidth', 'workareaHeight', 'videoLoadType', 'autoplay', 'shadow', 'muted', 'loop', 'code', 'icon', 'userProperty', 'trigger', 'configuration', 'superType', 'points', 'svg', 'loadType', ]; const defaultOption = { fill: 'rgba(0, 0, 0, 1)', stroke: 'rgba(255, 255, 255, 0)', strokeUniform: true, resource: {}, link: { enabled: false, type: 'resource', state: 'new', dashboard: {}, }, tooltip: { enabled: true, type: 'resource', template: '<div>{{message.name}}</div>', }, animation: { type: 'none', loop: true, autoplay: true, duration: 1000, }, userProperty: {}, trigger: { enabled: false, type: 'alarm', script: 'return message.value > 0;', effect: 'style', }, }; class ImageMapEditor extends react_1.Component { constructor(props) { super(props); this.hasEditData = () => this.state.editing; this.canvasHandlers = { onAdd: (target) => { const { editing } = this.state; this.forceUpdate(); if (!editing) { this.changeEditing(true); } if (target.type === 'activeSelection') { this.canvasHandlers.onSelect(null); return; } this.canvasRef.handler.select(target); }, onSelect: (target) => { const { selectedItem } = this.state; if (target && target.id && target.id !== 'workarea' && target.type !== 'activeSelection') { if (selectedItem && target.id === selectedItem.id) { return; } this.canvasRef.handler.getObjects().forEach((obj) => { if (obj) { this.canvasRef.handler.animationHandler.resetAnimation(obj, true); } }); this.setState({ selectedItem: target, }); return; } this.canvasRef.handler.getObjects().forEach((obj) => { if (obj) { this.canvasRef.handler.animationHandler.resetAnimation(obj, true); } }); this.setState({ selectedItem: null, }); }, onRemove: () => { const { editing } = this.state; if (!editing) { this.changeEditing(true); } this.canvasHandlers.onSelect(null); }, onModified: debounce_1.default(() => { const { editing } = this.state; this.forceUpdate(); if (!editing) { this.changeEditing(true); } }, 300), onZoom: (zoom) => { this.setState({ zoomRatio: zoom, }); }, onChange: (selectedItem, changedValues, allValues) => { const { editing } = this.state; if (!editing) { this.changeEditing(true); } const changedKey = Object.keys(changedValues)[0]; const changedValue = changedValues[changedKey]; if (allValues.workarea) { this.canvasHandlers.onChangeWorkarea(changedKey, changedValue, allValues.workarea); return; } if (changedKey === 'width' || changedKey === 'height') { this.canvasRef.handler.scaleToResize(allValues.width, allValues.height); return; } if (changedKey === 'angle') { this.canvasRef.handler.rotate(allValues.angle); return; } if (changedKey === 'locked') { this.canvasRef.handler.setObject({ lockMovementX: changedValue, lockMovementY: changedValue, hasControls: !changedValue, hoverCursor: changedValue ? 'pointer' : 'move', editable: !changedValue, locked: changedValue, }); return; } if (changedKey === 'file' || changedKey === 'src' || changedKey === 'code') { if (selectedItem.type === 'image') { this.canvasRef.handler.setImageById(selectedItem.id, changedValue); } else if (selectedItem.superType === 'element') { this.canvasRef.handler.elementHandler.setById(selectedItem.id, changedValue); } return; } if (changedKey === 'link') { const link = Object.assign({}, defaultOption.link, allValues.link); this.canvasRef.handler.set(changedKey, link); return; } if (changedKey === 'tooltip') { const tooltip = Object.assign({}, defaultOption.tooltip, allValues.tooltip); this.canvasRef.handler.set(changedKey, tooltip); return; } if (changedKey === 'animation') { const animation = Object.assign({}, defaultOption.animation, allValues.animation); this.canvasRef.handler.set(changedKey, animation); return; } if (changedKey === 'icon') { const { unicode, styles } = changedValue[Object.keys(changedValue)[0]]; const uni = parseInt(unicode, 16); if (styles[0] === 'brands') { this.canvasRef.handler.set('fontFamily', 'Font Awesome 5 Brands'); } else if (styles[0] === 'regular') { this.canvasRef.handler.set('fontFamily', 'Font Awesome 5 Regular'); } else { this.canvasRef.handler.set('fontFamily', 'Font Awesome 5 Free'); } this.canvasRef.handler.set('text', String.fromCodePoint(uni)); this.canvasRef.handler.set('icon', changedValue); return; } if (changedKey === 'shadow') { if (allValues.shadow.enabled) { if ('blur' in allValues.shadow) { this.canvasRef.handler.setShadow(allValues.shadow); } else { this.canvasRef.handler.setShadow({ enabled: true, blur: 15, offsetX: 10, offsetY: 10, }); } } else { this.canvasRef.handler.setShadow(null); } return; } if (changedKey === 'fontWeight') { this.canvasRef.handler.set(changedKey, changedValue ? 'bold' : 'normal'); return; } if (changedKey === 'fontStyle') { this.canvasRef.handler.set(changedKey, changedValue ? 'italic' : 'normal'); return; } if (changedKey === 'textAlign') { this.canvasRef.handler.set(changedKey, Object.keys(changedValue)[0]); return; } if (changedKey === 'trigger') { const trigger = Object.assign({}, defaultOption.trigger, allValues.trigger); this.canvasRef.handler.set(changedKey, trigger); return; } if (changedKey === 'filters') { const filterKey = Object.keys(changedValue)[0]; const filterValue = allValues.filters[filterKey]; if (filterKey === 'gamma') { const rgb = [filterValue.r, filterValue.g, filterValue.b]; this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { gamma: rgb, }); return; } if (filterKey === 'brightness') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { brightness: filterValue.brightness, }); return; } if (filterKey === 'contrast') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { contrast: filterValue.contrast, }); return; } if (filterKey === 'saturation') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { saturation: filterValue.saturation, }); return; } if (filterKey === 'hue') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { rotation: filterValue.rotation, }); return; } if (filterKey === 'noise') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { noise: filterValue.noise, }); return; } if (filterKey === 'pixelate') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { blocksize: filterValue.blocksize, }); return; } if (filterKey === 'blur') { this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey].enabled, { value: filterValue.value, }); return; } this.canvasRef.handler.imageHandler.applyFilterByType(filterKey, changedValue[filterKey]); return; } if (changedKey === 'chartOption') { try { const sandbox = new SandBox_1.default(); const compiled = sandbox.compile(changedValue); const { animations, styles } = this.state; const chartOption = compiled(3, animations, styles, selectedItem.userProperty); selectedItem.setChartOptionStr(changedValue); this.canvasRef.handler.elementHandler.setById(selectedItem.id, chartOption); } catch (error) { console.error(error); } return; } this.canvasRef.handler.set(changedKey, changedValue); }, onChangeWorkarea: (changedKey, changedValue, allValues) => { if (changedKey === 'layout') { this.canvasRef.handler.workareaHandler.setLayout(changedValue); return; } if (changedKey === 'file' || changedKey === 'src') { const { workarea: { file, src }, } = this.canvasRef.handler; /** * in this case, we don't need to reset workarea scale when already have image src */ let resetScale = true; if (file || (src && src.length > 0)) resetScale = false; this.setState({ pdfFile: changedValue, resetScale }); if (!changedValue || utils_1.isImage(changedValue.type)) { this.canvasRef.handler.workareaHandler.setImage(changedValue, { resetScale }); } return; } if (changedKey === 'width' || changedKey === 'height') { this.canvasRef.handler.originScaleToResize(this.canvasRef.handler.workarea, allValues.width, allValues.height); this.canvasRef.canvas.centerObject(this.canvasRef.handler.workarea); return; } this.canvasRef.handler.workarea.set(changedKey, changedValue); this.canvasRef.canvas.requestRenderAll(); }, onTooltip: (ref, target) => { const value = Math.random() * 10 + 1; // const { animations, styles } = this.state; // const { code } = target.trigger; // const compile = SandBox.compile(code); // const result = compile(value, animations, styles, target.userProperty); // console.log(result); return (react_1.default.createElement("div", null, react_1.default.createElement("div", null, react_1.default.createElement("div", null, react_1.default.createElement(antd_1.Button, null, target.id)), react_1.default.createElement(antd_1.Badge, { count: value })))); }, onClick: (canvas, target) => { const { link } = target; if (link.state === 'current') { document.location.href = link.url; return; } window.open(link.url); }, onContext: (ref, event, target) => { if ((target && target.id === 'workarea') || !target) { const { layerX: left, layerY: top } = event; return (react_1.default.createElement(antd_1.Menu, null, react_1.default.createElement(antd_1.Menu.SubMenu, { key: "add", style: { width: 120 }, title: i18next_1.default.t('action.add') }, this.transformList().map((item) => { const option = Object.assign({}, item.option, { left, top }); const newItem = Object.assign({}, item, { option }); return (react_1.default.createElement(antd_1.Menu.Item, { style: { padding: 0 }, key: item.name }, this.itemsRef.renderItem(newItem, false))); })))); } if (target.type === 'activeSelection') { return (react_1.default.createElement(antd_1.Menu, null, react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.toGroup(); } }, i18next_1.default.t('action.object-group')), react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.duplicate(); } }, i18next_1.default.t('action.clone')), react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.remove(); } }, i18next_1.default.t('action.delete')))); } if (target.type === 'group') { return (react_1.default.createElement(antd_1.Menu, null, react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.toActiveSelection(); } }, i18next_1.default.t('action.object-ungroup')), react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.duplicate(); } }, i18next_1.default.t('action.clone')), react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.remove(); } }, i18next_1.default.t('action.delete')))); } return (react_1.default.createElement(antd_1.Menu, null, react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.duplicateById(target.id); } }, i18next_1.default.t('action.clone')), react_1.default.createElement(antd_1.Menu.Item, { onClick: () => { this.canvasRef.handler.removeById(target.id); } }, i18next_1.default.t('action.delete')))); }, onTransaction: (transaction) => { this.forceUpdate(); }, }; this.handlers = { onChangePreview: (checked) => { let data; if (this.canvasRef) { data = this.canvasRef.handler.exportJSON().filter((obj) => { if (!obj.id) { return false; } return true; }); } this.setState({ preview: typeof checked === 'object' ? false : checked, objects: data, }); }, onProgress: (progress) => { this.setState({ progress, }); }, onImportFile: (files) => { if (files) { this.showLoading(true); setTimeout(() => { const reader = new FileReader(); reader.onprogress = (e) => { if (e.lengthComputable) { const progress = parseInt((e.loaded / e.total) * 100, 10); this.handlers.onProgress(progress); } }; reader.onload = (e) => { const { objects, animations, styles, dataSources } = JSON.parse(e.target.result); this.setState({ animations, styles, dataSources, }); if (objects) { this.canvasRef.handler.clear(true); const data = objects.filter((obj) => { if (!obj.id) { return false; } return true; }); this.canvasRef.handler.importJSON(data); } }; reader.onloadend = () => { this.showLoading(false); }; reader.onerror = () => { this.showLoading(false); }; reader.readAsText(files[0]); }, 500); } }, onImportData: (data) => { var _a; if (data) { const { objects, animations, styles, dataSources } = data; this.setState({ animations, styles, dataSources, }); if (objects) { const selectedID = (_a = this.canvasRef.canvas.getActiveObject()) === null || _a === void 0 ? void 0 : _a.id; this.canvasRef.handler.clear(true); const data = objects.filter((obj) => obj.id); this.canvasRef.handler.importJSON(data).then(() => { this.canvasRef.handler.selectById(selectedID); }); } } }, onUpload: () => { const { onLoad } = this.props; if (onLoad) { onLoad(); } else { const inputEl = document.createElement('input'); inputEl.accept = '.json'; inputEl.type = 'file'; inputEl.hidden = true; inputEl.onchange = (e) => { this.handlers.onImportFile(e.target.files); }; document.body.appendChild(inputEl); // required for firefox inputEl.click(); inputEl.remove(); } }, onExportData: () => { const editorData = this.canvasRef.handler.exportJSON(); const objects = editorData.filter((obj) => { if (!obj.id) { return false; } return true; }); const { animations, styles, dataSources } = this.state; return { objects, animations, styles, dataSources, }; }, onSave: () => { const { onSave } = this.props; if (onSave) { onSave(); } else { this.showLoading(true); // let svg = this.canvasRef.handler.canvas.toSVG(); // svg = svg.replace(/vector-effect="non-scaling-stroke"/g, ''); // console.log(svg); const editorData = this.handlers.onExportData(); const anchorEl = document.createElement('a'); anchorEl.href = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(editorData, null, '\t'))}`; anchorEl.download = `${this.canvasRef.handler.workarea.name || 'sample'}.json`; document.body.appendChild(anchorEl); // required for firefox anchorEl.click(); anchorEl.remove(); this.showLoading(false); } }, onChangeAnimations: (animations) => { if (!this.state.editing) { this.changeEditing(true); } this.setState({ animations, }); }, onChangeStyles: (styles) => { if (!this.state.editing) { this.changeEditing(true); } this.setState({ styles, }); }, onChangeDataSources: (dataSources) => { if (!this.state.editing) { this.changeEditing(true); } this.setState({ dataSources, }); }, onSaveImage: () => { this.canvasRef.handler.saveCanvasImage(); }, }; this.shortcutHandlers = { esc: () => { document.addEventListener('keydown', (e) => { if (e.keyCode === 27) { this.handlers.onChangePreview(false); } }); }, }; this.transformList = () => { return Object.values(this.state.descriptors).reduce((prev, curr) => prev.concat(curr), []); }; this.showLoading = (loading) => { this.setState({ loading, }); }; this.changeEditing = (editing) => { this.setState({ editing, }); }; this.state = { selectedItem: null, zoomRatio: 1, preview: false, loading: false, progress: 0, animations: [], styles: [], dataSources: [], editing: false, descriptors: {}, objects: undefined, pdfFile: undefined, resetScale: undefined, }; } componentDidMount() { this.setState({ selectedItem: null, descriptors: Descriptors_1.descriptors, }); this.shortcutHandlers.esc(); const { onLinkHandler } = this.props; if (onLinkHandler) onLinkHandler({ handlers: this.handlers, internalLoading: this.showLoading, canvasRef: this.canvasRef, hasEditData: this.hasEditData, changeInternalEditing: this.changeEditing, }); } render() { const { preview, selectedItem, zoomRatio, loading, animations, styles, dataSources, editing, descriptors, objects, pdfFile, resetScale, } = this.state; const { onAdd, onRemove, onSelect, onModified, onChange, onZoom, onTooltip, onClick, onContext, onTransaction } = this.canvasHandlers; const { onMigrate, fetcher } = this.props; const { onChangePreview, onSave, onUpload, onChangeAnimations, onChangeStyles, onChangeDataSources } = this.handlers; const content = (react_1.default.createElement("div", { className: "rde-editor" }, react_1.default.createElement(FlowContext_1.default.Consumer, null, (context) => { if (context) { switch (context.editMode) { case FlowContext_1.EditMode.EDITING: return (react_1.default.createElement(ImageMapItems_1.default, { ref: (c) => { this.itemsRef = c; }, canvasRef: this.canvasRef, descriptors: descriptors })); default: return null; } } }), react_1.default.createElement("div", { className: "rde-editor-canvas-container" }, react_1.default.createElement("div", { className: "rde-editor-header-toolbar" }, react_1.default.createElement(ImageMapHeaderToolbar_1.default, { canvasRef: this.canvasRef, selectedItem: selectedItem, onSelect: onSelect, onUpload: onUpload, onMigrate: onMigrate, onSave: onSave, editing: editing }), pdfFile && utils_1.isPdf(pdfFile.type) && (react_1.default.createElement(PDFProcessor_1.default, { file: pdfFile, onChange: (data) => { this.canvasRef.handler.workareaHandler.setImage(data, { resetScale }); } }))), react_1.default.createElement("div", { ref: (c) => { this.container = c; }, className: "rde-editor-canvas" }, react_1.default.createElement(Canvas_1.default, { ref: (c) => { this.canvasRef = c; }, className: "rde-canvas", minZoom: 1, maxZoom: 500, objectOption: Object.assign(Object.assign({}, defaultOption), this.props.objectOptions), propertiesToInclude: propertiesToInclude, onModified: onModified, onAdd: onAdd, onRemove: onRemove, onSelect: onSelect, onZoom: onZoom, onTooltip: onTooltip, onClick: onClick, onContext: onContext, onTransaction: onTransaction, keyEvent: { transaction: true, }, fetcher: fetcher })), react_1.default.createElement("div", { className: "rde-editor-footer-toolbar" }, react_1.default.createElement(ImageMapFooterToolbar_1.default, { canvasRef: this.canvasRef, preview: preview, onChangePreview: onChangePreview, zoomRatio: zoomRatio }))), react_1.default.createElement(ImageMapConfigurations_1.default, { canvasRef: this.canvasRef, onChange: onChange, selectedItem: selectedItem, onChangeAnimations: onChangeAnimations, onChangeStyles: onChangeStyles, onChangeDataSources: onChangeDataSources, animations: animations, styles: styles, dataSources: dataSources, tabsDefinition: this.props.tabsDefinition }), react_1.default.createElement(ImageMapPreview_1.default, { preview: preview, onChangePreview: onChangePreview, onTooltip: onTooltip, onClick: onClick, objects: objects }))); return react_1.default.createElement(Container_1.default, { content: content, loading: loading, className: "" }); } } ImageMapEditor.propTypes = { onLinkHandler: prop_types_1.default.func, onLoad: prop_types_1.default.func, onSave: prop_types_1.default.func, onMigrate: prop_types_1.default.func, objectOptions: prop_types_1.default.object, tabsDefinition: prop_types_1.default.object, editorName: prop_types_1.default.string, fetcher: prop_types_1.default.any, }; exports.default = ImageMapEditor;