plot-plan-designer
Version:
Design Editor Tools with React.js + ant.design + fabric.js
698 lines (697 loc) • 32.7 kB
JavaScript
"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;