|
@@ -0,0 +1,4842 @@
|
|
|
+var __defProp = Object.defineProperty;
|
|
|
+var __defProps = Object.defineProperties;
|
|
|
+var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
|
+var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
|
+var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
+var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
|
+var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
|
+var __spreadValues = (a, b) => {
|
|
|
+ for (var prop in b || (b = {}))
|
|
|
+ if (__hasOwnProp.call(b, prop))
|
|
|
+ __defNormalProp(a, prop, b[prop]);
|
|
|
+ if (__getOwnPropSymbols)
|
|
|
+ for (var prop of __getOwnPropSymbols(b)) {
|
|
|
+ if (__propIsEnum.call(b, prop))
|
|
|
+ __defNormalProp(a, prop, b[prop]);
|
|
|
+ }
|
|
|
+ return a;
|
|
|
+};
|
|
|
+var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
|
+var __async = (__this, __arguments, generator) => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ var fulfilled = (value) => {
|
|
|
+ try {
|
|
|
+ step(generator.next(value));
|
|
|
+ } catch (e) {
|
|
|
+ reject(e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ var rejected = (value) => {
|
|
|
+ try {
|
|
|
+ step(generator.throw(value));
|
|
|
+ } catch (e) {
|
|
|
+ reject(e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
|
+ step((generator = generator.apply(__this, __arguments)).next());
|
|
|
+ });
|
|
|
+};
|
|
|
+var eventemitter3 = { exports: {} };
|
|
|
+(function(module) {
|
|
|
+ var has = Object.prototype.hasOwnProperty, prefix = "~";
|
|
|
+ function Events() {
|
|
|
+ }
|
|
|
+ if (Object.create) {
|
|
|
+ Events.prototype = /* @__PURE__ */ Object.create(null);
|
|
|
+ if (!new Events().__proto__)
|
|
|
+ prefix = false;
|
|
|
+ }
|
|
|
+ function EE(fn, context, once) {
|
|
|
+ this.fn = fn;
|
|
|
+ this.context = context;
|
|
|
+ this.once = once || false;
|
|
|
+ }
|
|
|
+ function addListener(emitter, event, fn, context, once) {
|
|
|
+ if (typeof fn !== "function") {
|
|
|
+ throw new TypeError("The listener must be a function");
|
|
|
+ }
|
|
|
+ var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
|
|
|
+ if (!emitter._events[evt])
|
|
|
+ emitter._events[evt] = listener, emitter._eventsCount++;
|
|
|
+ else if (!emitter._events[evt].fn)
|
|
|
+ emitter._events[evt].push(listener);
|
|
|
+ else
|
|
|
+ emitter._events[evt] = [emitter._events[evt], listener];
|
|
|
+ return emitter;
|
|
|
+ }
|
|
|
+ function clearEvent(emitter, evt) {
|
|
|
+ if (--emitter._eventsCount === 0)
|
|
|
+ emitter._events = new Events();
|
|
|
+ else
|
|
|
+ delete emitter._events[evt];
|
|
|
+ }
|
|
|
+ function EventEmitter2() {
|
|
|
+ this._events = new Events();
|
|
|
+ this._eventsCount = 0;
|
|
|
+ }
|
|
|
+ EventEmitter2.prototype.eventNames = function eventNames() {
|
|
|
+ var names = [], events, name;
|
|
|
+ if (this._eventsCount === 0)
|
|
|
+ return names;
|
|
|
+ for (name in events = this._events) {
|
|
|
+ if (has.call(events, name))
|
|
|
+ names.push(prefix ? name.slice(1) : name);
|
|
|
+ }
|
|
|
+ if (Object.getOwnPropertySymbols) {
|
|
|
+ return names.concat(Object.getOwnPropertySymbols(events));
|
|
|
+ }
|
|
|
+ return names;
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.listeners = function listeners(event) {
|
|
|
+ var evt = prefix ? prefix + event : event, handlers = this._events[evt];
|
|
|
+ if (!handlers)
|
|
|
+ return [];
|
|
|
+ if (handlers.fn)
|
|
|
+ return [handlers.fn];
|
|
|
+ for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
|
|
|
+ ee[i] = handlers[i].fn;
|
|
|
+ }
|
|
|
+ return ee;
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.listenerCount = function listenerCount(event) {
|
|
|
+ var evt = prefix ? prefix + event : event, listeners = this._events[evt];
|
|
|
+ if (!listeners)
|
|
|
+ return 0;
|
|
|
+ if (listeners.fn)
|
|
|
+ return 1;
|
|
|
+ return listeners.length;
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
|
|
|
+ var evt = prefix ? prefix + event : event;
|
|
|
+ if (!this._events[evt])
|
|
|
+ return false;
|
|
|
+ var listeners = this._events[evt], len = arguments.length, args, i;
|
|
|
+ if (listeners.fn) {
|
|
|
+ if (listeners.once)
|
|
|
+ this.removeListener(event, listeners.fn, void 0, true);
|
|
|
+ switch (len) {
|
|
|
+ case 1:
|
|
|
+ return listeners.fn.call(listeners.context), true;
|
|
|
+ case 2:
|
|
|
+ return listeners.fn.call(listeners.context, a1), true;
|
|
|
+ case 3:
|
|
|
+ return listeners.fn.call(listeners.context, a1, a2), true;
|
|
|
+ case 4:
|
|
|
+ return listeners.fn.call(listeners.context, a1, a2, a3), true;
|
|
|
+ case 5:
|
|
|
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
|
|
|
+ case 6:
|
|
|
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
|
|
|
+ }
|
|
|
+ for (i = 1, args = new Array(len - 1); i < len; i++) {
|
|
|
+ args[i - 1] = arguments[i];
|
|
|
+ }
|
|
|
+ listeners.fn.apply(listeners.context, args);
|
|
|
+ } else {
|
|
|
+ var length = listeners.length, j;
|
|
|
+ for (i = 0; i < length; i++) {
|
|
|
+ if (listeners[i].once)
|
|
|
+ this.removeListener(event, listeners[i].fn, void 0, true);
|
|
|
+ switch (len) {
|
|
|
+ case 1:
|
|
|
+ listeners[i].fn.call(listeners[i].context);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ listeners[i].fn.call(listeners[i].context, a1);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ listeners[i].fn.call(listeners[i].context, a1, a2);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ listeners[i].fn.call(listeners[i].context, a1, a2, a3);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if (!args)
|
|
|
+ for (j = 1, args = new Array(len - 1); j < len; j++) {
|
|
|
+ args[j - 1] = arguments[j];
|
|
|
+ }
|
|
|
+ listeners[i].fn.apply(listeners[i].context, args);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.on = function on(event, fn, context) {
|
|
|
+ return addListener(this, event, fn, context, false);
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.once = function once(event, fn, context) {
|
|
|
+ return addListener(this, event, fn, context, true);
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.removeListener = function removeListener(event, fn, context, once) {
|
|
|
+ var evt = prefix ? prefix + event : event;
|
|
|
+ if (!this._events[evt])
|
|
|
+ return this;
|
|
|
+ if (!fn) {
|
|
|
+ clearEvent(this, evt);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ var listeners = this._events[evt];
|
|
|
+ if (listeners.fn) {
|
|
|
+ if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
|
|
|
+ clearEvent(this, evt);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for (var i = 0, events = [], length = listeners.length; i < length; i++) {
|
|
|
+ if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
|
|
|
+ events.push(listeners[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (events.length)
|
|
|
+ this._events[evt] = events.length === 1 ? events[0] : events;
|
|
|
+ else
|
|
|
+ clearEvent(this, evt);
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.removeAllListeners = function removeAllListeners(event) {
|
|
|
+ var evt;
|
|
|
+ if (event) {
|
|
|
+ evt = prefix ? prefix + event : event;
|
|
|
+ if (this._events[evt])
|
|
|
+ clearEvent(this, evt);
|
|
|
+ } else {
|
|
|
+ this._events = new Events();
|
|
|
+ this._eventsCount = 0;
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+ EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
|
|
|
+ EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
|
|
|
+ EventEmitter2.prefixed = prefix;
|
|
|
+ EventEmitter2.EventEmitter = EventEmitter2;
|
|
|
+ {
|
|
|
+ module.exports = EventEmitter2;
|
|
|
+ }
|
|
|
+})(eventemitter3);
|
|
|
+var EventEmitter = eventemitter3.exports;
|
|
|
+const createCanvas = (width, height, opt = { noStyle: false, noTranslate: false, className: "" }) => {
|
|
|
+ let canvas = document.createElement("canvas");
|
|
|
+ if (!opt.noStyle) {
|
|
|
+ canvas.style.cssText = `
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ if (opt.className) {
|
|
|
+ canvas.className = opt.className;
|
|
|
+ }
|
|
|
+ let ctx = canvas.getContext("2d");
|
|
|
+ canvas.width = width;
|
|
|
+ canvas.height = height;
|
|
|
+ if (!opt.noTranslate) {
|
|
|
+ ctx.translate(canvas.width / 2, canvas.height / 2);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ canvas,
|
|
|
+ ctx
|
|
|
+ };
|
|
|
+};
|
|
|
+const getTowPointDistance = (x1, y1, x2, y2) => {
|
|
|
+ return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
|
|
|
+};
|
|
|
+const getPointToLineDistance = (x, y, x1, y1, x2, y2) => {
|
|
|
+ if (x1 === x2) {
|
|
|
+ return Math.abs(x - x1);
|
|
|
+ } else {
|
|
|
+ let B = 1;
|
|
|
+ let A, C;
|
|
|
+ A = (y1 - y2) / (x2 - x1);
|
|
|
+ C = 0 - B * y1 - A * x1;
|
|
|
+ return Math.abs((A * x + B * y + C) / Math.sqrt(A * A + B * B));
|
|
|
+ }
|
|
|
+};
|
|
|
+const checkIsAtSegment = (x, y, x1, y1, x2, y2, dis = 10) => {
|
|
|
+ if (getPointToLineDistance(x, y, x1, y1, x2, y2) > dis) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ let dis1 = getTowPointDistance(x, y, x1, y1);
|
|
|
+ let dis2 = getTowPointDistance(x, y, x2, y2);
|
|
|
+ let dis3 = getTowPointDistance(x1, y1, x2, y2);
|
|
|
+ let max = Math.sqrt(dis * dis + dis3 * dis3);
|
|
|
+ if (dis1 <= max && dis2 <= max) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+};
|
|
|
+const radToDeg = (rad) => {
|
|
|
+ return rad * (180 / Math.PI);
|
|
|
+};
|
|
|
+const degToRad = (deg) => {
|
|
|
+ return deg * (Math.PI / 180);
|
|
|
+};
|
|
|
+const getTowPointRotate = (cx, cy, tx, ty, fx, fy) => {
|
|
|
+ return radToDeg(Math.atan2(ty - cy, tx - cx) - Math.atan2(fy - cy, fx - cx));
|
|
|
+};
|
|
|
+const getRotatedPoint = (x, y, cx, cy, rotate) => {
|
|
|
+ let deg = radToDeg(Math.atan2(y - cy, x - cx));
|
|
|
+ let del = deg + rotate;
|
|
|
+ let dis = getTowPointDistance(x, y, cx, cy);
|
|
|
+ return {
|
|
|
+ x: Math.cos(degToRad(del)) * dis + cx,
|
|
|
+ y: Math.sin(degToRad(del)) * dis + cy
|
|
|
+ };
|
|
|
+};
|
|
|
+const getElementCenterPoint = (element) => {
|
|
|
+ let { x, y, width, height } = element;
|
|
|
+ return {
|
|
|
+ x: x + width / 2,
|
|
|
+ y: y + height / 2
|
|
|
+ };
|
|
|
+};
|
|
|
+const transformPointReverseRotate = (x, y, cx, cy, rotate) => {
|
|
|
+ if (rotate !== 0) {
|
|
|
+ let rp = getRotatedPoint(x, y, cx, cy, -rotate);
|
|
|
+ x = rp.x;
|
|
|
+ y = rp.y;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+};
|
|
|
+const transformPointOnElement = (x, y, element) => {
|
|
|
+ let center = getElementCenterPoint(element);
|
|
|
+ return transformPointReverseRotate(x, y, center.x, center.y, element.rotate);
|
|
|
+};
|
|
|
+const getElementCornerPoint = (element, dir) => {
|
|
|
+ let { x, y, width, height } = element;
|
|
|
+ switch (dir) {
|
|
|
+ case "topLeft":
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ case "topRight":
|
|
|
+ return {
|
|
|
+ x: x + width,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ case "bottomRight":
|
|
|
+ return {
|
|
|
+ x: x + width,
|
|
|
+ y: y + height
|
|
|
+ };
|
|
|
+ case "bottomLeft":
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y: y + height
|
|
|
+ };
|
|
|
+ }
|
|
|
+};
|
|
|
+const getElementRotatedCornerPoint = (element, dir) => {
|
|
|
+ let center = getElementCenterPoint(element);
|
|
|
+ let dirPos = getElementCornerPoint(element, dir);
|
|
|
+ return getRotatedPoint(dirPos.x, dirPos.y, center.x, center.y, element.rotate);
|
|
|
+};
|
|
|
+const checkPointIsInRectangle = (x, y, rx, ry, rw, rh) => {
|
|
|
+ if (typeof rx === "object") {
|
|
|
+ let element = rx;
|
|
|
+ rx = element.x;
|
|
|
+ ry = element.y;
|
|
|
+ rw = element.width;
|
|
|
+ rh = element.height;
|
|
|
+ }
|
|
|
+ return x >= rx && x <= rx + rw && y >= ry && y <= ry + rh;
|
|
|
+};
|
|
|
+const getBoundingRect = (pointArr = [], returnCorners = false) => {
|
|
|
+ let minX = Infinity;
|
|
|
+ let maxX = -Infinity;
|
|
|
+ let minY = Infinity;
|
|
|
+ let maxY = -Infinity;
|
|
|
+ pointArr.forEach((point) => {
|
|
|
+ let [x2, y2] = point;
|
|
|
+ if (x2 < minX) {
|
|
|
+ minX = x2;
|
|
|
+ }
|
|
|
+ if (x2 > maxX) {
|
|
|
+ maxX = x2;
|
|
|
+ }
|
|
|
+ if (y2 < minY) {
|
|
|
+ minY = y2;
|
|
|
+ }
|
|
|
+ if (y2 > maxY) {
|
|
|
+ maxY = y2;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ let x = minX;
|
|
|
+ let y = minY;
|
|
|
+ let width = maxX - minX;
|
|
|
+ let height = maxY - minY;
|
|
|
+ if (returnCorners) {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x: x + width,
|
|
|
+ y
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x: x + width,
|
|
|
+ y: y + height
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x,
|
|
|
+ y: y + height
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ };
|
|
|
+};
|
|
|
+const deepCopy = (obj) => {
|
|
|
+ return JSON.parse(JSON.stringify(obj));
|
|
|
+};
|
|
|
+const getFontString = (fontSize, fontFamily) => {
|
|
|
+ return `${fontSize}px ${fontFamily}`;
|
|
|
+};
|
|
|
+const splitTextLines = (text) => {
|
|
|
+ return text.replace(/\r\n?/g, "\n").split("\n");
|
|
|
+};
|
|
|
+let textCheckEl = null;
|
|
|
+const getTextActWidth = (text, style) => {
|
|
|
+ if (!textCheckEl) {
|
|
|
+ textCheckEl = document.createElement("div");
|
|
|
+ textCheckEl.style.position = "fixed";
|
|
|
+ textCheckEl.style.left = "-99999px";
|
|
|
+ document.body.appendChild(textCheckEl);
|
|
|
+ }
|
|
|
+ let { fontSize, fontFamily } = style;
|
|
|
+ textCheckEl.innerText = text;
|
|
|
+ textCheckEl.style.fontSize = fontSize + "px";
|
|
|
+ textCheckEl.style.fontFamily = fontFamily;
|
|
|
+ let { width } = textCheckEl.getBoundingClientRect();
|
|
|
+ return width;
|
|
|
+};
|
|
|
+const getMaxFontSizeInWidth = (text, width, style) => {
|
|
|
+ let fontSize = 12;
|
|
|
+ while (getTextActWidth(text, __spreadProps(__spreadValues({}, style), {
|
|
|
+ fontSize: fontSize + 1
|
|
|
+ })) < width) {
|
|
|
+ fontSize++;
|
|
|
+ }
|
|
|
+ return fontSize;
|
|
|
+};
|
|
|
+const getWrapTextActWidth = (element) => {
|
|
|
+ let { text } = element;
|
|
|
+ let textArr = splitTextLines(text);
|
|
|
+ let maxWidth = -Infinity;
|
|
|
+ textArr.forEach((textRow) => {
|
|
|
+ let width = getTextActWidth(textRow, element.style);
|
|
|
+ if (width > maxWidth) {
|
|
|
+ maxWidth = width;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return maxWidth;
|
|
|
+};
|
|
|
+const getWrapTextMaxRowTextNumber = (text) => {
|
|
|
+ let textArr = splitTextLines(text);
|
|
|
+ let maxNumber = -Infinity;
|
|
|
+ textArr.forEach((textRow) => {
|
|
|
+ if (textRow.length > maxNumber) {
|
|
|
+ maxNumber = textRow.length;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return maxNumber;
|
|
|
+};
|
|
|
+const getTextElementSize = (element) => {
|
|
|
+ let { text, style } = element;
|
|
|
+ let width = getWrapTextActWidth(element);
|
|
|
+ const lines = Math.max(splitTextLines(text).length, 1);
|
|
|
+ let lineHeight = style.fontSize * style.lineHeightRatio;
|
|
|
+ let height = lines * lineHeight;
|
|
|
+ return {
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ };
|
|
|
+};
|
|
|
+const throttle = (fn, ctx, time = 100) => {
|
|
|
+ let timer = null;
|
|
|
+ return (...args) => {
|
|
|
+ if (timer) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ timer = setTimeout(() => {
|
|
|
+ fn.call(ctx, ...args);
|
|
|
+ timer = null;
|
|
|
+ }, time);
|
|
|
+ };
|
|
|
+};
|
|
|
+const computedLineWidthBySpeed = (speed, lastLineWidth, baseLineWidth = 2) => {
|
|
|
+ let lineWidth = 0;
|
|
|
+ let maxLineWidth = baseLineWidth;
|
|
|
+ let maxSpeed = 10;
|
|
|
+ let minSpeed = 0.5;
|
|
|
+ if (speed >= maxSpeed) {
|
|
|
+ lineWidth = baseLineWidth;
|
|
|
+ } else if (speed <= minSpeed) {
|
|
|
+ lineWidth = maxLineWidth + 1;
|
|
|
+ } else {
|
|
|
+ lineWidth = maxLineWidth - (speed - minSpeed) / (maxSpeed - minSpeed) * maxLineWidth;
|
|
|
+ }
|
|
|
+ if (lastLineWidth === -1) {
|
|
|
+ lastLineWidth = maxLineWidth;
|
|
|
+ }
|
|
|
+ return lineWidth * (1 / 2) + lastLineWidth * (1 / 2);
|
|
|
+};
|
|
|
+const downloadFile = (file, fileName) => {
|
|
|
+ let a = document.createElement("a");
|
|
|
+ a.href = file;
|
|
|
+ a.download = fileName;
|
|
|
+ a.click();
|
|
|
+};
|
|
|
+const getElementCorners = (element) => {
|
|
|
+ let topLeft = getElementRotatedCornerPoint(element, "topLeft");
|
|
|
+ let topRight = getElementRotatedCornerPoint(element, "topRight");
|
|
|
+ let bottomLeft = getElementRotatedCornerPoint(element, "bottomLeft");
|
|
|
+ let bottomRight = getElementRotatedCornerPoint(element, "bottomRight");
|
|
|
+ return [topLeft, topRight, bottomLeft, bottomRight];
|
|
|
+};
|
|
|
+const getMultiElementRectInfo = (elementList = []) => {
|
|
|
+ if (elementList.length <= 0) {
|
|
|
+ return {
|
|
|
+ minx: 0,
|
|
|
+ maxx: 0,
|
|
|
+ miny: 0,
|
|
|
+ maxy: 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+ let minx = Infinity;
|
|
|
+ let maxx = -Infinity;
|
|
|
+ let miny = Infinity;
|
|
|
+ let maxy = -Infinity;
|
|
|
+ elementList.forEach((element) => {
|
|
|
+ let pointList = element.getEndpointList();
|
|
|
+ pointList.forEach(({ x, y }) => {
|
|
|
+ if (x < minx) {
|
|
|
+ minx = x;
|
|
|
+ }
|
|
|
+ if (x > maxx) {
|
|
|
+ maxx = x;
|
|
|
+ }
|
|
|
+ if (y < miny) {
|
|
|
+ miny = y;
|
|
|
+ }
|
|
|
+ if (y > maxy) {
|
|
|
+ maxy = y;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return {
|
|
|
+ minx,
|
|
|
+ maxx,
|
|
|
+ miny,
|
|
|
+ maxy
|
|
|
+ };
|
|
|
+};
|
|
|
+const createImageObj = (url) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ let img = new Image();
|
|
|
+ img.setAttribute("crossOrigin", "anonymous");
|
|
|
+ img.onload = () => {
|
|
|
+ resolve(img);
|
|
|
+ };
|
|
|
+ img.onerror = () => {
|
|
|
+ resolve(null);
|
|
|
+ };
|
|
|
+ img.src = url;
|
|
|
+ });
|
|
|
+};
|
|
|
+let nodeKeyIndex = 0;
|
|
|
+const createNodeKey = () => {
|
|
|
+ return nodeKeyIndex++;
|
|
|
+};
|
|
|
+var utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
|
+ __proto__: null,
|
|
|
+ createCanvas,
|
|
|
+ getTowPointDistance,
|
|
|
+ getPointToLineDistance,
|
|
|
+ checkIsAtSegment,
|
|
|
+ radToDeg,
|
|
|
+ degToRad,
|
|
|
+ getTowPointRotate,
|
|
|
+ getRotatedPoint,
|
|
|
+ getElementCenterPoint,
|
|
|
+ transformPointReverseRotate,
|
|
|
+ transformPointOnElement,
|
|
|
+ getElementCornerPoint,
|
|
|
+ getElementRotatedCornerPoint,
|
|
|
+ checkPointIsInRectangle,
|
|
|
+ getBoundingRect,
|
|
|
+ deepCopy,
|
|
|
+ getFontString,
|
|
|
+ splitTextLines,
|
|
|
+ getTextActWidth,
|
|
|
+ getMaxFontSizeInWidth,
|
|
|
+ getWrapTextActWidth,
|
|
|
+ getWrapTextMaxRowTextNumber,
|
|
|
+ getTextElementSize,
|
|
|
+ throttle,
|
|
|
+ computedLineWidthBySpeed,
|
|
|
+ downloadFile,
|
|
|
+ getElementCorners,
|
|
|
+ getMultiElementRectInfo,
|
|
|
+ createImageObj,
|
|
|
+ createNodeKey
|
|
|
+}, Symbol.toStringTag, { value: "Module" }));
|
|
|
+const CORNERS = {
|
|
|
+ TOP_LEFT: "topLeft",
|
|
|
+ TOP_RIGHT: "topRight",
|
|
|
+ BOTTOM_RIGHT: "bottomRight",
|
|
|
+ BOTTOM_LEFT: "bottomLeft"
|
|
|
+};
|
|
|
+const DRAG_ELEMENT_PARTS = {
|
|
|
+ BODY: "body",
|
|
|
+ ROTATE: "rotate",
|
|
|
+ TOP_LEFT_BTN: "topLeftBtn",
|
|
|
+ TOP_RIGHT_BTN: "topRightBtn",
|
|
|
+ BOTTOM_RIGHT_BTN: "bottomRightBtn",
|
|
|
+ BOTTOM_LEFT_BTN: "bottomLeftBtn"
|
|
|
+};
|
|
|
+const HIT_DISTANCE = 10;
|
|
|
+const checkIsAtMultiSegment = (segments, rp) => {
|
|
|
+ let res = false;
|
|
|
+ segments.forEach((seg) => {
|
|
|
+ if (res)
|
|
|
+ return;
|
|
|
+ if (checkIsAtSegment(rp.x, rp.y, ...seg, HIT_DISTANCE)) {
|
|
|
+ res = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return res;
|
|
|
+};
|
|
|
+const checkIsAtRectangleEdge = (element, rp) => {
|
|
|
+ let { x, y, width, height } = element;
|
|
|
+ let segments = [
|
|
|
+ [x, y, x + width, y],
|
|
|
+ [x + width, y, x + width, y + height],
|
|
|
+ [x + width, y + height, x, y + height],
|
|
|
+ [x, y + height, x, y]
|
|
|
+ ];
|
|
|
+ return checkIsAtMultiSegment(segments, rp) ? element : null;
|
|
|
+};
|
|
|
+const checkIsAtRectangleInner = (element, rp) => {
|
|
|
+ return checkPointIsInRectangle(rp.x, rp.y, element) ? element : null;
|
|
|
+};
|
|
|
+const getCircleRadius = (width, height) => {
|
|
|
+ return Math.min(Math.abs(width), Math.abs(height)) / 2;
|
|
|
+};
|
|
|
+const checkIsAtCircleEdge = (element, rp) => {
|
|
|
+ let { width, height, x, y } = element;
|
|
|
+ let radius = getCircleRadius(width, height);
|
|
|
+ let dis = getTowPointDistance(rp.x, rp.y, x + radius, y + radius);
|
|
|
+ let onCircle = dis >= radius - HIT_DISTANCE && dis <= radius + HIT_DISTANCE;
|
|
|
+ return onCircle ? element : null;
|
|
|
+};
|
|
|
+const checkIsAtLineEdge = (element, rp) => {
|
|
|
+ let segments = [];
|
|
|
+ let len = element.pointArr.length;
|
|
|
+ let arr = element.pointArr;
|
|
|
+ for (let i = 0; i < len - 1; i++) {
|
|
|
+ segments.push([...arr[i], ...arr[i + 1]]);
|
|
|
+ }
|
|
|
+ return checkIsAtMultiSegment(segments, rp) ? element : null;
|
|
|
+};
|
|
|
+const checkIsAtFreedrawLineEdge = (element, rp) => {
|
|
|
+ let res = null;
|
|
|
+ element.pointArr.forEach((point) => {
|
|
|
+ if (res)
|
|
|
+ return;
|
|
|
+ let dis = getTowPointDistance(rp.x, rp.y, point[0], point[1]);
|
|
|
+ if (dis <= HIT_DISTANCE) {
|
|
|
+ res = element;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return res;
|
|
|
+};
|
|
|
+const checkIsAtDiamondEdge = (element, rp) => {
|
|
|
+ let { x, y, width, height } = element;
|
|
|
+ let segments = [
|
|
|
+ [x + width / 2, y, x + width, y + height / 2],
|
|
|
+ [x + width, y + height / 2, x + width / 2, y + height],
|
|
|
+ [x + width / 2, y + height, x, y + height / 2],
|
|
|
+ [x, y + height / 2, x + width / 2, y]
|
|
|
+ ];
|
|
|
+ return checkIsAtMultiSegment(segments, rp) ? element : null;
|
|
|
+};
|
|
|
+const checkIsAtTriangleEdge = (element, rp) => {
|
|
|
+ let { x, y, width, height } = element;
|
|
|
+ let segments = [
|
|
|
+ [x + width / 2, y, x + width, y + height],
|
|
|
+ [x + width, y + height, x, y + height],
|
|
|
+ [x, y + height, x + width / 2, y]
|
|
|
+ ];
|
|
|
+ return checkIsAtMultiSegment(segments, rp) ? element : null;
|
|
|
+};
|
|
|
+const checkIsAtArrowEdge = (element, rp) => {
|
|
|
+ let pointArr = element.pointArr;
|
|
|
+ let x = pointArr[0][0];
|
|
|
+ let y = pointArr[0][1];
|
|
|
+ let tx = pointArr[pointArr.length - 1][0];
|
|
|
+ let ty = pointArr[pointArr.length - 1][1];
|
|
|
+ let segments = [[x, y, tx, ty]];
|
|
|
+ return checkIsAtMultiSegment(segments, rp) ? element : null;
|
|
|
+};
|
|
|
+var checkHit = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
|
+ __proto__: null,
|
|
|
+ checkIsAtMultiSegment,
|
|
|
+ checkIsAtRectangleEdge,
|
|
|
+ checkIsAtRectangleInner,
|
|
|
+ getCircleRadius,
|
|
|
+ checkIsAtCircleEdge,
|
|
|
+ checkIsAtLineEdge,
|
|
|
+ checkIsAtFreedrawLineEdge,
|
|
|
+ checkIsAtDiamondEdge,
|
|
|
+ checkIsAtTriangleEdge,
|
|
|
+ checkIsAtArrowEdge
|
|
|
+}, Symbol.toStringTag, { value: "Module" }));
|
|
|
+const drawWrap = (ctx, fn, fill = false) => {
|
|
|
+ ctx.beginPath();
|
|
|
+ fn();
|
|
|
+ ctx.stroke();
|
|
|
+ if (fill) {
|
|
|
+ ctx.fill();
|
|
|
+ }
|
|
|
+};
|
|
|
+const drawRect = (ctx, x, y, width, height, fill = false) => {
|
|
|
+ drawWrap(ctx, () => {
|
|
|
+ ctx.rect(x, y, width, height);
|
|
|
+ if (fill) {
|
|
|
+ ctx.fillRect(x, y, width, height);
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+const drawDiamond = (ctx, x, y, width, height, fill = false) => {
|
|
|
+ drawWrap(
|
|
|
+ ctx,
|
|
|
+ () => {
|
|
|
+ ctx.moveTo(x + width / 2, y);
|
|
|
+ ctx.lineTo(x + width, y + height / 2);
|
|
|
+ ctx.lineTo(x + width / 2, y + height);
|
|
|
+ ctx.lineTo(x, y + height / 2);
|
|
|
+ ctx.closePath();
|
|
|
+ },
|
|
|
+ fill
|
|
|
+ );
|
|
|
+};
|
|
|
+const drawTriangle = (ctx, x, y, width, height, fill = false) => {
|
|
|
+ drawWrap(
|
|
|
+ ctx,
|
|
|
+ () => {
|
|
|
+ ctx.moveTo(x + width / 2, y);
|
|
|
+ ctx.lineTo(x + width, y + height);
|
|
|
+ ctx.lineTo(x, y + height);
|
|
|
+ ctx.closePath();
|
|
|
+ },
|
|
|
+ fill
|
|
|
+ );
|
|
|
+};
|
|
|
+const drawCircle = (ctx, x, y, r, fill = false) => {
|
|
|
+ drawWrap(
|
|
|
+ ctx,
|
|
|
+ () => {
|
|
|
+ ctx.arc(x, y, r, 0, 2 * Math.PI);
|
|
|
+ },
|
|
|
+ fill
|
|
|
+ );
|
|
|
+};
|
|
|
+const drawLine = (ctx, points) => {
|
|
|
+ drawWrap(ctx, () => {
|
|
|
+ let first = true;
|
|
|
+ points.forEach((point) => {
|
|
|
+ if (first) {
|
|
|
+ first = false;
|
|
|
+ ctx.moveTo(point[0], point[1]);
|
|
|
+ } else {
|
|
|
+ ctx.lineTo(point[0], point[1]);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
+const drawArrow = (ctx, pointArr) => {
|
|
|
+ let x = pointArr[0][0];
|
|
|
+ let y = pointArr[0][1];
|
|
|
+ let tx = pointArr[pointArr.length - 1][0];
|
|
|
+ let ty = pointArr[pointArr.length - 1][1];
|
|
|
+ drawWrap(
|
|
|
+ ctx,
|
|
|
+ () => {
|
|
|
+ ctx.moveTo(x, y);
|
|
|
+ ctx.lineTo(tx, ty);
|
|
|
+ },
|
|
|
+ true
|
|
|
+ );
|
|
|
+ let l = 30;
|
|
|
+ let deg = 30;
|
|
|
+ let lineDeg = radToDeg(Math.atan2(ty - y, tx - x));
|
|
|
+ drawWrap(
|
|
|
+ ctx,
|
|
|
+ () => {
|
|
|
+ let plusDeg = deg - lineDeg;
|
|
|
+ let _x = tx - l * Math.cos(degToRad(plusDeg));
|
|
|
+ let _y = ty + l * Math.sin(degToRad(plusDeg));
|
|
|
+ ctx.moveTo(_x, _y);
|
|
|
+ ctx.lineTo(tx, ty);
|
|
|
+ },
|
|
|
+ true
|
|
|
+ );
|
|
|
+ drawWrap(ctx, () => {
|
|
|
+ let plusDeg = 90 - lineDeg - deg;
|
|
|
+ let _x = tx - l * Math.sin(degToRad(plusDeg));
|
|
|
+ let _y = ty - l * Math.cos(degToRad(plusDeg));
|
|
|
+ ctx.moveTo(_x, _y);
|
|
|
+ ctx.lineTo(tx, ty);
|
|
|
+ });
|
|
|
+};
|
|
|
+const transformFreeLinePoint = (point, opt) => {
|
|
|
+ let { x, y } = opt.app.coordinate.transform(point[0], point[1]);
|
|
|
+ return [x - opt.cx, y - opt.cy, ...point.slice(2)];
|
|
|
+};
|
|
|
+const drawFreeLine = (ctx, points, opt) => {
|
|
|
+ for (let i = 0; i < points.length - 1; i++) {
|
|
|
+ drawWrap(
|
|
|
+ ctx,
|
|
|
+ () => {
|
|
|
+ let point = transformFreeLinePoint(points[i], opt);
|
|
|
+ let nextPoint = transformFreeLinePoint(points[i + 1], opt);
|
|
|
+ drawLineSegment(
|
|
|
+ ctx,
|
|
|
+ point[0],
|
|
|
+ point[1],
|
|
|
+ nextPoint[0],
|
|
|
+ nextPoint[1],
|
|
|
+ nextPoint[2]
|
|
|
+ );
|
|
|
+ },
|
|
|
+ true
|
|
|
+ );
|
|
|
+ }
|
|
|
+};
|
|
|
+const drawLineSegment = (ctx, mx, my, tx, ty, lineWidth = 0) => {
|
|
|
+ drawWrap(ctx, () => {
|
|
|
+ if (lineWidth > 0) {
|
|
|
+ ctx.lineWidth = lineWidth;
|
|
|
+ }
|
|
|
+ ctx.moveTo(mx, my);
|
|
|
+ ctx.lineTo(tx, ty);
|
|
|
+ ctx.lineCap = "round";
|
|
|
+ ctx.lineJoin = "round";
|
|
|
+ });
|
|
|
+};
|
|
|
+const drawText = (ctx, textObj, x, y, width, height) => {
|
|
|
+ let { text, style } = textObj;
|
|
|
+ let lineHeight = style.fontSize * style.lineHeightRatio;
|
|
|
+ drawWrap(ctx, () => {
|
|
|
+ ctx.font = getFontString(style.fontSize, style.fontFamily);
|
|
|
+ ctx.textBaseline = "middle";
|
|
|
+ let textArr = splitTextLines(text);
|
|
|
+ textArr.forEach((textRow, index) => {
|
|
|
+ ctx.fillText(textRow, x, y + (index * lineHeight + lineHeight / 2));
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
+const drawImage = (ctx, element, x, y, width, height) => {
|
|
|
+ drawWrap(ctx, () => {
|
|
|
+ let ratio = width / height;
|
|
|
+ let showWidth = 0;
|
|
|
+ let showHeight = 0;
|
|
|
+ if (ratio > element.ratio) {
|
|
|
+ showHeight = height;
|
|
|
+ showWidth = element.ratio * height;
|
|
|
+ } else {
|
|
|
+ showWidth = width;
|
|
|
+ showHeight = width / element.ratio;
|
|
|
+ }
|
|
|
+ ctx.drawImage(element.imageObj, x, y, showWidth, showHeight);
|
|
|
+ });
|
|
|
+};
|
|
|
+var draw = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
|
+ __proto__: null,
|
|
|
+ drawWrap,
|
|
|
+ drawRect,
|
|
|
+ drawDiamond,
|
|
|
+ drawTriangle,
|
|
|
+ drawCircle,
|
|
|
+ drawLine,
|
|
|
+ drawArrow,
|
|
|
+ drawFreeLine,
|
|
|
+ drawLineSegment,
|
|
|
+ drawText,
|
|
|
+ drawImage
|
|
|
+}, Symbol.toStringTag, { value: "Module" }));
|
|
|
+class Coordinate {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ }
|
|
|
+ addScrollY(y) {
|
|
|
+ return y + this.app.state.scrollY;
|
|
|
+ }
|
|
|
+ addScrollX(x) {
|
|
|
+ return x + this.app.state.scrollX;
|
|
|
+ }
|
|
|
+ subScrollY(y) {
|
|
|
+ return y - this.app.state.scrollY;
|
|
|
+ }
|
|
|
+ subScrollX(x) {
|
|
|
+ return x - this.app.state.scrollX;
|
|
|
+ }
|
|
|
+ transformToCanvasCoordinate(x, y) {
|
|
|
+ x -= this.app.width / 2;
|
|
|
+ y -= this.app.height / 2;
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ transformToScreenCoordinate(x, y) {
|
|
|
+ x += this.app.width / 2;
|
|
|
+ y += this.app.height / 2;
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ transform(x, y) {
|
|
|
+ let t = this.transformToCanvasCoordinate(x, y);
|
|
|
+ return {
|
|
|
+ x: this.subScrollX(t.x),
|
|
|
+ y: this.subScrollY(t.y)
|
|
|
+ };
|
|
|
+ }
|
|
|
+ windowToContainer(x, y) {
|
|
|
+ return {
|
|
|
+ x: x - this.app.left,
|
|
|
+ y: y - this.app.top
|
|
|
+ };
|
|
|
+ }
|
|
|
+ containerToWindow(x, y) {
|
|
|
+ return {
|
|
|
+ x: x + this.app.left,
|
|
|
+ y: y + this.app.top
|
|
|
+ };
|
|
|
+ }
|
|
|
+ scale(x, y) {
|
|
|
+ let { state } = this.app;
|
|
|
+ let wp = this.transformToCanvasCoordinate(x, y);
|
|
|
+ let sp = this.transformToScreenCoordinate(
|
|
|
+ wp.x * state.scale,
|
|
|
+ wp.y * state.scale
|
|
|
+ );
|
|
|
+ return {
|
|
|
+ x: sp.x,
|
|
|
+ y: sp.y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ reverseScale(x, y) {
|
|
|
+ let { state } = this.app;
|
|
|
+ let tp = this.transformToCanvasCoordinate(x, y);
|
|
|
+ let sp = this.transformToScreenCoordinate(
|
|
|
+ tp.x / state.scale,
|
|
|
+ tp.y / state.scale
|
|
|
+ );
|
|
|
+ return {
|
|
|
+ x: sp.x,
|
|
|
+ y: sp.y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ gridAdsorbent(x, y) {
|
|
|
+ let { gridConfig, showGrid } = this.app.state;
|
|
|
+ if (!showGrid) {
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ let gridSize = gridConfig.size;
|
|
|
+ return {
|
|
|
+ x: x - x % gridSize,
|
|
|
+ y: y - y % gridSize
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+class Event extends EventEmitter {
|
|
|
+ constructor(app) {
|
|
|
+ super();
|
|
|
+ this.app = app;
|
|
|
+ this.coordinate = app.coordinate;
|
|
|
+ this.isMousedown = false;
|
|
|
+ this.mousedownPos = {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ unGridClientX: 0,
|
|
|
+ unGridClientY: 0,
|
|
|
+ originClientX: 0,
|
|
|
+ originClientY: 0
|
|
|
+ };
|
|
|
+ this.mouseOffset = {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ originX: 0,
|
|
|
+ originY: 0
|
|
|
+ };
|
|
|
+ this.lastMousePos = {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ this.mouseDistance = 0;
|
|
|
+ this.lastMouseTime = Date.now();
|
|
|
+ this.mouseDuration = 0;
|
|
|
+ this.mouseSpeed = 0;
|
|
|
+ this.onMousedown = this.onMousedown.bind(this);
|
|
|
+ this.onMousemove = this.onMousemove.bind(this);
|
|
|
+ this.onMouseup = this.onMouseup.bind(this);
|
|
|
+ this.onDblclick = this.onDblclick.bind(this);
|
|
|
+ this.onMousewheel = this.onMousewheel.bind(this);
|
|
|
+ this.onKeydown = this.onKeydown.bind(this);
|
|
|
+ this.onKeyup = this.onKeyup.bind(this);
|
|
|
+ this.onContextmenu = this.onContextmenu.bind(this);
|
|
|
+ this.bindEvent();
|
|
|
+ }
|
|
|
+ bindEvent() {
|
|
|
+ this.app.container.addEventListener("mousedown", this.onMousedown);
|
|
|
+ this.app.container.addEventListener("mousemove", this.onMousemove);
|
|
|
+ this.app.container.addEventListener("mouseup", this.onMouseup);
|
|
|
+ this.app.container.addEventListener("dblclick", this.onDblclick);
|
|
|
+ this.app.container.addEventListener("mousewheel", this.onMousewheel);
|
|
|
+ this.app.container.addEventListener("contextmenu", this.onContextmenu);
|
|
|
+ window.addEventListener("keydown", this.onKeydown);
|
|
|
+ window.addEventListener("keyup", this.onKeyup);
|
|
|
+ }
|
|
|
+ unbindEvent() {
|
|
|
+ this.app.container.removeEventListener("mousedown", this.onMousedown);
|
|
|
+ this.app.container.removeEventListener("mousemove", this.onMousemove);
|
|
|
+ this.app.container.removeEventListener("mouseup", this.onMouseup);
|
|
|
+ this.app.container.removeEventListener("dblclick", this.onDblclick);
|
|
|
+ this.app.container.removeEventListener("mousewheel", this.onMousewheel);
|
|
|
+ this.app.container.removeEventListener("contextmenu", this.onContextmenu);
|
|
|
+ window.removeEventListener("keydown", this.onKeydown);
|
|
|
+ window.removeEventListener("keyup", this.onKeyup);
|
|
|
+ }
|
|
|
+ transformEvent(e) {
|
|
|
+ let { coordinate } = this.app;
|
|
|
+ let wp = coordinate.windowToContainer(e.clientX, e.clientY);
|
|
|
+ let { x, y } = coordinate.reverseScale(wp.x, wp.y);
|
|
|
+ x = coordinate.addScrollX(x);
|
|
|
+ y = coordinate.addScrollY(y);
|
|
|
+ let unGridClientX = x;
|
|
|
+ let unGridClientY = y;
|
|
|
+ let gp = coordinate.gridAdsorbent(x, y);
|
|
|
+ let newEvent = {
|
|
|
+ originEvent: e,
|
|
|
+ unGridClientX,
|
|
|
+ unGridClientY,
|
|
|
+ clientX: gp.x,
|
|
|
+ clientY: gp.y
|
|
|
+ };
|
|
|
+ return newEvent;
|
|
|
+ }
|
|
|
+ onMousedown(e) {
|
|
|
+ e = this.transformEvent(e);
|
|
|
+ this.isMousedown = true;
|
|
|
+ this.mousedownPos.x = e.clientX;
|
|
|
+ this.mousedownPos.y = e.clientY;
|
|
|
+ this.mousedownPos.unGridClientX = e.unGridClientX;
|
|
|
+ this.mousedownPos.unGridClientY = e.unGridClientY;
|
|
|
+ this.mousedownPos.originClientX = e.originEvent.clientX;
|
|
|
+ this.mousedownPos.originClientY = e.originEvent.clientY;
|
|
|
+ this.emit("mousedown", e, this);
|
|
|
+ }
|
|
|
+ onMousemove(e) {
|
|
|
+ e = this.transformEvent(e);
|
|
|
+ let x = e.clientX;
|
|
|
+ let y = e.clientY;
|
|
|
+ if (this.isMousedown) {
|
|
|
+ this.mouseOffset.x = x - this.mousedownPos.x;
|
|
|
+ this.mouseOffset.y = y - this.mousedownPos.y;
|
|
|
+ this.mouseOffset.originX = e.originEvent.clientX - this.mousedownPos.originClientX;
|
|
|
+ this.mouseOffset.originY = e.originEvent.clientY - this.mousedownPos.originClientY;
|
|
|
+ }
|
|
|
+ let curTime = Date.now();
|
|
|
+ this.mouseDuration = curTime - this.lastMouseTime;
|
|
|
+ this.mouseDistance = getTowPointDistance(
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ this.lastMousePos.x,
|
|
|
+ this.lastMousePos.y
|
|
|
+ );
|
|
|
+ this.mouseSpeed = this.mouseDistance / this.mouseDuration;
|
|
|
+ this.emit("mousemove", e, this);
|
|
|
+ this.lastMouseTime = curTime;
|
|
|
+ this.lastMousePos.x = x;
|
|
|
+ this.lastMousePos.y = y;
|
|
|
+ }
|
|
|
+ onMouseup(e) {
|
|
|
+ e = this.transformEvent(e);
|
|
|
+ this.isMousedown = false;
|
|
|
+ this.mousedownPos.x = 0;
|
|
|
+ this.mousedownPos.y = 0;
|
|
|
+ this.emit("mouseup", e, this);
|
|
|
+ }
|
|
|
+ onDblclick(e) {
|
|
|
+ e = this.transformEvent(e);
|
|
|
+ this.emit("dblclick", e, this);
|
|
|
+ }
|
|
|
+ onMousewheel(e) {
|
|
|
+ e = this.transformEvent(e);
|
|
|
+ this.emit("mousewheel", e.originEvent.wheelDelta < 0 ? "down" : "up");
|
|
|
+ }
|
|
|
+ onContextmenu(e) {
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ e = this.transformEvent(e);
|
|
|
+ this.emit("contextmenu", e, this);
|
|
|
+ }
|
|
|
+ onKeydown(e) {
|
|
|
+ this.emit("keydown", e, this);
|
|
|
+ }
|
|
|
+ onKeyup(e) {
|
|
|
+ this.emit("keyup", e, this);
|
|
|
+ }
|
|
|
+}
|
|
|
+class BaseElement extends EventEmitter {
|
|
|
+ constructor(opts = {}, app) {
|
|
|
+ super();
|
|
|
+ this.app = app;
|
|
|
+ this.groupId = opts.groupId || "";
|
|
|
+ this.type = opts.type || "";
|
|
|
+ this.key = createNodeKey();
|
|
|
+ this.isCreating = true;
|
|
|
+ this.isActive = true;
|
|
|
+ this.isSelected = false;
|
|
|
+ this.startX = 0;
|
|
|
+ this.startY = 0;
|
|
|
+ this.x = opts.x || 0;
|
|
|
+ this.y = opts.y || 0;
|
|
|
+ this.width = opts.width || 0;
|
|
|
+ this.height = opts.height || 0;
|
|
|
+ this.startRotate = 0;
|
|
|
+ this.rotate = opts.rotate || 0;
|
|
|
+ this.noRender = false;
|
|
|
+ this.style = __spreadValues({
|
|
|
+ strokeStyle: "",
|
|
|
+ fillStyle: "",
|
|
|
+ lineWidth: "small",
|
|
|
+ lineDash: 0,
|
|
|
+ globalAlpha: 1
|
|
|
+ }, opts.style || {});
|
|
|
+ this.dragElement = null;
|
|
|
+ }
|
|
|
+ serialize() {
|
|
|
+ return {
|
|
|
+ groupId: this.groupId,
|
|
|
+ type: this.type,
|
|
|
+ width: this.width,
|
|
|
+ height: this.height,
|
|
|
+ x: this.x,
|
|
|
+ y: this.y,
|
|
|
+ rotate: this.rotate,
|
|
|
+ style: __spreadValues({}, this.style)
|
|
|
+ };
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ throw new Error("\u5B50\u7C7B\u9700\u8981\u5B9E\u73B0\u8BE5\u65B9\u6CD5\uFF01");
|
|
|
+ }
|
|
|
+ setGroupId(groupId) {
|
|
|
+ this.groupId = groupId;
|
|
|
+ }
|
|
|
+ getGroupId() {
|
|
|
+ return this.groupId;
|
|
|
+ }
|
|
|
+ removeGroupId() {
|
|
|
+ this.groupId = "";
|
|
|
+ }
|
|
|
+ hasGroup() {
|
|
|
+ return !!this.groupId;
|
|
|
+ }
|
|
|
+ renderDragElement() {
|
|
|
+ if (this.isActive && !this.isCreating) {
|
|
|
+ this.dragElement.showAll();
|
|
|
+ this.dragElement.render();
|
|
|
+ } else if (this.isSelected) {
|
|
|
+ this.dragElement.onlyShowBody();
|
|
|
+ this.dragElement.render();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ handleStyle(style) {
|
|
|
+ Object.keys(style).forEach((key) => {
|
|
|
+ if (key === "lineWidth") {
|
|
|
+ if (style[key] === "small") {
|
|
|
+ style[key] = 2;
|
|
|
+ } else if (style[key] === "middle") {
|
|
|
+ style[key] = 4;
|
|
|
+ } else if (style[key] === "large") {
|
|
|
+ style[key] = 6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (style[key] === "") {
|
|
|
+ if (this.app.state[key] !== void 0 && this.app.state[key] !== null && this.app.state[key] !== "") {
|
|
|
+ style[key] = this.app.state[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+ setStyle(style = {}) {
|
|
|
+ let _style = this.handleStyle(style);
|
|
|
+ Object.keys(_style).forEach((key) => {
|
|
|
+ if (key === "lineDash") {
|
|
|
+ if (_style.lineDash > 0) {
|
|
|
+ this.app.ctx.setLineDash([_style.lineDash]);
|
|
|
+ }
|
|
|
+ } else if (_style[key] !== void 0 && _style[key] !== "" && _style[key] !== null) {
|
|
|
+ this.app.ctx[key] = _style[key];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ warpRender(renderFn) {
|
|
|
+ let { x, y, width, height, rotate, style } = this;
|
|
|
+ let { x: tx, y: ty } = this.app.coordinate.transform(x, y);
|
|
|
+ let halfWidth = width / 2;
|
|
|
+ let halfHeight = height / 2;
|
|
|
+ let cx = tx + halfWidth;
|
|
|
+ let cy = ty + halfHeight;
|
|
|
+ this.app.ctx.save();
|
|
|
+ this.app.ctx.translate(cx, cy);
|
|
|
+ this.app.ctx.rotate(degToRad(rotate));
|
|
|
+ this.setStyle(style);
|
|
|
+ renderFn({
|
|
|
+ halfWidth,
|
|
|
+ halfHeight,
|
|
|
+ tx,
|
|
|
+ ty,
|
|
|
+ cx,
|
|
|
+ cy
|
|
|
+ });
|
|
|
+ this.app.ctx.restore();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ saveState() {
|
|
|
+ let { rotate, x, y } = this;
|
|
|
+ this.startRotate = rotate;
|
|
|
+ this.startX = x;
|
|
|
+ this.startY = y;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ move(ox, oy) {
|
|
|
+ let { startX, startY } = this;
|
|
|
+ this.x = startX + ox;
|
|
|
+ this.y = startY + oy;
|
|
|
+ this.emit("elementPositionChange", this.x, this.y);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateRect(x, y, width, height) {
|
|
|
+ this.updatePos(x, y);
|
|
|
+ this.updateSize(width, height);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateSize(width, height) {
|
|
|
+ this.width = width;
|
|
|
+ this.height = height;
|
|
|
+ this.emit("elementSizeChange", this.width, this.height);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updatePos(x, y) {
|
|
|
+ this.x = x;
|
|
|
+ this.y = y;
|
|
|
+ this.emit("elementPositionChange", this.x, this.y);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ offsetRotate(or) {
|
|
|
+ this.updateRotate(this.startRotate + or);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateRotate(rotate) {
|
|
|
+ rotate = rotate % 360;
|
|
|
+ if (rotate < 0) {
|
|
|
+ rotate = 360 + rotate;
|
|
|
+ }
|
|
|
+ this.rotate = parseInt(rotate);
|
|
|
+ this.emit("elementRotateChange", this.rotate);
|
|
|
+ }
|
|
|
+ rotateByCenter(rotate, cx, cy) {
|
|
|
+ this.offsetRotate(rotate);
|
|
|
+ let np = getRotatedPoint(this.startX, this.startY, cx, cy, rotate);
|
|
|
+ this.updatePos(np.x, np.y);
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ throw new Error("\u5B50\u7C7B\u9700\u8981\u5B9E\u73B0\u8BE5\u65B9\u6CD5!");
|
|
|
+ }
|
|
|
+ startResize(resizeType, e) {
|
|
|
+ this.dragElement.startResize(resizeType, e);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ endResize() {
|
|
|
+ this.dragElement.endResize();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ resize(...args) {
|
|
|
+ this.dragElement.handleResizeElement(...args);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ getEndpointList() {
|
|
|
+ return getElementCorners(this);
|
|
|
+ }
|
|
|
+}
|
|
|
+class DragElement extends BaseElement {
|
|
|
+ constructor(element, app, opts = {}) {
|
|
|
+ super(
|
|
|
+ {
|
|
|
+ type: "dragElement",
|
|
|
+ notNeedDragElement: true
|
|
|
+ },
|
|
|
+ app
|
|
|
+ );
|
|
|
+ this.opts = __spreadValues({
|
|
|
+ lockRatio: false
|
|
|
+ }, opts);
|
|
|
+ this.style = {
|
|
|
+ strokeStyle: this.app.state.dragStrokeStyle,
|
|
|
+ fillStyle: "transparent",
|
|
|
+ lineWidth: "small",
|
|
|
+ lineDash: 0,
|
|
|
+ globalAlpha: 1
|
|
|
+ };
|
|
|
+ this.element = element;
|
|
|
+ this.offset = 5;
|
|
|
+ this.size = 10;
|
|
|
+ this.resizeType = "";
|
|
|
+ this.diagonalPoint = {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ this.mousedownPosAndElementPosOffset = {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ this.elementRatio = 0;
|
|
|
+ this.hideParts = [];
|
|
|
+ }
|
|
|
+ setHideParts(parts = []) {
|
|
|
+ this.hideParts = parts;
|
|
|
+ }
|
|
|
+ showAll() {
|
|
|
+ this.setHideParts([]);
|
|
|
+ }
|
|
|
+ onlyShowBody() {
|
|
|
+ this.setHideParts([
|
|
|
+ DRAG_ELEMENT_PARTS.ROTATE,
|
|
|
+ DRAG_ELEMENT_PARTS.TOP_LEFT_BTN,
|
|
|
+ DRAG_ELEMENT_PARTS.TOP_RIGHT_BTN,
|
|
|
+ DRAG_ELEMENT_PARTS.BOTTOM_RIGHT_BTN,
|
|
|
+ DRAG_ELEMENT_PARTS.BOTTOM_LEFT_BTN
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ update() {
|
|
|
+ this.x = this.element.x - this.offset;
|
|
|
+ this.y = this.element.y - this.offset;
|
|
|
+ this.width = this.element.width + this.offset * 2;
|
|
|
+ this.height = this.element.height + this.offset * 2;
|
|
|
+ this.rotate = this.element.rotate;
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ if (this.element.hasGroup())
|
|
|
+ return;
|
|
|
+ this.update();
|
|
|
+ let { width, height } = this;
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ this.app.ctx.save();
|
|
|
+ if (!this.hideParts.includes(DRAG_ELEMENT_PARTS.BODY)) {
|
|
|
+ this.app.ctx.setLineDash([5]);
|
|
|
+ drawRect(this.app.ctx, -halfWidth, -halfHeight, width, height);
|
|
|
+ this.app.ctx.restore();
|
|
|
+ }
|
|
|
+ if (!this.hideParts.includes(DRAG_ELEMENT_PARTS.TOP_LEFT_BTN)) {
|
|
|
+ drawRect(
|
|
|
+ this.app.ctx,
|
|
|
+ -halfWidth - this.size,
|
|
|
+ -halfHeight - this.size,
|
|
|
+ this.size,
|
|
|
+ this.size
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (!this.hideParts.includes(DRAG_ELEMENT_PARTS.TOP_RIGHT_BTN)) {
|
|
|
+ drawRect(
|
|
|
+ this.app.ctx,
|
|
|
+ -halfWidth + this.element.width + this.size,
|
|
|
+ -halfHeight - this.size,
|
|
|
+ this.size,
|
|
|
+ this.size
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (!this.hideParts.includes(DRAG_ELEMENT_PARTS.BOTTOM_RIGHT_BTN)) {
|
|
|
+ drawRect(
|
|
|
+ this.app.ctx,
|
|
|
+ -halfWidth + this.element.width + this.size,
|
|
|
+ -halfHeight + this.element.height + this.size,
|
|
|
+ this.size,
|
|
|
+ this.size
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (!this.hideParts.includes(DRAG_ELEMENT_PARTS.BOTTOM_LEFT_BTN)) {
|
|
|
+ drawRect(
|
|
|
+ this.app.ctx,
|
|
|
+ -halfWidth - this.size,
|
|
|
+ -halfHeight + this.element.height + this.size,
|
|
|
+ this.size,
|
|
|
+ this.size
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (!this.hideParts.includes(DRAG_ELEMENT_PARTS.ROTATE)) {
|
|
|
+ drawCircle(
|
|
|
+ this.app.ctx,
|
|
|
+ -halfWidth + this.element.width / 2 + this.size / 2,
|
|
|
+ -halfHeight - this.size * 2,
|
|
|
+ this.size
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ checkPointInDragElementWhere(x, y) {
|
|
|
+ let part = "";
|
|
|
+ let rp = transformPointOnElement(x, y, this.element);
|
|
|
+ if (checkPointIsInRectangle(rp.x, rp.y, this)) {
|
|
|
+ part = DRAG_ELEMENT_PARTS.BODY;
|
|
|
+ } else if (getTowPointDistance(
|
|
|
+ rp.x,
|
|
|
+ rp.y,
|
|
|
+ this.x + this.width / 2,
|
|
|
+ this.y - this.size * 2
|
|
|
+ ) <= this.size) {
|
|
|
+ part = DRAG_ELEMENT_PARTS.ROTATE;
|
|
|
+ } else if (this._checkPointIsInBtn(rp.x, rp.y, CORNERS.TOP_LEFT)) {
|
|
|
+ part = DRAG_ELEMENT_PARTS.TOP_LEFT_BTN;
|
|
|
+ } else if (this._checkPointIsInBtn(rp.x, rp.y, CORNERS.TOP_RIGHT)) {
|
|
|
+ part = DRAG_ELEMENT_PARTS.TOP_RIGHT_BTN;
|
|
|
+ } else if (this._checkPointIsInBtn(rp.x, rp.y, CORNERS.BOTTOM_RIGHT)) {
|
|
|
+ part = DRAG_ELEMENT_PARTS.BOTTOM_RIGHT_BTN;
|
|
|
+ } else if (this._checkPointIsInBtn(rp.x, rp.y, CORNERS.BOTTOM_LEFT)) {
|
|
|
+ part = DRAG_ELEMENT_PARTS.BOTTOM_LEFT_BTN;
|
|
|
+ }
|
|
|
+ if (this.hideParts.includes(part)) {
|
|
|
+ part = "";
|
|
|
+ }
|
|
|
+ return part;
|
|
|
+ }
|
|
|
+ _checkPointIsInBtn(x, y, dir) {
|
|
|
+ let _x = 0;
|
|
|
+ let _y = 0;
|
|
|
+ switch (dir) {
|
|
|
+ case CORNERS.TOP_LEFT:
|
|
|
+ _x = this.x - this.size;
|
|
|
+ _y = this.y - this.size;
|
|
|
+ break;
|
|
|
+ case CORNERS.TOP_RIGHT:
|
|
|
+ _x = this.x + this.width;
|
|
|
+ _y = this.y - this.size;
|
|
|
+ break;
|
|
|
+ case CORNERS.BOTTOM_RIGHT:
|
|
|
+ _x = this.x + this.width;
|
|
|
+ _y = this.y + this.height;
|
|
|
+ break;
|
|
|
+ case CORNERS.BOTTOM_LEFT:
|
|
|
+ _x = this.x - this.size;
|
|
|
+ _y = this.y + this.height;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return checkPointIsInRectangle(x, y, _x, _y, this.size, this.size);
|
|
|
+ }
|
|
|
+ startResize(resizeType, e) {
|
|
|
+ this.resizeType = resizeType;
|
|
|
+ if (this.opts.lockRatio) {
|
|
|
+ this.elementRatio = this.element.width / this.element.height;
|
|
|
+ }
|
|
|
+ if (resizeType === DRAG_ELEMENT_PARTS.BODY) {
|
|
|
+ this.element.saveState();
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.ROTATE) {
|
|
|
+ this.element.saveState();
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.TOP_LEFT_BTN) {
|
|
|
+ this.handleDragMousedown(e, CORNERS.TOP_LEFT);
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.TOP_RIGHT_BTN) {
|
|
|
+ this.handleDragMousedown(e, CORNERS.TOP_RIGHT);
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.BOTTOM_RIGHT_BTN) {
|
|
|
+ this.handleDragMousedown(e, CORNERS.BOTTOM_RIGHT);
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.BOTTOM_LEFT_BTN) {
|
|
|
+ this.handleDragMousedown(e, CORNERS.BOTTOM_LEFT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ endResize() {
|
|
|
+ this.resizeType = "";
|
|
|
+ this.diagonalPoint = {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ this.mousedownPosAndElementPosOffset = {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ this.elementRatio = 0;
|
|
|
+ }
|
|
|
+ handleDragMousedown(e, corner) {
|
|
|
+ let centerPos = getElementCenterPoint(this.element);
|
|
|
+ let pos = getElementRotatedCornerPoint(this.element, corner);
|
|
|
+ this.diagonalPoint.x = 2 * centerPos.x - pos.x;
|
|
|
+ this.diagonalPoint.y = 2 * centerPos.y - pos.y;
|
|
|
+ this.mousedownPosAndElementPosOffset.x = e.clientX - pos.x;
|
|
|
+ this.mousedownPosAndElementPosOffset.y = e.clientY - pos.y;
|
|
|
+ this.element.saveState();
|
|
|
+ }
|
|
|
+ handleResizeElement(e, mx, my, offsetX, offsetY) {
|
|
|
+ let resizeType = this.resizeType;
|
|
|
+ if (resizeType === DRAG_ELEMENT_PARTS.BODY) {
|
|
|
+ this.handleMoveElement(offsetX, offsetY);
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.ROTATE) {
|
|
|
+ this.handleRotateElement(e, mx, my);
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.TOP_LEFT_BTN) {
|
|
|
+ this.handleStretchElement(
|
|
|
+ e,
|
|
|
+ (newCenter, rp) => {
|
|
|
+ return {
|
|
|
+ width: (newCenter.x - rp.x) * 2,
|
|
|
+ height: (newCenter.y - rp.y) * 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (rp) => {
|
|
|
+ return {
|
|
|
+ x: rp.x,
|
|
|
+ y: rp.y
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (newRatio, newRect) => {
|
|
|
+ let x = newRect.x;
|
|
|
+ let y = newRect.y;
|
|
|
+ if (newRatio > this.elementRatio) {
|
|
|
+ x = newRect.x + newRect.width - this.elementRatio * newRect.height;
|
|
|
+ } else if (newRatio < this.elementRatio) {
|
|
|
+ y = newRect.y + (newRect.height - newRect.width / this.elementRatio);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ );
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.TOP_RIGHT_BTN) {
|
|
|
+ this.handleStretchElement(
|
|
|
+ e,
|
|
|
+ (newCenter, rp) => {
|
|
|
+ return {
|
|
|
+ width: (rp.x - newCenter.x) * 2,
|
|
|
+ height: (newCenter.y - rp.y) * 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (rp, newSize) => {
|
|
|
+ return {
|
|
|
+ x: rp.x - newSize.width,
|
|
|
+ y: rp.y
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (newRatio, newRect) => {
|
|
|
+ let x = newRect.x;
|
|
|
+ let y = newRect.y;
|
|
|
+ if (newRatio > this.elementRatio) {
|
|
|
+ x = newRect.x + this.elementRatio * newRect.height;
|
|
|
+ } else if (newRatio < this.elementRatio) {
|
|
|
+ x = newRect.x + newRect.width;
|
|
|
+ y = newRect.y + (newRect.height - newRect.width / this.elementRatio);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ );
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.BOTTOM_RIGHT_BTN) {
|
|
|
+ this.handleStretchElement(
|
|
|
+ e,
|
|
|
+ (newCenter, rp) => {
|
|
|
+ return {
|
|
|
+ width: (rp.x - newCenter.x) * 2,
|
|
|
+ height: (rp.y - newCenter.y) * 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (rp, newSize) => {
|
|
|
+ return {
|
|
|
+ x: rp.x - newSize.width,
|
|
|
+ y: rp.y - newSize.height
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (newRatio, newRect) => {
|
|
|
+ let x = newRect.x;
|
|
|
+ let y = newRect.y;
|
|
|
+ if (newRatio > this.elementRatio) {
|
|
|
+ x = newRect.x + this.elementRatio * newRect.height;
|
|
|
+ y = newRect.y + newRect.height;
|
|
|
+ } else if (newRatio < this.elementRatio) {
|
|
|
+ x = newRect.x + newRect.width;
|
|
|
+ y = newRect.y + newRect.width / this.elementRatio;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ );
|
|
|
+ } else if (resizeType === DRAG_ELEMENT_PARTS.BOTTOM_LEFT_BTN) {
|
|
|
+ this.handleStretchElement(
|
|
|
+ e,
|
|
|
+ (newCenter, rp) => {
|
|
|
+ return {
|
|
|
+ width: (newCenter.x - rp.x) * 2,
|
|
|
+ height: (rp.y - newCenter.y) * 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (rp, newSize) => {
|
|
|
+ return {
|
|
|
+ x: rp.x,
|
|
|
+ y: rp.y - newSize.height
|
|
|
+ };
|
|
|
+ },
|
|
|
+ (newRatio, newRect) => {
|
|
|
+ let x = newRect.x;
|
|
|
+ let y = newRect.y;
|
|
|
+ if (newRatio > this.elementRatio) {
|
|
|
+ x = newRect.x + newRect.width - this.elementRatio * newRect.height;
|
|
|
+ y = newRect.y + newRect.height;
|
|
|
+ } else if (newRatio < this.elementRatio) {
|
|
|
+ y = newRect.y + newRect.width / this.elementRatio;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ handleMoveElement(offsetX, offsetY) {
|
|
|
+ this.element.move(offsetX, offsetY);
|
|
|
+ }
|
|
|
+ handleRotateElement(e, mx, my) {
|
|
|
+ let centerPos = getElementCenterPoint(this.element);
|
|
|
+ let rotate = getTowPointRotate(
|
|
|
+ centerPos.x,
|
|
|
+ centerPos.y,
|
|
|
+ e.clientX,
|
|
|
+ e.clientY,
|
|
|
+ mx,
|
|
|
+ my
|
|
|
+ );
|
|
|
+ this.element.offsetRotate(rotate);
|
|
|
+ }
|
|
|
+ stretchCalc(x, y, calcSize, calcPos) {
|
|
|
+ let newCenter = {
|
|
|
+ x: (x + this.diagonalPoint.x) / 2,
|
|
|
+ y: (y + this.diagonalPoint.y) / 2
|
|
|
+ };
|
|
|
+ let rp = transformPointReverseRotate(
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ newCenter.x,
|
|
|
+ newCenter.y,
|
|
|
+ this.element.rotate
|
|
|
+ );
|
|
|
+ let newSize = calcSize(newCenter, rp);
|
|
|
+ let isWidthReverse = false;
|
|
|
+ if (newSize.width < 0) {
|
|
|
+ newSize.width = 0;
|
|
|
+ isWidthReverse = true;
|
|
|
+ }
|
|
|
+ let isHeightReverse = false;
|
|
|
+ if (newSize.height < 0) {
|
|
|
+ newSize.height = 0;
|
|
|
+ isHeightReverse = true;
|
|
|
+ }
|
|
|
+ let newPos = calcPos(rp, newSize);
|
|
|
+ let newRect = {
|
|
|
+ x: newPos.x,
|
|
|
+ y: newPos.y,
|
|
|
+ width: newSize.width,
|
|
|
+ height: newSize.height
|
|
|
+ };
|
|
|
+ if (isWidthReverse || isHeightReverse) {
|
|
|
+ newRect.x = this.element.x;
|
|
|
+ newRect.y = this.element.y;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ newRect,
|
|
|
+ newCenter
|
|
|
+ };
|
|
|
+ }
|
|
|
+ handleStretchElement(e, calcSize, calcPos, fixPos) {
|
|
|
+ let actClientX = e.clientX - this.mousedownPosAndElementPosOffset.x;
|
|
|
+ let actClientY = e.clientY - this.mousedownPosAndElementPosOffset.y;
|
|
|
+ let { newRect, newCenter } = this.stretchCalc(
|
|
|
+ actClientX,
|
|
|
+ actClientY,
|
|
|
+ calcSize,
|
|
|
+ calcPos
|
|
|
+ );
|
|
|
+ if (this.opts.lockRatio) {
|
|
|
+ this.fixStretch(newRect, newCenter, calcSize, calcPos, fixPos);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.element.updateRect(newRect.x, newRect.y, newRect.width, newRect.height);
|
|
|
+ }
|
|
|
+ fixStretch(newRect, newCenter, calcSize, calcPos, fixPos) {
|
|
|
+ let newRatio = newRect.width / newRect.height;
|
|
|
+ let fp = fixPos(newRatio, newRect);
|
|
|
+ let rp = getRotatedPoint(
|
|
|
+ fp.x,
|
|
|
+ fp.y,
|
|
|
+ newCenter.x,
|
|
|
+ newCenter.y,
|
|
|
+ this.element.rotate
|
|
|
+ );
|
|
|
+ let fixNewRect = this.stretchCalc(rp.x, rp.y, calcSize, calcPos).newRect;
|
|
|
+ if (fixNewRect.width === 0 && fixNewRect.height === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.element.updateRect(
|
|
|
+ fixNewRect.x,
|
|
|
+ fixNewRect.y,
|
|
|
+ fixNewRect.width,
|
|
|
+ fixNewRect.height
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+class Rectangle extends BaseElement {
|
|
|
+ constructor(...args) {
|
|
|
+ super(...args);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { width, height } = this;
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ drawRect(this.app.ctx, -halfWidth, -halfHeight, width, height, true);
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtRectangleEdge(this, rp);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Circle extends BaseElement {
|
|
|
+ constructor(...args) {
|
|
|
+ super(...args);
|
|
|
+ this.dragElement = new DragElement(this, this.app, {
|
|
|
+ lockRatio: true
|
|
|
+ });
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { width, height } = this;
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ drawCircle(this.app.ctx, 0, 0, getCircleRadius(width, height), true);
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtCircleEdge(this, rp);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Diamond extends BaseElement {
|
|
|
+ constructor(...args) {
|
|
|
+ super(...args);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { width, height } = this;
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ drawDiamond(this.app.ctx, -halfWidth, -halfHeight, width, height, true);
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtDiamondEdge(this, rp);
|
|
|
+ }
|
|
|
+ getEndpointList() {
|
|
|
+ let { x, y, width, height, rotate } = this;
|
|
|
+ let points = [
|
|
|
+ [x + width / 2, y],
|
|
|
+ [x + width, y + height / 2],
|
|
|
+ [x + width / 2, y + height],
|
|
|
+ [x, y + height / 2]
|
|
|
+ ];
|
|
|
+ let center = getElementCenterPoint(this);
|
|
|
+ return points.map((point) => {
|
|
|
+ return getRotatedPoint(point[0], point[1], center.x, center.y, rotate);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+class Triangle extends BaseElement {
|
|
|
+ constructor(...args) {
|
|
|
+ super(...args);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { width, height } = this;
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ drawTriangle(this.app.ctx, -halfWidth, -halfHeight, width, height, true);
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtTriangleEdge(this, rp);
|
|
|
+ }
|
|
|
+ getEndpointList() {
|
|
|
+ let { x, y, width, height, rotate } = this;
|
|
|
+ let points = [
|
|
|
+ [x + width / 2, y],
|
|
|
+ [x + width, y + height],
|
|
|
+ [x, y + height]
|
|
|
+ ];
|
|
|
+ let center = getElementCenterPoint(this);
|
|
|
+ return points.map((point) => {
|
|
|
+ return getRotatedPoint(point[0], point[1], center.x, center.y, rotate);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+class BaseMultiPointElement extends BaseElement {
|
|
|
+ constructor(opts = {}, app) {
|
|
|
+ super(opts, app);
|
|
|
+ this.startPointArr = [];
|
|
|
+ this.pointArr = opts.pointArr || [];
|
|
|
+ this.startWidth = 0;
|
|
|
+ this.startHeight = 0;
|
|
|
+ this.fictitiousPoint = {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+ serialize() {
|
|
|
+ let base = super.serialize();
|
|
|
+ return __spreadProps(__spreadValues({}, base), {
|
|
|
+ pointArr: [...this.pointArr]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ addPoint(x, y, ...args) {
|
|
|
+ if (!Array.isArray(this.pointArr)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.pointArr.push([x, y, ...args]);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateMultiPointBoundingRect() {
|
|
|
+ let rect = getBoundingRect(this.pointArr);
|
|
|
+ this.x = rect.x;
|
|
|
+ this.y = rect.y;
|
|
|
+ this.width = rect.width;
|
|
|
+ this.height = rect.height;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateFictitiousPoint(x, y) {
|
|
|
+ this.fictitiousPoint.x = x;
|
|
|
+ this.fictitiousPoint.y = y;
|
|
|
+ }
|
|
|
+ saveState() {
|
|
|
+ let { rotate, x, y, width, height, pointArr } = this;
|
|
|
+ this.startRotate = rotate;
|
|
|
+ this.startX = x;
|
|
|
+ this.startY = y;
|
|
|
+ this.startPointArr = deepCopy(pointArr);
|
|
|
+ this.startWidth = width;
|
|
|
+ this.startHeight = height;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ move(ox, oy) {
|
|
|
+ this.pointArr = this.startPointArr.map((point) => {
|
|
|
+ return [point[0] + ox, point[1] + oy, ...point.slice(2)];
|
|
|
+ });
|
|
|
+ let { startX, startY } = this;
|
|
|
+ this.x = startX + ox;
|
|
|
+ this.y = startY + oy;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateRect(x, y, width, height) {
|
|
|
+ let { startWidth, startHeight, startPointArr } = this;
|
|
|
+ let scaleX = width / startWidth;
|
|
|
+ let scaleY = height / startHeight;
|
|
|
+ this.pointArr = startPointArr.map((point) => {
|
|
|
+ let nx = point[0] * scaleX;
|
|
|
+ let ny = point[1] * scaleY;
|
|
|
+ return [nx, ny, ...point.slice(2)];
|
|
|
+ });
|
|
|
+ let rect = getBoundingRect(this.pointArr);
|
|
|
+ let offsetX = rect.x - x;
|
|
|
+ let offsetY = rect.y - y;
|
|
|
+ this.pointArr = this.pointArr.map((point) => {
|
|
|
+ return [point[0] - offsetX, point[1] - offsetY, ...point.slice(2)];
|
|
|
+ });
|
|
|
+ this.updatePos(x, y);
|
|
|
+ this.updateSize(width, height);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ rotateByCenter(rotate, cx, cy) {
|
|
|
+ this.pointArr = this.startPointArr.map((point) => {
|
|
|
+ let np = getRotatedPoint(point[0], point[1], cx, cy, rotate);
|
|
|
+ return [np.x, np.y, ...point.slice(2)];
|
|
|
+ });
|
|
|
+ this.updateMultiPointBoundingRect();
|
|
|
+ }
|
|
|
+ getEndpointList() {
|
|
|
+ return this.pointArr.map((point) => {
|
|
|
+ let center = getElementCenterPoint(this);
|
|
|
+ let np = getRotatedPoint(
|
|
|
+ point[0],
|
|
|
+ point[1],
|
|
|
+ center.x,
|
|
|
+ center.y,
|
|
|
+ this.rotate
|
|
|
+ );
|
|
|
+ return {
|
|
|
+ x: np.x,
|
|
|
+ y: np.y
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+class Freedraw extends BaseMultiPointElement {
|
|
|
+ constructor(...args) {
|
|
|
+ super(...args);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ this.lastLineWidth = -1;
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { pointArr } = this;
|
|
|
+ this.warpRender(({ cx, cy }) => {
|
|
|
+ drawFreeLine(this.app.ctx, pointArr, {
|
|
|
+ app: this.app,
|
|
|
+ cx,
|
|
|
+ cy
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtFreedrawLineEdge(this, rp);
|
|
|
+ }
|
|
|
+ singleRender(mx, my, tx, ty, lineWidth) {
|
|
|
+ this.app.ctx.save();
|
|
|
+ this.setStyle(this.style);
|
|
|
+ drawLineSegment(this.app.ctx, mx, my, tx, ty, lineWidth);
|
|
|
+ this.app.ctx.restore();
|
|
|
+ }
|
|
|
+}
|
|
|
+class Arrow extends BaseMultiPointElement {
|
|
|
+ constructor(...args) {
|
|
|
+ super(...args);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { pointArr, fictitiousPoint } = this;
|
|
|
+ this.warpRender(({ cx, cy }) => {
|
|
|
+ let realtimePoint = [];
|
|
|
+ if (pointArr.length > 0 && this.isCreating) {
|
|
|
+ let { x: fx, y: fy } = this.app.coordinate.transform(
|
|
|
+ fictitiousPoint.x - cx,
|
|
|
+ fictitiousPoint.y - cy
|
|
|
+ );
|
|
|
+ realtimePoint = [[fx, fy]];
|
|
|
+ }
|
|
|
+ drawArrow(
|
|
|
+ this.app.ctx,
|
|
|
+ pointArr.map((point) => {
|
|
|
+ let { x, y } = this.app.coordinate.transform(point[0], point[1]);
|
|
|
+ return [x - cx, y - cy];
|
|
|
+ }).concat(realtimePoint)
|
|
|
+ );
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtArrowEdge(this, rp);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Image$1 extends BaseElement {
|
|
|
+ constructor(opts = {}, app) {
|
|
|
+ super(opts, app);
|
|
|
+ this.dragElement = new DragElement(this, this.app, {
|
|
|
+ lockRatio: true
|
|
|
+ });
|
|
|
+ this.url = opts.url || "";
|
|
|
+ this.imageObj = opts.imageObj || null;
|
|
|
+ this.ratio = opts.ratio || 1;
|
|
|
+ }
|
|
|
+ serialize() {
|
|
|
+ let base = super.serialize();
|
|
|
+ return __spreadProps(__spreadValues({}, base), {
|
|
|
+ url: this.url,
|
|
|
+ ratio: this.ratio
|
|
|
+ });
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { width, height } = this;
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ drawImage(this.app.ctx, this, -halfWidth, -halfHeight, width, height);
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtRectangleInner(this, rp);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Line extends BaseMultiPointElement {
|
|
|
+ constructor(opts = {}, app) {
|
|
|
+ super(opts, app);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ this.isSingle = opts.isSingle;
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { pointArr, fictitiousPoint } = this;
|
|
|
+ this.warpRender(({ cx, cy }) => {
|
|
|
+ let realtimePoint = [];
|
|
|
+ if (pointArr.length > 0 && this.isCreating) {
|
|
|
+ let { x: fx, y: fy } = this.app.coordinate.transform(
|
|
|
+ fictitiousPoint.x - cx,
|
|
|
+ fictitiousPoint.y - cy
|
|
|
+ );
|
|
|
+ realtimePoint = [[fx, fy]];
|
|
|
+ }
|
|
|
+ drawLine(
|
|
|
+ this.app.ctx,
|
|
|
+ pointArr.map((point) => {
|
|
|
+ let { x, y } = this.app.coordinate.transform(point[0], point[1]);
|
|
|
+ return [x - cx, y - cy];
|
|
|
+ }).concat(realtimePoint)
|
|
|
+ );
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtLineEdge(this, rp);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Text extends BaseElement {
|
|
|
+ constructor(opts = {}, app) {
|
|
|
+ var _a, _b, _c, _d;
|
|
|
+ super(opts, app);
|
|
|
+ this.dragElement = new DragElement(this, this.app, {
|
|
|
+ lockRatio: true
|
|
|
+ });
|
|
|
+ this.text = opts.text || "";
|
|
|
+ this.style.fillStyle = ((_a = opts.style) == null ? void 0 : _a.fillStyle) || this.app.state.strokeStyle || "#000";
|
|
|
+ this.style.fontSize = ((_b = opts.style) == null ? void 0 : _b.fontSize) || this.app.state.fontSize || 18;
|
|
|
+ this.style.lineHeightRatio = ((_c = opts.style) == null ? void 0 : _c.lineHeightRatio) || 1.5;
|
|
|
+ this.style.fontFamily = ((_d = opts.style) == null ? void 0 : _d.fontFamily) || this.app.state.fontFamily || "\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei";
|
|
|
+ }
|
|
|
+ serialize() {
|
|
|
+ let base = super.serialize();
|
|
|
+ return __spreadProps(__spreadValues({}, base), {
|
|
|
+ text: this.text
|
|
|
+ });
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ this.warpRender(({ halfWidth, halfHeight }) => {
|
|
|
+ drawText(this.app.ctx, this, -halfWidth, -halfHeight);
|
|
|
+ });
|
|
|
+ this.renderDragElement();
|
|
|
+ }
|
|
|
+ isHit(x, y) {
|
|
|
+ let rp = transformPointOnElement(x, y, this);
|
|
|
+ return checkIsAtRectangleInner(this, rp);
|
|
|
+ }
|
|
|
+ updateRect(x, y, width, height) {
|
|
|
+ let { text, style } = this;
|
|
|
+ let fontSize = Math.floor(
|
|
|
+ height / splitTextLines(text).length / style.lineHeightRatio
|
|
|
+ );
|
|
|
+ this.style.fontSize = fontSize;
|
|
|
+ super.updateRect(x, y, width, height);
|
|
|
+ }
|
|
|
+ updateTextSize() {
|
|
|
+ let { width, height } = getTextElementSize(this);
|
|
|
+ this.width = width;
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+}
|
|
|
+class Elements$1 {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.elementList = [];
|
|
|
+ this.activeElement = null;
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ this.isResizing = false;
|
|
|
+ this.resizingElement = null;
|
|
|
+ this.handleResize = throttle(this.handleResize, this, 16);
|
|
|
+ }
|
|
|
+ serialize(stringify = false) {
|
|
|
+ let data = this.elementList.map((element) => {
|
|
|
+ return element.serialize();
|
|
|
+ });
|
|
|
+ return stringify ? JSON.stringify(data) : data;
|
|
|
+ }
|
|
|
+ getElementsNum() {
|
|
|
+ return this.elementList.length;
|
|
|
+ }
|
|
|
+ hasElements() {
|
|
|
+ return this.elementList.length > 0;
|
|
|
+ }
|
|
|
+ addElement(element) {
|
|
|
+ this.elementList.push(element);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ unshiftElement(element) {
|
|
|
+ this.elementList.unshift(element);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ insertElement(element, index) {
|
|
|
+ this.elementList.splice(index, 0, element);
|
|
|
+ }
|
|
|
+ deleteElement(element) {
|
|
|
+ let index = this.getElementIndex(element);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.elementList.splice(index, 1);
|
|
|
+ if (element.isActive) {
|
|
|
+ this.cancelActiveElement(element);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ deleteAllElements() {
|
|
|
+ this.activeElement = null;
|
|
|
+ this.elementList = [];
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ this.isResizing = false;
|
|
|
+ this.resizingElement = null;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ getElementIndex(element) {
|
|
|
+ return this.elementList.findIndex((item) => {
|
|
|
+ return item === element;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ createElementsFromData(data) {
|
|
|
+ data.forEach((item) => {
|
|
|
+ let element = this.pureCreateElement(item);
|
|
|
+ element.isActive = false;
|
|
|
+ element.isCreating = false;
|
|
|
+ this.addElement(element);
|
|
|
+ });
|
|
|
+ this.app.group.initIdToElementList(this.elementList);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ hasActiveElement() {
|
|
|
+ return !!this.activeElement;
|
|
|
+ }
|
|
|
+ setActiveElement(element) {
|
|
|
+ this.cancelActiveElement();
|
|
|
+ this.activeElement = element;
|
|
|
+ if (element) {
|
|
|
+ element.isActive = true;
|
|
|
+ }
|
|
|
+ this.app.emit("activeElementChange", this.activeElement);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ cancelActiveElement() {
|
|
|
+ if (!this.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.activeElement.isActive = false;
|
|
|
+ this.activeElement = null;
|
|
|
+ this.app.emit("activeElementChange", this.activeElement);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ checkIsHitElement(e) {
|
|
|
+ let x = e.unGridClientX;
|
|
|
+ let y = e.unGridClientY;
|
|
|
+ for (let i = this.elementList.length - 1; i >= 0; i--) {
|
|
|
+ let element = this.elementList[i];
|
|
|
+ if (element.isHit(x, y)) {
|
|
|
+ return element;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ pureCreateElement(opts = {}) {
|
|
|
+ switch (opts.type) {
|
|
|
+ case "rectangle":
|
|
|
+ return new Rectangle(opts, this.app);
|
|
|
+ case "diamond":
|
|
|
+ return new Diamond(opts, this.app);
|
|
|
+ case "triangle":
|
|
|
+ return new Triangle(opts, this.app);
|
|
|
+ case "circle":
|
|
|
+ return new Circle(opts, this.app);
|
|
|
+ case "freedraw":
|
|
|
+ return new Freedraw(opts, this.app);
|
|
|
+ case "image":
|
|
|
+ return new Image$1(opts, this.app);
|
|
|
+ case "arrow":
|
|
|
+ return new Arrow(opts, this.app);
|
|
|
+ case "line":
|
|
|
+ return new Line(opts, this.app);
|
|
|
+ case "text":
|
|
|
+ return new Text(opts, this.app);
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ createElement(opts = {}, callback = () => {
|
|
|
+ }, ctx = null, notActive) {
|
|
|
+ if (this.hasActiveElement() || this.isCreatingElement) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ let element = this.pureCreateElement(opts);
|
|
|
+ if (!element) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.addElement(element);
|
|
|
+ if (!notActive) {
|
|
|
+ this.setActiveElement(element);
|
|
|
+ }
|
|
|
+ this.isCreatingElement = true;
|
|
|
+ callback.call(ctx, element);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ copyElement(element, notActive = false, pos) {
|
|
|
+ return new Promise((resolve) => __async(this, null, function* () {
|
|
|
+ if (!element) {
|
|
|
+ return resolve();
|
|
|
+ }
|
|
|
+ let data = this.app.group.handleCopyElementData(element.serialize());
|
|
|
+ if (data.type === "image") {
|
|
|
+ data.imageObj = yield createImageObj(data.url);
|
|
|
+ }
|
|
|
+ if (data.type === "text") {
|
|
|
+ if (typeof data.text === "string") {
|
|
|
+ if (isNumeric$1(data.text)) {
|
|
|
+ const num = parseFloat(data.text);
|
|
|
+ const incrementedNum = num + 1;
|
|
|
+ data.text = incrementedNum.toString(10).padStart(data.text.length, "0");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.createElement(
|
|
|
+ data,
|
|
|
+ (element2) => {
|
|
|
+ this.app.group.handleCopyElement(element2);
|
|
|
+ element2.startResize(DRAG_ELEMENT_PARTS.BODY);
|
|
|
+ let ox = 20;
|
|
|
+ let oy = 20;
|
|
|
+ if (pos) {
|
|
|
+ ox = pos.x - element2.x - element2.width / 2;
|
|
|
+ oy = pos.y - element2.y - element2.height / 2;
|
|
|
+ }
|
|
|
+ let gridAdsorbentPos = this.app.coordinate.gridAdsorbent(ox, oy);
|
|
|
+ element2.resize(
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ gridAdsorbentPos.x,
|
|
|
+ gridAdsorbentPos.y
|
|
|
+ );
|
|
|
+ element2.isCreating = false;
|
|
|
+ if (notActive) {
|
|
|
+ element2.isActive = false;
|
|
|
+ }
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ resolve(element2);
|
|
|
+ },
|
|
|
+ this,
|
|
|
+ notActive
|
|
|
+ );
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ creatingRectangleLikeElement(type, x, y, offsetX, offsetY) {
|
|
|
+ this.createElement({
|
|
|
+ type,
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ width: offsetX,
|
|
|
+ height: offsetY
|
|
|
+ });
|
|
|
+ this.activeElement.updateSize(offsetX, offsetY);
|
|
|
+ }
|
|
|
+ creatingCircle(x, y, e) {
|
|
|
+ this.createElement({
|
|
|
+ type: "circle",
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ });
|
|
|
+ let radius = getTowPointDistance(e.clientX, e.clientY, x, y);
|
|
|
+ this.activeElement.updateSize(radius, radius);
|
|
|
+ }
|
|
|
+ creatingFreedraw(e, event) {
|
|
|
+ this.createElement({
|
|
|
+ type: "freedraw"
|
|
|
+ });
|
|
|
+ let element = this.activeElement;
|
|
|
+ let lineWidth = computedLineWidthBySpeed(
|
|
|
+ event.mouseSpeed,
|
|
|
+ element.lastLineWidth
|
|
|
+ );
|
|
|
+ element.lastLineWidth = lineWidth;
|
|
|
+ element.addPoint(e.clientX, e.clientY, lineWidth);
|
|
|
+ let { coordinate, ctx, state } = this.app;
|
|
|
+ let tfp = coordinate.transformToCanvasCoordinate(
|
|
|
+ coordinate.subScrollX(event.lastMousePos.x),
|
|
|
+ coordinate.subScrollY(event.lastMousePos.y)
|
|
|
+ );
|
|
|
+ let ttp = coordinate.transformToCanvasCoordinate(
|
|
|
+ coordinate.subScrollX(e.clientX),
|
|
|
+ coordinate.subScrollY(e.clientY)
|
|
|
+ );
|
|
|
+ ctx.save();
|
|
|
+ ctx.scale(state.scale, state.scale);
|
|
|
+ element.singleRender(tfp.x, tfp.y, ttp.x, ttp.y, lineWidth);
|
|
|
+ ctx.restore();
|
|
|
+ }
|
|
|
+ creatingImage(e, { width, height, imageObj, url, ratio }) {
|
|
|
+ let gp = this.app.coordinate.gridAdsorbent(
|
|
|
+ e.unGridClientX - width / 2,
|
|
|
+ e.unGridClientY - height / 2
|
|
|
+ );
|
|
|
+ this.createElement({
|
|
|
+ type: "image",
|
|
|
+ x: gp.x,
|
|
|
+ y: gp.y,
|
|
|
+ url,
|
|
|
+ imageObj,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ ratio
|
|
|
+ });
|
|
|
+ }
|
|
|
+ editingText(element) {
|
|
|
+ if (element.type !== "text") {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ element.noRender = true;
|
|
|
+ this.setActiveElement(element);
|
|
|
+ }
|
|
|
+ completeEditingText() {
|
|
|
+ let element = this.activeElement;
|
|
|
+ if (!element || element.type !== "text") {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!element.text.trim()) {
|
|
|
+ this.deleteElement(element);
|
|
|
+ this.setActiveElement(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ element.noRender = false;
|
|
|
+ }
|
|
|
+ completeCreateArrow(e) {
|
|
|
+ this.activeElement.addPoint(e.clientX, e.clientY);
|
|
|
+ }
|
|
|
+ creatingArrow(x, y, e) {
|
|
|
+ this.createElement(
|
|
|
+ {
|
|
|
+ type: "arrow",
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ },
|
|
|
+ (element) => {
|
|
|
+ element.addPoint(x, y);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ this.activeElement.updateFictitiousPoint(e.clientX, e.clientY);
|
|
|
+ }
|
|
|
+ creatingLine(x, y, e, isSingle = false, notCreate = false) {
|
|
|
+ if (!notCreate) {
|
|
|
+ this.createElement(
|
|
|
+ {
|
|
|
+ type: "line",
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ isSingle
|
|
|
+ },
|
|
|
+ (element2) => {
|
|
|
+ element2.addPoint(x, y);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ let element = this.activeElement;
|
|
|
+ if (element) {
|
|
|
+ element.updateFictitiousPoint(e.clientX, e.clientY);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ completeCreateLine(e, completeCallback = () => {
|
|
|
+ }) {
|
|
|
+ let element = this.activeElement;
|
|
|
+ let x = e.clientX;
|
|
|
+ let y = e.clientY;
|
|
|
+ if (element && element.isSingle) {
|
|
|
+ element.addPoint(x, y);
|
|
|
+ completeCallback();
|
|
|
+ } else {
|
|
|
+ this.createElement({
|
|
|
+ type: "line",
|
|
|
+ isSingle: false
|
|
|
+ });
|
|
|
+ element = this.activeElement;
|
|
|
+ element.addPoint(x, y);
|
|
|
+ element.updateFictitiousPoint(x, y);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ completeCreateElement() {
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ let element = this.activeElement;
|
|
|
+ if (!element) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ if (["freedraw", "arrow", "line"].includes(element.type)) {
|
|
|
+ element.updateMultiPointBoundingRect();
|
|
|
+ }
|
|
|
+ element.isCreating = false;
|
|
|
+ this.app.emitChange();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ setActiveElementStyle(style = {}) {
|
|
|
+ if (!this.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ Object.keys(style).forEach((key) => {
|
|
|
+ this.activeElement.style[key] = style[key];
|
|
|
+ if (key === "fontSize" && this.activeElement.type === "text") {
|
|
|
+ this.activeElement.updateTextSize();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ checkInResizeHand(x, y) {
|
|
|
+ let element = this.activeElement;
|
|
|
+ let hand = element.dragElement.checkPointInDragElementWhere(x, y);
|
|
|
+ if (hand) {
|
|
|
+ return {
|
|
|
+ element,
|
|
|
+ hand
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ checkIsResize(x, y, e) {
|
|
|
+ if (!this.hasActiveElement()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ let res = this.checkInResizeHand(x, y);
|
|
|
+ if (res) {
|
|
|
+ this.isResizing = true;
|
|
|
+ this.resizingElement = res.element;
|
|
|
+ this.resizingElement.startResize(res.hand, e);
|
|
|
+ this.app.cursor.setResize(res.hand);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ handleResize(...args) {
|
|
|
+ if (!this.isResizing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.resizingElement.resize(...args);
|
|
|
+ this.app.render.render();
|
|
|
+ }
|
|
|
+ endResize() {
|
|
|
+ this.isResizing = false;
|
|
|
+ this.resizingElement.endResize();
|
|
|
+ this.resizingElement = null;
|
|
|
+ }
|
|
|
+}
|
|
|
+function isNumeric$1(str) {
|
|
|
+ const trimmedStr = str.trim();
|
|
|
+ const num = Number(trimmedStr);
|
|
|
+ return !isNaN(num) && trimmedStr === num.toString().padStart(trimmedStr.length, "0");
|
|
|
+}
|
|
|
+class ImageEdit extends EventEmitter {
|
|
|
+ constructor(app) {
|
|
|
+ super();
|
|
|
+ this.app = app;
|
|
|
+ this.el = null;
|
|
|
+ this.isReady = false;
|
|
|
+ this.previewEl = null;
|
|
|
+ this.imageData = null;
|
|
|
+ this.maxWidth = 750;
|
|
|
+ this.maxHeight = 450;
|
|
|
+ this.maxRatio = this.maxWidth / this.maxHeight;
|
|
|
+ this.onImageSelectChange = this.onImageSelectChange.bind(this);
|
|
|
+ }
|
|
|
+ reset() {
|
|
|
+ this.el.value = "";
|
|
|
+ this.isReady = false;
|
|
|
+ document.body.removeChild(this.previewEl);
|
|
|
+ this.previewEl = null;
|
|
|
+ this.imageData = null;
|
|
|
+ }
|
|
|
+ selectImage() {
|
|
|
+ if (!this.el) {
|
|
|
+ this.el = document.createElement("input");
|
|
|
+ this.el.type = "file";
|
|
|
+ this.el.accept = "image/*";
|
|
|
+ this.el.style.position = "fixed";
|
|
|
+ this.el.style.left = "-999999px";
|
|
|
+ this.el.addEventListener("change", this.onImageSelectChange);
|
|
|
+ document.body.appendChild(this.el);
|
|
|
+ }
|
|
|
+ this.el.click();
|
|
|
+ }
|
|
|
+ updatePreviewElPos(x, y) {
|
|
|
+ let width = 100;
|
|
|
+ let height = width / this.imageData.ratio;
|
|
|
+ if (!this.previewEl) {
|
|
|
+ this.previewEl = document.createElement("div");
|
|
|
+ this.previewEl.style.position = "fixed";
|
|
|
+ this.previewEl.style.width = width + "px";
|
|
|
+ this.previewEl.style.height = height + "px";
|
|
|
+ this.previewEl.style.backgroundImage = `url('${this.imageData.url}')`;
|
|
|
+ this.previewEl.style.backgroundSize = "cover";
|
|
|
+ this.previewEl.style.pointerEvents = "none";
|
|
|
+ document.body.appendChild(this.previewEl);
|
|
|
+ }
|
|
|
+ let tp = this.app.coordinate.containerToWindow(x, y);
|
|
|
+ this.previewEl.style.left = tp.x - width / 2 + "px";
|
|
|
+ this.previewEl.style.top = tp.y - height / 2 + "px";
|
|
|
+ }
|
|
|
+ getImageSize(url) {
|
|
|
+ return __async(this, null, function* () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ let img = new Image();
|
|
|
+ img.setAttribute("crossOrigin", "anonymous");
|
|
|
+ img.onload = () => {
|
|
|
+ let width = img.width;
|
|
|
+ let height = img.height;
|
|
|
+ let ratio = img.width / img.height;
|
|
|
+ if (img.width > this.maxWidth || img.height > this.maxHeight) {
|
|
|
+ if (ratio > this.maxRatio) {
|
|
|
+ width = this.maxWidth;
|
|
|
+ height = this.maxWidth / ratio;
|
|
|
+ } else {
|
|
|
+ height = this.maxHeight;
|
|
|
+ width = this.maxHeight * ratio;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ resolve({
|
|
|
+ imageObj: img,
|
|
|
+ size: {
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ },
|
|
|
+ ratio
|
|
|
+ });
|
|
|
+ };
|
|
|
+ img.onerror = () => {
|
|
|
+ reject();
|
|
|
+ };
|
|
|
+ img.src = url;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ onImageSelectChange(e) {
|
|
|
+ return __async(this, null, function* () {
|
|
|
+ let url = yield this.getImageUrl(e.target.files[0]);
|
|
|
+ let { imageObj, size, ratio } = yield this.getImageSize(url);
|
|
|
+ this.isReady = true;
|
|
|
+ this.imageData = __spreadProps(__spreadValues({
|
|
|
+ url
|
|
|
+ }, size), {
|
|
|
+ ratio,
|
|
|
+ imageObj
|
|
|
+ });
|
|
|
+ this.emit("imageSelectChange", this.imageData);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ getImageUrl(file) {
|
|
|
+ return __async(this, null, function* () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ let reader = new FileReader();
|
|
|
+ reader.onloadend = () => {
|
|
|
+ resolve(reader.result);
|
|
|
+ };
|
|
|
+ reader.onerror = () => {
|
|
|
+ reject();
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+class Cursor {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.currentType = "default";
|
|
|
+ }
|
|
|
+ set(type = "default") {
|
|
|
+ this.currentType = type;
|
|
|
+ let style = type;
|
|
|
+ if (type === "eraser") {
|
|
|
+ style = `url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAARRJREFUOE/dlDFLxEAQhd+BVouFZ3vlQuwSyI+5a7PBRkk6k9KzTOwStJFsWv0xgaQzkNLWszim0kL2OOFc9oKRYHFTz37Lm/dmJhi5JiPzcBjAOYDz7WheADz3jalP8oIxds85P3Zd90RBqqpad133SUSXAJ5M4H3AhWVZd1EUzYQQP96VZYkkSV7btr02QY1Axtgqz/NTz/OM6qSUCMNwRURneoMJOLdt+7Gu643MfeU4zrppmgt9pibgjRBiWRRFb0R934eUcgngdrfxX4CjSwZj7C3Lsqnu8Lc05XQQBO9ENP2NKapnE5s4jme608rhNE2HxWb7qwr2A+f8SAv2BxFdDQ32rpLRVu9Pl+0wztcg6V/VPW4Vw1FsawAAAABJRU5ErkJggg==) 10 10, auto`;
|
|
|
+ }
|
|
|
+ this.app.container.style.cursor = style;
|
|
|
+ }
|
|
|
+ hide() {
|
|
|
+ this.set("none");
|
|
|
+ }
|
|
|
+ reset() {
|
|
|
+ this.set();
|
|
|
+ }
|
|
|
+ setCrosshair() {
|
|
|
+ this.set("crosshair");
|
|
|
+ }
|
|
|
+ setMove() {
|
|
|
+ this.set("move");
|
|
|
+ }
|
|
|
+ setResize(dir) {
|
|
|
+ let type = "";
|
|
|
+ switch (dir) {
|
|
|
+ case DRAG_ELEMENT_PARTS.BODY:
|
|
|
+ type = "move";
|
|
|
+ break;
|
|
|
+ case DRAG_ELEMENT_PARTS.ROTATE:
|
|
|
+ type = "grab";
|
|
|
+ break;
|
|
|
+ case DRAG_ELEMENT_PARTS.TOP_LEFT_BTN:
|
|
|
+ type = "nw-resize";
|
|
|
+ break;
|
|
|
+ case DRAG_ELEMENT_PARTS.TOP_RIGHT_BTN:
|
|
|
+ type = "ne-resize";
|
|
|
+ break;
|
|
|
+ case DRAG_ELEMENT_PARTS.BOTTOM_RIGHT_BTN:
|
|
|
+ type = "se-resize";
|
|
|
+ break;
|
|
|
+ case DRAG_ELEMENT_PARTS.BOTTOM_LEFT_BTN:
|
|
|
+ type = "sw-resize";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ this.set(type);
|
|
|
+ }
|
|
|
+ setEraser() {
|
|
|
+ this.set("eraser");
|
|
|
+ }
|
|
|
+}
|
|
|
+class TextEdit extends EventEmitter {
|
|
|
+ constructor(app) {
|
|
|
+ super();
|
|
|
+ this.app = app;
|
|
|
+ this.editable = null;
|
|
|
+ this.isEditing = false;
|
|
|
+ this.onTextInput = this.onTextInput.bind(this);
|
|
|
+ this.onTextBlur = this.onTextBlur.bind(this);
|
|
|
+ }
|
|
|
+ crateTextInputEl() {
|
|
|
+ this.editable = document.createElement("textarea");
|
|
|
+ this.editable.dir = "auto";
|
|
|
+ this.editable.tabIndex = 0;
|
|
|
+ this.editable.wrap = "off";
|
|
|
+ this.editable.className = "textInput";
|
|
|
+ Object.assign(this.editable.style, {
|
|
|
+ position: "fixed",
|
|
|
+ display: "block",
|
|
|
+ minHeight: "1em",
|
|
|
+ backfaceVisibility: "hidden",
|
|
|
+ margin: 0,
|
|
|
+ padding: 0,
|
|
|
+ border: 0,
|
|
|
+ outline: 0,
|
|
|
+ resize: "none",
|
|
|
+ background: "transparent",
|
|
|
+ overflow: "hidden",
|
|
|
+ whiteSpace: "pre"
|
|
|
+ });
|
|
|
+ this.editable.addEventListener("input", this.onTextInput);
|
|
|
+ this.editable.addEventListener("blur", this.onTextBlur);
|
|
|
+ document.body.appendChild(this.editable);
|
|
|
+ }
|
|
|
+ updateTextInputStyle() {
|
|
|
+ let activeElement = this.app.elements.activeElement;
|
|
|
+ if (!activeElement) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let { x, y, width, height, style, text, rotate } = activeElement;
|
|
|
+ let { coordinate, state } = this.app;
|
|
|
+ this.editable.value = text;
|
|
|
+ x = coordinate.subScrollX(x);
|
|
|
+ y = coordinate.subScrollY(y);
|
|
|
+ let sp = coordinate.scale(x, y);
|
|
|
+ let tp = coordinate.containerToWindow(sp.x, sp.y);
|
|
|
+ let fontSize = style.fontSize * state.scale;
|
|
|
+ let styles = {
|
|
|
+ font: getFontString(fontSize, style.fontFamily),
|
|
|
+ lineHeight: `${fontSize * style.lineHeightRatio}px`,
|
|
|
+ left: `${tp.x}px`,
|
|
|
+ top: `${tp.y}px`,
|
|
|
+ color: style.fillStyle,
|
|
|
+ width: Math.max(width, 100) * state.scale + "px",
|
|
|
+ height: height * state.scale + "px",
|
|
|
+ transform: `rotate(${rotate}deg)`,
|
|
|
+ opacity: style.globalAlpha
|
|
|
+ };
|
|
|
+ Object.assign(this.editable.style, styles);
|
|
|
+ }
|
|
|
+ onTextInput() {
|
|
|
+ let activeElement = this.app.elements.activeElement;
|
|
|
+ if (!activeElement) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ activeElement.text = this.editable.value;
|
|
|
+ let { width, height } = getTextElementSize(activeElement);
|
|
|
+ activeElement.width = width;
|
|
|
+ activeElement.height = height;
|
|
|
+ this.updateTextInputStyle();
|
|
|
+ }
|
|
|
+ onTextBlur() {
|
|
|
+ this.editable.style.display = "none";
|
|
|
+ this.editable.value = "";
|
|
|
+ this.emit("blur");
|
|
|
+ this.isEditing = false;
|
|
|
+ }
|
|
|
+ showTextEdit() {
|
|
|
+ if (!this.editable) {
|
|
|
+ this.crateTextInputEl();
|
|
|
+ } else {
|
|
|
+ this.editable.style.display = "block";
|
|
|
+ }
|
|
|
+ this.updateTextInputStyle();
|
|
|
+ this.editable.focus();
|
|
|
+ this.editable.select();
|
|
|
+ this.isEditing = true;
|
|
|
+ }
|
|
|
+}
|
|
|
+class History {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.historyStack = [];
|
|
|
+ this.length = 0;
|
|
|
+ this.index = -1;
|
|
|
+ }
|
|
|
+ add(data) {
|
|
|
+ let prev = this.length > 0 ? this.historyStack[this.length - 1] : null;
|
|
|
+ let copyData = deepCopy(data);
|
|
|
+ if (copyData === prev) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.historyStack.push(copyData);
|
|
|
+ this.length++;
|
|
|
+ this.index = this.length - 1;
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ undo() {
|
|
|
+ if (this.index <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.index--;
|
|
|
+ this.shuttle();
|
|
|
+ }
|
|
|
+ redo() {
|
|
|
+ if (this.index >= this.length - 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.index++;
|
|
|
+ this.shuttle();
|
|
|
+ }
|
|
|
+ shuttle() {
|
|
|
+ return __async(this, null, function* () {
|
|
|
+ let data = this.historyStack[this.index];
|
|
|
+ yield this.app.setData(data, true);
|
|
|
+ this.emitChange();
|
|
|
+ this.app.emit("change", data);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ clear() {
|
|
|
+ this.index = -1;
|
|
|
+ this.length = 0;
|
|
|
+ this.historyStack = [];
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ emitChange() {
|
|
|
+ this.app.emit("shuttle", this.index, this.length);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Export {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.openTest = false;
|
|
|
+ this.saveState = {
|
|
|
+ scale: 0,
|
|
|
+ scrollX: 0,
|
|
|
+ scrollY: 0,
|
|
|
+ width: 0,
|
|
|
+ height: 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+ show(canvas) {
|
|
|
+ if (this.openTest) {
|
|
|
+ canvas.style.cssText = `
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ background-color: #fff;
|
|
|
+ `;
|
|
|
+ document.body.appendChild(canvas);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ getElementList(onlySelected = true) {
|
|
|
+ if (!onlySelected) {
|
|
|
+ return this.app.elements.elementList;
|
|
|
+ } else {
|
|
|
+ let selectedElements = [];
|
|
|
+ if (this.app.elements.activeElement) {
|
|
|
+ selectedElements.push(this.app.elements.activeElement);
|
|
|
+ } else if (this.app.selection.hasSelectionElements()) {
|
|
|
+ selectedElements = this.app.selection.getSelectionElements();
|
|
|
+ }
|
|
|
+ let res = this.app.elements.elementList.filter((element) => {
|
|
|
+ return selectedElements.includes(element);
|
|
|
+ });
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ exportImage({
|
|
|
+ type = "image/png",
|
|
|
+ renderBg = true,
|
|
|
+ useBlob = false,
|
|
|
+ paddingX = 10,
|
|
|
+ paddingY = 10,
|
|
|
+ onlySelected
|
|
|
+ } = {}) {
|
|
|
+ let { minx, maxx, miny, maxy } = getMultiElementRectInfo(
|
|
|
+ this.getElementList(onlySelected)
|
|
|
+ );
|
|
|
+ let width = maxx - minx + paddingX * 2;
|
|
|
+ let height = maxy - miny + paddingY * 2;
|
|
|
+ let { canvas, ctx } = createCanvas(width, height, {
|
|
|
+ noStyle: true,
|
|
|
+ noTranslate: true
|
|
|
+ });
|
|
|
+ this.show(canvas);
|
|
|
+ this.saveAppState();
|
|
|
+ this.changeAppState(minx - paddingX, miny - paddingY, ctx);
|
|
|
+ if (renderBg && this.app.state.backgroundColor) {
|
|
|
+ this.app.background.canvasAddBackgroundColor(
|
|
|
+ ctx,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ this.app.state.backgroundColor
|
|
|
+ );
|
|
|
+ }
|
|
|
+ this.render(ctx, onlySelected);
|
|
|
+ this.recoveryAppState();
|
|
|
+ if (useBlob) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ canvas.toBlob((blob) => {
|
|
|
+ if (blob) {
|
|
|
+ resolve(blob);
|
|
|
+ } else {
|
|
|
+ reject();
|
|
|
+ }
|
|
|
+ }, type);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ return canvas.toDataURL(type);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ saveAppState() {
|
|
|
+ let { width, height, state, ctx } = this.app;
|
|
|
+ this.saveState.width = width;
|
|
|
+ this.saveState.height = height;
|
|
|
+ this.saveState.scale = state.scale;
|
|
|
+ this.saveState.scrollX = state.scrollX;
|
|
|
+ this.saveState.scrollY = state.scrollY;
|
|
|
+ this.saveState.ctx = ctx;
|
|
|
+ }
|
|
|
+ changeAppState(minx, miny, ctx) {
|
|
|
+ this.app.ctx = ctx;
|
|
|
+ this.app.state.scale = 1;
|
|
|
+ this.app.state.scrollX = 0;
|
|
|
+ this.app.state.scrollY = 0;
|
|
|
+ this.app.width = minx * 2;
|
|
|
+ this.app.height = miny * 2;
|
|
|
+ }
|
|
|
+ recoveryAppState() {
|
|
|
+ let { width, height, scale, scrollX, scrollY, ctx } = this.saveState;
|
|
|
+ this.app.state.scale = scale;
|
|
|
+ this.app.state.scrollX = scrollX;
|
|
|
+ this.app.state.scrollY = scrollY;
|
|
|
+ this.app.width = width;
|
|
|
+ this.app.height = height;
|
|
|
+ this.app.ctx = ctx;
|
|
|
+ }
|
|
|
+ render(ctx, onlySelected) {
|
|
|
+ ctx.save();
|
|
|
+ this.getElementList(onlySelected).forEach((element) => {
|
|
|
+ if (element.noRender) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let cacheActive = element.isActive;
|
|
|
+ let cacheSelected = element.isSelected;
|
|
|
+ element.isActive = false;
|
|
|
+ element.isSelected = false;
|
|
|
+ element.render();
|
|
|
+ element.isActive = cacheActive;
|
|
|
+ element.isSelected = cacheSelected;
|
|
|
+ });
|
|
|
+ ctx.restore();
|
|
|
+ }
|
|
|
+ exportJson() {
|
|
|
+ return this.app.getData();
|
|
|
+ }
|
|
|
+}
|
|
|
+class Background {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ }
|
|
|
+ set() {
|
|
|
+ if (this.app.state.backgroundColor) {
|
|
|
+ this.addBackgroundColor();
|
|
|
+ } else {
|
|
|
+ this.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ addBackgroundColor() {
|
|
|
+ this.app.container.style.backgroundColor = this.app.state.backgroundColor;
|
|
|
+ }
|
|
|
+ remove() {
|
|
|
+ this.app.container.style.backgroundColor = "";
|
|
|
+ }
|
|
|
+ canvasAddBackgroundColor(ctx, width, height, backgroundColor) {
|
|
|
+ ctx.save();
|
|
|
+ ctx.rect(0, 0, width, height);
|
|
|
+ ctx.fillStyle = backgroundColor;
|
|
|
+ ctx.fill();
|
|
|
+ ctx.restore();
|
|
|
+ }
|
|
|
+}
|
|
|
+class Canvas {
|
|
|
+ constructor(width, height, opt) {
|
|
|
+ this.width = width;
|
|
|
+ this.height = height;
|
|
|
+ let { canvas, ctx } = createCanvas(width, height, opt);
|
|
|
+ this.el = canvas;
|
|
|
+ this.ctx = ctx;
|
|
|
+ }
|
|
|
+ clearCanvas() {
|
|
|
+ let { width, height } = this;
|
|
|
+ this.ctx.clearRect(-width / 2, -height / 2, width, height);
|
|
|
+ }
|
|
|
+}
|
|
|
+class MultiSelectElement extends BaseElement {
|
|
|
+ constructor(opts = {}, app) {
|
|
|
+ super(opts, app);
|
|
|
+ this.dragElement = new DragElement(this, this.app);
|
|
|
+ this.selectedElementList = [];
|
|
|
+ this.wholeCenterPos = { x: 0, y: 0 };
|
|
|
+ }
|
|
|
+ setSelectedElementList(list) {
|
|
|
+ this.selectedElementList.forEach((element) => {
|
|
|
+ element.isSelected = false;
|
|
|
+ });
|
|
|
+ this.selectedElementList = list;
|
|
|
+ this.selectedElementList.forEach((element) => {
|
|
|
+ element.isSelected = true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ updateElements(elements) {
|
|
|
+ let exists = [];
|
|
|
+ this.selectedElementList.forEach((element) => {
|
|
|
+ if (elements.includes(element)) {
|
|
|
+ exists.push(element);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.setSelectedElementList(exists);
|
|
|
+ }
|
|
|
+ updateRect() {
|
|
|
+ if (this.selectedElementList.length <= 0) {
|
|
|
+ super.updateRect(0, 0, 0, 0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let { minx, maxx, miny, maxy } = getMultiElementRectInfo(
|
|
|
+ this.selectedElementList
|
|
|
+ );
|
|
|
+ super.updateRect(minx, miny, maxx - minx, maxy - miny);
|
|
|
+ }
|
|
|
+ startResize(...args) {
|
|
|
+ this.selectedElementList.forEach((element) => {
|
|
|
+ if (args[0] === "rotate") {
|
|
|
+ this.wholeCenterPos = getElementCenterPoint(this);
|
|
|
+ }
|
|
|
+ element.startResize(...args);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ resize(...args) {
|
|
|
+ this.selectedElementList.forEach((element) => {
|
|
|
+ if (element.dragElement.resizeType === "rotate") {
|
|
|
+ this.handleRotate(element, ...args);
|
|
|
+ } else {
|
|
|
+ element.resize(...args);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ handleRotate(element, e, mx, my, offsetX, offsetY) {
|
|
|
+ let rotate = getTowPointRotate(
|
|
|
+ this.wholeCenterPos.x,
|
|
|
+ this.wholeCenterPos.y,
|
|
|
+ e.clientX,
|
|
|
+ e.clientY,
|
|
|
+ mx,
|
|
|
+ my
|
|
|
+ );
|
|
|
+ element.rotateByCenter(rotate, this.wholeCenterPos.x, this.wholeCenterPos.y);
|
|
|
+ }
|
|
|
+ endResize() {
|
|
|
+ this.selectedElementList.forEach((element) => {
|
|
|
+ element.endResize();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ if (this.selectedElementList.length > 0) {
|
|
|
+ if (this.width <= 0 || this.height <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.dragElement.render();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+class Selection {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.canvas = null;
|
|
|
+ this.ctx = null;
|
|
|
+ this.creatingSelection = false;
|
|
|
+ this.hasSelection = false;
|
|
|
+ this.isResizing = false;
|
|
|
+ this.state = this.app.state;
|
|
|
+ this.width = this.app.width;
|
|
|
+ this.height = this.app.height;
|
|
|
+ this.coordinate = new Coordinate(this);
|
|
|
+ this.rectangle = new Rectangle(
|
|
|
+ {
|
|
|
+ type: "rectangle",
|
|
|
+ style: {
|
|
|
+ strokeStyle: "rgba(9,132,227,0.3)",
|
|
|
+ fillStyle: "rgba(9,132,227,0.3)"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ this
|
|
|
+ );
|
|
|
+ this.multiSelectElement = new MultiSelectElement(
|
|
|
+ {
|
|
|
+ type: "multiSelectElement"
|
|
|
+ },
|
|
|
+ this
|
|
|
+ );
|
|
|
+ this.checkInNodes = throttle(this.checkInNodes, this, 500);
|
|
|
+ this.handleResize = throttle(this.handleResize, this, 16);
|
|
|
+ this.init();
|
|
|
+ this.bindEvent();
|
|
|
+ }
|
|
|
+ init() {
|
|
|
+ if (this.canvas) {
|
|
|
+ this.app.container.removeChild(this.canvas.el);
|
|
|
+ }
|
|
|
+ this.width = this.app.width;
|
|
|
+ this.height = this.app.height;
|
|
|
+ this.canvas = new Canvas(this.width, this.height, {
|
|
|
+ className: "selection"
|
|
|
+ });
|
|
|
+ this.ctx = this.canvas.ctx;
|
|
|
+ this.app.container.appendChild(this.canvas.el);
|
|
|
+ }
|
|
|
+ bindEvent() {
|
|
|
+ this.app.on("change", () => {
|
|
|
+ this.state = this.app.state;
|
|
|
+ this.multiSelectElement.updateElements(this.app.elements.elementList);
|
|
|
+ this.renderSelection();
|
|
|
+ });
|
|
|
+ this.app.on("scrollChange", () => {
|
|
|
+ this.renderSelection();
|
|
|
+ });
|
|
|
+ this.app.on("zoomChange", () => {
|
|
|
+ this.renderSelection();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ onMousedown(e, event) {
|
|
|
+ if (e.originEvent.which !== 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.creatingSelection = true;
|
|
|
+ this.rectangle.updatePos(event.mousedownPos.x, event.mousedownPos.y);
|
|
|
+ }
|
|
|
+ onMousemove(e, event) {
|
|
|
+ if (Math.abs(event.mouseOffset.x) <= 10 && Math.abs(event.mouseOffset.y) <= 10) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.onMove(e, event);
|
|
|
+ }
|
|
|
+ onMouseup() {
|
|
|
+ this.creatingSelection = false;
|
|
|
+ this.rectangle.updateRect(0, 0, 0, 0);
|
|
|
+ this.hasSelection = this.hasSelectionElements();
|
|
|
+ this.multiSelectElement.updateRect();
|
|
|
+ this.renderSelection();
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ reset() {
|
|
|
+ this.setMultiSelectElements([]);
|
|
|
+ this.hasSelection = false;
|
|
|
+ this.renderSelection();
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ renderSelection() {
|
|
|
+ this.canvas.clearCanvas();
|
|
|
+ this.ctx.save();
|
|
|
+ this.ctx.scale(this.app.state.scale, this.app.state.scale);
|
|
|
+ this.rectangle.render();
|
|
|
+ this.multiSelectElement.render();
|
|
|
+ this.ctx.restore();
|
|
|
+ }
|
|
|
+ onMove(e, event) {
|
|
|
+ this.rectangle.updateSize(event.mouseOffset.x, event.mouseOffset.y);
|
|
|
+ this.renderSelection();
|
|
|
+ this.checkInElements(e, event);
|
|
|
+ }
|
|
|
+ checkInElements(e, event) {
|
|
|
+ let minx = Math.min(event.mousedownPos.x, e.clientX);
|
|
|
+ let miny = Math.min(event.mousedownPos.y, e.clientY);
|
|
|
+ let maxx = Math.max(event.mousedownPos.x, e.clientX);
|
|
|
+ let maxy = Math.max(event.mousedownPos.y, e.clientY);
|
|
|
+ let selectedElementList = [];
|
|
|
+ this.app.elements.elementList.forEach((element) => {
|
|
|
+ let _minx = Infinity;
|
|
|
+ let _maxx = -Infinity;
|
|
|
+ let _miny = Infinity;
|
|
|
+ let _maxy = -Infinity;
|
|
|
+ let endPointList = element.getEndpointList();
|
|
|
+ let rect = getBoundingRect(
|
|
|
+ endPointList.map((point) => {
|
|
|
+ return [point.x, point.y];
|
|
|
+ }),
|
|
|
+ true
|
|
|
+ );
|
|
|
+ rect.forEach(({ x, y }) => {
|
|
|
+ if (x < _minx) {
|
|
|
+ _minx = x;
|
|
|
+ }
|
|
|
+ if (x > _maxx) {
|
|
|
+ _maxx = x;
|
|
|
+ }
|
|
|
+ if (y < _miny) {
|
|
|
+ _miny = y;
|
|
|
+ }
|
|
|
+ if (y > _maxy) {
|
|
|
+ _maxy = y;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (_minx >= minx && _maxx <= maxx && _miny >= miny && _maxy <= maxy) {
|
|
|
+ selectedElementList.push(element);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ let finalList = [...selectedElementList];
|
|
|
+ selectedElementList.forEach((item) => {
|
|
|
+ if (item.hasGroup()) {
|
|
|
+ finalList.push(...this.app.group.getGroupElements(item));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ finalList = new Set(finalList);
|
|
|
+ finalList = Array.from(finalList);
|
|
|
+ this.setMultiSelectElements(finalList, true);
|
|
|
+ this.app.render.render();
|
|
|
+ }
|
|
|
+ checkInResizeHand(x, y) {
|
|
|
+ return this.multiSelectElement.dragElement.checkPointInDragElementWhere(
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ );
|
|
|
+ }
|
|
|
+ checkIsResize(x, y, e) {
|
|
|
+ if (!this.hasSelection) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ let hand = this.multiSelectElement.dragElement.checkPointInDragElementWhere(
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ );
|
|
|
+ if (hand) {
|
|
|
+ this.isResizing = true;
|
|
|
+ this.multiSelectElement.startResize(hand, e);
|
|
|
+ this.app.cursor.setResize(hand);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ handleResize(...args) {
|
|
|
+ if (!this.isResizing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.multiSelectElement.resize(...args);
|
|
|
+ this.app.render.render();
|
|
|
+ this.multiSelectElement.updateRect();
|
|
|
+ this.renderSelection();
|
|
|
+ }
|
|
|
+ endResize() {
|
|
|
+ this.isResizing = false;
|
|
|
+ this.multiSelectElement.endResize();
|
|
|
+ }
|
|
|
+ setSelectedElementStyle(style = {}) {
|
|
|
+ if (!this.hasSelectionElements()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Object.keys(style).forEach((key) => {
|
|
|
+ this.getSelectionElements().forEach((element) => {
|
|
|
+ element.style[key] = style[key];
|
|
|
+ if (key === "fontSize" && element.type === "text") {
|
|
|
+ element.updateTextSize();
|
|
|
+ this.multiSelectElement.updateRect();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.app.render.render();
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ deleteSelectedElements() {
|
|
|
+ this.getSelectionElements().forEach((element) => {
|
|
|
+ this.app.elements.deleteElement(element);
|
|
|
+ });
|
|
|
+ this.selectElements([]);
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ hasSelectionElements() {
|
|
|
+ return this.getSelectionElements().length > 0;
|
|
|
+ }
|
|
|
+ getSelectionElements() {
|
|
|
+ return this.multiSelectElement.selectedElementList;
|
|
|
+ }
|
|
|
+ copySelectionElements(pos) {
|
|
|
+ return __async(this, null, function* () {
|
|
|
+ let task = this.getSelectionElements().map((element) => {
|
|
|
+ return this.app.elements.copyElement(element, true);
|
|
|
+ });
|
|
|
+ this.app.group.clearCopyMap();
|
|
|
+ let elements = yield Promise.all(task);
|
|
|
+ this.setMultiSelectElements(elements);
|
|
|
+ if (pos) {
|
|
|
+ this.multiSelectElement.startResize(DRAG_ELEMENT_PARTS.BODY);
|
|
|
+ let ox = pos.x - this.multiSelectElement.x - this.multiSelectElement.width / 2;
|
|
|
+ let oy = pos.y - this.multiSelectElement.y - this.multiSelectElement.height / 2;
|
|
|
+ let gridAdsorbentPos = this.app.coordinate.gridAdsorbent(ox, oy);
|
|
|
+ this.multiSelectElement.resize(
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ gridAdsorbentPos.x,
|
|
|
+ gridAdsorbentPos.y
|
|
|
+ );
|
|
|
+ this.multiSelectElement.endResize();
|
|
|
+ this.multiSelectElement.updateRect();
|
|
|
+ }
|
|
|
+ this.app.render.render();
|
|
|
+ this.renderSelection();
|
|
|
+ this.app.emitChange();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ selectElements(elements = []) {
|
|
|
+ this.hasSelection = elements.length > 0;
|
|
|
+ this.setMultiSelectElements(elements);
|
|
|
+ this.app.render.render();
|
|
|
+ this.renderSelection();
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ setMultiSelectElements(elements = [], notUpdateRect) {
|
|
|
+ this.multiSelectElement.setSelectedElementList(elements);
|
|
|
+ if (!notUpdateRect) {
|
|
|
+ this.multiSelectElement.updateRect();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ emitChange() {
|
|
|
+ this.app.emit("multiSelectChange", this.getSelectionElements());
|
|
|
+ }
|
|
|
+}
|
|
|
+class Grid {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.canvas = null;
|
|
|
+ this.ctx = null;
|
|
|
+ this.init();
|
|
|
+ this.app.on("zoomChange", this.renderGrid, this);
|
|
|
+ this.app.on("scrollChange", this.renderGrid, this);
|
|
|
+ }
|
|
|
+ init() {
|
|
|
+ if (this.canvas) {
|
|
|
+ this.app.container.removeChild(this.canvas.el);
|
|
|
+ }
|
|
|
+ let { width, height } = this.app;
|
|
|
+ this.canvas = new Canvas(width, height, {
|
|
|
+ className: "grid"
|
|
|
+ });
|
|
|
+ this.ctx = this.canvas.ctx;
|
|
|
+ this.app.container.insertBefore(
|
|
|
+ this.canvas.el,
|
|
|
+ this.app.container.children[0]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ drawHorizontalLine(i) {
|
|
|
+ let { coordinate, width, state } = this.app;
|
|
|
+ let _i = coordinate.subScrollY(i);
|
|
|
+ this.ctx.beginPath();
|
|
|
+ this.ctx.moveTo(-width / state.scale / 2, _i);
|
|
|
+ this.ctx.lineTo(width / state.scale / 2, _i);
|
|
|
+ this.ctx.stroke();
|
|
|
+ }
|
|
|
+ renderHorizontalLines() {
|
|
|
+ let { coordinate, height, state } = this.app;
|
|
|
+ let { gridConfig, scale } = state;
|
|
|
+ let maxBottom = 0;
|
|
|
+ for (let i = -height / 2; i < height / 2; i += gridConfig.size) {
|
|
|
+ this.drawHorizontalLine(i);
|
|
|
+ maxBottom = i;
|
|
|
+ }
|
|
|
+ for (let i = -height / 2 - gridConfig.size; i > -coordinate.subScrollY(height / scale / 2); i -= gridConfig.size) {
|
|
|
+ this.drawHorizontalLine(i);
|
|
|
+ }
|
|
|
+ for (let i = maxBottom + gridConfig.size; i < coordinate.addScrollY(height / scale / 2); i += gridConfig.size) {
|
|
|
+ this.drawHorizontalLine(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ drawVerticalLine(i) {
|
|
|
+ let { coordinate, height, state } = this.app;
|
|
|
+ let _i = coordinate.subScrollX(i);
|
|
|
+ this.ctx.beginPath();
|
|
|
+ this.ctx.moveTo(_i, -height / state.scale / 2);
|
|
|
+ this.ctx.lineTo(_i, height / state.scale / 2);
|
|
|
+ this.ctx.stroke();
|
|
|
+ }
|
|
|
+ renderVerticalLines() {
|
|
|
+ let { coordinate, width, state } = this.app;
|
|
|
+ let { gridConfig, scale } = state;
|
|
|
+ let maxRight = 0;
|
|
|
+ for (let i = -width / 2; i < width / 2; i += gridConfig.size) {
|
|
|
+ this.drawVerticalLine(i);
|
|
|
+ maxRight = i;
|
|
|
+ }
|
|
|
+ for (let i = -width / 2 - gridConfig.size; i > -coordinate.subScrollX(width / scale / 2); i -= gridConfig.size) {
|
|
|
+ this.drawVerticalLine(i);
|
|
|
+ }
|
|
|
+ for (let i = maxRight + gridConfig.size; i < coordinate.addScrollX(width / scale / 2); i += gridConfig.size) {
|
|
|
+ this.drawVerticalLine(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ renderGrid() {
|
|
|
+ this.canvas.clearCanvas();
|
|
|
+ let { gridConfig, scale, showGrid } = this.app.state;
|
|
|
+ if (!showGrid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.ctx.save();
|
|
|
+ this.ctx.scale(scale, scale);
|
|
|
+ this.ctx.strokeStyle = gridConfig.strokeStyle;
|
|
|
+ this.ctx.lineWidth = gridConfig.lineWidth;
|
|
|
+ this.renderHorizontalLines();
|
|
|
+ this.renderVerticalLines();
|
|
|
+ this.ctx.restore();
|
|
|
+ }
|
|
|
+ showGrid() {
|
|
|
+ this.app.updateState({
|
|
|
+ showGrid: true
|
|
|
+ });
|
|
|
+ this.renderGrid();
|
|
|
+ }
|
|
|
+ hideGrid() {
|
|
|
+ this.app.updateState({
|
|
|
+ showGrid: false
|
|
|
+ });
|
|
|
+ this.canvas.clearCanvas();
|
|
|
+ }
|
|
|
+ updateGrid(config = {}) {
|
|
|
+ this.app.updateState({
|
|
|
+ gridConfig: __spreadValues(__spreadValues({}, this.app.state.gridConfig), config)
|
|
|
+ });
|
|
|
+ if (this.app.state.showGrid) {
|
|
|
+ this.hideGrid();
|
|
|
+ this.showGrid();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+const map = {
|
|
|
+ Tab: 9,
|
|
|
+ Enter: 13,
|
|
|
+ Shift: 16,
|
|
|
+ Control: 17,
|
|
|
+ Alt: 18,
|
|
|
+ CapsLock: 20,
|
|
|
+ Esc: 27,
|
|
|
+ Space: 32,
|
|
|
+ PageUp: 33,
|
|
|
+ PageDown: 34,
|
|
|
+ End: 35,
|
|
|
+ Home: 36,
|
|
|
+ Insert: 45,
|
|
|
+ Left: 37,
|
|
|
+ Up: 38,
|
|
|
+ Right: 39,
|
|
|
+ Down: 40,
|
|
|
+ Del: 46,
|
|
|
+ NumLock: 144,
|
|
|
+ Cmd: 91,
|
|
|
+ CmdFF: 224,
|
|
|
+ F1: 112,
|
|
|
+ F2: 113,
|
|
|
+ F3: 114,
|
|
|
+ F4: 115,
|
|
|
+ F5: 116,
|
|
|
+ F6: 117,
|
|
|
+ F7: 118,
|
|
|
+ F8: 119,
|
|
|
+ F9: 120,
|
|
|
+ F10: 121,
|
|
|
+ F11: 122,
|
|
|
+ F12: 123,
|
|
|
+ "`": 192,
|
|
|
+ "=": 187,
|
|
|
+ "+": 187,
|
|
|
+ "-": 189,
|
|
|
+ "'": 222,
|
|
|
+ "/": 191,
|
|
|
+ ".": 190
|
|
|
+};
|
|
|
+for (let i = 0; i <= 9; i++) {
|
|
|
+ map[i] = i + 48;
|
|
|
+}
|
|
|
+"abcdefghijklmnopqrstuvwxyz".split("").forEach((n, index) => {
|
|
|
+ map[n] = index + 65;
|
|
|
+});
|
|
|
+const keyMap = map;
|
|
|
+class Mode {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.startScrollX = 0;
|
|
|
+ this.startScrollY = 0;
|
|
|
+ this.isDragMode = false;
|
|
|
+ this.onMove = throttle(this.onMove, this, 16);
|
|
|
+ this.bindEvent();
|
|
|
+ }
|
|
|
+ bindEvent() {
|
|
|
+ this.app.event.on("keydown", (e) => {
|
|
|
+ if (e.keyCode === keyMap.Space) {
|
|
|
+ this.isDragMode = true;
|
|
|
+ this.app.cursor.set("grab");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.app.event.on("keyup", (e) => {
|
|
|
+ if (this.isDragMode) {
|
|
|
+ this.isDragMode = false;
|
|
|
+ this.app.cursor.set("default");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setEditMode() {
|
|
|
+ this.app.cursor.set("default");
|
|
|
+ this.app.updateState({
|
|
|
+ readonly: false
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setReadonlyMode() {
|
|
|
+ this.app.cursor.set("grab");
|
|
|
+ this.app.updateState({
|
|
|
+ readonly: true
|
|
|
+ });
|
|
|
+ }
|
|
|
+ onStart() {
|
|
|
+ this.startScrollX = this.app.state.scrollX;
|
|
|
+ this.startScrollY = this.app.state.scrollY;
|
|
|
+ }
|
|
|
+ onMove(e, event) {
|
|
|
+ this.app.scrollTo(
|
|
|
+ this.startScrollX - event.mouseOffset.originX / this.app.state.scale,
|
|
|
+ this.startScrollY - event.mouseOffset.originY / this.app.state.scale
|
|
|
+ );
|
|
|
+ }
|
|
|
+ onEnd() {
|
|
|
+ this.startScrollX = 0;
|
|
|
+ this.startScrollY = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+class KeyCommand {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.keyMap = keyMap;
|
|
|
+ this.shortcutMap = {};
|
|
|
+ this.bindEvent();
|
|
|
+ }
|
|
|
+ bindEvent() {
|
|
|
+ this.app.event.on("keydown", this.onKeydown, this);
|
|
|
+ }
|
|
|
+ unBindEvent() {
|
|
|
+ this.app.event.off("keydown", this.onKeydown);
|
|
|
+ }
|
|
|
+ onKeydown(e) {
|
|
|
+ Object.keys(this.shortcutMap).forEach((key) => {
|
|
|
+ if (this.checkKey(e, key)) {
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ this.shortcutMap[key].forEach((f) => {
|
|
|
+ f.fn.call(f.ctx);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ checkKey(e, key) {
|
|
|
+ let o = this.getOriginEventCodeArr(e);
|
|
|
+ let k = this.getKeyCodeArr(key);
|
|
|
+ if (o.length !== k.length) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ for (let i = 0; i < o.length; i++) {
|
|
|
+ let index = k.findIndex((item) => {
|
|
|
+ return item === o[i];
|
|
|
+ });
|
|
|
+ if (index === -1) {
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ k.splice(index, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ getOriginEventCodeArr(e) {
|
|
|
+ let arr = [];
|
|
|
+ if (e.ctrlKey || e.metaKey) {
|
|
|
+ arr.push(keyMap["Control"]);
|
|
|
+ }
|
|
|
+ if (e.altKey) {
|
|
|
+ arr.push(keyMap["Alt"]);
|
|
|
+ }
|
|
|
+ if (e.shiftKey) {
|
|
|
+ arr.push(keyMap["Shift"]);
|
|
|
+ }
|
|
|
+ if (!arr.includes(e.keyCode)) {
|
|
|
+ arr.push(e.keyCode);
|
|
|
+ }
|
|
|
+ return arr;
|
|
|
+ }
|
|
|
+ getKeyCodeArr(key) {
|
|
|
+ key = key.replace(/\+\+/, "+add");
|
|
|
+ let keyArr = key.split(/\s*\+\s*/).map((item) => {
|
|
|
+ return item === "add" ? "+" : item;
|
|
|
+ });
|
|
|
+ let arr = [];
|
|
|
+ keyArr.forEach((item) => {
|
|
|
+ arr.push(keyMap[item]);
|
|
|
+ });
|
|
|
+ return arr;
|
|
|
+ }
|
|
|
+ addShortcut(key, fn, ctx) {
|
|
|
+ key.split(/\s*\|\s*/).forEach((item) => {
|
|
|
+ if (this.shortcutMap[item]) {
|
|
|
+ this.shortcutMap[item].push({
|
|
|
+ fn,
|
|
|
+ ctx
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.shortcutMap[item] = [
|
|
|
+ {
|
|
|
+ fn,
|
|
|
+ ctx
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ removeShortcut(key, fn) {
|
|
|
+ key.split(/\s*\|\s*/).forEach((item) => {
|
|
|
+ if (this.shortcutMap[item]) {
|
|
|
+ if (fn) {
|
|
|
+ let index = this.shortcutMap[item].findIndex((f) => {
|
|
|
+ return f.fn === fn;
|
|
|
+ });
|
|
|
+ if (index !== -1) {
|
|
|
+ this.shortcutMap[item].splice(index, 1);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.shortcutMap[item] = [];
|
|
|
+ delete this.shortcutMap[item];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+class Render {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.beingCopyActiveElement = null;
|
|
|
+ this.beingCopySelectedElements = [];
|
|
|
+ this.registerShortcutKeys();
|
|
|
+ }
|
|
|
+ clearCanvas() {
|
|
|
+ let { width, height } = this.app;
|
|
|
+ this.app.ctx.clearRect(-width / 2, -height / 2, width, height);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ let { state } = this.app;
|
|
|
+ this.clearCanvas();
|
|
|
+ this.app.ctx.save();
|
|
|
+ this.app.ctx.scale(state.scale, state.scale);
|
|
|
+ this.app.elements.elementList.forEach((element) => {
|
|
|
+ if (element.noRender) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ element.render();
|
|
|
+ });
|
|
|
+ this.app.group.render();
|
|
|
+ this.app.ctx.restore();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ registerShortcutKeys() {
|
|
|
+ this.app.keyCommand.addShortcut("Del|Backspace", () => {
|
|
|
+ this.deleteCurrentElements();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+c", () => {
|
|
|
+ this.copyCurrentElement();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+x", () => {
|
|
|
+ this.cutCurrentElement();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+z", () => {
|
|
|
+ this.app.history.undo();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+y", () => {
|
|
|
+ this.app.history.redo();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+v", () => {
|
|
|
+ this.pasteCurrentElement(true);
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control++", () => {
|
|
|
+ this.zoomIn();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+-", () => {
|
|
|
+ this.zoomOut();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Shift+1", () => {
|
|
|
+ this.fit();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+a", () => {
|
|
|
+ this.selectAll();
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+0", () => {
|
|
|
+ this.setZoom(1);
|
|
|
+ });
|
|
|
+ this.app.keyCommand.addShortcut("Control+'", () => {
|
|
|
+ if (this.app.state.showGrid) {
|
|
|
+ this.app.grid.hideGrid();
|
|
|
+ } else {
|
|
|
+ this.app.grid.showGrid();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ copyCurrentElement() {
|
|
|
+ if (this.app.elements.activeElement) {
|
|
|
+ this.beingCopySelectedElements = [];
|
|
|
+ this.beingCopyElement = this.app.elements.activeElement;
|
|
|
+ } else if (this.app.selection.hasSelectionElements()) {
|
|
|
+ this.beingCopyElement = null;
|
|
|
+ this.beingCopySelectedElements = this.app.selection.getSelectionElements();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cutCurrentElement() {
|
|
|
+ if (this.app.elements.activeElement) {
|
|
|
+ this.copyCurrentElement();
|
|
|
+ this.deleteCurrentElements();
|
|
|
+ } else if (this.app.selection.hasSelectionElements()) {
|
|
|
+ this.copyCurrentElement();
|
|
|
+ this.deleteCurrentElements();
|
|
|
+ this.app.selection.setMultiSelectElements(this.beingCopySelectedElements);
|
|
|
+ this.app.selection.emitChange();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pasteCurrentElement(useCurrentEventPos = false) {
|
|
|
+ let pos = null;
|
|
|
+ if (useCurrentEventPos) {
|
|
|
+ let x = this.app.event.lastMousePos.x;
|
|
|
+ let y = this.app.event.lastMousePos.y;
|
|
|
+ pos = {
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ };
|
|
|
+ }
|
|
|
+ if (this.beingCopyElement) {
|
|
|
+ this.copyElement(this.beingCopyElement, false, pos);
|
|
|
+ } else if (this.beingCopySelectedElements.length > 0) {
|
|
|
+ this.app.selection.selectElements(this.beingCopySelectedElements);
|
|
|
+ this.app.selection.copySelectionElements(useCurrentEventPos ? pos : null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ deleteElement(element) {
|
|
|
+ this.app.elements.deleteElement(element);
|
|
|
+ this.render();
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ copyElement(element, notActive = false, pos) {
|
|
|
+ return __async(this, null, function* () {
|
|
|
+ this.app.elements.cancelActiveElement();
|
|
|
+ yield this.app.elements.copyElement(element, notActive, pos);
|
|
|
+ this.app.group.clearCopyMap();
|
|
|
+ this.render();
|
|
|
+ this.app.emitChange();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ deleteActiveElement() {
|
|
|
+ if (!this.app.elements.hasActiveElement()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.deleteElement(this.app.elements.activeElement);
|
|
|
+ }
|
|
|
+ deleteCurrentElements() {
|
|
|
+ this.deleteActiveElement();
|
|
|
+ this.app.selection.deleteSelectedElements();
|
|
|
+ }
|
|
|
+ moveUpCurrentElement() {
|
|
|
+ this.moveLevelCurrentElement("up");
|
|
|
+ }
|
|
|
+ moveDownCurrentElement() {
|
|
|
+ this.moveLevelCurrentElement("down");
|
|
|
+ }
|
|
|
+ moveTopCurrentElement() {
|
|
|
+ this.moveLevelCurrentElement("top");
|
|
|
+ }
|
|
|
+ moveBottomCurrentElement() {
|
|
|
+ this.moveLevelCurrentElement("bottom");
|
|
|
+ }
|
|
|
+ moveLevelCurrentElement(level) {
|
|
|
+ let element = null;
|
|
|
+ if (this.app.elements.hasActiveElement()) {
|
|
|
+ element = this.app.elements.activeElement;
|
|
|
+ } else if (this.app.selection.getSelectionElements().length === 1) {
|
|
|
+ element = this.app.selection.getSelectionElements()[0];
|
|
|
+ }
|
|
|
+ if (!element) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let index = this.app.elements.getElementIndex(element);
|
|
|
+ this.app.elements.elementList.splice(index, 1);
|
|
|
+ if (level === "up") {
|
|
|
+ this.app.elements.insertElement(element, index + 1);
|
|
|
+ } else if (level === "down") {
|
|
|
+ this.app.elements.insertElement(element, index - 1);
|
|
|
+ } else if (level === "top") {
|
|
|
+ this.app.elements.addElement(element);
|
|
|
+ } else if (level === "bottom") {
|
|
|
+ this.app.elements.unshiftElement(element);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setActiveElementStyle(style = {}) {
|
|
|
+ if (!this.app.elements.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.app.elements.setActiveElementStyle(style);
|
|
|
+ this.render();
|
|
|
+ if (!this.app.elements.isCreatingElement) {
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ setCurrentElementsStyle(style = {}) {
|
|
|
+ this.setActiveElementStyle(style);
|
|
|
+ this.app.selection.setSelectedElementStyle(style);
|
|
|
+ }
|
|
|
+ cancelActiveElement() {
|
|
|
+ if (!this.app.elements.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.app.elements.cancelActiveElement();
|
|
|
+ this.render();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateActiveElementPosition(x, y) {
|
|
|
+ if (!this.app.elements.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.app.elements.activeElement.updatePos(x, y);
|
|
|
+ this.render();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateActiveElementSize(width, height) {
|
|
|
+ if (!this.app.elements.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.app.elements.activeElement.updateSize(width, height);
|
|
|
+ this.render();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ updateActiveElementRotate(rotate) {
|
|
|
+ if (!this.app.elements.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.app.elements.activeElement.updateRotate(rotate);
|
|
|
+ this.render();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ empty() {
|
|
|
+ this.app.elements.deleteAllElements();
|
|
|
+ this.render();
|
|
|
+ this.app.history.clear();
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ zoomIn(num = 0.1) {
|
|
|
+ this.app.updateState({
|
|
|
+ scale: this.app.state.scale + num
|
|
|
+ });
|
|
|
+ this.render();
|
|
|
+ this.app.emit("zoomChange", this.app.state.scale);
|
|
|
+ }
|
|
|
+ zoomOut(num = 0.1) {
|
|
|
+ this.app.updateState({
|
|
|
+ scale: this.app.state.scale - num > 0 ? this.app.state.scale - num : 0
|
|
|
+ });
|
|
|
+ this.render();
|
|
|
+ this.app.emit("zoomChange", this.app.state.scale);
|
|
|
+ }
|
|
|
+ setZoom(zoom) {
|
|
|
+ if (zoom < 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.app.updateState({
|
|
|
+ scale: zoom
|
|
|
+ });
|
|
|
+ this.render();
|
|
|
+ this.app.emit("zoomChange", this.app.state.scale);
|
|
|
+ }
|
|
|
+ fit() {
|
|
|
+ if (!this.app.elements.hasElements()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.scrollToCenter();
|
|
|
+ let { minx, maxx, miny, maxy } = getMultiElementRectInfo(
|
|
|
+ this.app.elements.elementList
|
|
|
+ );
|
|
|
+ let width = maxx - minx;
|
|
|
+ let height = maxy - miny;
|
|
|
+ let maxScale = Math.min(this.app.width / width, this.app.height / height);
|
|
|
+ console.log(maxScale);
|
|
|
+ this.setZoom(maxScale);
|
|
|
+ }
|
|
|
+ scrollTo(scrollX, scrollY) {
|
|
|
+ this.app.updateState({
|
|
|
+ scrollX,
|
|
|
+ scrollY
|
|
|
+ });
|
|
|
+ this.render();
|
|
|
+ this.app.emit(
|
|
|
+ "scrollChange",
|
|
|
+ this.app.state.scrollX,
|
|
|
+ this.app.state.scrollY
|
|
|
+ );
|
|
|
+ }
|
|
|
+ scrollToCenter() {
|
|
|
+ if (!this.app.elements.hasElements()) {
|
|
|
+ this.scrollTo(0, 0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let { minx, maxx, miny, maxy } = getMultiElementRectInfo(
|
|
|
+ this.app.elements.elementList
|
|
|
+ );
|
|
|
+ let width = maxx - minx;
|
|
|
+ let height = maxy - miny;
|
|
|
+ this.scrollTo(
|
|
|
+ minx - (this.app.width - width) / 2,
|
|
|
+ miny - (this.app.height - height) / 2
|
|
|
+ );
|
|
|
+ }
|
|
|
+ copyPasteCurrentElements() {
|
|
|
+ this.copyCurrentElement();
|
|
|
+ this.pasteCurrentElement();
|
|
|
+ }
|
|
|
+ setBackgroundColor(color) {
|
|
|
+ this.app.updateState({
|
|
|
+ backgroundColor: color
|
|
|
+ });
|
|
|
+ this.app.background.set();
|
|
|
+ }
|
|
|
+ selectAll() {
|
|
|
+ this.app.selection.selectElements(this.app.elements.elementList);
|
|
|
+ }
|
|
|
+}
|
|
|
+class Elements {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.elementList = [];
|
|
|
+ this.activeElement = null;
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ this.isResizing = false;
|
|
|
+ this.resizingElement = null;
|
|
|
+ this.handleResize = throttle(this.handleResize, this, 16);
|
|
|
+ }
|
|
|
+ serialize(stringify = false) {
|
|
|
+ let data = this.elementList.map((element) => {
|
|
|
+ return element.serialize();
|
|
|
+ });
|
|
|
+ return stringify ? JSON.stringify(data) : data;
|
|
|
+ }
|
|
|
+ getElementsNum() {
|
|
|
+ return this.elementList.length;
|
|
|
+ }
|
|
|
+ hasElements() {
|
|
|
+ return this.elementList.length > 0;
|
|
|
+ }
|
|
|
+ addElement(element) {
|
|
|
+ this.elementList.push(element);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ unshiftElement(element) {
|
|
|
+ this.elementList.unshift(element);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ insertElement(element, index) {
|
|
|
+ this.elementList.splice(index, 0, element);
|
|
|
+ }
|
|
|
+ deleteElement(element) {
|
|
|
+ let index = this.getElementIndex(element);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.elementList.splice(index, 1);
|
|
|
+ if (element.isActive) {
|
|
|
+ this.cancelActiveElement(element);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ deleteAllElements() {
|
|
|
+ this.activeElement = null;
|
|
|
+ this.elementList = [];
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ this.isResizing = false;
|
|
|
+ this.resizingElement = null;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ getElementIndex(element) {
|
|
|
+ return this.elementList.findIndex((item) => {
|
|
|
+ return item === element;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ createElementsFromData(data) {
|
|
|
+ data.forEach((item) => {
|
|
|
+ let element = this.pureCreateElement(item);
|
|
|
+ element.isActive = false;
|
|
|
+ element.isCreating = false;
|
|
|
+ this.addElement(element);
|
|
|
+ });
|
|
|
+ this.app.group.initIdToElementList(this.elementList);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ hasActiveElement() {
|
|
|
+ return !!this.activeElement;
|
|
|
+ }
|
|
|
+ setActiveElement(element) {
|
|
|
+ this.cancelActiveElement();
|
|
|
+ this.activeElement = element;
|
|
|
+ if (element) {
|
|
|
+ element.isActive = true;
|
|
|
+ }
|
|
|
+ this.app.emit("activeElementChange", this.activeElement);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ cancelActiveElement() {
|
|
|
+ if (!this.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.activeElement.isActive = false;
|
|
|
+ this.activeElement = null;
|
|
|
+ this.app.emit("activeElementChange", this.activeElement);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ checkIsHitElement(e) {
|
|
|
+ let x = e.unGridClientX;
|
|
|
+ let y = e.unGridClientY;
|
|
|
+ for (let i = this.elementList.length - 1; i >= 0; i--) {
|
|
|
+ let element = this.elementList[i];
|
|
|
+ if (element.isHit(x, y)) {
|
|
|
+ return element;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ pureCreateElement(opts = {}) {
|
|
|
+ switch (opts.type) {
|
|
|
+ case "rectangle":
|
|
|
+ return new Rectangle(opts, this.app);
|
|
|
+ case "diamond":
|
|
|
+ return new Diamond(opts, this.app);
|
|
|
+ case "triangle":
|
|
|
+ return new Triangle(opts, this.app);
|
|
|
+ case "circle":
|
|
|
+ return new Circle(opts, this.app);
|
|
|
+ case "freedraw":
|
|
|
+ return new Freedraw(opts, this.app);
|
|
|
+ case "image":
|
|
|
+ return new Image$1(opts, this.app);
|
|
|
+ case "arrow":
|
|
|
+ return new Arrow(opts, this.app);
|
|
|
+ case "line":
|
|
|
+ return new Line(opts, this.app);
|
|
|
+ case "text":
|
|
|
+ return new Text(opts, this.app);
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ createElement(opts = {}, callback = () => {
|
|
|
+ }, ctx = null, notActive) {
|
|
|
+ if (this.hasActiveElement() || this.isCreatingElement) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ let element = this.pureCreateElement(opts);
|
|
|
+ if (!element) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.addElement(element);
|
|
|
+ if (!notActive) {
|
|
|
+ this.setActiveElement(element);
|
|
|
+ }
|
|
|
+ this.isCreatingElement = true;
|
|
|
+ callback.call(ctx, element);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ copyElement(element, notActive = false, pos) {
|
|
|
+ return new Promise((resolve) => __async(this, null, function* () {
|
|
|
+ if (!element) {
|
|
|
+ return resolve();
|
|
|
+ }
|
|
|
+ let data = this.app.group.handleCopyElementData(element.serialize());
|
|
|
+ if (data.type === "image") {
|
|
|
+ data.imageObj = yield createImageObj(data.url);
|
|
|
+ }
|
|
|
+ if (data.type === "text") {
|
|
|
+ if (typeof data.text === "string") {
|
|
|
+ if (isNumeric(data.text)) {
|
|
|
+ const num = parseFloat(data.text);
|
|
|
+ const incrementedNum = num + 1;
|
|
|
+ data.text = incrementedNum.toString(10).padStart(data.text.length, "0");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.createElement(
|
|
|
+ data,
|
|
|
+ (element2) => {
|
|
|
+ this.app.group.handleCopyElement(element2);
|
|
|
+ element2.startResize(DRAG_ELEMENT_PARTS.BODY);
|
|
|
+ let ox = 20;
|
|
|
+ let oy = 20;
|
|
|
+ if (pos) {
|
|
|
+ ox = pos.x - element2.x - element2.width / 2;
|
|
|
+ oy = pos.y - element2.y - element2.height / 2;
|
|
|
+ }
|
|
|
+ let gridAdsorbentPos = this.app.coordinate.gridAdsorbent(ox, oy);
|
|
|
+ element2.resize(
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ gridAdsorbentPos.x,
|
|
|
+ gridAdsorbentPos.y
|
|
|
+ );
|
|
|
+ element2.isCreating = false;
|
|
|
+ if (notActive) {
|
|
|
+ element2.isActive = false;
|
|
|
+ }
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ resolve(element2);
|
|
|
+ },
|
|
|
+ this,
|
|
|
+ notActive
|
|
|
+ );
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ creatingRectangleLikeElement(type, x, y, offsetX, offsetY) {
|
|
|
+ this.createElement({
|
|
|
+ type,
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ width: offsetX,
|
|
|
+ height: offsetY
|
|
|
+ });
|
|
|
+ this.activeElement.updateSize(offsetX, offsetY);
|
|
|
+ }
|
|
|
+ creatingCircle(x, y, e) {
|
|
|
+ this.createElement({
|
|
|
+ type: "circle",
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ });
|
|
|
+ let radius = getTowPointDistance(e.clientX, e.clientY, x, y);
|
|
|
+ this.activeElement.updateSize(radius, radius);
|
|
|
+ }
|
|
|
+ creatingFreedraw(e, event) {
|
|
|
+ this.createElement({
|
|
|
+ type: "freedraw"
|
|
|
+ });
|
|
|
+ let element = this.activeElement;
|
|
|
+ let lineWidth = computedLineWidthBySpeed(
|
|
|
+ event.mouseSpeed,
|
|
|
+ element.lastLineWidth
|
|
|
+ );
|
|
|
+ element.lastLineWidth = lineWidth;
|
|
|
+ element.addPoint(e.clientX, e.clientY, lineWidth);
|
|
|
+ let { coordinate, ctx, state } = this.app;
|
|
|
+ let tfp = coordinate.transformToCanvasCoordinate(
|
|
|
+ coordinate.subScrollX(event.lastMousePos.x),
|
|
|
+ coordinate.subScrollY(event.lastMousePos.y)
|
|
|
+ );
|
|
|
+ let ttp = coordinate.transformToCanvasCoordinate(
|
|
|
+ coordinate.subScrollX(e.clientX),
|
|
|
+ coordinate.subScrollY(e.clientY)
|
|
|
+ );
|
|
|
+ ctx.save();
|
|
|
+ ctx.scale(state.scale, state.scale);
|
|
|
+ element.singleRender(tfp.x, tfp.y, ttp.x, ttp.y, lineWidth);
|
|
|
+ ctx.restore();
|
|
|
+ }
|
|
|
+ creatingImage(e, { width, height, imageObj, url, ratio }) {
|
|
|
+ let gp = this.app.coordinate.gridAdsorbent(
|
|
|
+ e.unGridClientX - width / 2,
|
|
|
+ e.unGridClientY - height / 2
|
|
|
+ );
|
|
|
+ this.createElement({
|
|
|
+ type: "image",
|
|
|
+ x: gp.x,
|
|
|
+ y: gp.y,
|
|
|
+ url,
|
|
|
+ imageObj,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ ratio
|
|
|
+ });
|
|
|
+ }
|
|
|
+ editingText(element) {
|
|
|
+ if (element.type !== "text") {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ element.noRender = true;
|
|
|
+ this.setActiveElement(element);
|
|
|
+ }
|
|
|
+ completeEditingText() {
|
|
|
+ let element = this.activeElement;
|
|
|
+ if (!element || element.type !== "text") {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!element.text.trim()) {
|
|
|
+ this.deleteElement(element);
|
|
|
+ this.setActiveElement(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ element.noRender = false;
|
|
|
+ }
|
|
|
+ completeCreateArrow(e) {
|
|
|
+ this.activeElement.addPoint(e.clientX, e.clientY);
|
|
|
+ }
|
|
|
+ creatingArrow(x, y, e) {
|
|
|
+ this.createElement(
|
|
|
+ {
|
|
|
+ type: "arrow",
|
|
|
+ x,
|
|
|
+ y
|
|
|
+ },
|
|
|
+ (element) => {
|
|
|
+ element.addPoint(x, y);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ this.activeElement.updateFictitiousPoint(e.clientX, e.clientY);
|
|
|
+ }
|
|
|
+ creatingLine(x, y, e, isSingle = false, notCreate = false) {
|
|
|
+ if (!notCreate) {
|
|
|
+ this.createElement(
|
|
|
+ {
|
|
|
+ type: "line",
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ isSingle
|
|
|
+ },
|
|
|
+ (element2) => {
|
|
|
+ element2.addPoint(x, y);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ let element = this.activeElement;
|
|
|
+ if (element) {
|
|
|
+ element.updateFictitiousPoint(e.clientX, e.clientY);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ completeCreateLine(e, completeCallback = () => {
|
|
|
+ }) {
|
|
|
+ let element = this.activeElement;
|
|
|
+ let x = e.clientX;
|
|
|
+ let y = e.clientY;
|
|
|
+ if (element && element.isSingle) {
|
|
|
+ element.addPoint(x, y);
|
|
|
+ completeCallback();
|
|
|
+ } else {
|
|
|
+ this.createElement({
|
|
|
+ type: "line",
|
|
|
+ isSingle: false
|
|
|
+ });
|
|
|
+ element = this.activeElement;
|
|
|
+ element.addPoint(x, y);
|
|
|
+ element.updateFictitiousPoint(x, y);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ completeCreateElement() {
|
|
|
+ this.isCreatingElement = false;
|
|
|
+ let element = this.activeElement;
|
|
|
+ if (!element) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ if (["freedraw", "arrow", "line"].includes(element.type)) {
|
|
|
+ element.updateMultiPointBoundingRect();
|
|
|
+ }
|
|
|
+ element.isCreating = false;
|
|
|
+ this.app.emitChange();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ setActiveElementStyle(style = {}) {
|
|
|
+ if (!this.hasActiveElement()) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ Object.keys(style).forEach((key) => {
|
|
|
+ this.activeElement.style[key] = style[key];
|
|
|
+ if (key === "fontSize" && this.activeElement.type === "text") {
|
|
|
+ this.activeElement.updateTextSize();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ checkInResizeHand(x, y) {
|
|
|
+ let element = this.activeElement;
|
|
|
+ let hand = element.dragElement.checkPointInDragElementWhere(x, y);
|
|
|
+ if (hand) {
|
|
|
+ return {
|
|
|
+ element,
|
|
|
+ hand
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ checkIsResize(x, y, e) {
|
|
|
+ if (!this.hasActiveElement()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ let res = this.checkInResizeHand(x, y);
|
|
|
+ if (res) {
|
|
|
+ this.isResizing = true;
|
|
|
+ this.resizingElement = res.element;
|
|
|
+ this.resizingElement.startResize(res.hand, e);
|
|
|
+ this.app.cursor.setResize(res.hand);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ handleResize(...args) {
|
|
|
+ if (!this.isResizing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.resizingElement.resize(...args);
|
|
|
+ this.app.render.render();
|
|
|
+ }
|
|
|
+ endResize() {
|
|
|
+ this.isResizing = false;
|
|
|
+ this.resizingElement.endResize();
|
|
|
+ this.resizingElement = null;
|
|
|
+ }
|
|
|
+}
|
|
|
+function isNumeric(str) {
|
|
|
+ const trimmedStr = str.trim();
|
|
|
+ const num = Number(trimmedStr);
|
|
|
+ return !isNaN(num) && trimmedStr === num.toString().padStart(trimmedStr.length, "0");
|
|
|
+}
|
|
|
+let getRandomValues;
|
|
|
+const rnds8 = new Uint8Array(16);
|
|
|
+function rng() {
|
|
|
+ if (!getRandomValues) {
|
|
|
+ getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
|
|
|
+ if (!getRandomValues) {
|
|
|
+ throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return getRandomValues(rnds8);
|
|
|
+}
|
|
|
+const byteToHex = [];
|
|
|
+for (let i = 0; i < 256; ++i) {
|
|
|
+ byteToHex.push((i + 256).toString(16).slice(1));
|
|
|
+}
|
|
|
+function unsafeStringify(arr, offset = 0) {
|
|
|
+ return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
|
|
+}
|
|
|
+const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
|
+var native = {
|
|
|
+ randomUUID
|
|
|
+};
|
|
|
+function v4(options, buf, offset) {
|
|
|
+ if (native.randomUUID && !buf && !options) {
|
|
|
+ return native.randomUUID();
|
|
|
+ }
|
|
|
+ options = options || {};
|
|
|
+ const rnds = options.random || (options.rng || rng)();
|
|
|
+ rnds[6] = rnds[6] & 15 | 64;
|
|
|
+ rnds[8] = rnds[8] & 63 | 128;
|
|
|
+ if (buf) {
|
|
|
+ offset = offset || 0;
|
|
|
+ for (let i = 0; i < 16; ++i) {
|
|
|
+ buf[offset + i] = rnds[i];
|
|
|
+ }
|
|
|
+ return buf;
|
|
|
+ }
|
|
|
+ return unsafeStringify(rnds);
|
|
|
+}
|
|
|
+class Group {
|
|
|
+ constructor(app) {
|
|
|
+ this.app = app;
|
|
|
+ this.groupIdToElementList = {};
|
|
|
+ this.newGroupIdMap = {};
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ Object.keys(this.groupIdToElementList).forEach((groupId) => {
|
|
|
+ let group = this.groupIdToElementList[groupId];
|
|
|
+ let selected = group[0].isSelected;
|
|
|
+ if (selected) {
|
|
|
+ let mElement = new MultiSelectElement(
|
|
|
+ {
|
|
|
+ type: "multiSelectElement"
|
|
|
+ },
|
|
|
+ this.app
|
|
|
+ );
|
|
|
+ mElement.setSelectedElementList(group);
|
|
|
+ mElement.updateRect();
|
|
|
+ mElement.dragElement.onlyShowBody();
|
|
|
+ mElement.render();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setToMap(element) {
|
|
|
+ let groupId = element.getGroupId();
|
|
|
+ if (groupId) {
|
|
|
+ if (!this.groupIdToElementList[groupId]) {
|
|
|
+ this.groupIdToElementList[groupId] = [];
|
|
|
+ }
|
|
|
+ this.groupIdToElementList[groupId].push(element);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ initIdToElementList(elementList) {
|
|
|
+ this.groupIdToElementList = {};
|
|
|
+ elementList.forEach((element) => {
|
|
|
+ this.setToMap(element);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ handleCopyElementData(data) {
|
|
|
+ if (data.groupId) {
|
|
|
+ if (this.newGroupIdMap[data.groupId]) {
|
|
|
+ data.groupId = this.newGroupIdMap[data.groupId];
|
|
|
+ } else {
|
|
|
+ data.groupId = this.newGroupIdMap[data.groupId] = v4();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+ clearCopyMap() {
|
|
|
+ this.newGroupIdMap = {};
|
|
|
+ }
|
|
|
+ handleCopyElement(element) {
|
|
|
+ this.setToMap(element);
|
|
|
+ }
|
|
|
+ dogroup() {
|
|
|
+ if (!this.app.selection.hasSelection || this.app.selection.multiSelectElement.selectedElementList.length <= 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let groupElement = this.app.selection.multiSelectElement.selectedElementList;
|
|
|
+ let groupId = v4();
|
|
|
+ this.groupIdToElementList[groupId] = groupElement;
|
|
|
+ groupElement.forEach((element) => {
|
|
|
+ element.setGroupId(groupId);
|
|
|
+ });
|
|
|
+ this.app.render.render();
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ ungroup() {
|
|
|
+ if (!this.app.selection.hasSelection || this.app.selection.multiSelectElement.selectedElementList.length <= 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let groupElement = this.app.selection.multiSelectElement.selectedElementList;
|
|
|
+ let groupId = groupElement[0].getGroupId();
|
|
|
+ this.groupIdToElementList[groupId] = [];
|
|
|
+ delete this.groupIdToElementList[groupId];
|
|
|
+ groupElement.forEach((element) => {
|
|
|
+ element.removeGroupId(groupId);
|
|
|
+ });
|
|
|
+ this.app.render.render();
|
|
|
+ this.app.emitChange();
|
|
|
+ }
|
|
|
+ setSelection(element) {
|
|
|
+ let groupId = element.getGroupId();
|
|
|
+ if (this.groupIdToElementList[groupId]) {
|
|
|
+ this.app.selection.selectElements(this.groupIdToElementList[groupId]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ getGroupElements(element) {
|
|
|
+ let groupId = element.getGroupId();
|
|
|
+ return this.groupIdToElementList[groupId] || [];
|
|
|
+ }
|
|
|
+}
|
|
|
+class TinyWhiteboard extends EventEmitter {
|
|
|
+ constructor(opts = {}) {
|
|
|
+ super();
|
|
|
+ this.opts = opts;
|
|
|
+ this.container = opts.container;
|
|
|
+ this.drawType = opts.drawType || "selection";
|
|
|
+ if (!this.container) {
|
|
|
+ throw new Error("\u7F3A\u5C11 container \u53C2\u6570\uFF01");
|
|
|
+ }
|
|
|
+ if (!["absolute", "fixed", "relative"].includes(
|
|
|
+ window.getComputedStyle(this.container).position
|
|
|
+ )) {
|
|
|
+ throw new Error("container\u5143\u7D20\u9700\u8981\u8BBE\u7F6E\u5B9A\u4F4D\uFF01");
|
|
|
+ }
|
|
|
+ this.width = 0;
|
|
|
+ this.height = 0;
|
|
|
+ this.left = 0;
|
|
|
+ this.top = 0;
|
|
|
+ this.canvas = null;
|
|
|
+ this.ctx = null;
|
|
|
+ this.state = __spreadValues({
|
|
|
+ scale: 1,
|
|
|
+ scrollX: 0,
|
|
|
+ scrollY: 0,
|
|
|
+ scrollStep: 50,
|
|
|
+ backgroundColor: "",
|
|
|
+ strokeStyle: "#000000",
|
|
|
+ fillStyle: "transparent",
|
|
|
+ fontFamily: "\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",
|
|
|
+ fontSize: 18,
|
|
|
+ dragStrokeStyle: "#666",
|
|
|
+ showGrid: false,
|
|
|
+ readonly: false,
|
|
|
+ gridConfig: {
|
|
|
+ size: 20,
|
|
|
+ strokeStyle: "#dfe0e1",
|
|
|
+ lineWidth: 1
|
|
|
+ }
|
|
|
+ }, opts.state || {});
|
|
|
+ this.initCanvas();
|
|
|
+ this.coordinate = new Coordinate(this);
|
|
|
+ this.event = new Event(this);
|
|
|
+ this.event.on("mousedown", this.onMousedown, this);
|
|
|
+ this.event.on("mousemove", this.onMousemove, this);
|
|
|
+ this.event.on("mouseup", this.onMouseup, this);
|
|
|
+ this.event.on("dblclick", this.onDblclick, this);
|
|
|
+ this.event.on("mousewheel", this.onMousewheel, this);
|
|
|
+ this.event.on("contextmenu", this.onContextmenu, this);
|
|
|
+ this.keyCommand = new KeyCommand(this);
|
|
|
+ this.imageEdit = new ImageEdit(this);
|
|
|
+ this.imageEdit.on("imageSelectChange", this.onImageSelectChange, this);
|
|
|
+ this.textEdit = new TextEdit(this);
|
|
|
+ this.textEdit.on("blur", this.onTextInputBlur, this);
|
|
|
+ this.cursor = new Cursor(this);
|
|
|
+ this.history = new History(this);
|
|
|
+ this.export = new Export(this);
|
|
|
+ this.background = new Background(this);
|
|
|
+ this.selection = new Selection(this);
|
|
|
+ this.group = new Group(this);
|
|
|
+ this.grid = new Grid(this);
|
|
|
+ this.mode = new Mode(this);
|
|
|
+ this.elements = new Elements$1(this);
|
|
|
+ this.render = new Render(this);
|
|
|
+ this.proxy();
|
|
|
+ this.checkIsOnElement = throttle(this.checkIsOnElement, this);
|
|
|
+ this.emitChange();
|
|
|
+ this.helpUpdate();
|
|
|
+ }
|
|
|
+ proxy() {
|
|
|
+ ["undo", "redo"].forEach((method) => {
|
|
|
+ this[method] = this.history[method].bind(this.history);
|
|
|
+ });
|
|
|
+ [].forEach((method) => {
|
|
|
+ this[method] = this.elements[method].bind(this.elements);
|
|
|
+ });
|
|
|
+ [
|
|
|
+ "deleteElement",
|
|
|
+ "setActiveElementStyle",
|
|
|
+ "setCurrentElementsStyle",
|
|
|
+ "cancelActiveElement",
|
|
|
+ "deleteActiveElement",
|
|
|
+ "deleteCurrentElements",
|
|
|
+ "empty",
|
|
|
+ "zoomIn",
|
|
|
+ "zoomOut",
|
|
|
+ "setZoom",
|
|
|
+ "scrollTo",
|
|
|
+ "scrollToCenter",
|
|
|
+ "copyPasteCurrentElements",
|
|
|
+ "setBackgroundColor",
|
|
|
+ "copyElement",
|
|
|
+ "copyCurrentElement",
|
|
|
+ "cutCurrentElement",
|
|
|
+ "pasteCurrentElement",
|
|
|
+ "updateActiveElementRotate",
|
|
|
+ "updateActiveElementSize",
|
|
|
+ "updateActiveElementPosition",
|
|
|
+ "moveBottomCurrentElement",
|
|
|
+ "moveTopCurrentElement",
|
|
|
+ "moveUpCurrentElement",
|
|
|
+ "moveDownCurrentElement",
|
|
|
+ "selectAll",
|
|
|
+ "fit"
|
|
|
+ ].forEach((method) => {
|
|
|
+ this[method] = this.render[method].bind(this.render);
|
|
|
+ });
|
|
|
+ ["exportImage", "exportJson"].forEach((method) => {
|
|
|
+ this[method] = this.export[method].bind(this.export);
|
|
|
+ });
|
|
|
+ ["setSelectedElementStyle"].forEach((method) => {
|
|
|
+ this[method] = this.selection[method].bind(this.selection);
|
|
|
+ });
|
|
|
+ ["dogroup", "ungroup"].forEach((method) => {
|
|
|
+ this[method] = this.group[method].bind(this.group);
|
|
|
+ });
|
|
|
+ ["showGrid", "hideGrid", "updateGrid"].forEach((method) => {
|
|
|
+ this[method] = this.grid[method].bind(this.grid);
|
|
|
+ });
|
|
|
+ ["setEditMode", "setReadonlyMode"].forEach((method) => {
|
|
|
+ this[method] = this.mode[method].bind(this.mode);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ getContainerRectInfo() {
|
|
|
+ let { width, height, left, top } = this.container.getBoundingClientRect();
|
|
|
+ this.width = width;
|
|
|
+ this.height = height;
|
|
|
+ this.left = left;
|
|
|
+ this.top = top;
|
|
|
+ }
|
|
|
+ helpUpdate() {
|
|
|
+ this.background.set();
|
|
|
+ if (this.state.showGrid) {
|
|
|
+ this.grid.showGrid();
|
|
|
+ }
|
|
|
+ if (this.state.readonly) {
|
|
|
+ this.setReadonlyMode();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setData(_0, _1) {
|
|
|
+ return __async(this, arguments, function* ({ state = {}, elements = [] }, noEmitChange) {
|
|
|
+ this.state = state;
|
|
|
+ for (let i = 0; i < elements.length; i++) {
|
|
|
+ if (elements[i].type === "image") {
|
|
|
+ elements[i].imageObj = yield createImageObj(elements[i].url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.helpUpdate();
|
|
|
+ this.elements.deleteAllElements().createElementsFromData(elements);
|
|
|
+ this.render.render();
|
|
|
+ if (!noEmitChange) {
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ initCanvas() {
|
|
|
+ this.getContainerRectInfo();
|
|
|
+ if (this.canvas) {
|
|
|
+ this.container.removeChild(this.canvas);
|
|
|
+ }
|
|
|
+ let { canvas, ctx } = createCanvas(this.width, this.height, {
|
|
|
+ className: "main"
|
|
|
+ });
|
|
|
+ this.canvas = canvas;
|
|
|
+ this.ctx = ctx;
|
|
|
+ this.container.appendChild(this.canvas);
|
|
|
+ }
|
|
|
+ resize() {
|
|
|
+ this.initCanvas();
|
|
|
+ this.render.render();
|
|
|
+ this.selection.init();
|
|
|
+ this.grid.init();
|
|
|
+ this.grid.renderGrid();
|
|
|
+ }
|
|
|
+ updateState(data = {}) {
|
|
|
+ this.state = __spreadValues(__spreadValues({}, this.state), data);
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ updateCurrentType(drawType) {
|
|
|
+ this.drawType = drawType;
|
|
|
+ if (this.drawType === "image") {
|
|
|
+ this.imageEdit.selectImage();
|
|
|
+ }
|
|
|
+ if (this.drawType === "eraser") {
|
|
|
+ this.cursor.setEraser();
|
|
|
+ this.cancelActiveElement();
|
|
|
+ } else if (this.drawType !== "selection") {
|
|
|
+ this.cursor.setCrosshair();
|
|
|
+ } else {
|
|
|
+ this.cursor.reset();
|
|
|
+ }
|
|
|
+ this.emit("currentTypeChange", this.drawType);
|
|
|
+ }
|
|
|
+ getData() {
|
|
|
+ return {
|
|
|
+ state: __spreadValues({}, this.state),
|
|
|
+ elements: this.elements.serialize()
|
|
|
+ };
|
|
|
+ }
|
|
|
+ onImageSelectChange() {
|
|
|
+ this.cursor.hide();
|
|
|
+ }
|
|
|
+ onMousedown(e, event) {
|
|
|
+ if (this.state.readonly || this.mode.isDragMode) {
|
|
|
+ this.mode.onStart();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!this.elements.isCreatingElement && !this.textEdit.isEditing) {
|
|
|
+ let hitElement = this.elements.checkIsHitElement(e);
|
|
|
+ if (this.drawType === "selection") {
|
|
|
+ if (this.elements.hasActiveElement()) {
|
|
|
+ let isResizing = this.elements.checkIsResize(
|
|
|
+ event.mousedownPos.unGridClientX,
|
|
|
+ event.mousedownPos.unGridClientY,
|
|
|
+ e
|
|
|
+ );
|
|
|
+ if (!isResizing) {
|
|
|
+ this.elements.setActiveElement(hitElement);
|
|
|
+ this.render.render();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (this.selection.hasSelection) {
|
|
|
+ let isResizing = this.selection.checkIsResize(
|
|
|
+ event.mousedownPos.unGridClientX,
|
|
|
+ event.mousedownPos.unGridClientY,
|
|
|
+ e
|
|
|
+ );
|
|
|
+ if (!isResizing) {
|
|
|
+ this.selection.reset();
|
|
|
+ this.elements.setActiveElement(hitElement);
|
|
|
+ this.render.render();
|
|
|
+ }
|
|
|
+ } else if (hitElement) {
|
|
|
+ if (hitElement.hasGroup()) {
|
|
|
+ this.group.setSelection(hitElement);
|
|
|
+ this.onMousedown(e, event);
|
|
|
+ } else {
|
|
|
+ this.elements.setActiveElement(hitElement);
|
|
|
+ this.render.render();
|
|
|
+ this.onMousedown(e, event);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.selection.onMousedown(e, event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (this.drawType === "eraser") {
|
|
|
+ this.deleteElement(hitElement);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onMousemove(e, event) {
|
|
|
+ if (this.state.readonly || this.mode.isDragMode) {
|
|
|
+ if (event.isMousedown) {
|
|
|
+ this.mode.onMove(e, event);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (event.isMousedown) {
|
|
|
+ let mx = event.mousedownPos.x;
|
|
|
+ let my = event.mousedownPos.y;
|
|
|
+ let offsetX = Math.max(event.mouseOffset.x, 0);
|
|
|
+ let offsetY = Math.max(event.mouseOffset.y, 0);
|
|
|
+ if (this.drawType === "selection") {
|
|
|
+ if (this.selection.isResizing) {
|
|
|
+ this.selection.handleResize(
|
|
|
+ e,
|
|
|
+ mx,
|
|
|
+ my,
|
|
|
+ event.mouseOffset.x,
|
|
|
+ event.mouseOffset.y
|
|
|
+ );
|
|
|
+ } else if (this.selection.creatingSelection) {
|
|
|
+ this.selection.onMousemove(e, event);
|
|
|
+ } else {
|
|
|
+ this.elements.handleResize(
|
|
|
+ e,
|
|
|
+ mx,
|
|
|
+ my,
|
|
|
+ event.mouseOffset.x,
|
|
|
+ event.mouseOffset.y
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } else if (["rectangle", "diamond", "triangle"].includes(this.drawType)) {
|
|
|
+ this.elements.creatingRectangleLikeElement(
|
|
|
+ this.drawType,
|
|
|
+ mx,
|
|
|
+ my,
|
|
|
+ offsetX,
|
|
|
+ offsetY
|
|
|
+ );
|
|
|
+ this.render.render();
|
|
|
+ } else if (this.drawType === "circle") {
|
|
|
+ this.elements.creatingCircle(mx, my, e);
|
|
|
+ this.render.render();
|
|
|
+ } else if (this.drawType === "freedraw") {
|
|
|
+ this.elements.creatingFreedraw(e, event);
|
|
|
+ } else if (this.drawType === "arrow") {
|
|
|
+ this.elements.creatingArrow(mx, my, e);
|
|
|
+ this.render.render();
|
|
|
+ } else if (this.drawType === "line") {
|
|
|
+ if (getTowPointDistance(mx, my, e.clientX, e.clientY) > 3) {
|
|
|
+ this.elements.creatingLine(mx, my, e, true);
|
|
|
+ this.render.render();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (this.imageEdit.isReady) {
|
|
|
+ this.cursor.hide();
|
|
|
+ this.imageEdit.updatePreviewElPos(
|
|
|
+ e.originEvent.clientX,
|
|
|
+ e.originEvent.clientY
|
|
|
+ );
|
|
|
+ } else if (this.drawType === "selection") {
|
|
|
+ if (this.elements.hasActiveElement()) {
|
|
|
+ let handData = "";
|
|
|
+ if (handData = this.elements.checkInResizeHand(
|
|
|
+ e.unGridClientX,
|
|
|
+ e.unGridClientY
|
|
|
+ )) {
|
|
|
+ this.cursor.setResize(handData.hand);
|
|
|
+ } else {
|
|
|
+ this.checkIsOnElement(e);
|
|
|
+ }
|
|
|
+ } else if (this.selection.hasSelection) {
|
|
|
+ let hand = this.selection.checkInResizeHand(
|
|
|
+ e.unGridClientX,
|
|
|
+ e.unGridClientY
|
|
|
+ );
|
|
|
+ if (hand) {
|
|
|
+ this.cursor.setResize(hand);
|
|
|
+ } else {
|
|
|
+ this.checkIsOnElement(e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.checkIsOnElement(e);
|
|
|
+ }
|
|
|
+ } else if (this.drawType === "line") {
|
|
|
+ this.elements.creatingLine(null, null, e, false, true);
|
|
|
+ this.render.render();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ checkIsOnElement(e) {
|
|
|
+ let hitElement = this.elements.checkIsHitElement(e);
|
|
|
+ if (hitElement) {
|
|
|
+ this.cursor.setMove();
|
|
|
+ } else {
|
|
|
+ this.cursor.reset();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ resetCurrentType() {
|
|
|
+ if (this.drawType !== "selection") {
|
|
|
+ this.drawType = "selection";
|
|
|
+ this.emit("currentTypeChange", "selection");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ completeCreateNewElement() {
|
|
|
+ this.resetCurrentType();
|
|
|
+ this.elements.completeCreateElement();
|
|
|
+ this.render.render();
|
|
|
+ }
|
|
|
+ onMouseup(e) {
|
|
|
+ if (this.state.readonly || this.mode.isDragMode) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (this.drawType === "text") {
|
|
|
+ if (!this.textEdit.isEditing) {
|
|
|
+ this.createTextElement(e);
|
|
|
+ this.resetCurrentType();
|
|
|
+ }
|
|
|
+ } else if (this.imageEdit.isReady) {
|
|
|
+ this.elements.creatingImage(e, this.imageEdit.imageData);
|
|
|
+ this.completeCreateNewElement();
|
|
|
+ this.cursor.reset();
|
|
|
+ this.imageEdit.reset();
|
|
|
+ } else if (this.drawType === "arrow") {
|
|
|
+ this.elements.completeCreateArrow(e);
|
|
|
+ this.completeCreateNewElement();
|
|
|
+ } else if (this.drawType === "line") {
|
|
|
+ this.elements.completeCreateLine(e, () => {
|
|
|
+ this.completeCreateNewElement();
|
|
|
+ });
|
|
|
+ this.render.render();
|
|
|
+ } else if (this.elements.isCreatingElement) {
|
|
|
+ if (this.drawType === "freedraw") {
|
|
|
+ this.elements.completeCreateElement();
|
|
|
+ this.elements.setActiveElement();
|
|
|
+ } else {
|
|
|
+ this.completeCreateNewElement();
|
|
|
+ }
|
|
|
+ } else if (this.elements.isResizing) {
|
|
|
+ this.elements.endResize();
|
|
|
+ this.emitChange();
|
|
|
+ } else if (this.selection.creatingSelection) {
|
|
|
+ this.selection.onMouseup(e);
|
|
|
+ } else if (this.selection.isResizing) {
|
|
|
+ this.selection.endResize();
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onDblclick(e) {
|
|
|
+ if (this.drawType === "line") {
|
|
|
+ this.completeCreateNewElement();
|
|
|
+ } else {
|
|
|
+ let hitElement = this.elements.checkIsHitElement(e);
|
|
|
+ if (hitElement) {
|
|
|
+ if (hitElement.type === "text") {
|
|
|
+ this.elements.editingText(hitElement);
|
|
|
+ this.render.render();
|
|
|
+ this.keyCommand.unBindEvent();
|
|
|
+ this.textEdit.showTextEdit();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!this.textEdit.isEditing) {
|
|
|
+ this.createTextElement(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onTextInputBlur() {
|
|
|
+ this.keyCommand.bindEvent();
|
|
|
+ this.elements.completeEditingText();
|
|
|
+ this.render.render();
|
|
|
+ this.emitChange();
|
|
|
+ }
|
|
|
+ createTextElement(e) {
|
|
|
+ this.elements.createElement({
|
|
|
+ type: "text",
|
|
|
+ x: e.clientX,
|
|
|
+ y: e.clientY
|
|
|
+ });
|
|
|
+ this.keyCommand.unBindEvent();
|
|
|
+ this.textEdit.showTextEdit();
|
|
|
+ }
|
|
|
+ onMousewheel(dir) {
|
|
|
+ let stepNum = this.state.scrollStep / this.state.scale;
|
|
|
+ let step = dir === "down" ? stepNum : -stepNum;
|
|
|
+ this.scrollTo(this.state.scrollX, this.state.scrollY + step);
|
|
|
+ }
|
|
|
+ onContextmenu(e) {
|
|
|
+ let elements = [];
|
|
|
+ if (this.elements.hasActiveElement()) {
|
|
|
+ elements = [this.elements.activeElement];
|
|
|
+ } else if (this.selection.hasSelectionElements()) {
|
|
|
+ elements = this.selection.getSelectionElements();
|
|
|
+ }
|
|
|
+ this.emit("contextmenu", e.originEvent, elements);
|
|
|
+ }
|
|
|
+ emitChange() {
|
|
|
+ let data = this.getData();
|
|
|
+ this.history.add(data);
|
|
|
+ this.emit("change", data);
|
|
|
+ }
|
|
|
+}
|
|
|
+TinyWhiteboard.utils = utils;
|
|
|
+TinyWhiteboard.checkHit = checkHit;
|
|
|
+TinyWhiteboard.draw = draw;
|
|
|
+TinyWhiteboard.elements = Elements;
|
|
|
+export { TinyWhiteboard as default };
|