UNPKG

@inweb/markup

Version:
1,422 lines (1,407 loc) 66.4 kB
import Konva from "konva"; class WorldTransform { screenToWorld(position) { return { x: position.x, y: position.y, z: 0 }; } worldToScreen(position) { return { x: position.x, y: position.y }; } getScale() { return { x: 1, y: 1, z: 1 }; } } class MarkupColor { constructor(r, g, b) { this.setColor(r, g, b); } asHex() { return "#" + this.HEX; } asRGB() { return { r: this.R, g: this.G, b: this.B }; } setColor(r, g, b) { this.R = r; this.G = g; this.B = b; this.HEX = this.rgbToHex(r, g, b); } rgbToHex(r, g, b) { const valueToHex = c => { const hex = c.toString(16); return hex === "0" ? "00" : hex; }; return valueToHex(r) + valueToHex(g) + valueToHex(b); } } const LineTypeSpecs = new Map([ [ "solid", [] ], [ "dot", [ 30, 30, .001, 30 ] ], [ "dash", [ 30, 30 ] ] ]); class KonvaLine { constructor(params, ref = null) { var _a, _b; if (ref) { this._ref = ref; return; } if (!params) params = {}; if (!params.points) params.points = [ { x: 0, y: 0 }, { x: 100, y: 100 } ]; const konvaPoints = []; params.points.forEach((point => konvaPoints.push(point.x, point.y))); this._ref = new Konva.Line({ stroke: (_a = params.color) !== null && _a !== undefined ? _a : "#ff0000", strokeWidth: (_b = params.width) !== null && _b !== undefined ? _b : 4, globalCompositeOperation: "source-over", lineCap: "round", lineJoin: "round", points: konvaPoints, draggable: true, strokeScaleEnabled: false, dash: LineTypeSpecs.get(params.type) || [] }); this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); })); this._ref.id(this._ref._id.toString()); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Line"; } getColor() { return this._ref.stroke(); } setColor(hex) { this._ref.stroke(hex); } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } getPoints() { return this._ref.points(); } setLineWidth(size) { this._ref.strokeWidth(size); } getLineWidth() { return this._ref.strokeWidth(); } getLineType() { const typeSpecs = this._ref.dash() || []; let type; switch (typeSpecs) { case LineTypeSpecs.get("dot"): type = "dot"; break; case LineTypeSpecs.get("dash"): type = "dash"; break; default: type = "solid"; break; } return type; } setLineType(type) { const specs = LineTypeSpecs.get(type); if (specs) this._ref.dash(specs); } addPoints(points) { let newPoints = this._ref.points(); points.forEach((point => { newPoints = newPoints.concat([ point.x, point.y ]); })); this._ref.points(newPoints); } } class KonvaText { constructor(params, ref = null) { var _a, _b, _c; this.TEXT_FONT_FAMILY = "Calibri"; if (ref) { this._ref = ref; return; } if (!params) params = {}; if (!params.position) params.position = { x: 0, y: 0 }; if (!params.text) params.text = "default"; this._ref = new Konva.Text({ x: params.position.x, y: params.position.y, text: params.text, fontSize: (_a = params.fontSize) !== null && _a !== undefined ? _a : 34, fontFamily: this.TEXT_FONT_FAMILY, fill: (_b = params.color) !== null && _b !== undefined ? _b : "#ff0000", align: "left", draggable: true, rotation: (_c = params.rotation) !== null && _c !== undefined ? _c : 0 }); this._ref.width(this._ref.getTextWidth()); this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); const scaleByX = Math.abs(attrs.scaleX - 1) > 1e-5; const scaleByY = Math.abs(attrs.scaleY - 1) > 1e-5; let newWidth = this._ref.width(); if (scaleByX) newWidth *= attrs.scaleX; let newHeight = this._ref.height(); if (scaleByY) newHeight *= attrs.scaleY; const minWidth = 50; if (newWidth < minWidth) newWidth = minWidth; if (newHeight < Math.round(this.getFontSize())) newHeight = Math.round(this.getFontSize()); if (scaleByX) { this._ref.width(newWidth); } if (scaleByY) { this._ref.height(newHeight); } this._ref.scale({ x: 1, y: 1 }); })); this._ref.id(this._ref._id.toString()); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Text"; } getColor() { return this._ref.fill(); } setColor(hex) { this._ref.fill(hex); } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } getText() { return this._ref.text(); } setText(text) { this._ref.text(text); } getPosition() { return this._ref.getPosition(); } setPosition(x, y) { this._ref.setPosition({ x: x, y: y }); } getFontSize() { return this._ref.fontSize(); } setFontSize(size) { this._ref.fontSize(size); } } class KonvaRectangle { constructor(params, ref = null) { var _a, _b, _c, _d; if (ref) { this._ref = ref; return; } if (!params) params = {}; if (!params.position) params.position = { x: 0, y: 0 }; this._ref = new Konva.Rect({ stroke: (_a = params.color) !== null && _a !== undefined ? _a : "#ff0000", strokeWidth: (_b = params.lineWidth) !== null && _b !== undefined ? _b : 4, globalCompositeOperation: "source-over", lineCap: "round", lineJoin: "round", x: params.position.x, y: params.position.y, width: (_c = params.width) !== null && _c !== undefined ? _c : 200, height: (_d = params.height) !== null && _d !== undefined ? _d : 200, draggable: true, strokeScaleEnabled: false }); this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); const scaleByX = Math.abs(attrs.scaleX - 1) > 1e-5; const scaleByY = Math.abs(attrs.scaleY - 1) > 1e-5; let newWidth = this._ref.width(); if (scaleByX) newWidth *= attrs.scaleX; let newHeight = this._ref.height(); if (scaleByY) newHeight *= attrs.scaleY; const minWidth = 50; const minHeight = 50; if (newWidth < minWidth) newWidth = minWidth; if (newHeight < minHeight) newHeight = minHeight; if (scaleByX) { this._ref.width(newWidth); } if (scaleByY) { this._ref.height(newHeight); } this._ref.scale({ x: 1, y: 1 }); })); this._ref.id(this._ref._id.toString()); } getPosition() { return this._ref.position(); } getWidth() { return this._ref.width(); } getHeigth() { return this._ref.height(); } setWidth(w) { this._ref.width(w); } setHeight(h) { this._ref.height(h); } setPosition(x, y) { this._ref.setPosition({ x: x, y: y }); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Rectangle"; } getColor() { return this._ref.stroke(); } setColor(hex) { this._ref.stroke(hex); } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } setLineWidth(size) { this._ref.strokeWidth(size); } getLineWidth() { return this._ref.strokeWidth(); } } class KonvaEllipse { constructor(params, ref = null) { var _a, _b; if (ref) { this._ref = ref; return; } if (!params) params = {}; if (!params.position) params.position = { x: 0, y: 0 }; if (!params.radius) params.radius = { x: 25, y: 25 }; this._ref = new Konva.Ellipse({ stroke: (_a = params.color) !== null && _a !== undefined ? _a : "#ff0000", strokeWidth: (_b = params.lineWidth) !== null && _b !== undefined ? _b : 4, globalCompositeOperation: "source-over", lineCap: "round", lineJoin: "round", x: params.position.x, y: params.position.y, radiusX: params.radius.x, radiusY: params.radius.y, draggable: true, strokeScaleEnabled: false }); this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); const scaleByX = Math.abs(attrs.scaleX - 1) > 1e-5; const scaleByY = Math.abs(attrs.scaleY - 1) > 1e-5; let newRadiusX = this._ref.radiusX(); if (scaleByX) newRadiusX *= attrs.scaleX; let newRadiusY = this._ref.radiusY(); if (scaleByY) newRadiusY *= attrs.scaleY; const minRadiusX = 25; const minRadiusY = 25; if (newRadiusX < minRadiusX) newRadiusX = minRadiusX; if (newRadiusY < minRadiusY) newRadiusY = minRadiusY; if (e.evt.ctrlKey || e.evt.shiftKey) { if (scaleByX) { this._ref.radius({ x: newRadiusX, y: newRadiusX }); } else { this._ref.radius({ x: newRadiusY, y: newRadiusY }); } } else { this._ref.radius({ x: newRadiusX, y: newRadiusY }); } this._ref.scale({ x: 1, y: 1 }); })); this._ref.id(this._ref._id.toString()); } getPosition() { return this._ref.position(); } setPosition(x, y) { this._ref.setPosition({ x: x, y: y }); } getRadiusX() { return this._ref.radiusX(); } setRadiusX(r) { this._ref.radiusX(r); } getRadiusY() { return this._ref.radiusY(); } setRadiusY(r) { this._ref.radiusY(r); } getLineWidth() { return this._ref.strokeWidth(); } setLineWidth(size) { this._ref.strokeWidth(size); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Ellipse"; } getColor() { return this._ref.stroke(); } setColor(hex) { this._ref.stroke(hex); } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } } class KonvaArrow { constructor(params, ref = null) { var _a, _b; if (ref) { this._ref = ref; return; } if (!params) params = {}; if (!params.start) params.start = { x: 0, y: 0 }; if (!params.end) params.end = { x: 100, y: 100 }; this._ref = new Konva.Arrow({ stroke: (_a = params.color) !== null && _a !== undefined ? _a : "#ff0000", fill: (_b = params.color) !== null && _b !== undefined ? _b : "#ff0000", strokeWidth: 4, globalCompositeOperation: "source-over", lineCap: "round", lineJoin: "round", points: [ params.start.x, params.start.y, params.end.x, params.end.y ], draggable: true, strokeScaleEnabled: false }); this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); })); this._ref.id(this._ref._id.toString()); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Arrow"; } getColor() { return this._ref.stroke(); } setColor(hex) { this._ref.stroke(hex); this._ref.fill(hex); } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } getPoints() { const points = this._ref.points(); return [ { x: points[0], y: points[1] }, { x: points[2], y: points[3] } ]; } setPoints(points) { if (points.length === 2) { this._ref.points([ points[0].x, points[0].y, points[1].x, points[1].y ]); } } getStartPoint() { const points = this._ref.points(); return { x: points[0], y: points[1] }; } setStartPoint(x, y) { const points = this._ref.points(); this._ref.points([ x, y, points[2], points[3] ]); } getEndPoint() { const points = this._ref.points(); return { x: points[2], y: points[3] }; } setEndPoint(x, y) { const points = this._ref.points(); this._ref.points([ points[0], points[1], x, y ]); } } class KonvaImage { constructor(params, ref = null) { var _a, _b; this._ratio = 1; this.EPSILON = 1e-5; this.BASE64_HEADER_START = "data:image/"; this.BASE64_NOT_FOUND = "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAADsAAAA7AF5KHG9AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAmhJREFUWIXtlr9rVEEQxz+H8RQUJIdeIopYm0vkCg0GBBtbG1NF7Kxt7dR/IGIw/uhTaBNLERURg2kCEUyCYCPi70b0InjGS57FzOZN3r19d+9HJIVfWO52dma/s7Mz8xa2KAaBCWAR+AkECWOmSOIdwC1gtQOpHc+NfQ8wClQ8+1d0vcdH/lQ3bSIRGAZ2pTjAqNovANXIWlXlAXA2zvi2Ln4AjqYgtagYEutENSLvjRoOImFv5iB32Ae8UrLXwFBk3h9ndF0VJnKSO9gTu3yKu5Z1LKnS8YIcABgw5Ks692JZFXcXRJ46Aq6kikCnHNi/mQ50WwVtfaIoBzL3gRk2drSscJ2wrc4VvUoe2wn/41/iBfoVLRnBGnDSY3AAKacy8AmYR+o7K1zCl6wgrgpOAc/MuhvfgMuk+1JGHQgSBcAloKXy78AjYBppJk5/noTulseBMZ23iD/piHFkEdgTQzKk+5wHjmHC3cmBg0BD5xcSTrFXyQPgIWFtDwMvab+2N8DpbhyY1v/3E8gdDgNfVX9SCVZ0/gW4B0wB71S2BpxLcuCM/jaQSHSDEeAX4VMuAG4gTzyHbcAVXXO6GxxwIX+vvxe7JHcYQ07nHqklj96UIW/YhSWzMKcep8VVtf8B1Dw6h4DfhB+sdbgn2R+gnoEc5NR3dZ+3QJ9H74HqXLPCGlJyTfI9y3YCs0owq3OLOpKkLeBI1HhSDT/mdKIPiUCARMTlQx34TMLjtww8IczmO8AJ/N/2JNSQXAiQ671JePePge0+wzJSQq4FFzlaenIvucUAkiQLhC/mLGNZ9xgn5s63BP4CCk0QDtm4BhoAAAAASUVORK5CYII="; if (ref) { if (!ref.src || !ref.src.startsWith(this.BASE64_HEADER_START)) ref.src = this.BASE64_NOT_FOUND; if (ref.height() <= this.EPSILON) ref.height(32); if (ref.width() <= this.EPSILON) ref.width(32); this._ref = ref; this._canvasImage = ref.image(); this._ratio = this._ref.height() <= this.EPSILON || this._ref.width() <= this.EPSILON ? 1 : this._ref.height() / this._ref.width(); return; } if (!params) params = {}; if (!params.position) params.position = { x: 0, y: 0 }; if (!params.src || !params.src.startsWith(this.BASE64_HEADER_START)) params.src = this.BASE64_NOT_FOUND; this._canvasImage = new Image; this._canvasImage.onload = () => { this._ref.image(this._canvasImage); if (this._ref.height() <= this.EPSILON) this._ref.height(this._canvasImage.height); if (this._ref.width() <= this.EPSILON) this._ref.width(this._canvasImage.width); this._ratio = this._ref.height() <= this.EPSILON || this._ref.width() <= this.EPSILON ? 1 : this._ref.height() / this._ref.width(); if ((params.width <= this.EPSILON || params.height <= this.EPSILON) && (params.maxWidth >= this.EPSILON || params.maxWidth >= this.EPSILON)) { const heightOutOfCanvas = params.maxHeight - this._canvasImage.height; const widthOutOfCanvas = params.maxWidth - this._canvasImage.width; if (heightOutOfCanvas <= this.EPSILON || widthOutOfCanvas <= this.EPSILON) { if (widthOutOfCanvas <= this.EPSILON && widthOutOfCanvas < heightOutOfCanvas / this._ratio) { this._ref.height(params.maxWidth * this._ratio); this._ref.width(params.maxWidth); } else { this._ref.width(params.maxHeight / this._ratio); this._ref.height(params.maxHeight); } } } }; this._canvasImage.onerror = () => { this._canvasImage.onerror = function() {}; this._canvasImage.src = this.BASE64_NOT_FOUND; }; this._canvasImage.src = params.src; this._ref = new Konva.Image({ x: params.position.x, y: params.position.y, image: this._canvasImage, width: (_a = params.width) !== null && _a !== undefined ? _a : 0, height: (_b = params.height) !== null && _b !== undefined ? _b : 0, draggable: true }); this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); const scaleByX = Math.abs(attrs.scaleX - 1) > 1e-5; const scaleByY = Math.abs(attrs.scaleY - 1) > 1e-5; let newWidth = this._ref.width(); if (scaleByX) newWidth *= attrs.scaleX; let newHeight = this._ref.height(); if (scaleByY) newHeight *= attrs.scaleY; if (e.evt.ctrlKey || e.evt.shiftKey) { if (scaleByX) { this._ref.width(newWidth); this._ref.height(newWidth * this._ratio); } else { this._ref.width(newHeight / this._ratio); this._ref.height(newHeight); } } else { if (scaleByX) { this._ref.width(newWidth); } if (scaleByY) { this._ref.height(newHeight); } } this._ref.scale({ x: 1, y: 1 }); })); this._ref.id(this._ref._id.toString()); } getSrc() { return this._canvasImage.src; } setSrc(src) { this._canvasImage.src = src; } getWidth() { return this._ref.width(); } setWidth(w) { this._ref.width(w); this._ref.height(w * this._ratio); } getHeight() { return this._ref.height(); } setHeight(h) { this._ref.height(h); this._ref.width(h / this._ratio); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Image"; } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } getPosition() { return this._ref.getPosition(); } setPosition(x, y) { this._ref.setPosition({ x: x, y: y }); } } class KonvaCloud { constructor(params, ref = null) { var _a, _b, _c, _d; if (ref) { this._ref = ref; return; } if (!params) params = {}; if (!params.position) params.position = { x: 0, y: 0 }; const arcRadius = 16; this._ref = new Konva.Shape({ x: params.position.x, y: params.position.y, width: (_a = params.width) !== null && _a !== undefined ? _a : 200, height: (_b = params.height) !== null && _b !== undefined ? _b : 200, stroke: (_c = params.color) !== null && _c !== undefined ? _c : "#ff0000", strokeWidth: (_d = params.lineWidth) !== null && _d !== undefined ? _d : 4, draggable: true, strokeScaleEnabled: false, globalCompositeOperation: "source-over", sceneFunc: (context, shape) => { function calculateMidpoint(position, width, height) { const midX = position.x + width / 2; const midY = position.y + height / 2; return { x: midX, y: midY }; } const points = [ { x: 0, y: 0 }, { x: 0 + this._ref.width(), y: 0 }, { x: 0 + this._ref.width(), y: 0 + this._ref.height() }, { x: 0, y: 0 + this._ref.height() }, { x: 0, y: 0 } ]; const midPoint = calculateMidpoint({ x: 0, y: 0 }, this._ref.width(), this._ref.height()); const baseArcLength = 30; context.beginPath(); for (let iPoint = 0; iPoint < points.length - 1; iPoint++) { let approxArcLength = baseArcLength; const dx = points[iPoint + 1].x - points[iPoint].x; const dy = points[iPoint + 1].y - points[iPoint].y; const length = Math.sqrt(dx * dx + dy * dy); const arcCount = Math.floor(length / approxArcLength); const lengthMod = length % approxArcLength; approxArcLength = baseArcLength + arcCount / lengthMod; let pX = points[iPoint].x + dx / arcCount / 2; let pY = points[iPoint].y + dy / arcCount / 2; const pEndX = points[iPoint + 1].x; const pEndY = points[iPoint + 1].y; const endAngle = Math.atan((pEndY - pY) / (pEndX - pX)); const startAngle = endAngle + Math.PI; const counterClockwise = pX > midPoint.x && pY > midPoint.y; for (let iArc = 0; iArc < arcCount; iArc++) { if (counterClockwise) { context.arc(pX, pY, arcRadius, endAngle, startAngle); } else { context.arc(pX, pY, arcRadius, startAngle, endAngle); } pX += dx / arcCount; pY += dy / arcCount; } } context.closePath(); context.fillStrokeShape(shape); } }); this._ref.className = "Cloud"; this._ref.on("transform", (e => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); const scaleByX = Math.abs(attrs.scaleX - 1) > 1e-5; const scaleByY = Math.abs(attrs.scaleY - 1) > 1e-5; let newWidth = this._ref.width(); if (scaleByX) newWidth *= attrs.scaleX; let newHeight = this._ref.height(); if (scaleByY) newHeight *= attrs.scaleY; const minWidth = 100; const minHeight = 100; if (newWidth < minWidth) newWidth = minWidth; if (newHeight < minHeight) newHeight = minHeight; if (scaleByX) { this._ref.width(newWidth); } if (scaleByY) { this._ref.height(newHeight); } this._ref.scale({ x: 1, y: 1 }); })); this._ref.getSelfRect = () => ({ x: 0 - arcRadius, y: 0 - arcRadius, width: this._ref.width() + 2 * arcRadius, height: this._ref.height() + 2 * arcRadius }); this._ref.id(this._ref._id.toString()); } ref() { return this._ref; } id() { return this._ref.id(); } enableMouseEditing(value) { this._ref.draggable(value); } type() { return "Cloud"; } getColor() { return this._ref.stroke(); } setColor(hex) { this._ref.stroke(hex); } getRotation() { return this._ref.rotation(); } setRotation(degrees) { this._ref.rotation(degrees); } getZIndex() { return this._ref.zIndex(); } setZIndex(zIndex) { this._ref.zIndex(zIndex); } delete() { this._ref.destroy(); this._ref = null; } getPosition() { return this._ref.position(); } setPosition(x, y) { this._ref.position({ x: x, y: y }); } getWidth() { return this._ref.width(); } setWidth(w) { this._ref.width(w); } getHeigth() { return this._ref.height(); } setHeight(h) { this._ref.height(h); } getLineWidth() { return this._ref.strokeWidth(); } setLineWidth(size) { this._ref.strokeWidth(size); } } const MarkupMode2Konva = { SelectMarkup: { name: "SelectMarkup", initializer: null }, Line: { name: "Line", initializer: (ref, params = null) => new KonvaLine(params, ref) }, Text: { name: "Text", initializer: (ref, params = null) => new KonvaText(params, ref) }, Rectangle: { name: "Rect", initializer: (ref, params = null) => new KonvaRectangle(params, ref) }, Ellipse: { name: "Ellipse", initializer: (ref, params = null) => new KonvaEllipse(params, ref) }, Arrow: { name: "Arrow", initializer: (ref, params = null) => new KonvaArrow(params, ref) }, Image: { name: "Image", initializer: (ref, params = null) => new KonvaImage(params, ref) }, Cloud: { name: "Cloud", initializer: (ref, params = null) => new KonvaCloud(params, ref) } }; class KonvaMarkup { constructor() { this._containerEvents = []; this._markupIsActive = false; this._markupColor = new MarkupColor(255, 0, 0); this.lineWidth = 4; this.lineType = "solid"; this.fontSize = 34; this.changeActiveDragger = event => { const draggerName = event.data; this._markupContainer.className = this._container.className.split(" ").filter((x => !x.startsWith("oda-cursor-"))).filter((x => x)).concat(`oda-cursor-${draggerName.toLowerCase()}`).join(" "); this.removeTextInput(); this.removeImageInput(); this.enableEditMode(draggerName); }; this.resizeContainer = entries => { const {width: width, height: height} = entries[0].contentRect; if (!width || !height) return; if (!this._konvaStage) return; this._konvaStage.width(width); this._konvaStage.height(height); }; this.pan = event => { const newPos = { x: this._konvaStage.x() + event.dX, y: this._konvaStage.y() + event.dY }; this._konvaStage.position(newPos); }; this.zoomAt = event => { const newScale = this._konvaStage.scaleX() * event.data; this._konvaStage.scale({ x: newScale, y: newScale }); const newPos = { x: event.x - (event.x - this._konvaStage.x()) * event.data, y: event.y - (event.y - this._konvaStage.y()) * event.data }; this._konvaStage.position(newPos); }; this.redirectToViewer = event => { if (this._viewer) this._viewer.emit(event); }; this.getRelativePointPosition = (point, node) => { const transform = node.getAbsoluteTransform().copy(); transform.invert(); return transform.point(point); }; this.getRelativePointerPosition = node => this.getRelativePointPosition(node.getStage().getPointerPosition(), node); } initialize(container, containerEvents, viewer, worldTransformer) { if (!Konva) throw new Error('Markup error: Konva is not initialized. Forgot to add <script src="https://unpkg.com/konva@9/konva.min.js"><\/script> to your page?'); this._viewer = viewer; this._worldTransformer = worldTransformer !== null && worldTransformer !== undefined ? worldTransformer : new WorldTransform; this._container = container; this._containerEvents = containerEvents !== null && containerEvents !== undefined ? containerEvents : []; this._markupContainer = document.createElement("div"); this._markupContainer.id = "markup-container"; this._markupContainer.style.position = "absolute"; this._markupContainer.style.top = "0px"; this._markupContainer.style.left = "0px"; this._markupContainer.style.outline = "0px"; this._markupContainer.style.pointerEvents = "none"; const parentDiv = this._container.parentElement; parentDiv.appendChild(this._markupContainer); this._resizeObserver = new ResizeObserver(this.resizeContainer); this._resizeObserver.observe(parentDiv); this._markupColor.setColor(255, 0, 0); this.initializeKonva(); if (this._viewer) { this._viewer.addEventListener("changeactivedragger", this.changeActiveDragger); this._viewer.addEventListener("pan", this.pan); this._viewer.addEventListener("zoomat", this.zoomAt); } } dispose() { var _a, _b; if (this._viewer) { this._viewer.removeEventListener("zoomat", this.zoomAt); this._viewer.removeEventListener("pan", this.pan); this._viewer.removeEventListener("changeactivedragger", this.changeActiveDragger); } this.destroyKonva(); (_a = this._resizeObserver) === null || _a === undefined ? undefined : _a.disconnect(); this._resizeObserver = undefined; (_b = this._markupContainer) === null || _b === undefined ? undefined : _b.remove(); this._markupContainer = undefined; this._container = undefined; this._viewer = undefined; this._worldTransformer = undefined; this._markupIsActive = false; } syncOverlay() {} clearOverlay() { this.removeTextInput(); this.removeImageInput(); this.clearSelected(); this.getObjects().forEach((obj => obj.delete())); } getMarkupColor() { return this._markupColor.asRGB(); } setMarkupColor(r, g, b) { this._markupColor.setColor(r, g, b); this.redirectToViewer({ type: "changemarkupcolor", data: { r: r, g: g, b: b } }); } colorizeAllMarkup(r, g, b) { const hexColor = new MarkupColor(r, g, b).asHex(); this.getObjects().filter((obj => { var _a; return (_a = obj.setColor) === null || _a === undefined ? undefined : _a.call(obj, hexColor); })); } colorizeSelectedMarkups(r, g, b) { const hexColor = new MarkupColor(r, g, b).asHex(); this.getSelectedObjects().filter((obj => { var _a; return (_a = obj.setColor) === null || _a === undefined ? undefined : _a.call(obj, hexColor); })); } setViewpoint(viewpoint) { var _a, _b, _c, _d, _e, _f, _g, _h; this.clearSelected(); this.removeTextInput(); this.removeImageInput(); this._konvaStage.scale({ x: 1, y: 1 }); this._konvaStage.position({ x: 0, y: 0 }); const markupColor = ((_a = viewpoint.custom_fields) === null || _a === undefined ? undefined : _a.markup_color) || { r: 255, g: 0, b: 0 }; this.setMarkupColor(markupColor.r, markupColor.g, markupColor.b); (_b = viewpoint.lines) === null || _b === undefined ? undefined : _b.forEach((line => { const linePoints = []; line.points.forEach((point => { const screenPoint = this._worldTransformer.worldToScreen(point); linePoints.push(screenPoint.x); linePoints.push(screenPoint.y); })); this.addLine(linePoints, line.color, line.type, line.width, line.id); })); (_c = viewpoint.texts) === null || _c === undefined ? undefined : _c.forEach((text => { const screenPoint = this._worldTransformer.worldToScreen(text.position); this.addText(text.text, screenPoint, text.angle, text.color, text.text_size, text.font_size, text.id); })); (_d = viewpoint.rectangles) === null || _d === undefined ? undefined : _d.forEach((rect => { const screenPoint = this._worldTransformer.worldToScreen(rect.position); this.addRectangle(screenPoint, rect.width, rect.height, rect.line_width, rect.color, rect.id); })); (_e = viewpoint.ellipses) === null || _e === undefined ? undefined : _e.forEach((ellipse => { const screenPoint = this._worldTransformer.worldToScreen(ellipse.position); this.addEllipse(screenPoint, ellipse.radius, ellipse.line_width, ellipse.color, ellipse.id); })); (_f = viewpoint.arrows) === null || _f === undefined ? undefined : _f.forEach((arrow => { const startPoint = this._worldTransformer.worldToScreen(arrow.start); const endPoint = this._worldTransformer.worldToScreen(arrow.end); this.addArrow(startPoint, endPoint, arrow.color, arrow.id); })); (_g = viewpoint.clouds) === null || _g === undefined ? undefined : _g.forEach((cloud => { const screenPoint = this._worldTransformer.worldToScreen(cloud.position); this.addCloud(screenPoint, cloud.width, cloud.height, cloud.line_width, cloud.color, cloud.id); })); (_h = viewpoint.images) === null || _h === undefined ? undefined : _h.forEach((image => { const screenPoint = this._worldTransformer.worldToScreen(image.position); this.addImage(screenPoint, image.src, image.width, image.height, image.id); })); } getViewpoint(viewpoint) { if (!viewpoint) viewpoint = {}; viewpoint.lines = this.getMarkupLines(); viewpoint.texts = this.getMarkupTexts(); viewpoint.arrows = this.getMarkupArrows(); viewpoint.clouds = this.getMarkupClouds(); viewpoint.ellipses = this.getMarkupEllipses(); viewpoint.images = this.getMarkupImages(); viewpoint.rectangles = this.getMarkupRectangles(); viewpoint.custom_fields = { markup_color: this.getMarkupColor() }; viewpoint.snapshot = { data: this.combineMarkupWithDrawing() }; return viewpoint; } enableEditMode(mode) { if (!mode || !MarkupMode2Konva[mode]) { this.clearSelected(); this.removeTextInput(); this.removeImageInput(); this._markupContainer.style.pointerEvents = "none"; this._markupIsActive = false; } else { this._markupMode = mode; this._markupContainer.style.pointerEvents = "all"; this._markupIsActive = true; } return this; } createObject(type, params) { const konvaShape = MarkupMode2Konva[type]; if (!konvaShape || !konvaShape.initializer) throw new Error(`Markup CreateObject - unsupported markup type ${type}`); const object = konvaShape.initializer(null, params); this.addObject(object); return object; } getObjects() { const objects = []; Object.keys(MarkupMode2Konva).forEach((type => { const konvaShape = MarkupMode2Konva[type]; this.konvaLayerFind(type).forEach((ref => objects.push(konvaShape.initializer(ref)))); })); return objects; } getSelectedObjects() { if (!this._konvaTransformer) return []; return this._konvaTransformer.nodes().map((ref => { const name = ref.className; const konvaShape = Object.values(MarkupMode2Konva).find((shape => shape.name === name)); return konvaShape ? konvaShape.initializer(ref) : null; })).filter((x => x)); } selectObjects(objects) { if (!this._konvaTransformer) return; const selectedObjs = this._konvaTransformer.nodes().concat(objects.map((x => x.ref()))); this._konvaTransformer.nodes(selectedObjs); } clearSelected() { if (this._konvaTransformer) this._konvaTransformer.nodes([]); } addObject(object) { if (object.type() === "Image") this._groupImages.add(object.ref()); else if (object.type() === "Text") this._groupTexts.add(object.ref()); else this._groupGeometry.add(object.ref()); } konvaLayerFind(type) { if (!this._konvaLayer) return []; const konvaShape = MarkupMode2Konva[type]; if (!konvaShape || !konvaShape.initializer) return []; return this._konvaLayer.find(konvaShape.name).filter((ref => ref.parent === this._konvaLayer || ref.parent === this._groupImages || ref.parent === this._groupGeometry || ref.parent === this._groupTexts)); } initializeKonva() { const stage = new Konva.Stage({ container: this._markupContainer, width: this._container.clientWidth, height: this._container.clientHeight }); this._konvaStage = stage; const layer = new Konva.Layer({ pixelRation: window.devicePixelRatio }); stage.add(layer); this._groupImages = new Konva.Group; layer.add(this._groupImages); this._groupGeometry = new Konva.Group; layer.add(this._groupGeometry); this._groupTexts = new Konva.Group; layer.add(this._groupTexts); this._konvaLayer = layer; const transformer = new Konva.Transformer({ shouldOverdrawWholeArea: false, keepRatio: false, flipEnabled: false }); layer.add(transformer); this._konvaTransformer = transformer; let isPaint = false; let lastLine; let mouseDownPos; let lastObj; stage.on("mousedown touchstart", (e => { if (!this._markupIsActive || e.target !== stage || this._markupMode === "Text" || this._markupMode === "Image") return; if (e.target === stage && transformer.nodes().length > 0) { transformer.nodes([]); return; } const pos = this.getRelativePointerPosition(stage); mouseDownPos = pos; isPaint = [ "Arrow", "Cloud", "Ellipse", "Line", "Rectangle" ].some((m => m === this._markupMode)); if (this._markupMode === "Line") { lastLine = this.addLine([ pos.x, pos.y, pos.x, pos.y ]); } })); stage.on("mouseup touchend", (e => { if (!this._markupIsActive) return; if (isPaint) { const pos = this.getRelativePointerPosition(stage); const defParams = mouseDownPos && pos.x === mouseDownPos.x && pos.y === mouseDownPos.y; const startX = defParams ? mouseDownPos.x : Math.min(mouseDownPos.x, pos.x); const startY = defParams ? mouseDownPos.y : Math.min(mouseDownPos.y, pos.y); const dX = defParams ? 200 : Math.abs(mouseDownPos.x - pos.x); const dY = defParams ? 200 : Math.abs(mouseDownPos.y - pos.y); if (defParams) { if (this._markupMode === "Rectangle") { this.addRectangle({ x: startX, y: startY }, dX, dY); } else if (this._markupMode === "Ellipse") { this.addEllipse({ x: startX, y: startY }, { x: dX / 2, y: dY / 2 }); } else if (this._markupMode === "Arrow") { this.addArrow({ x: mouseDownPos.x, y: mouseDownPos.y }, { x: defParams ? mouseDownPos.x + 200 : pos.x, y: defParams ? startY : pos.y }); } else if (this._markupMode === "Cloud") { this.addCloud({ x: startX, y: startY }, Math.max(100, dX), Math.max(100, dY)); } } } lastObj = undefined; isPaint = false; })); stage.on("mousemove touchmove", (e => { if (!this._markupIsActive) return; if (!isPaint) { return; } const pos = this.getRelativePointerPosition(stage); const defParams = mouseDownPos && pos.x === mouseDownPos.x && pos.y === mouseDownPos.y; const startX = defParams ? mouseDownPos.x : Math.min(mouseDownPos.x, pos.x); const startY = defParams ? mouseDownPos.y : Math.min(mouseDownPos.y, pos.y); const dX = defParams ? 200 : Math.abs(mouseDownPos.x - pos.x); const dY = defParams ? 200 : Math.abs(mouseDownPos.y - pos.y); if (this._markupMode === "Line") { lastLine.addPoints([ { x: pos.x, y: pos.y } ]); } else if (this._markupMode === "Arrow") { if (lastObj) lastObj.setEndPoint(pos.x, pos.y); else lastObj = this.addArrow({ x: mouseDownPos.x, y: mouseDownPos.y }, { x: pos.x, y: pos.y }); } else if (this._markupMode === "Rectangle") { if (lastObj) { lastObj.setPosition(startX, startY); lastObj.setWidth(dX); lastObj.setHeight(dY); } else lastObj = this.addRectangle({ x: startX, y: startY }, dX, dY); } else if (this._markupMode === "Ellipse") { if (lastObj) { lastObj.setPosition(startX, startY); lastObj.setRadiusX(dX); lastObj.setRadiusY(dY); } else lastObj = this.addEllipse({ x: startX, y: startY }, { x: dX, y: dY }); } else if (this._markupMode === "Cloud") { if (lastObj) { lastObj.setPosition(startX, startY); lastObj.setWidth(Math.max(100, dX)); lastObj.setHeight(Math.max(100, dY)); } else lastObj = this.addCloud({ x: startX, y: startY }, dX, dY); } })); stage.on("click tap", (e => { if (!this._markupIsActive) return; if (e.target === stage) { if (this._markupMode === "Text") { if (this._textInputRef && this._textInputRef.value) this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle); else if (transformer.nodes().length === 0) { const pos = this.getRelativePointerPosition(stage); this.createTextInput(pos, e.evt.pageX, e.evt.pageY, 0, null); } } else if (this._markupMode === "Image") { if (this._imageInputRef && this._imageInputRef.value) this.addImage({ x: this._imageInputPos.x, y: this._imageInputPos.y }, this._imageInputRef.value, 0, 0, this._imageInputRef.value); else if (transformer.nodes().length === 0) { const pos = this.getRelativePointerPosition(stage); this.createImageInput(pos); } } transformer.nodes([]); return; } if (this._markupMode === "Text" || this._markupMode === "SelectMarkup") { if (e.target.className === "Text" && transformer.nodes().length === 1 && transformer.nodes()[0] === e.target) { if (this._textInputRef && this._textInputRef.value) this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle); else this.createTextInput({ x: e.target.attrs.x, y: e.target.attrs.y }, e.evt.pageX, e.evt.pageY, e.target.attrs.rotation, e.target.attrs.text); return; } else { this.removeTextInput(); } } if (this._markupMode === "Image" || this._markupMode === "SelectMarkup") { if (e.target.className === "Image" && transformer.nodes().length === 1 && transformer.nodes()[0] === e.target) { if (this._imageInputRef && this._imageInputRef.value) this.addImage(this._imageInputPos, this._imageInputRef.value, 0, 0); else this.createImageInput({ x: e.target.attrs.x, y: e.target.attrs.y }); return; } else { this.removeImageInput(); } } if (transformer.nodes().filter((x => x.className === "Cloud" || x.className === "Image")).length > 0 || e.target.className === "Cloud" || e.target.className === "Image") { transformer.rotateEnabled(false); } else {