diff --git a/app/__init__.py b/app/__init__.py index 81aad63..c7ab2ea 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -36,6 +36,7 @@ def create_app(config_name: str = "default") -> Flask: from app.routes.api import api_bp from app.routes.admin import admin_bp from app.routes.sonoff import sonoff_bp + from app.routes.layouts import layouts_bp app.register_blueprint(auth_bp) app.register_blueprint(dashboard_bp) @@ -44,6 +45,7 @@ def create_app(config_name: str = "default") -> Flask: app.register_blueprint(api_bp, url_prefix="/api") app.register_blueprint(admin_bp, url_prefix="/admin") app.register_blueprint(sonoff_bp) + app.register_blueprint(layouts_bp, url_prefix="/layouts") # ── user loader ─────────────────────────────────────────────────────────── from app.models.user import User @@ -62,6 +64,7 @@ def create_app(config_name: str = "default") -> Flask: # Import all models so their tables are registered before create_all from app.models import board, user, workflow # noqa: F401 from app.models import sonoff_device # noqa: F401 + from app.models import layout # noqa: F401 db.create_all() _seed_admin(app) _add_entities_column(app) diff --git a/app/models/layout.py b/app/models/layout.py new file mode 100644 index 0000000..a274bf4 --- /dev/null +++ b/app/models/layout.py @@ -0,0 +1,27 @@ +"""Layout model – stores a floor-plan / property map with placed device widgets.""" +from datetime import datetime +from app import db + + +class Layout(db.Model): + __tablename__ = "layouts" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(128), nullable=False) + description = db.Column(db.String(256), nullable=True) + + # Custom JSON schema (not raw Konva JSON) so we stay schema-independent: + # { + # "structure": [ {id, tool, x, y, w, h, rotation, text, points}, … ], + # "devices": [ {id, boardId, entityType, entityNum, x, y}, … ] + # } + canvas_json = db.Column(db.Text, nullable=True) + + # PNG data-url for thumbnail shown on the list page + thumbnail_b64 = db.Column(db.Text, nullable=True) + + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def __repr__(self): + return f"" diff --git a/app/routes/api.py b/app/routes/api.py index 92c4921..1e517a7 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -1,10 +1,12 @@ """REST API – board webhook receiver and JSON relay control.""" from datetime import datetime -from flask import Blueprint, request, jsonify, abort +from flask import Blueprint, request, jsonify, abort, current_app +from flask_login import login_required from app import db, socketio from app.models.board import Board from app.services import workflow_engine +from app.services.board_service import poll_board api_bp = Blueprint("api", __name__) @@ -46,6 +48,28 @@ def webhook(board_id: int): return jsonify({"status": "ok"}) +# ── Manual ping / status check ─────────────────────────────────────────────── + +@api_bp.route("/boards//ping", methods=["POST"]) +@login_required +def ping_board(board_id: int): + """Trigger an immediate poll for any board (online or offline). + + Called by the 'Check Status' button in the UI. The poll result is both + returned as JSON *and* broadcast via socket.io so all open tabs update. + """ + db.get_or_404(Board, board_id) # 404 if unknown + poll_board(current_app._get_current_object(), board_id) + board = db.session.get(Board, board_id) + return jsonify({ + "board_id": board.id, + "is_online": board.is_online, + "relay_states": board.relay_states, + "input_states": board.input_states, + "last_seen": board.last_seen.isoformat() if board.last_seen else None, + }) + + # ── JSON relay status ───────────────────────────────────────────────────────── @api_bp.route("/boards//relays") diff --git a/app/routes/layouts.py b/app/routes/layouts.py new file mode 100644 index 0000000..c152d9a --- /dev/null +++ b/app/routes/layouts.py @@ -0,0 +1,143 @@ +"""Layout management routes.""" +import json +from datetime import datetime + +from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, abort +from flask_login import login_required, current_user + +from app import db +from app.models.layout import Layout +from app.models.board import Board +from app.models.sonoff_device import SonoffDevice + +layouts_bp = Blueprint("layouts", __name__) + + +# ── list ────────────────────────────────────────────────────────────────────── + +@layouts_bp.route("/") +@login_required +def list_layouts(): + layouts = Layout.query.order_by(Layout.name).all() + return render_template("layouts/list.html", layouts=layouts) + + +# ── create ──────────────────────────────────────────────────────────────────── + +@layouts_bp.route("/create", methods=["POST"]) +@login_required +def create_layout(): + if not current_user.is_admin(): + abort(403) + name = request.form.get("name", "").strip() + description = request.form.get("description", "").strip() + if not name: + flash("Layout name is required.", "danger") + return redirect(url_for("layouts.list_layouts")) + layout = Layout(name=name, description=description or None) + db.session.add(layout) + db.session.commit() + return redirect(url_for("layouts.builder", layout_id=layout.id)) + + +# ── builder ─────────────────────────────────────────────────────────────────── + +@layouts_bp.route("//builder") +@login_required +def builder(layout_id: int): + layout = db.get_or_404(Layout, layout_id) + boards = Board.query.order_by(Board.name).all() + + # Build a boards palette for the JS device sidebar + boards_data = [] + for b in boards: + bd = {"id": b.id, "name": b.name, "online": b.is_online, + "relays": [], "inputs": [], "sonoff_channels": [], + "board_type": b.board_type} + + # ── Standard relay/input boards ────────────────────────────────────── + for n in range(1, b.num_relays + 1): + e = b.get_relay_entity(n) + bd["relays"].append({ + "num": n, + "name": e["name"], + "icon": e["icon"], + "onColor": e["on_color"], + "offColor": e["off_color"], + "isOn": b.relay_states.get(f"relay_{n}", False), + }) + for n in range(1, b.num_inputs + 1): + e = b.get_input_entity(n) + bd["inputs"].append({ + "num": n, + "name": e["name"], + "icon": e["icon"], + "activeColor": e["active_color"], + "idleColor": e["idle_color"], + "rawState": b.input_states.get(f"input_{n}", True), + }) + + # ── Sonoff eWeLink sub-devices ──────────────────────────────────────── + if b.board_type == "sonoff_ewelink": + KIND_ICON = { + "switch": "bi-toggles", + "light": "bi-lightbulb-fill", + "fan": "bi-fan", + "sensor": "bi-thermometer-half", + "remote": "bi-broadcast", + } + devices = SonoffDevice.query.filter_by(board_id=b.id).order_by( + SonoffDevice.name).all() + for dev in devices: + icon = KIND_ICON.get(dev.kind, "bi-toggles") + num_ch = max(dev.num_channels, 1) + for ch in range(num_ch): + label = dev.name if num_ch == 1 else f"{dev.name} – Ch{ch + 1}" + bd["sonoff_channels"].append({ + "deviceId": dev.device_id, + "deviceDbId": dev.id, + "channel": ch, + "name": label, + "icon": icon, + "kind": dev.kind, + "isOn": dev.get_channel_state(ch), + "isOnline": dev.is_online, + }) + + boards_data.append(bd) + + return render_template( + "layouts/builder.html", + layout=layout, + boards_data=boards_data, + ) + + +# ── save (AJAX) ─────────────────────────────────────────────────────────────── + +@layouts_bp.route("//save", methods=["POST"]) +@login_required +def save_layout(layout_id: int): + layout = db.get_or_404(Layout, layout_id) + data = request.get_json(silent=True) or {} + if "canvas" in data: + layout.canvas_json = json.dumps(data["canvas"]) + if data.get("thumbnail"): + layout.thumbnail_b64 = data["thumbnail"] + layout.updated_at = datetime.utcnow() + db.session.commit() + return jsonify({"status": "ok"}) + + +# ── delete ──────────────────────────────────────────────────────────────────── + +@layouts_bp.route("//delete", methods=["POST"]) +@login_required +def delete_layout(layout_id: int): + if not current_user.is_admin(): + abort(403) + layout = db.get_or_404(Layout, layout_id) + db.session.delete(layout) + db.session.commit() + flash(f"Layout '{layout.name}' deleted.", "warning") + return redirect(url_for("layouts.list_layouts")) diff --git a/app/services/board_service.py b/app/services/board_service.py index 67ff27d..5637bc9 100644 --- a/app/services/board_service.py +++ b/app/services/board_service.py @@ -84,14 +84,14 @@ def poll_board(app, board_id: int) -> None: "is_online": board.is_online, "relay_states": board.relay_states, "input_states": board.input_states, + "last_seen": board.last_seen.isoformat() if board.last_seen else None, }) -def poll_all_boards(app) -> None: - """Poll every registered board in parallel.""" - with app.app_context(): - board_ids = [r[0] for r in db.session.query(Board.id).all()] - +def _poll_boards_by_ids(app, board_ids: list) -> None: + """Spawn one thread per board_id and poll them in parallel.""" + if not board_ids: + return threads = [ threading.Thread(target=poll_board, args=(app, bid), daemon=True) for bid in board_ids @@ -99,7 +99,30 @@ def poll_all_boards(app) -> None: for t in threads: t.start() for t in threads: - t.join(timeout=4) + t.join(timeout=6) + + +def poll_online_boards(app) -> None: + """Poll only boards currently marked online (fast background loop).""" + with app.app_context(): + board_ids = [ + r[0] for r in db.session.query(Board.id).filter_by(is_online=True).all() + ] + _poll_boards_by_ids(app, board_ids) + + +def recheck_offline_boards(app) -> None: + """Single-pass connectivity check for boards marked offline. + + Called infrequently (default every 60 s) so we don't flood the network + with timeout requests for devices that are simply powered off. + Also triggered immediately when the user clicks 'Check Status'. + """ + with app.app_context(): + board_ids = [ + r[0] for r in db.session.query(Board.id).filter_by(is_online=False).all() + ] + _poll_boards_by_ids(app, board_ids) # ── webhook registration ────────────────────────────────────────────────────── diff --git a/app/static/css/style.css b/app/static/css/style.css index 5e531e2..4899ad2 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -51,6 +51,15 @@ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); } +/* ── Spinner for ping button ────────────────────────────────────────────── */ +@keyframes spin { + to { transform: rotate(360deg); } +} +.spin { + display: inline-block; + animation: spin 0.8s linear infinite; +} + /* ── Relay buttons ──────────────────────────────────────────────────────── */ .relay-btn { min-width: 90px; diff --git a/app/static/js/layout_builder.js b/app/static/js/layout_builder.js new file mode 100644 index 0000000..c029f4a --- /dev/null +++ b/app/static/js/layout_builder.js @@ -0,0 +1,1064 @@ +/** + * Layout Builder – powered by Konva.js + * + * Three modes: + * structure – draw walls, rooms, doors, windows, fences, text + * devices – drag board entities (relays/inputs) onto the canvas + * view – live read-only view; relays are clickable for toggling + * + * Persistence schema (canvas_json): + * { + * structure: [ { id, tool, x, y, w, h, rotation, text, stroke, fill }, … ], + * devices: [ { id, boardId, entityType, entityNum, x, y }, … ] + * } + */ +(function () { + "use strict"; + + const CFG = window.LB_CONFIG; + + // ── constants ────────────────────────────────────────────────────────────── + const GRID = 20; + const SNAP = true; + + // Colour palette for structure tools + const TOOL_STYLE = { + wall: { fill: "#4a4a52", stroke: "#6e7681", strokeWidth: 2 }, + room: { fill: "rgba(79,140,205,.07)", stroke: "#4f8ccd", strokeWidth: 1.5, dash: [] }, + door: { fill: "transparent", stroke: "#f0883e", strokeWidth: 2 }, + window: { fill: "transparent", stroke: "#79c0ff", strokeWidth: 2, dash: [8, 4] }, + fence: { fill: "transparent", stroke: "#8b5cf6", strokeWidth: 1.5, dash: [4, 4] }, + }; + + // ── state ────────────────────────────────────────────────────────────────── + let mode = "structure"; // structure | devices | view + let activeTool = "select"; // select | pan | wall | room | door | window | fence | text + let isDrawing = false; + let drawStart = null; + let tempShape = null; + let selectedNode = null; + let selectedDeviceGroup = null; + let transformer = null; + let saveTimer = null; + let isDirty = false; + + // incremental ID + let nextId = Date.now(); + const uid = () => `s${nextId++}`; + + // ── stage & layers ───────────────────────────────────────────────────────── + const container = document.getElementById("konva-container"); + const stageWrap = document.getElementById("lb-stage"); + + const stage = new Konva.Stage({ + container: "konva-container", + width: stageWrap.clientWidth, + height: stageWrap.clientHeight, + }); + + const gridLayer = new Konva.Layer({ listening: false }); + const structureLayer = new Konva.Layer(); + const deviceLayer = new Konva.Layer(); + const uiLayer = new Konva.Layer(); // transformer lives here + + stage.add(gridLayer); + stage.add(structureLayer); + stage.add(deviceLayer); + stage.add(uiLayer); + + // ── Transformer (selection handles) ─────────────────────────────────────── + transformer = new Konva.Transformer({ + rotateAnchorOffset: 20, + enabledAnchors: ["top-left","top-right","bottom-left","bottom-right", + "middle-left","middle-right","top-center","bottom-center"], + boundBoxFunc: (old, nw) => ({ + ...nw, + x: snap(nw.x), y: snap(nw.y), + width: Math.max(GRID, nw.width), height: Math.max(GRID, nw.height), + }), + }); + uiLayer.add(transformer); + + // ── helpers ──────────────────────────────────────────────────────────────── + function snap(v) { return SNAP ? Math.round(v / GRID) * GRID : v; } + + function stagePos(e) { + const pt = stage.getPointerPosition(); + const sc = stage.scaleX(); + const off = stage.position(); + return { + x: (pt.x - off.x) / sc, + y: (pt.y - off.y) / sc, + }; + } + + function setDirty() { + isDirty = true; + setSaveStatus("Unsaved"); + clearTimeout(saveTimer); + saveTimer = setTimeout(() => LB.save(), 8000); // auto-save after 8 s idle + } + + function setSaveStatus(msg, cls = "") { + const el = document.getElementById("lb-save-status"); + el.textContent = msg; + el.className = cls; + } + + function deselect() { + transformer.nodes([]); + selectedNode = null; + uiLayer.batchDraw(); + } + + function colorForState(isOn, onColor = "warning", offColor = "secondary") { + const map = { + success : "#3fb950", warning: "#f0883e", danger: "#f85149", + info : "#58a6ff", primary: "#4f8ccd", secondary: "#484f58", dark: "#30363d", + }; + return isOn ? (map[onColor] || "#3fb950") : (map[offColor] || "#484f58"); + } + + // ── grid ─────────────────────────────────────────────────────────────────── + function drawGrid() { + gridLayer.destroyChildren(); + const sc = stage.scaleX(); + const off = stage.position(); + const W = stage.width(); + const H = stage.height(); + const step = GRID * sc; + + const startX = ((-off.x / sc) - ((-off.x / sc) % GRID)); + const startY = ((-off.y / sc) - ((-off.y / sc) % GRID)); + + for (let x = startX; x < (-off.x + W) / sc; x += GRID) { + gridLayer.add(new Konva.Line({ + points: [x, -off.y / sc, x, (-off.y + H) / sc], + stroke: "#1c2129", strokeWidth: 1 / sc, listening: false, + })); + } + for (let y = startY; y < (-off.y + H) / sc; y += GRID) { + gridLayer.add(new Konva.Line({ + points: [-off.x / sc, y, (-off.x + W) / sc, y], + stroke: "#1c2129", strokeWidth: 1 / sc, listening: false, + })); + } + gridLayer.batchDraw(); + } + + // ── draw structure shapes ────────────────────────────────────────────────── + function shapeForTool(tool, x, y, w, h) { + const st = TOOL_STYLE[tool] || TOOL_STYLE.wall; + if (tool === "door") { + // arc that represents a door swing + return new Konva.Arc({ + x, y, + innerRadius: 0, outerRadius: Math.max(Math.abs(w), Math.abs(h)), + angle: 90, + fill: st.fill, stroke: st.stroke, strokeWidth: st.strokeWidth, + rotation: w < 0 ? 90 : 0, + id: uid(), name: "structure-shape", toolType: tool, + }); + } + if (tool === "window" || tool === "fence") { + return new Konva.Rect({ + x: Math.min(x, x + w), y: Math.min(y, y + h), + width: Math.abs(w), height: Math.abs(h), + fill: st.fill, stroke: st.stroke, strokeWidth: st.strokeWidth, + dash: st.dash ?? [8, 4], + id: uid(), name: "structure-shape", toolType: tool, + draggable: false, + }); + } + // wall and room + return new Konva.Rect({ + x: Math.min(x, x + w), y: Math.min(y, y + h), + width: Math.abs(w), height: Math.abs(h), + fill: st.fill, stroke: st.stroke, strokeWidth: st.strokeWidth, + id: uid(), name: "structure-shape", toolType: tool, + draggable: false, + }); + } + + // ── device widget ────────────────────────────────────────────────────────── + function deviceIcon(iconClass) { + // Map Bootstrap Icon class to a unicode character for Konva.Text + // We embed the Bootstrap Icons font directly via CSS; Konva can render it + // because Konva.Text uses the page's loaded fonts. + // Strip "bi-" prefix and build the proper text character: + // We'll fall back to simple label if the font isn't available. + return iconClass.replace("bi-", ""); + } + + function createDeviceGroup(opts) { + /* + * opts: { id, boardId, entityType, entityNum, x, y, + * name, icon, stateColor, isRelay } + */ + const W = 120, H = 52; + + const group = new Konva.Group({ + id: opts.id, + x: snap(opts.x - W / 2), + y: snap(opts.y - H / 2), + draggable: true, + name: "device-group", + // store binding in attrs + boardId: opts.boardId, + entityType: opts.entityType, + entityNum: opts.entityNum, + }); + + // background card + const bg = new Konva.Rect({ + width: W, height: H, cornerRadius: 8, + fill: "#1c2129", stroke: "#30363d", strokeWidth: 1, + shadowColor: "#000", shadowBlur: 8, shadowOpacity: .4, shadowOffset: {x:0,y:2}, + name: "device-bg", + }); + + // colored state indicator dot + const dot = new Konva.Circle({ + x: W - 14, y: 14, radius: 5, + fill: opts.stateColor || "#484f58", + name: "state-dot", + }); + + // icon (Bootstrap Icons via font) + const ico = new Konva.Text({ + x: 10, y: H / 2 - 10, + text: "\uf4a5", // fallback lightning bolt – overridden below + fontSize: 18, + fontFamily: "'bootstrap-icons'", + fill: opts.stateColor || "#8b949e", + name: "device-icon", + }); + + // label + const lbl = new Konva.Text({ + x: 36, y: 10, + width: W - 46, + text: opts.name || "Device", + fontSize: 11, fontFamily: "system-ui, sans-serif", + fill: "#c9d1d9", ellipsis: true, wrap: "none", + name: "device-label", + }); + + // board name sub-label + const sub = new Konva.Text({ + x: 36, y: 26, + width: W - 46, + text: opts.boardName || "", + fontSize: 9.5, fontFamily: "system-ui, sans-serif", + fill: "#484f58", ellipsis: true, wrap: "none", + name: "device-sub", + }); + + // relay type badge + const typeText = opts.entityType === "relay" ? "relay" + : opts.entityType === "sonoff" ? "sonoff" + : "input"; + const badge = new Konva.Text({ + x: 36, y: 38, + text: typeText, fontSize: 9, fontFamily: "system-ui, sans-serif", + fill: opts.entityType === "relay" ? "#f0883e" : opts.entityType === "sonoff" ? "#58a6ff" : "#3fb950", + name: "device-type", + }); + + group.add(bg, dot, ico, lbl, sub, badge); + + // Also store extra fields in group attrs + group.setAttr('deviceId', opts.deviceId || null); + group.setAttr('channel', opts.channel != null ? opts.channel : null); + + // hover highlight + group.on("mouseenter", () => { bg.stroke("#4f8ccd"); uiLayer.batchDraw(); deviceLayer.batchDraw(); }); + group.on("mouseleave", () => { bg.stroke("#30363d"); uiLayer.batchDraw(); deviceLayer.batchDraw(); }); + + // drag snap + group.on("dragmove", () => { + group.x(snap(group.x())); + group.y(snap(group.y())); + deviceLayer.batchDraw(); + setDirty(); + }); + + // right-click = delete in structure / devices mode + group.on("contextmenu", (e) => { + if (mode === "view") return; + e.evt.preventDefault(); + if (confirm(`Remove "${opts.name}" from layout?`)) { + group.destroy(); + deviceLayer.batchDraw(); + setDirty(); + } + }); + + // click = select in devices mode; toggle relay in view mode + group.on("click tap", async (e) => { + if (mode === "devices") { + e.cancelBubble = true; // prevent stage click from immediately deselecting + selectDevice(group); + return; + } + if (mode !== "view") return; + + let url = null; + if (opts.entityType === "relay") { + url = CFG.toggleBase + .replace("{boardId}", opts.boardId) + .replace("{relayNum}", opts.entityNum); + } else if (opts.entityType === "sonoff") { + url = `/sonoff/${opts.boardId}/device/${encodeURIComponent(opts.deviceId)}/ch/${opts.channel}/toggle`; + } else { + return; // inputs are read-only + } + + try { + await fetch(url, { + method: "POST", + headers: { "Accept": "application/json", "X-Requested-With": "XMLHttpRequest" }, + }); + } catch (_) {} + }); + + return group; + } + + function updateDeviceState(group, isOn, onColor, offColor) { + const color = colorForState(isOn, onColor, offColor); + const dot = group.findOne(".state-dot"); + const ico = group.findOne(".device-icon"); + if (dot) dot.fill(color); + if (ico) ico.fill(isOn ? color : "#484f58"); + deviceLayer.batchDraw(); + } + + // ── load saved canvas ────────────────────────────────────────────────────── + function loadCanvas(data) { + if (!data) return; + + // Structure shapes + (data.structure || []).forEach(s => { + if (s.tool === "text") { + const t = new Konva.Text({ + id: s.id, x: s.x, y: s.y, + text: s.text || "Label", fontSize: 14, + fontFamily: "system-ui, sans-serif", + fill: "#e6edf3", + draggable: false, + name: "structure-shape", toolType: "text", + rotation: s.rotation || 0, + }); + makeStructureDraggable(t); + structureLayer.add(t); + } else { + let sh; + if (s.tool === "door") { + // restore Arc using saved outerRadius (or fall back to w/h for old saves) + const st = TOOL_STYLE.door; + const radius = s.r != null + ? s.r + : Math.max(Math.abs(s.w || GRID), Math.abs(s.h || GRID)); + sh = new Konva.Arc({ + id: s.id, x: s.x, y: s.y, + innerRadius: 0, outerRadius: radius, + angle: 90, + fill: st.fill, stroke: st.stroke, strokeWidth: st.strokeWidth, + rotation: s.rotation || 0, + name: "structure-shape", toolType: "door", + }); + } else { + sh = shapeForTool(s.tool, s.x, s.y, s.w, s.h); + sh.id(s.id); + if (s.rotation) sh.rotation(s.rotation); + } + // restore Transformer scale if shape was resized + if (s.sx != null) sh.scaleX(s.sx); + if (s.sy != null) sh.scaleY(s.sy); + makeStructureDraggable(sh); + structureLayer.add(sh); + } + }); + + // Devices + (data.devices || []).forEach(d => { + const board = CFG.boards.find(b => b.id === d.boardId); + if (!board) return; + + let entityInfo = null; + let isOn = false; + let onColor = "warning"; + let offColor = "secondary"; + + if (d.entityType === "sonoff") { + // Match by deviceId + channel stored in sonoff_channels + entityInfo = board.sonoff_channels && + board.sonoff_channels.find(sc => sc.deviceId === d.deviceId && sc.channel === d.channel); + if (!entityInfo) return; + isOn = entityInfo.isOn; + onColor = entityInfo.kind === "light" ? "warning" : "success"; + offColor = "secondary"; + } else if (d.entityType === "relay") { + entityInfo = board.relays.find(r => r.num === d.entityNum); + if (!entityInfo) return; + isOn = entityInfo.isOn; + onColor = entityInfo.onColor; + offColor = entityInfo.offColor; + } else { + entityInfo = board.inputs.find(i => i.num === d.entityNum); + if (!entityInfo) return; + isOn = !entityInfo.rawState; + onColor = entityInfo.activeColor; + offColor = entityInfo.idleColor; + } + + const group = createDeviceGroup({ + id: d.id, + boardId: d.boardId, + entityType: d.entityType, + entityNum: d.entityNum || null, + // Sonoff-specific + deviceId: d.deviceId || null, + channel: d.channel != null ? d.channel : null, + x: d.x + 60, + y: d.y + 26, + name: entityInfo.name, + boardName: board.name, + icon: entityInfo.icon, + stateColor: colorForState(isOn, onColor, offColor), + isRelay: d.entityType === "relay" || d.entityType === "sonoff", + }); + deviceLayer.add(group); + }); + + structureLayer.batchDraw(); + deviceLayer.batchDraw(); + } + + // ── persist structure shapes (make selectable + draggable) ───────────────── + function makeStructureDraggable(shape) { + shape.draggable(false); // only move via transformer + shape.on("click tap", () => { + if (mode !== "structure" || activeTool !== "select") return; + deselect(); + transformer.nodes([shape]); + selectedNode = shape; + uiLayer.batchDraw(); + }); + shape.on("dragend", () => setDirty()); + } + + // ── save ─────────────────────────────────────────────────────────────────── + window.LB = window.LB || {}; + + LB.save = async function () { + setSaveStatus("Saving…", "saving"); + + const structure = []; + structureLayer.find(".structure-shape").forEach(sh => { + if (sh.className === "Text") { + structure.push({ + id: sh.id(), tool: "text", + x: sh.x(), y: sh.y(), + text: sh.text(), rotation: sh.rotation(), + }); + } else { + const isArc = sh.className === "Arc"; + const entry = { + id: sh.id(), tool: sh.attrs.toolType || "wall", + x: sh.x(), y: sh.y(), + rotation: sh.rotation(), + }; + if (isArc) { + entry.r = sh.outerRadius(); // save radius, not bounding-box w/h + } else { + entry.w = sh.width(); entry.h = sh.height(); + } + // preserve Transformer-applied scale so resized shapes survive reload + const sx = sh.scaleX(), sy = sh.scaleY(); + if (sx !== 1) entry.sx = sx; + if (sy !== 1) entry.sy = sy; + structure.push(entry); + } + }); + + const devices = []; + deviceLayer.find(".device-group").forEach(g => { + const entry = { + id: g.id(), + boardId: g.attrs.boardId, + entityType: g.attrs.entityType, + entityNum: g.attrs.entityNum || null, + // Sonoff extra fields + deviceId: g.attrs.deviceId || null, + channel: g.attrs.channel != null ? g.attrs.channel : null, + x: g.x(), + y: g.y(), + }; + devices.push(entry); + }); + + const thumbnail = stage.toDataURL({ pixelRatio: 0.4 }); + + try { + const resp = await fetch(CFG.saveUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ canvas: { structure, devices }, thumbnail }), + }); + if (resp.ok) { + isDirty = false; + setSaveStatus("Saved ✓", "saved"); + setTimeout(() => setSaveStatus(""), 3000); + } else { + setSaveStatus("Save failed", "saving"); + } + } catch (e) { + setSaveStatus("Save failed", "saving"); + } + }; + + // ── zoom ─────────────────────────────────────────────────────────────────── + LB.zoom = function (factor) { + const oldScale = stage.scaleX(); + const newScale = Math.min(5, Math.max(0.15, oldScale * factor)); + const cx = stage.width() / 2; + const cy = stage.height() / 2; + const pos = stage.position(); + stage.scale({ x: newScale, y: newScale }); + stage.position({ + x: cx - (cx - pos.x) * (newScale / oldScale), + y: cy - (cy - pos.y) * (newScale / oldScale), + }); + stage.batchDraw(); + drawGrid(); + }; + + LB.resetView = function () { + stage.scale({ x: 1, y: 1 }); + stage.position({ x: 0, y: 0 }); + stage.batchDraw(); + drawGrid(); + }; + + // ── clear helpers ────────────────────────────────────────────────────────── + LB.clearStructure = function () { + if (!confirm("Remove all structure shapes? This cannot be undone.")) return; + structureLayer.destroyChildren(); + structureLayer.batchDraw(); + deselect(); + setDirty(); + }; + + LB.clearDevices = function () { + if (!confirm("Remove all devices from canvas? This cannot be undone.")) return; + deselectDevice(); + deviceLayer.destroyChildren(); + deviceLayer.batchDraw(); + setDirty(); + }; + + // ── device selection ─────────────────────────────────────────────────────── + function selectDevice(group) { + // un-highlight previous + if (selectedDeviceGroup && selectedDeviceGroup !== group) { + const prevBg = selectedDeviceGroup.findOne(".device-bg"); + if (prevBg) prevBg.stroke("#30363d"); + } + selectedDeviceGroup = group; + + // highlight selected + const bg = group.findOne(".device-bg"); + if (bg) bg.stroke("#4f8ccd"); + deviceLayer.batchDraw(); + + // populate sidebar info + const nameEl = document.getElementById("lb-sel-name"); + const boardEl = document.getElementById("lb-sel-board"); + const typeEl = document.getElementById("lb-sel-type"); + const lbl = group.findOne(".device-label"); + const sub = group.findOne(".device-sub"); + const typ = group.findOne(".device-type"); + if (nameEl) nameEl.textContent = lbl ? lbl.text() : ""; + if (boardEl) boardEl.textContent = sub ? sub.text() : ""; + if (typeEl) typeEl.textContent = typ ? typ.text() : ""; + + const panel = document.getElementById("lb-device-selected"); + const hint = document.getElementById("lb-drag-hint"); + if (panel) panel.style.display = ""; + if (hint) hint.style.display = "none"; + } + + function deselectDevice() { + if (selectedDeviceGroup) { + const bg = selectedDeviceGroup.findOne(".device-bg"); + if (bg) bg.stroke("#30363d"); + deviceLayer.batchDraw(); + } + selectedDeviceGroup = null; + const panel = document.getElementById("lb-device-selected"); + const hint = document.getElementById("lb-drag-hint"); + if (panel) panel.style.display = "none"; + if (hint) hint.style.display = ""; + } + + LB.removeSelectedDevice = function () { + if (!selectedDeviceGroup) return; + const lbl = selectedDeviceGroup.findOne(".device-label"); + const name = lbl ? lbl.text() : "device"; + if (!confirm(`Remove "${name}" from layout?`)) return; + selectedDeviceGroup.destroy(); + deviceLayer.batchDraw(); + deselectDevice(); + setDirty(); + }; + + LB.deselectDevice = function () { deselectDevice(); }; + + // ── mode switching ───────────────────────────────────────────────────────── + function applyMode(newMode) { + mode = newMode; + deselect(); + deselectDevice(); + + // toolbar buttons + document.querySelectorAll(".tb-mode").forEach(b => + b.classList.toggle("active", b.dataset.mode === mode)); + + // sidebar panels + document.getElementById("lb-structure-palette").style.display = + mode === "structure" ? "" : "none"; + document.getElementById("lb-devices-palette").style.display = + mode === "devices" ? "" : "none"; + document.getElementById("lb-view-info").style.display = + mode === "view" ? "" : "none"; + + // clear-devices button visibility + document.getElementById("btn-clear-devices").style.display = + mode === "devices" ? "" : "none"; + + // cursor + container.style.cursor = mode === "view" ? "default" : "crosshair"; + + if (mode === "structure") { + selectTool("select"); + } else if (mode === "view") { + activeTool = "select"; + // make devices clickable, structures not draggable + deviceLayer.listening(true); + structureLayer.listening(false); + } + + if (mode !== "view") { + structureLayer.listening(true); + } + } + + // ── tool switching ───────────────────────────────────────────────────────── + function selectTool(tool) { + activeTool = tool; + document.querySelectorAll(".lb-tool-btn").forEach(b => + b.classList.toggle("active", b.dataset.tool === tool)); + const isPan = tool === "pan"; + stage.draggable(isPan); + container.style.cursor = isPan ? "grab" : tool === "select" ? "default" : "crosshair"; + deselect(); + } + + // ── stage mouse events for drawing ──────────────────────────────────────── + stage.on("mousedown touchstart", (e) => { + if (mode !== "structure") return; + if (activeTool === "pan" || activeTool === "select") return; + if (e.target !== stage && e.target.name() === "structure-shape") return; + + const pos = stagePos(e); + isDrawing = true; + drawStart = { x: snap(pos.x), y: snap(pos.y) }; + + if (activeTool === "text") { + isDrawing = false; + const label = prompt("Label text:", "Label"); + if (!label) return; + const t = new Konva.Text({ + id: uid(), x: snap(pos.x), y: snap(pos.y), + text: label, fontSize: 14, + fontFamily: "system-ui, sans-serif", + fill: "#e6edf3", draggable: false, + name: "structure-shape", toolType: "text", + }); + makeStructureDraggable(t); + structureLayer.add(t); + structureLayer.batchDraw(); + setDirty(); + return; + } + + tempShape = shapeForTool(activeTool, snap(pos.x), snap(pos.y), 0, 0); + structureLayer.add(tempShape); + }); + + stage.on("mousemove touchmove", (e) => { + if (!isDrawing || !tempShape) return; + const pos = stagePos(e); + const w = snap(pos.x) - drawStart.x; + const h = snap(pos.y) - drawStart.y; + + if (activeTool === "door") { + tempShape.outerRadius(Math.max(Math.abs(w), Math.abs(h))); + tempShape.rotation(w < 0 && h >= 0 ? 90 : w >= 0 && h < 0 ? -90 : w < 0 && h < 0 ? 180 : 0); + } else { + tempShape.x(Math.min(drawStart.x, drawStart.x + w)); + tempShape.y(Math.min(drawStart.y, drawStart.y + h)); + tempShape.width(Math.abs(w)); + tempShape.height(Math.abs(h)); + } + structureLayer.batchDraw(); + }); + + stage.on("mouseup touchend", () => { + if (!isDrawing || !tempShape) return; + isDrawing = false; + + const tooSmall = ( + tempShape.className !== "Arc" && + (tempShape.width() < 4 || tempShape.height() < 4) + ) || ( + tempShape.className === "Arc" && tempShape.outerRadius() < 4 + ); + + if (tooSmall) { + tempShape.destroy(); + } else { + tempShape.draggable(false); + makeStructureDraggable(tempShape); + setDirty(); + } + tempShape = null; + structureLayer.batchDraw(); + }); + + // click empty stage = deselect + stage.on("click", (e) => { + if (e.target === stage) { + deselect(); + deselectDevice(); + } + }); + + // Delete key = remove selected structure shape or device + window.addEventListener("keydown", (e) => { + if (e.key !== "Delete" && e.key !== "Backspace") return; + if (document.activeElement !== document.body) return; + if (selectedNode) { + selectedNode.destroy(); + deselect(); + structureLayer.batchDraw(); + setDirty(); + } else if (selectedDeviceGroup) { + LB.removeSelectedDevice(); + } + }); + + // ── mouse-wheel zoom ─────────────────────────────────────────────────────── + container.addEventListener("wheel", (e) => { + e.preventDefault(); + const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1; + const pointer = stage.getPointerPosition(); + const oldScale = stage.scaleX(); + const newScale = Math.min(5, Math.max(0.15, oldScale * factor)); + const mousePos = { + x: (pointer.x - stage.x()) / oldScale, + y: (pointer.y - stage.y()) / oldScale, + }; + stage.scale({ x: newScale, y: newScale }); + stage.position({ + x: pointer.x - mousePos.x * newScale, + y: pointer.y - mousePos.y * newScale, + }); + stage.batchDraw(); + drawGrid(); + }, { passive: false }); + + // pan on drag in pan mode + stage.on("dragend", () => drawGrid()); + + // ── resize ───────────────────────────────────────────────────────────────── + window.addEventListener("resize", () => { + stage.width(stageWrap.clientWidth); + stage.height(stageWrap.clientHeight); + stage.batchDraw(); + drawGrid(); + }); + + // ── toolbar mode buttons ─────────────────────────────────────────────────── + document.querySelectorAll(".tb-mode").forEach(btn => { + btn.addEventListener("click", () => applyMode(btn.dataset.mode)); + }); + + // ── sidebar tool buttons ────────────────────────────────────────────────── + document.querySelectorAll(".lb-tool-btn[data-tool]").forEach(btn => { + btn.addEventListener("click", () => selectTool(btn.dataset.tool)); + }); + + // ── build device palette sidebar ───────────────────────────────────────── + function buildDevicePalette() { + const list = document.getElementById("lb-device-list"); + list.innerHTML = ""; + + if (!CFG.boards || CFG.boards.length === 0) { + list.innerHTML = '

No boards configured.

'; + return; + } + + CFG.boards.forEach(board => { + const grp = document.createElement("div"); + grp.className = "lb-board-group"; + + const hdr = document.createElement("div"); + hdr.className = "lb-board-name"; + hdr.innerHTML = ` ${escHtml(board.name)}`; + grp.appendChild(hdr); + + let hasAny = false; + + board.relays.forEach(r => { + grp.appendChild(makeChip(board, "relay", r)); + hasAny = true; + }); + board.inputs.forEach(inp => { + grp.appendChild(makeChip(board, "input", inp)); + hasAny = true; + }); + // ── Sonoff sub-devices ─────────────────────────────────────────── + if (board.sonoff_channels && board.sonoff_channels.length) { + board.sonoff_channels.forEach(sc => { + grp.appendChild(makeSonoffChip(board, sc)); + hasAny = true; + }); + } + + if (hasAny) list.appendChild(grp); + }); + } + + function escHtml(s) { + return s.replace(/&/g,"&").replace(//g,">"); + } + + function makeChip(board, entityType, entity) { + const chip = document.createElement("div"); + chip.className = "lb-device-chip"; + chip.draggable = true; + chip.dataset.boardId = board.id; + chip.dataset.entityType = entityType; + chip.dataset.entityNum = entity.num; + chip.dataset.name = entity.name; + chip.dataset.boardName = board.name; + chip.dataset.onColor = entity.onColor || entity.activeColor || "warning"; + chip.dataset.offColor = entity.offColor || entity.idleColor || "secondary"; + chip.dataset.isOn = entityType === "relay" + ? entity.isOn + : !entity.rawState; + + const dotColor = colorForState(chip.dataset.isOn === "true", + chip.dataset.onColor, chip.dataset.offColor); + + chip.innerHTML = ` + + + ${escHtml(entity.name)} + ${entityType}`; + + // ── HTML5 drag from sidebar onto canvas ───────────────────────────────── + chip.addEventListener("dragstart", (e) => { + e.dataTransfer.effectAllowed = "copy"; + e.dataTransfer.setData("application/json", JSON.stringify({ + boardId: parseInt(board.id), + entityType, + entityNum: entity.num, + name: entity.name, + boardName: board.name, + onColor: chip.dataset.onColor, + offColor: chip.dataset.offColor, + isOn: chip.dataset.isOn === "true", + })); + }); + + return chip; + } + + function makeSonoffChip(board, sc) { + const chip = document.createElement("div"); + chip.className = "lb-device-chip"; + chip.draggable = true; + chip.dataset.boardId = board.id; + chip.dataset.entityType = "sonoff"; + chip.dataset.deviceId = sc.deviceId; + chip.dataset.channel = sc.channel; + chip.dataset.name = sc.name; + chip.dataset.boardName = board.name; + chip.dataset.isOn = sc.isOn; + + const onColor = sc.kind === "light" ? "warning" : "success"; + const dotColor = colorForState(sc.isOn, onColor, "secondary"); + + chip.innerHTML = ` + + + ${escHtml(sc.name)} + ${escHtml(sc.kind || 'switch')}`; + + chip.addEventListener("dragstart", (e) => { + e.dataTransfer.effectAllowed = "copy"; + e.dataTransfer.setData("application/json", JSON.stringify({ + boardId: parseInt(board.id), + entityType: "sonoff", + entityNum: null, + deviceId: sc.deviceId, + channel: sc.channel, + name: sc.name, + boardName: board.name, + onColor, + offColor: "secondary", + isOn: sc.isOn, + })); + }); + + return chip; + } + + // ── canvas drop target ──────────────────────────────────────────────────── + const ghost = document.getElementById("lb-drag-ghost"); + + container.addEventListener("dragover", (e) => { + if (mode !== "devices") return; + e.preventDefault(); + e.dataTransfer.dropEffect = "copy"; + container.classList.add("drag-over"); + ghost.style.display = "block"; + ghost.style.left = (e.clientX + 14) + "px"; + ghost.style.top = (e.clientY + 14) + "px"; + ghost.textContent = "+ Drop device here"; + }); + + container.addEventListener("dragleave", () => { + container.classList.remove("drag-over"); + ghost.style.display = "none"; + }); + + container.addEventListener("drop", (e) => { + e.preventDefault(); + container.classList.remove("drag-over"); + ghost.style.display = "none"; + if (mode !== "devices") return; + + let dragged; + try { dragged = JSON.parse(e.dataTransfer.getData("application/json")); } + catch (_) { return; } + + // convert page coords → stage coords + const rect = container.getBoundingClientRect(); + const sc = stage.scaleX(); + const off = stage.position(); + const stageX = (e.clientX - rect.left - off.x) / sc; + const stageY = (e.clientY - rect.top - off.y) / sc; + + const stateColor = colorForState(dragged.isOn, dragged.onColor, dragged.offColor); + + const group = createDeviceGroup({ + id: uid(), + boardId: dragged.boardId, + entityType: dragged.entityType, + entityNum: dragged.entityNum || null, + // Sonoff-specific + deviceId: dragged.deviceId || null, + channel: dragged.channel != null ? dragged.channel : null, + x: stageX, + y: stageY, + name: dragged.name, + boardName: dragged.boardName, + stateColor, + isRelay: dragged.entityType === "relay" || dragged.entityType === "sonoff", + }); + + deviceLayer.add(group); + deviceLayer.batchDraw(); + setDirty(); + }); + + // ── socket.io live updates ──────────────────────────────────────────────── + // Re-use the socket already created by the base template + if (typeof io !== "undefined") { + const socket = io(); + + socket.on("board_update", (data) => { + const bid = data.board_id; + + deviceLayer.find(".device-group").forEach(group => { + if (group.attrs.boardId !== bid) return; + + if (group.attrs.entityType === "relay" && data.relay_states) { + const isOn = data.relay_states[`relay_${group.attrs.entityNum}`]; + // find onColor / offColor from CFG + const board = CFG.boards.find(b => b.id === bid); + const relay = board && board.relays.find(r => r.num === group.attrs.entityNum); + if (relay) { + updateDeviceState(group, isOn, relay.onColor, relay.offColor); + } + } + + if (group.attrs.entityType === "input" && data.input_states) { + const rawState = data.input_states[`input_${group.attrs.entityNum}`]; + const isActive = !rawState; // NC inversion + const board = CFG.boards.find(b => b.id === bid); + const inp = board && board.inputs.find(i => i.num === group.attrs.entityNum); + if (inp) { + updateDeviceState(group, isActive, inp.activeColor, inp.idleColor); + } + } + }); + }); + + // Sonoff sub-devices emit a separate "sonoff_update" event + socket.on("sonoff_update", (data) => { + const bid = data.board_id; + + deviceLayer.find(".device-group").forEach(group => { + if (group.attrs.boardId !== bid) return; + if (group.attrs.entityType !== "sonoff") return; + if (group.attrs.deviceId !== data.device_id) return; + if (group.attrs.channel !== data.channel) return; + + const board = CFG.boards.find(b => b.id === bid); + const sc = board && board.sonoff_channels && + board.sonoff_channels.find(s => + s.deviceId === data.device_id && s.channel === data.channel); + const onColor = sc ? (sc.kind === "light" ? "warning" : "success") : "success"; + updateDeviceState(group, data.state, onColor, "secondary"); + }); + }); + } + + // ── init ─────────────────────────────────────────────────────────────────── + drawGrid(); + buildDevicePalette(); + applyMode("structure"); + selectTool("select"); + + // Load persisted data + if (CFG.canvasJson) { + loadCanvas(CFG.canvasJson); + } + + setSaveStatus(CFG.canvasJson ? "" : "New layout"); + document.getElementById("btn-clear-devices").style.display = "none"; + + // Warn on close if unsaved + window.addEventListener("beforeunload", (e) => { + if (!isDirty) return; + e.preventDefault(); + e.returnValue = ""; + }); + +})(); diff --git a/app/static/vendor/konva/konva.min.js b/app/static/vendor/konva/konva.min.js new file mode 100644 index 0000000..3265945 --- /dev/null +++ b/app/static/vendor/konva/konva.min.js @@ -0,0 +1,12 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Konva=e()}(this,(function(){"use strict"; +/* + * Konva JavaScript Framework v9.3.22 + * http://konvajs.org/ + * Licensed under the MIT + * Date: Tue Jul 08 2025 + * + * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) + * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) + * + * @license + */const t=Math.PI/180;const e="undefined"!=typeof global?global:"undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope?self:{},i={_global:e,version:"9.3.22",isBrowser:"undefined"!=typeof window&&("[object Window]"==={}.toString.call(window)||"[object global]"==={}.toString.call(window)),isUnminified:/param/.test(function(t){}.toString()),dblClickWindow:400,getAngle:e=>i.angleDeg?e*t:e,enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_fixTextRendering:!1,pixelRatio:"undefined"!=typeof window&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging:()=>i.DD.isDragging,isTransforming(){var t;return null===(t=i.Transformer)||void 0===t?void 0:t.isTransforming()},isDragReady:()=>!!i.DD.node,releaseCanvasOnDestroy:!0,document:e.document,_injectGlobal(t){e.Konva=t}},n=t=>{i[t.prototype.getClassName()]=t};i._injectGlobal(i);class s{constructor(t=[1,0,0,1,0,0]){this.dirty=!1,this.m=t&&t.slice()||[1,0,0,1,0,0]}reset(){this.m[0]=1,this.m[1]=0,this.m[2]=0,this.m[3]=1,this.m[4]=0,this.m[5]=0}copy(){return new s(this.m)}copyInto(t){t.m[0]=this.m[0],t.m[1]=this.m[1],t.m[2]=this.m[2],t.m[3]=this.m[3],t.m[4]=this.m[4],t.m[5]=this.m[5]}point(t){const e=this.m;return{x:e[0]*t.x+e[2]*t.y+e[4],y:e[1]*t.x+e[3]*t.y+e[5]}}translate(t,e){return this.m[4]+=this.m[0]*t+this.m[2]*e,this.m[5]+=this.m[1]*t+this.m[3]*e,this}scale(t,e){return this.m[0]*=t,this.m[1]*=t,this.m[2]*=e,this.m[3]*=e,this}rotate(t){const e=Math.cos(t),i=Math.sin(t),n=this.m[0]*e+this.m[2]*i,s=this.m[1]*e+this.m[3]*i,r=this.m[0]*-i+this.m[2]*e,a=this.m[1]*-i+this.m[3]*e;return this.m[0]=n,this.m[1]=s,this.m[2]=r,this.m[3]=a,this}getTranslation(){return{x:this.m[4],y:this.m[5]}}skew(t,e){const i=this.m[0]+this.m[2]*e,n=this.m[1]+this.m[3]*e,s=this.m[2]+this.m[0]*t,r=this.m[3]+this.m[1]*t;return this.m[0]=i,this.m[1]=n,this.m[2]=s,this.m[3]=r,this}multiply(t){const e=this.m[0]*t.m[0]+this.m[2]*t.m[1],i=this.m[1]*t.m[0]+this.m[3]*t.m[1],n=this.m[0]*t.m[2]+this.m[2]*t.m[3],s=this.m[1]*t.m[2]+this.m[3]*t.m[3],r=this.m[0]*t.m[4]+this.m[2]*t.m[5]+this.m[4],a=this.m[1]*t.m[4]+this.m[3]*t.m[5]+this.m[5];return this.m[0]=e,this.m[1]=i,this.m[2]=n,this.m[3]=s,this.m[4]=r,this.m[5]=a,this}invert(){const t=1/(this.m[0]*this.m[3]-this.m[1]*this.m[2]),e=this.m[3]*t,i=-this.m[1]*t,n=-this.m[2]*t,s=this.m[0]*t,r=t*(this.m[2]*this.m[5]-this.m[3]*this.m[4]),a=t*(this.m[1]*this.m[4]-this.m[0]*this.m[5]);return this.m[0]=e,this.m[1]=i,this.m[2]=n,this.m[3]=s,this.m[4]=r,this.m[5]=a,this}getMatrix(){return this.m}decompose(){const t=this.m[0],e=this.m[1],i=this.m[2],n=this.m[3],s=t*n-e*i,r={x:this.m[4],y:this.m[5],rotation:0,scaleX:0,scaleY:0,skewX:0,skewY:0};if(0!=t||0!=e){const a=Math.sqrt(t*t+e*e);r.rotation=e>0?Math.acos(t/a):-Math.acos(t/a),r.scaleX=a,r.scaleY=s/a,r.skewX=(t*i+e*n)/s,r.skewY=0}else if(0!=i||0!=n){const a=Math.sqrt(i*i+n*n);r.rotation=Math.PI/2-(n>0?Math.acos(-i/a):-Math.acos(i/a)),r.scaleX=s/a,r.scaleY=a,r.skewX=0,r.skewY=(t*i+e*n)/s}return r.rotation=g._getRotation(r.rotation),r}}const r=Math.PI/180,a=180/Math.PI,o="Konva error: ",h={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,132,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,255,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,203],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[119,128,144],slategrey:[119,128,144],snow:[255,255,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],transparent:[255,255,255,0],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,5]},l=/rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/;let d=[];const c="undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||function(t){setTimeout(t,60)},g={_isElement:t=>!(!t||1!=t.nodeType),_isFunction:t=>!!(t&&t.constructor&&t.call&&t.apply),_isPlainObject:t=>!!t&&t.constructor===Object,_isArray:t=>"[object Array]"===Object.prototype.toString.call(t),_isNumber:t=>"[object Number]"===Object.prototype.toString.call(t)&&!isNaN(t)&&isFinite(t),_isString:t=>"[object String]"===Object.prototype.toString.call(t),_isBoolean:t=>"[object Boolean]"===Object.prototype.toString.call(t),isObject:t=>t instanceof Object,isValidSelector(t){if("string"!=typeof t)return!1;const e=t[0];return"#"===e||"."===e||e===e.toUpperCase()},_sign:t=>0===t||t>0?1:-1,requestAnimFrame(t){d.push(t),1===d.length&&c((function(){const t=d;d=[],t.forEach((function(t){t()}))}))},createCanvasElement(){const t=document.createElement("canvas");try{t.style=t.style||{}}catch(t){}return t},createImageElement:()=>document.createElement("img"),_isInDocument(t){for(;t=t.parentNode;)if(t==document)return!0;return!1},_urlToImage(t,e){const i=g.createImageElement();i.onload=function(){e(i)},i.src=t},_rgbToHex:(t,e,i)=>((1<<24)+(t<<16)+(e<<8)+i).toString(16).slice(1),_hexToRgb(t){t=t.replace("#","");const e=parseInt(t,16);return{r:e>>16&255,g:e>>8&255,b:255&e}},getRandomColor(){let t=(16777215*Math.random()|0).toString(16);for(;t.length<6;)t="0"+t;return"#"+t},getRGB(t){let e;return t in h?(e=h[t],{r:e[0],g:e[1],b:e[2]}):"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=l.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:t=>(t=t||"black",g._namedColorToRBA(t)||g._hex3ColorToRGBA(t)||g._hex4ColorToRGBA(t)||g._hex6ColorToRGBA(t)||g._hex8ColorToRGBA(t)||g._rgbColorToRGBA(t)||g._rgbaColorToRGBA(t)||g._hslColorToRGBA(t)),_namedColorToRBA(t){const e=h[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA(t){if(0===t.indexOf("rgb(")){const e=(t=t.match(/rgb\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA(t){if(0===t.indexOf("rgba(")){const e=(t=t.match(/rgba\(([^)]+)\)/)[1]).split(/ *, */).map(((t,e)=>"%"===t.slice(-1)?3===e?parseInt(t)/100:parseInt(t)/100*255:Number(t)));return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex8ColorToRGBA(t){if("#"===t[0]&&9===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:parseInt(t.slice(7,9),16)/255}},_hex6ColorToRGBA(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex4ColorToRGBA(t){if("#"===t[0]&&5===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:parseInt(t[4]+t[4],16)/255}},_hex3ColorToRGBA(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},_hslColorToRGBA(t){if(/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.test(t)){const[e,...i]=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(t),n=Number(i[0])/360,s=Number(i[1])/100,r=Number(i[2])/100;let a,o,h;if(0===s)return h=255*r,{r:Math.round(h),g:Math.round(h),b:Math.round(h),a:1};a=r<.5?r*(1+s):r+s-r*s;const l=2*r-a,d=[0,0,0];for(let t=0;t<3;t++)o=n+1/3*-(t-1),o<0&&o++,o>1&&o--,h=6*o<1?l+6*(a-l)*o:2*o<1?a:3*o<2?l+(a-l)*(2/3-o)*6:l,d[t]=255*h;return{r:Math.round(d[0]),g:Math.round(d[1]),b:Math.round(d[2]),a:1}}},haveIntersection:(t,e)=>!(e.x>t.x+t.width||e.x+e.widtht.y+t.height||e.y+e.heightt.slice(0),degToRad:t=>t*r,radToDeg:t=>t*a,_degToRad:t=>(g.warn("Util._degToRad is removed. Please use public Util.degToRad instead."),g.degToRad(t)),_radToDeg:t=>(g.warn("Util._radToDeg is removed. Please use public Util.radToDeg instead."),g.radToDeg(t)),_getRotation:t=>i.angleDeg?g.radToDeg(t):t,_capitalize:t=>t.charAt(0).toUpperCase()+t.slice(1),throw(t){throw new Error(o+t)},error(t){console.error(o+t)},warn(t){i.showWarnings&&console.warn("Konva warning: "+t)},each(t,e){for(const i in t)e(i,t[i])},_inRange:(t,e,i)=>e<=t&&t1?(a=i,o=n,h=(i-s)*(i-s)+(n-r)*(n-r)):(a=t+d*(i-t),o=e+d*(n-e),h=(a-s)*(a-s)+(o-r)*(o-r))}return[a,o,h]},_getProjectionToLine(t,e,i){const n=g.cloneObject(t);let s=Number.MAX_VALUE;return e.forEach((function(r,a){if(!i&&a===e.length-1)return;const o=e[(a+1)%e.length],h=g._getProjectionToSegment(r.x,r.y,o.x,o.y,t.x,t.y),l=h[0],d=h[1],c=h[2];ce.length){const i=e;e=t,t=i}for(let e=0;et.touches?t.changedTouches[0].identifier:t.pointerId||999,releaseCanvas(...t){i.releaseCanvasOnDestroy&&t.forEach((t=>{t.width=0,t.height=0}))},drawRoundedRectPath(t,e,i,n){let s=0,r=0,a=0,o=0;"number"==typeof n?s=r=a=o=Math.min(n,e/2,i/2):(s=Math.min(n[0]||0,e/2,i/2),r=Math.min(n[1]||0,e/2,i/2),o=Math.min(n[2]||0,e/2,i/2),a=Math.min(n[3]||0,e/2,i/2)),t.moveTo(s,0),t.lineTo(e-r,0),t.arc(e-r,r,r,3*Math.PI/2,0,!1),t.lineTo(e,i-o),t.arc(e-o,i-o,o,0,Math.PI/2,!1),t.lineTo(a,i),t.arc(a,i-a,a,Math.PI/2,Math.PI,!1),t.lineTo(0,s),t.arc(s,s,s,Math.PI,3*Math.PI/2,!1)}};const u=["arc","arcTo","beginPath","bezierCurveTo","clearRect","clip","closePath","createLinearGradient","createPattern","createRadialGradient","drawImage","ellipse","fill","fillText","getImageData","createImageData","lineTo","moveTo","putImageData","quadraticCurveTo","rect","roundRect","restore","rotate","save","scale","setLineDash","setTransform","stroke","strokeText","transform","translate"];class f{constructor(t){this.canvas=t,i.enableTrace&&(this.traceArr=[],this._enableTrace())}fillShape(t){t.fillEnabled()&&this._fill(t)}_fill(t){}strokeShape(t){t.hasStroke()&&this._stroke(t)}_stroke(t){}fillStrokeShape(t){t.attrs.fillAfterStrokeEnabled?(this.strokeShape(t),this.fillShape(t)):(this.fillShape(t),this.strokeShape(t))}getTrace(t,e){let i,n,s,r,a=this.traceArr,o=a.length,h="";for(i=0;i"number"==typeof t?Math.floor(t):t))),h+="("+r.join(",")+")")):(h+=n.property,t||(h+="="+n.val)),h+=";";return h}clearTrace(){this.traceArr=[]}_trace(t){let e,i=this.traceArr;i.push(t),e=i.length,e>=100&&i.shift()}reset(){const t=this.getCanvas().getPixelRatio();this.setTransform(1*t,0,0,1*t,0,0)}getCanvas(){return this.canvas}clear(t){const e=this.getCanvas();t?this.clearRect(t.x||0,t.y||0,t.width||0,t.height||0):this.clearRect(0,0,e.getWidth()/e.pixelRatio,e.getHeight()/e.pixelRatio)}_applyLineCap(t){const e=t.attrs.lineCap;e&&this.setAttr("lineCap",e)}_applyOpacity(t){const e=t.getAbsoluteOpacity();1!==e&&this.setAttr("globalAlpha",e)}_applyLineJoin(t){const e=t.attrs.lineJoin;e&&this.setAttr("lineJoin",e)}setAttr(t,e){this._context[t]=e}arc(t,e,i,n,s,r){this._context.arc(t,e,i,n,s,r)}arcTo(t,e,i,n,s){this._context.arcTo(t,e,i,n,s)}beginPath(){this._context.beginPath()}bezierCurveTo(t,e,i,n,s,r){this._context.bezierCurveTo(t,e,i,n,s,r)}clearRect(t,e,i,n){this._context.clearRect(t,e,i,n)}clip(...t){this._context.clip.apply(this._context,t)}closePath(){this._context.closePath()}createImageData(t,e){const i=arguments;return 2===i.length?this._context.createImageData(t,e):1===i.length?this._context.createImageData(t):void 0}createLinearGradient(t,e,i,n){return this._context.createLinearGradient(t,e,i,n)}createPattern(t,e){return this._context.createPattern(t,e)}createRadialGradient(t,e,i,n,s,r){return this._context.createRadialGradient(t,e,i,n,s,r)}drawImage(t,e,i,n,s,r,a,o,h){const l=arguments,d=this._context;3===l.length?d.drawImage(t,e,i):5===l.length?d.drawImage(t,e,i,n,s):9===l.length&&d.drawImage(t,e,i,n,s,r,a,o,h)}ellipse(t,e,i,n,s,r,a,o){this._context.ellipse(t,e,i,n,s,r,a,o)}isPointInPath(t,e,i,n){return i?this._context.isPointInPath(i,t,e,n):this._context.isPointInPath(t,e,n)}fill(...t){this._context.fill.apply(this._context,t)}fillRect(t,e,i,n){this._context.fillRect(t,e,i,n)}strokeRect(t,e,i,n){this._context.strokeRect(t,e,i,n)}fillText(t,e,i,n){n?this._context.fillText(t,e,i,n):this._context.fillText(t,e,i)}measureText(t){return this._context.measureText(t)}getImageData(t,e,i,n){return this._context.getImageData(t,e,i,n)}lineTo(t,e){this._context.lineTo(t,e)}moveTo(t,e){this._context.moveTo(t,e)}rect(t,e,i,n){this._context.rect(t,e,i,n)}roundRect(t,e,i,n,s){this._context.roundRect(t,e,i,n,s)}putImageData(t,e,i){this._context.putImageData(t,e,i)}quadraticCurveTo(t,e,i,n){this._context.quadraticCurveTo(t,e,i,n)}restore(){this._context.restore()}rotate(t){this._context.rotate(t)}save(){this._context.save()}scale(t,e){this._context.scale(t,e)}setLineDash(t){this._context.setLineDash?this._context.setLineDash(t):"mozDash"in this._context?this._context.mozDash=t:"webkitLineDash"in this._context&&(this._context.webkitLineDash=t)}getLineDash(){return this._context.getLineDash()}setTransform(t,e,i,n,s,r){this._context.setTransform(t,e,i,n,s,r)}stroke(t){t?this._context.stroke(t):this._context.stroke()}strokeText(t,e,i,n){this._context.strokeText(t,e,i,n)}transform(t,e,i,n,s,r){this._context.transform(t,e,i,n,s,r)}translate(t,e){this._context.translate(t,e)}_enableTrace(){let t,e,i=this,n=u.length,s=this.setAttr;const r=function(t){let n,s=i[t];i[t]=function(){return e=function(t){const e=[],i=t.length,n=g;for(let s=0;s{"dragging"===e.dragStatus&&(t=!0)})),t},justDragged:!1,get node(){let t;return b._dragElements.forEach((e=>{t=e.node})),t},_dragElements:new Map,_drag(t){const e=[];b._dragElements.forEach(((i,n)=>{const{node:s}=i,r=s.getStage();r.setPointersPositions(t),void 0===i.pointerId&&(i.pointerId=g._getFirstPointerId(t));const a=r._changedPointerPositions.find((t=>t.id===i.pointerId));if(a){if("dragging"!==i.dragStatus){const e=s.dragDistance();if(Math.max(Math.abs(a.x-i.startPointerPos.x),Math.abs(a.y-i.startPointerPos.y)){e.fire("dragmove",{type:"dragmove",target:e,evt:t},!0)}))},_endDragBefore(t){const e=[];b._dragElements.forEach((n=>{const{node:s}=n,r=s.getStage();t&&r.setPointersPositions(t);if(!r._changedPointerPositions.find((t=>t.id===n.pointerId)))return;"dragging"!==n.dragStatus&&"stopped"!==n.dragStatus||(b.justDragged=!0,i._mouseListenClick=!1,i._touchListenClick=!1,i._pointerListenClick=!1,n.dragStatus="stopped");const a=n.node.getLayer()||n.node instanceof i.Stage&&n.node;a&&-1===e.indexOf(a)&&e.push(a)})),e.forEach((t=>{t.draw()}))},_endDragAfter(t){b._dragElements.forEach(((e,i)=>{"stopped"===e.dragStatus&&e.node.fire("dragend",{type:"dragend",target:e.node,evt:t},!0),"dragging"!==e.dragStatus&&b._dragElements.delete(i)}))}};function S(t){return g._isString(t)?'"'+t+'"':"[object Number]"===Object.prototype.toString.call(t)||g._isBoolean(t)?t:Object.prototype.toString.call(t)}function w(t){return t>255?255:t<0?0:Math.round(t)}function C(){if(i.isUnminified)return function(t,e){return g._isNumber(t)||g.warn(S(t)+' is a not valid value for "'+e+'" attribute. The value should be a number.'),t}}function P(t){if(i.isUnminified)return function(e,i){let n=g._isNumber(e),s=g._isArray(e)&&e.length==t;return n||s||g.warn(S(e)+' is a not valid value for "'+i+'" attribute. The value should be a number or Array('+t+")"),e}}function k(){if(i.isUnminified)return function(t,e){return g._isNumber(t)||"auto"===t||g.warn(S(t)+' is a not valid value for "'+e+'" attribute. The value should be a number or "auto".'),t}}function A(){if(i.isUnminified)return function(t,e){return g._isString(t)||g.warn(S(t)+' is a not valid value for "'+e+'" attribute. The value should be a string.'),t}}function T(){if(i.isUnminified)return function(t,e){const i=g._isString(t),n="[object CanvasGradient]"===Object.prototype.toString.call(t)||t&&t.addColorStop;return i||n||g.warn(S(t)+' is a not valid value for "'+e+'" attribute. The value should be a string or a native gradient.'),t}}function M(){if(i.isUnminified)return function(t,e){return!0===t||!1===t||g.warn(S(t)+' is a not valid value for "'+e+'" attribute. The value should be a boolean.'),t}}i.isBrowser&&(window.addEventListener("mouseup",b._endDragBefore,!0),window.addEventListener("touchend",b._endDragBefore,!0),window.addEventListener("touchcancel",b._endDragBefore,!0),window.addEventListener("mousemove",b._drag),window.addEventListener("touchmove",b._drag),window.addEventListener("mouseup",b._endDragAfter,!1),window.addEventListener("touchend",b._endDragAfter,!1),window.addEventListener("touchcancel",b._endDragAfter,!1));const G="get",R="set",E={addGetterSetter(t,e,i,n,s){E.addGetter(t,e,i),E.addSetter(t,e,n,s),E.addOverloadedGetterSetter(t,e)},addGetter(t,e,i){const n=G+g._capitalize(e);t.prototype[n]=t.prototype[n]||function(){const t=this.attrs[e];return void 0===t?i:t}},addSetter(t,e,i,n){const s=R+g._capitalize(e);t.prototype[s]||E.overWriteSetter(t,e,i,n)},overWriteSetter(t,e,i,n){const s=R+g._capitalize(e);t.prototype[s]=function(t){return i&&null!=t&&(t=i.call(this,t,e)),this._setAttr(e,t),n&&n.call(this),this}},addComponentsGetterSetter(t,e,n,s,r){const a=n.length,o=g._capitalize,h=G+o(e),l=R+o(e);t.prototype[h]=function(){const t={};for(let i=0;i{this._setAttr(e+o(t),void 0)})),this._fireChangeEvent(e,i,t),r&&r.call(this),this},E.addOverloadedGetterSetter(t,e)},addOverloadedGetterSetter(t,e){const i=g._capitalize(e),n=R+i,s=G+i;t.prototype[e]=function(){return arguments.length?(this[n](arguments[0]),this):this[s]()}},addDeprecatedGetterSetter(t,e,i,n){g.error("Adding deprecated "+e);const s=G+g._capitalize(e),r=e+" property is deprecated and will be removed soon. Look at Konva change log for more information.";t.prototype[s]=function(){g.error(r);const t=this.attrs[e];return void 0===t?i:t},E.addSetter(t,e,n,(function(){g.error(r)})),E.addOverloadedGetterSetter(t,e)},backCompat(t,e){g.each(e,(function(e,i){const n=t.prototype[i],s=G+g._capitalize(e),r=R+g._capitalize(e);function a(){n.apply(this,arguments),g.error('"'+e+'" method is deprecated and will be removed soon. Use ""'+i+'" instead.')}t.prototype[e]=a,t.prototype[s]=a,t.prototype[r]=a}))},afterSetFilter(){this._filterUpToDate=!1}},D="absoluteOpacity",L="allEventListeners",O="absoluteTransform",I="absoluteScale",F="canvas",B="listening",N="Shape",H=" ",z="stage",W="transform",Y="visible",X=["xChange.konva","yChange.konva","scaleXChange.konva","scaleYChange.konva","skewXChange.konva","skewYChange.konva","rotationChange.konva","offsetXChange.konva","offsetYChange.konva","transformsEnabledChange.konva"].join(H);let j=1;class q{constructor(t){this._id=j++,this.eventListeners={},this.attrs={},this.index=0,this._allEventListeners=null,this.parent=null,this._cache=new Map,this._attachedDepsListeners=new Map,this._lastPos=null,this._batchingTransformChange=!1,this._needClearTransformCache=!1,this._filterUpToDate=!1,this._isUnderCache=!1,this._dragEventId=null,this._shouldFireChangeEvents=!1,this.setAttrs(t),this._shouldFireChangeEvents=!0}hasChildren(){return!1}_clearCache(t){t!==W&&t!==O||!this._cache.get(t)?t?this._cache.delete(t):this._cache.clear():this._cache.get(t).dirty=!0}_getCache(t,e){let i=this._cache.get(t);return(void 0===i||(t===W||t===O)&&!0===i.dirty)&&(i=e.call(this),this._cache.set(t,i)),i}_calculate(t,e,i){if(!this._attachedDepsListeners.get(t)){const i=e.map((t=>t+"Change.konva")).join(H);this.on(i,(()=>{this._clearCache(t)})),this._attachedDepsListeners.set(t,!0)}return this._getCache(t,i)}_getCanvasCache(){return this._cache.get(F)}_clearSelfAndDescendantCache(t){this._clearCache(t),t===O&&this.fire("absoluteTransformChange")}clearCache(){if(this._cache.has(F)){const{scene:t,filter:e,hit:i,buffer:n}=this._cache.get(F);g.releaseCanvas(t,e,i,n),this._cache.delete(F)}return this._clearSelfAndDescendantCache(),this._requestDraw(),this}cache(t){const e=t||{};let i={};void 0!==e.x&&void 0!==e.y&&void 0!==e.width&&void 0!==e.height||(i=this.getClientRect({skipTransform:!0,relativeTo:this.getParent()||void 0}));let n=Math.ceil(e.width||i.width),s=Math.ceil(e.height||i.height),r=e.pixelRatio,a=void 0===e.x?Math.floor(i.x):e.x,o=void 0===e.y?Math.floor(i.y):e.y,h=e.offset||0,l=e.drawBorder||!1,d=e.hitCanvasPixelRatio||1;if(!n||!s)return void g.error("Can not cache the node. Width or height of the node equals 0. Caching is skipped.");n+=2*h+(Math.abs(Math.round(i.x)-a)>.5?1:0),s+=2*h+(Math.abs(Math.round(i.y)-o)>.5?1:0),a-=h,o-=h;const c=new v({pixelRatio:r,width:n,height:s}),u=new v({pixelRatio:r,width:0,height:0,willReadFrequently:!0}),f=new x({pixelRatio:d,width:n,height:s}),p=c.getContext(),m=f.getContext(),_=new v({width:c.width/c.pixelRatio+Math.abs(a),height:c.height/c.pixelRatio+Math.abs(o),pixelRatio:c.pixelRatio}),y=_.getContext();return f.isCache=!0,c.isCache=!0,this._cache.delete(F),this._filterUpToDate=!1,!1===e.imageSmoothingEnabled&&(c.getContext()._context.imageSmoothingEnabled=!1,u.getContext()._context.imageSmoothingEnabled=!1),p.save(),m.save(),y.save(),p.translate(-a,-o),m.translate(-a,-o),y.translate(-a,-o),_.x=a,_.y=o,this._isUnderCache=!0,this._clearSelfAndDescendantCache(D),this._clearSelfAndDescendantCache(I),this.drawScene(c,this,_),this.drawHit(f,this),this._isUnderCache=!1,p.restore(),m.restore(),l&&(p.save(),p.beginPath(),p.rect(0,0,n,s),p.closePath(),p.setAttr("strokeStyle","red"),p.setAttr("lineWidth",5),p.stroke(),p.restore()),this._cache.set(F,{scene:c,filter:u,hit:f,buffer:_,x:a,y:o}),this._requestDraw(),this}isCached(){return this._cache.has(F)}getClientRect(t){throw new Error('abstract "getClientRect" method call')}_transformedRect(t,e){const i=[{x:t.x,y:t.y},{x:t.x+t.width,y:t.y},{x:t.x+t.width,y:t.y+t.height},{x:t.x,y:t.y+t.height}];let n=1/0,s=1/0,r=-1/0,a=-1/0;const o=this.getAbsoluteTransform(e);return i.forEach((function(t){const e=o.point(t);void 0===n&&(n=r=e.x,s=a=e.y),n=Math.min(n,e.x),s=Math.min(s,e.y),r=Math.max(r,e.x),a=Math.max(a,e.y)})),{x:n,y:s,width:r-n,height:a-s}}_drawCachedSceneCanvas(t){t.save(),t._applyOpacity(this),t._applyGlobalCompositeOperation(this);const e=this._getCanvasCache();t.translate(e.x,e.y);const i=this._getCachedSceneCanvas(),n=i.pixelRatio;t.drawImage(i._canvas,0,0,i.width/n,i.height/n),t.restore()}_drawCachedHitCanvas(t){const e=this._getCanvasCache(),i=e.hit;t.save(),t.translate(e.x,e.y),t.drawImage(i._canvas,0,0,i.width/i.pixelRatio,i.height/i.pixelRatio),t.restore()}_getCachedSceneCanvas(){let t,e,i,n,s=this.filters(),r=this._getCanvasCache(),a=r.scene,o=r.filter,h=o.getContext();if(s){if(!this._filterUpToDate){const r=a.pixelRatio;o.setSize(a.width/a.pixelRatio,a.height/a.pixelRatio);try{for(t=s.length,h.clear(),h.drawImage(a._canvas,0,0,a.getWidth()/r,a.getHeight()/r),e=h.getImageData(0,0,o.getWidth(),o.getHeight()),i=0;i{let e,i;if(!t)return this;for(e in t)"children"!==e&&(i="set"+g._capitalize(e),g._isFunction(this[i])?this[i](t[e]):this._setAttr(e,t[e]))})),this}isListening(){return this._getCache(B,this._isListening)}_isListening(t){if(!this.listening())return!1;const e=this.getParent();return!e||e===t||this===t||e._isListening(t)}isVisible(){return this._getCache(Y,this._isVisible)}_isVisible(t){if(!this.visible())return!1;const e=this.getParent();return!e||e===t||this===t||e._isVisible(t)}shouldDrawHit(t,e=!1){if(t)return this._isVisible(t)&&this._isListening(t);const n=this.getLayer();let s=!1;b._dragElements.forEach((t=>{"dragging"===t.dragStatus&&("Stage"===t.node.nodeType||t.node.getLayer()===n)&&(s=!0)}));const r=!e&&!i.hitOnDragEnabled&&(s||i.isTransforming());return this.isListening()&&this.isVisible()&&!r}show(){return this.visible(!0),this}hide(){return this.visible(!1),this}getZIndex(){return this.index||0}getAbsoluteZIndex(){let t,e,i,n,s=this.getDepth(),r=this,a=0;const o=this.getStage();return"Stage"!==r.nodeType&&o&&function o(h){for(t=[],e=h.length,i=0;i0&&t[0].getDepth()<=s&&o(t)}(o.getChildren()),a}getDepth(){let t=0,e=this.parent;for(;e;)t++,e=e.parent;return t}_batchTransformChanges(t){this._batchingTransformChange=!0,t(),this._batchingTransformChange=!1,this._needClearTransformCache&&(this._clearCache(W),this._clearSelfAndDescendantCache(O)),this._needClearTransformCache=!1}setPosition(t){return this._batchTransformChanges((()=>{this.x(t.x),this.y(t.y)})),this}getPosition(){return{x:this.x(),y:this.y()}}getRelativePointerPosition(){const t=this.getStage();if(!t)return null;const e=t.getPointerPosition();if(!e)return null;const i=this.getAbsoluteTransform().copy();return i.invert(),i.point(e)}getAbsolutePosition(t){let e=!1,i=this.parent;for(;i;){if(i.isCached()){e=!0;break}i=i.parent}e&&!t&&(t=!0);const n=this.getAbsoluteTransform(t).getMatrix(),r=new s,a=this.offset();return r.m=n.slice(),r.translate(a.x,a.y),r.getTranslation()}setAbsolutePosition(t){const{x:e,y:i,...n}=this._clearTransform();this.attrs.x=e,this.attrs.y=i,this._clearCache(W);const s=this._getAbsoluteTransform().copy();return s.invert(),s.translate(t.x,t.y),t={x:this.attrs.x+s.getTranslation().x,y:this.attrs.y+s.getTranslation().y},this._setTransform(n),this.setPosition({x:t.x,y:t.y}),this._clearCache(W),this._clearSelfAndDescendantCache(O),this}_setTransform(t){let e;for(e in t)this.attrs[e]=t[e]}_clearTransform(){const t={x:this.x(),y:this.y(),rotation:this.rotation(),scaleX:this.scaleX(),scaleY:this.scaleY(),offsetX:this.offsetX(),offsetY:this.offsetY(),skewX:this.skewX(),skewY:this.skewY()};return this.attrs.x=0,this.attrs.y=0,this.attrs.rotation=0,this.attrs.scaleX=1,this.attrs.scaleY=1,this.attrs.offsetX=0,this.attrs.offsetY=0,this.attrs.skewX=0,this.attrs.skewY=0,t}move(t){let e=t.x,i=t.y,n=this.x(),s=this.y();return void 0!==e&&(n+=e),void 0!==i&&(s+=i),this.setPosition({x:n,y:s}),this}_eachAncestorReverse(t,e){let i,n,s=[],r=this.getParent();if(!e||e._id!==this._id){for(s.unshift(this);r&&(!e||r._id!==e._id);)s.unshift(r),r=r.parent;for(i=s.length,n=0;n0&&(this.parent.children.splice(t,1),this.parent.children.splice(t-1,0,this),this.parent._setChildrenIndices(),!0)}moveToBottom(){if(!this.parent)return g.warn("Node has no parent. moveToBottom function is ignored."),!1;const t=this.index;return t>0&&(this.parent.children.splice(t,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0)}setZIndex(t){if(!this.parent)return g.warn("Node has no parent. zIndex parameter is ignored."),this;(t<0||t>=this.parent.children.length)&&g.warn("Unexpected value "+t+" for zIndex property. zIndex is just index of a node in children of its parent. Expected value is from 0 to "+(this.parent.children.length-1)+".");const e=this.index;return this.parent.children.splice(e,1),this.parent.children.splice(t,0,this),this.parent._setChildrenIndices(),this}getAbsoluteOpacity(){return this._getCache(D,this._getAbsoluteOpacity)}_getAbsoluteOpacity(){let t=this.opacity();const e=this.getParent();return e&&!e._isUnderCache&&(t*=e.getAbsoluteOpacity()),t}moveTo(t){return this.getParent()!==t&&(this._remove(),t.add(this)),this}toObject(){let t,e,i,n,s,r=this.getAttrs();const a={attrs:{},className:this.getClassName()};for(t in r)e=r[t],s=g.isObject(e)&&!g._isPlainObject(e)&&!g._isArray(e),s||(i="function"==typeof this[t]&&this[t],delete r[t],n=i?i.call(this):null,r[t]=e,n!==e&&(a.attrs[t]=e));return g._prepareToStringify(a)}toJSON(){return JSON.stringify(this.toObject())}getParent(){return this.parent}findAncestors(t,e,i){const n=[];e&&this._isMatch(t)&&n.push(this);let s=this.parent;for(;s;){if(s===i)return n;s._isMatch(t)&&n.push(s),s=s.parent}return n}isAncestorOf(t){return!1}findAncestor(t,e,i){return this.findAncestors(t,e,i)[0]}_isMatch(t){if(!t)return!1;if("function"==typeof t)return t(this);let e,i,n=t.replace(/ /g,"").split(","),s=n.length;for(e=0;e{try{const i=null==t?void 0:t.callback;i&&delete t.callback,g._urlToImage(this.toDataURL(t),(function(t){e(t),null==i||i(t)}))}catch(t){i(t)}}))}toBlob(t){return new Promise(((e,i)=>{try{const i=null==t?void 0:t.callback;i&&delete t.callback,this.toCanvas(t).toBlob((t=>{e(t),null==i||i(t)}),null==t?void 0:t.mimeType,null==t?void 0:t.quality)}catch(t){i(t)}}))}setSize(t){return this.width(t.width),this.height(t.height),this}getSize(){return{width:this.width(),height:this.height()}}getClassName(){return this.className||this.nodeType}getType(){return this.nodeType}getDragDistance(){return void 0!==this.attrs.dragDistance?this.attrs.dragDistance:this.parent?this.parent.getDragDistance():i.dragDistance}_off(t,e,i){let n,s,r,a=this.eventListeners[t];for(n=0;n=0))return;if(this.isDragging())return;let e=!1;b._dragElements.forEach((t=>{this.isAncestorOf(t.node)&&(e=!0)})),e||this._createDragElement(t)}))}_dragChange(){if(this.attrs.draggable)this._listenDrag();else{this._dragCleanup();if(!this.getStage())return;const t=b._dragElements.get(this._id),e=t&&"dragging"===t.dragStatus,i=t&&"ready"===t.dragStatus;e?this.stopDrag():i&&b._dragElements.delete(this._id)}}_dragCleanup(){this.off("mousedown.konva"),this.off("touchstart.konva")}isClientRectOnScreen(t={x:0,y:0}){const e=this.getStage();if(!e)return!1;const i={x:-t.x,y:-t.y,width:e.width()+2*t.x,height:e.height()+2*t.y};return g.haveIntersection(i,this.getClientRect())}static create(t,e){return g._isString(t)&&(t=JSON.parse(t)),this._createNode(t,e)}static _createNode(t,e){let n,s,r,a=q.prototype.getClassName.call(t),o=t.children;e&&(t.attrs.container=e),i[a]||(g.warn('Can not find a node with class name "'+a+'". Fallback to "Shape".'),a="Shape");if(n=new(0,i[a])(t.attrs),o)for(s=o.length,r=0;r0}removeChildren(){return this.getChildren().forEach((t=>{t.parent=null,t.index=0,t.remove()})),this.children=[],this._requestDraw(),this}destroyChildren(){return this.getChildren().forEach((t=>{t.parent=null,t.index=0,t.destroy()})),this.children=[],this._requestDraw(),this}add(...t){if(0===t.length)return this;if(t.length>1){for(let e=0;e0?e[0]:void 0}_generalFind(t,e){const i=[];return this._descendants((n=>{const s=n._isMatch(t);return s&&i.push(n),!(!s||!e)})),i}_descendants(t){let e=!1;const i=this.getChildren();for(const n of i){if(e=t(n),e)return!0;if(n.hasChildren()&&(e=n._descendants(t),e))return!0}return!1}toObject(){const t=q.prototype.toObject.call(this);return t.children=[],this.getChildren().forEach((e=>{t.children.push(e.toObject())})),t}isAncestorOf(t){let e=t.getParent();for(;e;){if(e._id===this._id)return!0;e=e.getParent()}return!1}clone(t){const e=q.prototype.clone.call(this,t);return this.getChildren().forEach((function(t){e.add(t.clone())})),e}getAllIntersections(t){const e=[];return this.find("Shape").forEach((i=>{i.isVisible()&&i.intersects(t)&&e.push(i)})),e}_clearSelfAndDescendantCache(t){var e;super._clearSelfAndDescendantCache(t),this.isCached()||null===(e=this.children)||void 0===e||e.forEach((function(e){e._clearSelfAndDescendantCache(t)}))}_setChildrenIndices(){var t;null===(t=this.children)||void 0===t||t.forEach((function(t,e){t.index=e})),this._requestDraw()}drawScene(t,e,i){const n=this.getLayer(),s=t||n&&n.getCanvas(),r=s&&s.getContext(),a=this._getCanvasCache(),o=a&&a.scene,h=s&&s.isCache;if(!this.isVisible()&&!h)return this;if(o){r.save();const t=this.getAbsoluteTransform(e).getMatrix();r.transform(t[0],t[1],t[2],t[3],t[4],t[5]),this._drawCachedSceneCanvas(r),r.restore()}else this._drawChildren("drawScene",s,e,i);return this}drawHit(t,e){if(!this.shouldDrawHit(e))return this;const i=this.getLayer(),n=t||i&&i.hitCanvas,s=n&&n.getContext(),r=this._getCanvasCache();if(r&&r.hit){s.save();const t=this.getAbsoluteTransform(e).getMatrix();s.transform(t[0],t[1],t[2],t[3],t[4],t[5]),this._drawCachedHitCanvas(s),s.restore()}else this._drawChildren("drawHit",n,e);return this}_drawChildren(t,e,i,n){var s;const r=e&&e.getContext(),a=this.clipWidth(),o=this.clipHeight(),h=this.clipFunc(),l="number"==typeof a&&"number"==typeof o||h,d=i===this;if(l){r.save();const t=this.getAbsoluteTransform(i);let e,n=t.getMatrix();if(r.transform(n[0],n[1],n[2],n[3],n[4],n[5]),r.beginPath(),h)e=h.call(this,r,this);else{const t=this.clipX(),e=this.clipY();r.rect(t||0,e||0,a,o)}r.clip.apply(r,e),n=t.copy().invert().getMatrix(),r.transform(n[0],n[1],n[2],n[3],n[4],n[5])}const c=!d&&"source-over"!==this.globalCompositeOperation()&&"drawScene"===t;c&&(r.save(),r._applyGlobalCompositeOperation(this)),null===(s=this.children)||void 0===s||s.forEach((function(s){s[t](e,i,n)})),c&&r.restore(),l&&r.restore()}getClientRect(t={}){var e;const i=t.skipTransform,n=t.relativeTo;let s,r,a,o,h={x:1/0,y:1/0,width:0,height:0};const l=this;null===(e=this.children)||void 0===e||e.forEach((function(e){if(!e.visible())return;const i=e.getClientRect({relativeTo:l,skipShadow:t.skipShadow,skipStroke:t.skipStroke});0===i.width&&0===i.height||(void 0===s?(s=i.x,r=i.y,a=i.x+i.width,o=i.y+i.height):(s=Math.min(s,i.x),r=Math.min(r,i.y),a=Math.max(a,i.x+i.width),o=Math.max(o,i.y+i.height)))}));const d=this.find("Shape");let c=!1;for(let t=0;tt.indexOf("pointer")>=0?"pointer":t.indexOf("touch")>=0?"touch":"mouse",Pt=t=>{const e=Ct(t);return"pointer"===e?i.pointerEventsEnabled&&wt.pointer:"touch"===e?wt.touch:"mouse"===e?wt.mouse:void 0};function kt(t={}){return(t.clipFunc||t.clipWidth||t.clipHeight)&&g.warn("Stage does not support clipping. Please use clip for Layers or Groups."),t}const At=[];class Tt extends V{constructor(t){super(kt(t)),this._pointerPositions=[],this._changedPointerPositions=[],this._buildDOM(),this._bindContentEvents(),At.push(this),this.on("widthChange.konva heightChange.konva",this._resizeDOM),this.on("visibleChange.konva",this._checkVisibility),this.on("clipWidthChange.konva clipHeightChange.konva clipFuncChange.konva",(()=>{kt(this.attrs)})),this._checkVisibility()}_validateAdd(t){const e="Layer"===t.getType(),i="FastLayer"===t.getType();e||i||g.throw("You may only add layers to the stage.")}_checkVisibility(){if(!this.content)return;const t=this.visible()?"":"none";this.content.style.display=t}setContainer(t){if("string"==typeof t){let e;if("."===t.charAt(0)){const e=t.slice(1);t=document.getElementsByClassName(e)[0]}else e="#"!==t.charAt(0)?t:t.slice(1),t=document.getElementById(e);if(!t)throw"Can not find container in document with id "+e}return this._setAttr("container",t),this.content&&(this.content.parentElement&&this.content.parentElement.removeChild(this.content),t.appendChild(this.content)),this}shouldDrawHit(){return!0}clear(){const t=this.children,e=t.length;for(let i=0;i-1&&At.splice(e,1),g.releaseCanvas(this.bufferCanvas._canvas,this.bufferHitCanvas._canvas),this}getPointerPosition(){const t=this._pointerPositions[0]||this._changedPointerPositions[0];return t?{x:t.x,y:t.y}:(g.warn("Pointer position is missing and not registered by the stage. Looks like it is outside of the stage container. You can set it manually from event: stage.setPointersPositions(event);"),null)}_getPointerById(t){return this._pointerPositions.find((e=>e.id===t))}getPointersPositions(){return this._pointerPositions}getStage(){return this}getContent(){return this.content}_toKonvaCanvas(t){(t=t||{}).x=t.x||0,t.y=t.y||0,t.width=t.width||this.width(),t.height=t.height||this.height();const e=new v({width:t.width,height:t.height,pixelRatio:t.pixelRatio||1}),i=e.getContext()._context,n=this.children;return(t.x||t.y)&&i.translate(-1*t.x,-1*t.y),n.forEach((function(e){if(!e.isVisible())return;const n=e._toKonvaCanvas(t);i.drawImage(n._canvas,t.x,t.y,n.getWidth()/n.getPixelRatio(),n.getHeight()/n.getPixelRatio())})),e}getIntersection(t){if(!t)return null;const e=this.children;for(let i=e.length-1;i>=0;i--){const n=e[i].getIntersection(t);if(n)return n}return null}_resizeDOM(){const t=this.width(),e=this.height();this.content&&(this.content.style.width=t+"px",this.content.style.height=e+"px"),this.bufferCanvas.setSize(t,e),this.bufferHitCanvas.setSize(t,e),this.children.forEach((i=>{i.setSize({width:t,height:e}),i.draw()}))}add(t,...e){if(arguments.length>1){for(let t=0;t5&&g.warn("The stage has "+n+" layers. Recommended maximum number of layers is 3-5. Adding more layers into the stage may drop the performance. Rethink your tree structure, you can use Konva.Group."),t.setSize({width:this.width(),height:this.height()}),t.draw(),i.isBrowser&&this.content.appendChild(t.canvas._canvas),this}getParent(){return null}getLayer(){return null}hasPointerCapture(t){return Z(t,this)}setPointerCapture(t){tt(t,this)}releaseCapture(t){et(t)}getLayers(){return this.children}_bindContentEvents(){i.isBrowser&&St.forEach((([t,e])=>{this.content.addEventListener(t,(t=>{this[e](t)}),{passive:!1})}))}_pointerenter(t){this.setPointersPositions(t);const e=Pt(t.type);e&&this._fire(e.pointerenter,{evt:t,target:this,currentTarget:this})}_pointerover(t){this.setPointersPositions(t);const e=Pt(t.type);e&&this._fire(e.pointerover,{evt:t,target:this,currentTarget:this})}_getTargetShape(t){let e=this[t+"targetShape"];return e&&!e.getStage()&&(e=null),e}_pointerleave(t){const e=Pt(t.type),n=Ct(t.type);if(!e)return;this.setPointersPositions(t);const s=this._getTargetShape(n),r=!(i.isDragging()||i.isTransforming())||i.hitOnDragEnabled;s&&r?(s._fireAndBubble(e.pointerout,{evt:t}),s._fireAndBubble(e.pointerleave,{evt:t}),this._fire(e.pointerleave,{evt:t,target:this,currentTarget:this}),this[n+"targetShape"]=null):r&&(this._fire(e.pointerleave,{evt:t,target:this,currentTarget:this}),this._fire(e.pointerout,{evt:t,target:this,currentTarget:this})),this.pointerPos=null,this._pointerPositions=[]}_pointerdown(t){const e=Pt(t.type),n=Ct(t.type);if(!e)return;this.setPointersPositions(t);let s=!1;this._changedPointerPositions.forEach((r=>{const a=this.getIntersection(r);if(b.justDragged=!1,i["_"+n+"ListenClick"]=!0,!a||!a.isListening())return void(this[n+"ClickStartShape"]=void 0);i.capturePointerEventsEnabled&&a.setPointerCapture(r.id),this[n+"ClickStartShape"]=a,a._fireAndBubble(e.pointerdown,{evt:t,pointerId:r.id}),s=!0;const o=t.type.indexOf("touch")>=0;a.preventDefault()&&t.cancelable&&o&&t.preventDefault()})),s||this._fire(e.pointerdown,{evt:t,target:this,currentTarget:this,pointerId:this._pointerPositions[0].id})}_pointermove(t){const e=Pt(t.type),n=Ct(t.type);if(!e)return;i.isDragging()&&b.node.preventDefault()&&t.cancelable&&t.preventDefault(),this.setPointersPositions(t);if(!(!(i.isDragging()||i.isTransforming())||i.hitOnDragEnabled))return;const s={};let r=!1;const a=this._getTargetShape(n);this._changedPointerPositions.forEach((i=>{const o=J(i.id)||this.getIntersection(i),h=i.id,l={evt:t,pointerId:h},d=a!==o;if(d&&a&&(a._fireAndBubble(e.pointerout,{...l},o),a._fireAndBubble(e.pointerleave,{...l},o)),o){if(s[o._id])return;s[o._id]=!0}o&&o.isListening()?(r=!0,d&&(o._fireAndBubble(e.pointerover,{...l},a),o._fireAndBubble(e.pointerenter,{...l},a),this[n+"targetShape"]=o),o._fireAndBubble(e.pointermove,{...l})):a&&(this._fire(e.pointerover,{evt:t,target:this,currentTarget:this,pointerId:h}),this[n+"targetShape"]=null)})),r||this._fire(e.pointermove,{evt:t,target:this,currentTarget:this,pointerId:this._changedPointerPositions[0].id})}_pointerup(t){const e=Pt(t.type),n=Ct(t.type);if(!e)return;this.setPointersPositions(t);const s=this[n+"ClickStartShape"],r=this[n+"ClickEndShape"],a={};let o=!1;this._changedPointerPositions.forEach((h=>{const l=J(h.id)||this.getIntersection(h);if(l){if(l.releaseCapture(h.id),a[l._id])return;a[l._id]=!0}const d=h.id,c={evt:t,pointerId:d};let g=!1;i["_"+n+"InDblClickWindow"]?(g=!0,clearTimeout(this[n+"DblTimeout"])):b.justDragged||(i["_"+n+"InDblClickWindow"]=!0,clearTimeout(this[n+"DblTimeout"])),this[n+"DblTimeout"]=setTimeout((function(){i["_"+n+"InDblClickWindow"]=!1}),i.dblClickWindow),l&&l.isListening()?(o=!0,this[n+"ClickEndShape"]=l,l._fireAndBubble(e.pointerup,{...c}),i["_"+n+"ListenClick"]&&s&&s===l&&(l._fireAndBubble(e.pointerclick,{...c}),g&&r&&r===l&&l._fireAndBubble(e.pointerdblclick,{...c}))):(this[n+"ClickEndShape"]=null,i["_"+n+"ListenClick"]&&this._fire(e.pointerclick,{evt:t,target:this,currentTarget:this,pointerId:d}),g&&this._fire(e.pointerdblclick,{evt:t,target:this,currentTarget:this,pointerId:d}))})),o||this._fire(e.pointerup,{evt:t,target:this,currentTarget:this,pointerId:this._changedPointerPositions[0].id}),i["_"+n+"ListenClick"]=!1,t.cancelable&&"touch"!==n&&"pointer"!==n&&t.preventDefault()}_contextmenu(t){this.setPointersPositions(t);const e=this.getIntersection(this.getPointerPosition());e&&e.isListening()?e._fireAndBubble(mt,{evt:t}):this._fire(mt,{evt:t,target:this,currentTarget:this})}_wheel(t){this.setPointersPositions(t);const e=this.getIntersection(this.getPointerPosition());e&&e.isListening()?e._fireAndBubble(bt,{evt:t}):this._fire(bt,{evt:t,target:this,currentTarget:this})}_pointercancel(t){this.setPointersPositions(t);const e=J(t.pointerId)||this.getIntersection(this.getPointerPosition());e&&e._fireAndBubble(dt,$(t)),et(t.pointerId)}_lostpointercapture(t){et(t.pointerId)}setPointersPositions(t){const e=this._getContentPosition();let i=null,n=null;void 0!==(t=t||window.event).touches?(this._pointerPositions=[],this._changedPointerPositions=[],Array.prototype.forEach.call(t.touches,(t=>{this._pointerPositions.push({id:t.identifier,x:(t.clientX-e.left)/e.scaleX,y:(t.clientY-e.top)/e.scaleY})})),Array.prototype.forEach.call(t.changedTouches||t.touches,(t=>{this._changedPointerPositions.push({id:t.identifier,x:(t.clientX-e.left)/e.scaleX,y:(t.clientY-e.top)/e.scaleY})}))):(i=(t.clientX-e.left)/e.scaleX,n=(t.clientY-e.top)/e.scaleY,this.pointerPos={x:i,y:n},this._pointerPositions=[{x:i,y:n,id:g._getFirstPointerId(t)}],this._changedPointerPositions=[{x:i,y:n,id:g._getFirstPointerId(t)}])}_setPointerPosition(t){g.warn('Method _setPointerPosition is deprecated. Use "stage.setPointersPositions(event)" instead.'),this.setPointersPositions(t)}_getContentPosition(){if(!this.content||!this.content.getBoundingClientRect)return{top:0,left:0,scaleX:1,scaleY:1};const t=this.content.getBoundingClientRect();return{top:t.top,left:t.left,scaleX:t.width/this.content.clientWidth||1,scaleY:t.height/this.content.clientHeight||1}}_buildDOM(){if(this.bufferCanvas=new v({width:this.width(),height:this.height()}),this.bufferHitCanvas=new x({pixelRatio:1,width:this.width(),height:this.height()}),!i.isBrowser)return;const t=this.container();if(!t)throw"Stage has no container. A container is required.";t.innerHTML="",this.content=document.createElement("div"),this.content.style.position="relative",this.content.style.userSelect="none",this.content.className="konvajs-content",this.content.setAttribute("role","presentation"),t.appendChild(this.content),this._resizeDOM()}cache(){return g.warn("Cache function is not allowed for stage. You may use cache only for layers, groups and shapes."),this}clearCache(){return this}batchDraw(){return this.getChildren().forEach((function(t){t.batchDraw()})),this}}Tt.prototype.nodeType="Stage",n(Tt),E.addGetterSetter(Tt,"container"),i.isBrowser&&document.addEventListener("visibilitychange",(()=>{At.forEach((t=>{t.batchDraw()}))}));const Mt="hasShadow",Gt="shadowRGBA",Rt="patternImage",Et="linearGradient",Dt="radialGradient";let Lt;function Ot(){return Lt||(Lt=g.createCanvasElement().getContext("2d"),Lt)}const It={};class Ft extends q{constructor(t){let e;for(super(t);e=g.getRandomColor(),!e||e in It;);this.colorKey=e,It[e]=this}getContext(){return g.warn("shape.getContext() method is deprecated. Please do not use it."),this.getLayer().getContext()}getCanvas(){return g.warn("shape.getCanvas() method is deprecated. Please do not use it."),this.getLayer().getCanvas()}getSceneFunc(){return this.attrs.sceneFunc||this._sceneFunc}getHitFunc(){return this.attrs.hitFunc||this._hitFunc}hasShadow(){return this._getCache(Mt,this._hasShadow)}_hasShadow(){return this.shadowEnabled()&&0!==this.shadowOpacity()&&!!(this.shadowColor()||this.shadowBlur()||this.shadowOffsetX()||this.shadowOffsetY())}_getFillPattern(){return this._getCache(Rt,this.__getFillPattern)}__getFillPattern(){if(this.fillPatternImage()){const t=Ot().createPattern(this.fillPatternImage(),this.fillPatternRepeat()||"repeat");if(t&&t.setTransform){const e=new s;e.translate(this.fillPatternX(),this.fillPatternY()),e.rotate(i.getAngle(this.fillPatternRotation())),e.scale(this.fillPatternScaleX(),this.fillPatternScaleY()),e.translate(-1*this.fillPatternOffsetX(),-1*this.fillPatternOffsetY());const n=e.getMatrix(),r="undefined"==typeof DOMMatrix?{a:n[0],b:n[1],c:n[2],d:n[3],e:n[4],f:n[5]}:new DOMMatrix(n);t.setTransform(r)}return t}}_getLinearGradient(){return this._getCache(Et,this.__getLinearGradient)}__getLinearGradient(){const t=this.fillLinearGradientColorStops();if(t){const e=Ot(),i=this.fillLinearGradientStartPoint(),n=this.fillLinearGradientEndPoint(),s=e.createLinearGradient(i.x,i.y,n.x,n.y);for(let e=0;ethis.fillEnabled()&&!!(this.fill()||this.fillPatternImage()||this.fillLinearGradientColorStops()||this.fillRadialGradientColorStops())))}hasStroke(){return this._calculate("hasStroke",["strokeEnabled","strokeWidth","stroke","strokeLinearGradientColorStops"],(()=>this.strokeEnabled()&&this.strokeWidth()&&!(!this.stroke()&&!this.strokeLinearGradientColorStops())))}hasHitStroke(){const t=this.hitStrokeWidth();return"auto"===t?this.hasStroke():this.strokeEnabled()&&!!t}intersects(t){const e=this.getStage();if(!e)return!1;const i=e.bufferHitCanvas;i.getContext().clear(),this.drawHit(i,void 0,!0);return i.context.getImageData(Math.round(t.x),Math.round(t.y),1,1).data[3]>0}destroy(){return q.prototype.destroy.call(this),delete It[this.colorKey],delete this.colorKey,this}_useBufferCanvas(t){var e;if(!(null===(e=this.attrs.perfectDrawEnabled)||void 0===e||e))return!1;const i=t||this.hasFill(),n=this.hasStroke(),s=1!==this.getAbsoluteOpacity();if(i&&n&&s)return!0;const r=this.hasShadow(),a=this.shadowForStrokeEnabled();return!!(i&&n&&r&&a)}setStrokeHitEnabled(t){g.warn("strokeHitEnabled property is deprecated. Please use hitStrokeWidth instead."),t?this.hitStrokeWidth("auto"):this.hitStrokeWidth(0)}getStrokeHitEnabled(){return 0!==this.hitStrokeWidth()}getSelfRect(){const t=this.size();return{x:this._centroid?-t.width/2:0,y:this._centroid?-t.height/2:0,width:t.width,height:t.height}}getClientRect(t={}){let e=!1,i=this.getParent();for(;i;){if(i.isCached()){e=!0;break}i=i.getParent()}const n=t.skipTransform,s=t.relativeTo||e&&this.getStage()||void 0,r=this.getSelfRect(),a=!t.skipStroke&&this.hasStroke()&&this.strokeWidth()||0,o=r.width+a,h=r.height+a,l=!t.skipShadow&&this.hasShadow(),d=l?this.shadowOffsetX():0,c=l?this.shadowOffsetY():0,g=o+Math.abs(d),u=h+Math.abs(c),f=l&&this.shadowBlur()||0,p={width:g+2*f,height:u+2*f,x:-(a/2+f)+Math.min(d,0)+r.x,y:-(a/2+f)+Math.min(c,0)+r.y};return n?p:this._transformedRect(p,s)}drawScene(t,e,i){const n=this.getLayer(),s=(t||n.getCanvas()).getContext(),r=this._getCanvasCache(),a=this.getSceneFunc(),o=this.hasShadow();let h;const l=e===this;if(!this.isVisible()&&!l)return this;if(r){s.save();const t=this.getAbsoluteTransform(e).getMatrix();return s.transform(t[0],t[1],t[2],t[3],t[4],t[5]),this._drawCachedSceneCanvas(s),s.restore(),this}if(!a)return this;if(s.save(),this._useBufferCanvas()){h=this.getStage();const t=i||h.bufferCanvas,n=t.getContext();n.clear(),n.save(),n._applyLineJoin(this);const r=this.getAbsoluteTransform(e).getMatrix();n.transform(r[0],r[1],r[2],r[3],r[4],r[5]),a.call(this,n,this),n.restore();const l=t.pixelRatio;o&&s._applyShadow(this),s._applyOpacity(this),s._applyGlobalCompositeOperation(this),s.drawImage(t._canvas,t.x||0,t.y||0,t.width/l,t.height/l)}else{if(s._applyLineJoin(this),!l){const t=this.getAbsoluteTransform(e).getMatrix();s.transform(t[0],t[1],t[2],t[3],t[4],t[5]),s._applyOpacity(this),s._applyGlobalCompositeOperation(this)}o&&s._applyShadow(this),a.call(this,s,this)}return s.restore(),this}drawHit(t,e,i=!1){if(!this.shouldDrawHit(e,i))return this;const n=this.getLayer(),s=t||n.hitCanvas,r=s&&s.getContext(),a=this.hitFunc()||this.sceneFunc(),o=this._getCanvasCache(),h=o&&o.hit;if(this.colorKey||g.warn("Looks like your canvas has a destroyed shape in it. Do not reuse shape after you destroyed it. If you want to reuse shape you should call remove() instead of destroy()"),h){r.save();const t=this.getAbsoluteTransform(e).getMatrix();return r.transform(t[0],t[1],t[2],t[3],t[4],t[5]),this._drawCachedHitCanvas(r),r.restore(),this}if(!a)return this;r.save(),r._applyLineJoin(this);if(!(this===e)){const t=this.getAbsoluteTransform(e).getMatrix();r.transform(t[0],t[1],t[2],t[3],t[4],t[5])}return a.call(this,r,this),r.restore(),this}drawHitFromCache(t=0){const e=this._getCanvasCache(),i=this._getCachedSceneCanvas(),n=e.hit,s=n.getContext(),r=n.getWidth(),a=n.getHeight();s.clear(),s.drawImage(i._canvas,0,0,r,a);try{const e=s.getImageData(0,0,r,a),i=e.data,n=i.length,o=g._hexToRgb(this.colorKey);for(let e=0;et?(i[e]=o.r,i[e+1]=o.g,i[e+2]=o.b,i[e+3]=255):i[e+3]=0}s.putImageData(e,0,0)}catch(t){g.error("Unable to draw hit graph from cached scene canvas. "+t.message)}return this}hasPointerCapture(t){return Z(t,this)}setPointerCapture(t){tt(t,this)}releaseCapture(t){et(t)}}Ft.prototype._fillFunc=function(t){const e=this.attrs.fillRule;e?t.fill(e):t.fill()},Ft.prototype._strokeFunc=function(t){t.stroke()},Ft.prototype._fillFuncHit=function(t){const e=this.attrs.fillRule;e?t.fill(e):t.fill()},Ft.prototype._strokeFuncHit=function(t){t.stroke()},Ft.prototype._centroid=!1,Ft.prototype.nodeType="Shape",n(Ft),Ft.prototype.eventListeners={},Ft.prototype.on.call(Ft.prototype,"shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva",(function(){this._clearCache(Mt)})),Ft.prototype.on.call(Ft.prototype,"shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva",(function(){this._clearCache(Gt)})),Ft.prototype.on.call(Ft.prototype,"fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva fillPatternScaleXChange.konva fillPatternScaleYChange.konva fillPatternOffsetXChange.konva fillPatternOffsetYChange.konva fillPatternXChange.konva fillPatternYChange.konva fillPatternRotationChange.konva",(function(){this._clearCache(Rt)})),Ft.prototype.on.call(Ft.prototype,"fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva",(function(){this._clearCache(Et)})),Ft.prototype.on.call(Ft.prototype,"fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva",(function(){this._clearCache(Dt)})),E.addGetterSetter(Ft,"stroke",void 0,T()),E.addGetterSetter(Ft,"strokeWidth",2,C()),E.addGetterSetter(Ft,"fillAfterStrokeEnabled",!1),E.addGetterSetter(Ft,"hitStrokeWidth","auto",k()),E.addGetterSetter(Ft,"strokeHitEnabled",!0,M()),E.addGetterSetter(Ft,"perfectDrawEnabled",!0,M()),E.addGetterSetter(Ft,"shadowForStrokeEnabled",!0,M()),E.addGetterSetter(Ft,"lineJoin"),E.addGetterSetter(Ft,"lineCap"),E.addGetterSetter(Ft,"sceneFunc"),E.addGetterSetter(Ft,"hitFunc"),E.addGetterSetter(Ft,"dash"),E.addGetterSetter(Ft,"dashOffset",0,C()),E.addGetterSetter(Ft,"shadowColor",void 0,A()),E.addGetterSetter(Ft,"shadowBlur",0,C()),E.addGetterSetter(Ft,"shadowOpacity",1,C()),E.addComponentsGetterSetter(Ft,"shadowOffset",["x","y"]),E.addGetterSetter(Ft,"shadowOffsetX",0,C()),E.addGetterSetter(Ft,"shadowOffsetY",0,C()),E.addGetterSetter(Ft,"fillPatternImage"),E.addGetterSetter(Ft,"fill",void 0,T()),E.addGetterSetter(Ft,"fillPatternX",0,C()),E.addGetterSetter(Ft,"fillPatternY",0,C()),E.addGetterSetter(Ft,"fillLinearGradientColorStops"),E.addGetterSetter(Ft,"strokeLinearGradientColorStops"),E.addGetterSetter(Ft,"fillRadialGradientStartRadius",0),E.addGetterSetter(Ft,"fillRadialGradientEndRadius",0),E.addGetterSetter(Ft,"fillRadialGradientColorStops"),E.addGetterSetter(Ft,"fillPatternRepeat","repeat"),E.addGetterSetter(Ft,"fillEnabled",!0),E.addGetterSetter(Ft,"strokeEnabled",!0),E.addGetterSetter(Ft,"shadowEnabled",!0),E.addGetterSetter(Ft,"dashEnabled",!0),E.addGetterSetter(Ft,"strokeScaleEnabled",!0),E.addGetterSetter(Ft,"fillPriority","color"),E.addComponentsGetterSetter(Ft,"fillPatternOffset",["x","y"]),E.addGetterSetter(Ft,"fillPatternOffsetX",0,C()),E.addGetterSetter(Ft,"fillPatternOffsetY",0,C()),E.addComponentsGetterSetter(Ft,"fillPatternScale",["x","y"]),E.addGetterSetter(Ft,"fillPatternScaleX",1,C()),E.addGetterSetter(Ft,"fillPatternScaleY",1,C()),E.addComponentsGetterSetter(Ft,"fillLinearGradientStartPoint",["x","y"]),E.addComponentsGetterSetter(Ft,"strokeLinearGradientStartPoint",["x","y"]),E.addGetterSetter(Ft,"fillLinearGradientStartPointX",0),E.addGetterSetter(Ft,"strokeLinearGradientStartPointX",0),E.addGetterSetter(Ft,"fillLinearGradientStartPointY",0),E.addGetterSetter(Ft,"strokeLinearGradientStartPointY",0),E.addComponentsGetterSetter(Ft,"fillLinearGradientEndPoint",["x","y"]),E.addComponentsGetterSetter(Ft,"strokeLinearGradientEndPoint",["x","y"]),E.addGetterSetter(Ft,"fillLinearGradientEndPointX",0),E.addGetterSetter(Ft,"strokeLinearGradientEndPointX",0),E.addGetterSetter(Ft,"fillLinearGradientEndPointY",0),E.addGetterSetter(Ft,"strokeLinearGradientEndPointY",0),E.addComponentsGetterSetter(Ft,"fillRadialGradientStartPoint",["x","y"]),E.addGetterSetter(Ft,"fillRadialGradientStartPointX",0),E.addGetterSetter(Ft,"fillRadialGradientStartPointY",0),E.addComponentsGetterSetter(Ft,"fillRadialGradientEndPoint",["x","y"]),E.addGetterSetter(Ft,"fillRadialGradientEndPointX",0),E.addGetterSetter(Ft,"fillRadialGradientEndPointY",0),E.addGetterSetter(Ft,"fillPatternRotation",0),E.addGetterSetter(Ft,"fillRule",void 0,A()),E.backCompat(Ft,{dashArray:"dash",getDashArray:"getDash",setDashArray:"getDash",drawFunc:"sceneFunc",getDrawFunc:"getSceneFunc",setDrawFunc:"setSceneFunc",drawHitFunc:"hitFunc",getDrawHitFunc:"getHitFunc",setDrawHitFunc:"setHitFunc"});const Bt=[{x:0,y:0},{x:-1,y:-1},{x:1,y:-1},{x:1,y:1},{x:-1,y:1}],Nt=Bt.length;class Ht extends V{constructor(t){super(t),this.canvas=new v,this.hitCanvas=new x({pixelRatio:1}),this._waitingForDraw=!1,this.on("visibleChange.konva",this._checkVisibility),this._checkVisibility(),this.on("imageSmoothingEnabledChange.konva",this._setSmoothEnabled),this._setSmoothEnabled()}createPNGStream(){return this.canvas._canvas.createPNGStream()}getCanvas(){return this.canvas}getNativeCanvasElement(){return this.canvas._canvas}getHitCanvas(){return this.hitCanvas}getContext(){return this.getCanvas().getContext()}clear(t){return this.getContext().clear(t),this.getHitCanvas().getContext().clear(t),this}setZIndex(t){super.setZIndex(t);const e=this.getStage();return e&&e.content&&(e.content.removeChild(this.getNativeCanvasElement()),t{this.draw(),this._waitingForDraw=!1}))),this}getIntersection(t){if(!this.isListening()||!this.isVisible())return null;let e=1,i=!1;for(;;){for(let n=0;n0?{antialiased:!0}:{}}drawScene(t,e,i){const n=this.getLayer(),s=t||n&&n.getCanvas();return this._fire("beforeDraw",{node:this}),this.clearBeforeDraw()&&s.getContext().clear(),V.prototype.drawScene.call(this,s,e,i),this._fire("draw",{node:this}),this}drawHit(t,e){const i=this.getLayer(),n=t||i&&i.hitCanvas;return i&&i.clearBeforeDraw()&&i.getHitCanvas().getContext().clear(),V.prototype.drawHit.call(this,n,e),this}enableHitGraph(){return this.hitGraphEnabled(!0),this}disableHitGraph(){return this.hitGraphEnabled(!1),this}setHitGraphEnabled(t){g.warn("hitGraphEnabled method is deprecated. Please use layer.listening() instead."),this.listening(t)}getHitGraphEnabled(t){return g.warn("hitGraphEnabled method is deprecated. Please use layer.listening() instead."),this.listening()}toggleHitCanvas(){if(!this.parent||!this.parent.content)return;const t=this.parent;!!this.hitCanvas._canvas.parentNode?t.content.removeChild(this.hitCanvas._canvas):t.content.appendChild(this.hitCanvas._canvas)}destroy(){return g.releaseCanvas(this.getNativeCanvasElement(),this.getHitCanvas()._canvas),super.destroy()}}Ht.prototype.nodeType="Layer",n(Ht),E.addGetterSetter(Ht,"imageSmoothingEnabled",!0),E.addGetterSetter(Ht,"clearBeforeDraw",!0),E.addGetterSetter(Ht,"hitGraphEnabled",!0,M());class zt extends Ht{constructor(t){super(t),this.listening(!1),g.warn('Konva.Fast layer is deprecated. Please use "new Konva.Layer({ listening: false })" instead.')}}zt.prototype.nodeType="FastLayer",n(zt);class Wt extends V{_validateAdd(t){const e=t.getType();"Group"!==e&&"Shape"!==e&&g.throw("You may only add groups and shapes to groups.")}}Wt.prototype.nodeType="Group",n(Wt);const Yt=e.performance&&e.performance.now?function(){return e.performance.now()}:function(){return(new Date).getTime()};class Xt{constructor(t,e){this.id=Xt.animIdCounter++,this.frame={time:0,timeDiff:0,lastTime:Yt(),frameRate:0},this.func=t,this.setLayers(e)}setLayers(t){let e=[];return t&&(e=Array.isArray(t)?t:[t]),this.layers=e,this}getLayers(){return this.layers}addLayer(t){const e=this.layers,i=e.length;for(let n=0;nthis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())}getTime(){return this._time}setPosition(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t}getPosition(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)}play(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")}reverse(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")}seek(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")}reset(){this.pause(),this._time=0,this.update(),this.fire("onReset")}finish(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")}update(){this.setPosition(this.getPosition(this._time)),this.fire("onUpdate")}onEnterFrame(){const t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)}pause(){this.state=1,this.fire("onPause")}getTimer(){return(new Date).getTime()}}class Kt{constructor(t){const e=this,n=t.node,s=n._id,r=t.easing||Qt.Linear,a=!!t.yoyo;let o,h;o=void 0===t.duration?.3:0===t.duration?.001:t.duration,this.node=n,this._id=Ut++;const l=n.getLayer()||(n instanceof i.Stage?n.getLayers():null);for(h in l||g.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new Xt((function(){e.tween.onEnterFrame()}),l),this.tween=new Vt(h,(function(t){e._tweenFunc(t)}),r,0,1,1e3*o,a),this._addListeners(),Kt.attrs[s]||(Kt.attrs[s]={}),Kt.attrs[s][this._id]||(Kt.attrs[s][this._id]={}),Kt.tweens[s]||(Kt.tweens[s]={}),t)void 0===jt[h]&&this._addAttr(h,t[h]);this.reset(),this.onFinish=t.onFinish,this.onReset=t.onReset,this.onUpdate=t.onUpdate}_addAttr(t,e){const i=this.node,n=i._id;let s,r,a,o,h;const l=Kt.tweens[n][t];l&&delete Kt.attrs[n][l][t];let d=i.getAttr(t);if(g._isArray(e))if(s=[],r=Math.max(e.length,d.length),"points"===t&&e.length!==d.length&&(e.length>d.length?(o=d,d=g._prepareArrayForTween(d,e,i.closed())):(a=e,e=g._prepareArrayForTween(e,d,i.closed()))),0===t.indexOf("fill"))for(let t=0;t{this.anim.start()},this.tween.onReverse=()=>{this.anim.start()},this.tween.onPause=()=>{this.anim.stop()},this.tween.onFinish=()=>{const t=this.node,e=Kt.attrs[t._id][this._id];e.points&&e.points.trueEnd&&t.setAttr("points",e.points.trueEnd),this.onFinish&&this.onFinish.call(this)},this.tween.onReset=()=>{const t=this.node,e=Kt.attrs[t._id][this._id];e.points&&e.points.trueStart&&t.points(e.points.trueStart),this.onReset&&this.onReset()},this.tween.onUpdate=()=>{this.onUpdate&&this.onUpdate.call(this)}}play(){return this.tween.play(),this}reverse(){return this.tween.reverse(),this}reset(){return this.tween.reset(),this}seek(t){return this.tween.seek(1e3*t),this}pause(){return this.tween.pause(),this}finish(){return this.tween.finish(),this}destroy(){const t=this.node._id,e=this._id,i=Kt.tweens[t];this.pause(),this.anim&&this.anim.stop();for(const e in i)delete Kt.tweens[t][e];delete Kt.attrs[t][e],Kt.tweens[t]&&(0===Object.keys(Kt.tweens[t]).length&&delete Kt.tweens[t],0===Object.keys(Kt.attrs[t]).length&&delete Kt.attrs[t])}}Kt.attrs={},Kt.tweens={},q.prototype.to=function(t){const e=t.onFinish;t.node=this,t.onFinish=function(){this.destroy(),e&&e()};new Kt(t).play()};const Qt={BackEaseIn(t,e,i,n){const s=1.70158;return i*(t/=n)*t*((s+1)*t-s)+e},BackEaseOut(t,e,i,n){const s=1.70158;return i*((t=t/n-1)*t*((s+1)*t+s)+1)+e},BackEaseInOut(t,e,i,n){let s=1.70158;return(t/=n/2)<1?i/2*(t*t*((1+(s*=1.525))*t-s))+e:i/2*((t-=2)*t*((1+(s*=1.525))*t+s)+2)+e},ElasticEaseIn(t,e,i,n,s,r){let a=0;return 0===t?e:1==(t/=n)?e+i:(r||(r=.3*n),!s||s(t/=n)<1/2.75?i*(7.5625*t*t)+e:t<2/2.75?i*(7.5625*(t-=1.5/2.75)*t+.75)+e:t<2.5/2.75?i*(7.5625*(t-=2.25/2.75)*t+.9375)+e:i*(7.5625*(t-=2.625/2.75)*t+.984375)+e,BounceEaseIn:(t,e,i,n)=>i-Qt.BounceEaseOut(n-t,0,i,n)+e,BounceEaseInOut:(t,e,i,n)=>ti*(t/=n)*t+e,EaseOut:(t,e,i,n)=>-i*(t/=n)*(t-2)+e,EaseInOut:(t,e,i,n)=>(t/=n/2)<1?i/2*t*t+e:-i/2*(--t*(t-2)-1)+e,StrongEaseIn:(t,e,i,n)=>i*(t/=n)*t*t*t*t+e,StrongEaseOut:(t,e,i,n)=>i*((t=t/n-1)*t*t*t*t+1)+e,StrongEaseInOut:(t,e,i,n)=>(t/=n/2)<1?i/2*t*t*t*t*t+e:i/2*((t-=2)*t*t*t*t+2)+e,Linear:(t,e,i,n)=>i*t/n+e},Jt=g._assign(i,{Util:g,Transform:s,Node:q,Container:V,Stage:Tt,stages:At,Layer:Ht,FastLayer:zt,Group:Wt,DD:b,Shape:Ft,shapes:It,Animation:Xt,Tween:Kt,Easings:Qt,Context:f,Canvas:y});class $t extends Ft{_sceneFunc(t){const e=i.getAngle(this.angle()),n=this.clockwise();t.beginPath(),t.arc(0,0,this.outerRadius(),0,e,n),t.arc(0,0,this.innerRadius(),e,0,!n),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.outerRadius()}getHeight(){return 2*this.outerRadius()}setWidth(t){this.outerRadius(t/2)}setHeight(t){this.outerRadius(t/2)}getSelfRect(){const t=this.innerRadius(),e=this.outerRadius(),n=this.clockwise(),s=i.getAngle(n?360-this.angle():this.angle()),r=Math.cos(Math.min(s,Math.PI)),a=Math.sin(Math.min(Math.max(Math.PI,s),3*Math.PI/2)),o=Math.sin(Math.min(s,Math.PI/2)),h=r*(r>0?t:e),l=a*(a>0?t:e),d=o*(o>0?e:t);return{x:h,y:n?-1*d:l,width:1*e-h,height:d-l}}}function Zt(t,e,i,n,s,r,a){const o=Math.sqrt(Math.pow(i-t,2)+Math.pow(n-e,2)),h=Math.sqrt(Math.pow(s-i,2)+Math.pow(r-n,2)),l=a*o/(o+h),d=a*h/(o+h);return[i-l*(s-t),n-l*(r-e),i+d*(s-t),n+d*(r-e)]}function te(t,e){const i=t.length,n=[];for(let s=2;s4){const n=this.getTensionPoints(),r=n.length;for(a=s?0:4,s||t.quadraticCurveTo(n[0],n[1],n[2],n[3]);a{let n,s;const r=i/2;n=0;for(let i=0;i<20;i++)s=r*ie[20][i]+r,n+=ne[20][i]*oe(t,e,s);return r*n},ae=(t,e,i)=>{void 0===i&&(i=1);const n=t[0]-2*t[1]+t[2],s=e[0]-2*e[1]+e[2],r=2*t[1]-2*t[0],a=2*e[1]-2*e[0],o=4*(n*n+s*s),h=4*(n*r+s*a),l=r*r+a*a;if(0===o)return i*Math.sqrt(Math.pow(t[2]-t[0],2)+Math.pow(e[2]-e[0],2));const d=h/(2*o),c=i+d,g=l/o-d*d,u=c*c+g>0?Math.sqrt(c*c+g):0,f=d*d+g>0?Math.sqrt(d*d+g):0,p=d+Math.sqrt(d*d+g)!==0?g*Math.log(Math.abs((c+u)/(d+f))):0;return Math.sqrt(o)/2*(c*u-d*f+p)};function oe(t,e,i){const n=he(1,i,t),s=he(1,i,e),r=n*n+s*s;return Math.sqrt(r)}const he=(t,e,i)=>{const n=i.length-1;let s,r;if(0===n)return 0;if(0===t){r=0;for(let t=0;t<=n;t++)r+=se[n][t]*Math.pow(1-e,n-t)*Math.pow(e,t)*i[t];return r}s=new Array(n);for(let t=0;t{let n=1,s=t/e,r=(t-i(s))/e,a=0;for(;n>.001;){const o=i(s+r),h=Math.abs(t-o)/e;if(h500)break}return s};class de extends Ft{constructor(t){super(t),this.dataArray=[],this.pathLength=0,this._readDataAttribute(),this.on("dataChange.konva",(function(){this._readDataAttribute()}))}_readDataAttribute(){this.dataArray=de.parsePathData(this.data()),this.pathLength=de.getPathLength(this.dataArray)}_sceneFunc(t){const e=this.dataArray;t.beginPath();let i=!1;for(let n=0;na?s:a,g=s>a?1:s/a,u=s>a?a/s:1;t.translate(e,n),t.rotate(l),t.scale(g,u),t.arc(0,0,c,o,o+h,1-d),t.scale(1/g,1/u),t.rotate(-l),t.translate(-e,-n);break;case"z":i=!0,t.closePath()}}i||this.hasFill()?t.fillStrokeShape(this):t.strokeShape(this)}getSelfRect(){let t=[];this.dataArray.forEach((function(e){if("A"===e.command){const i=e.points[4],n=e.points[5],s=e.points[4]+n;let r=Math.PI/180;if(Math.abs(i-s)s;n-=r){const i=de.getPointOnEllipticalArc(e.points[0],e.points[1],e.points[2],e.points[3],n,0);t.push(i.x,i.y)}else for(let n=i+r;ne[n].pathLength;)t-=e[n].pathLength,++n;if(n===s)return i=e[n-1].points.slice(-2),{x:i[0],y:i[1]};if(t<.01){return"M"===e[n].command?(i=e[n].points.slice(0,2),{x:i[0],y:i[1]}):{x:e[n].start.x,y:e[n].start.y}}const r=e[n],a=r.points;switch(r.command){case"L":return de.getPointOnLine(t,r.start.x,r.start.y,a[0],a[1]);case"C":return de.getPointOnCubicBezier(le(t,de.getPathLength(e),(t=>re([r.start.x,a[0],a[2],a[4]],[r.start.y,a[1],a[3],a[5]],t))),r.start.x,r.start.y,a[0],a[1],a[2],a[3],a[4],a[5]);case"Q":return de.getPointOnQuadraticBezier(le(t,de.getPathLength(e),(t=>ae([r.start.x,a[0],a[2]],[r.start.y,a[1],a[3]],t))),r.start.x,r.start.y,a[0],a[1],a[2],a[3]);case"A":const i=a[0],n=a[1],s=a[2],o=a[3],h=a[5],l=a[6];let d=a[4];return d+=h*t/r.pathLength,de.getPointOnEllipticalArc(i,n,s,o,d,l)}return null}static getPointOnLine(t,e,i,n,s,r,a){r=null!=r?r:e,a=null!=a?a:i;const o=this.getLineLength(e,i,n,s);if(o<1e-10)return{x:e,y:i};if(n===e)return{x:r,y:a+(s>i?t:-t)};const h=(s-i)/(n-e),l=Math.sqrt(t*t/(1+h*h))*(n0&&!isNaN(d[0]);){let t="",e=[];const n=a,r=o;let h,l,c,g,u,f,p,m,_,y;switch(i){case"l":a+=d.shift(),o+=d.shift(),t="L",e.push(a,o);break;case"L":a=d.shift(),o=d.shift(),e.push(a,o);break;case"m":const n=d.shift(),r=d.shift();if(a+=n,o+=r,t="M",s.length>2&&"z"===s[s.length-1].command)for(let t=s.length-2;t>=0;t--)if("M"===s[t].command){a=s[t].points[0]+n,o=s[t].points[1]+r;break}e.push(a,o),i="l";break;case"M":a=d.shift(),o=d.shift(),t="M",e.push(a,o),i="L";break;case"h":a+=d.shift(),t="L",e.push(a,o);break;case"H":a=d.shift(),t="L",e.push(a,o);break;case"v":o+=d.shift(),t="L",e.push(a,o);break;case"V":o=d.shift(),t="L",e.push(a,o);break;case"C":e.push(d.shift(),d.shift(),d.shift(),d.shift()),a=d.shift(),o=d.shift(),e.push(a,o);break;case"c":e.push(a+d.shift(),o+d.shift(),a+d.shift(),o+d.shift()),a+=d.shift(),o+=d.shift(),t="C",e.push(a,o);break;case"S":l=a,c=o,h=s[s.length-1],"C"===h.command&&(l=a+(a-h.points[2]),c=o+(o-h.points[3])),e.push(l,c,d.shift(),d.shift()),a=d.shift(),o=d.shift(),t="C",e.push(a,o);break;case"s":l=a,c=o,h=s[s.length-1],"C"===h.command&&(l=a+(a-h.points[2]),c=o+(o-h.points[3])),e.push(l,c,a+d.shift(),o+d.shift()),a+=d.shift(),o+=d.shift(),t="C",e.push(a,o);break;case"Q":e.push(d.shift(),d.shift()),a=d.shift(),o=d.shift(),e.push(a,o);break;case"q":e.push(a+d.shift(),o+d.shift()),a+=d.shift(),o+=d.shift(),t="Q",e.push(a,o);break;case"T":l=a,c=o,h=s[s.length-1],"Q"===h.command&&(l=a+(a-h.points[0]),c=o+(o-h.points[1])),a=d.shift(),o=d.shift(),t="Q",e.push(l,c,a,o);break;case"t":l=a,c=o,h=s[s.length-1],"Q"===h.command&&(l=a+(a-h.points[0]),c=o+(o-h.points[1])),a+=d.shift(),o+=d.shift(),t="Q",e.push(l,c,a,o);break;case"A":g=d.shift(),u=d.shift(),f=d.shift(),p=d.shift(),m=d.shift(),_=a,y=o,a=d.shift(),o=d.shift(),t="A",e=this.convertEndpointToCenterParameterization(_,y,a,o,p,m,g,u,f);break;case"a":g=d.shift(),u=d.shift(),f=d.shift(),p=d.shift(),m=d.shift(),_=a,y=o,a+=d.shift(),o+=d.shift(),t="A",e=this.convertEndpointToCenterParameterization(_,y,a,o,p,m,g,u,f)}s.push({command:t||i,points:e,start:{x:n,y:r},pathLength:this.calcLength(n,r,t||i,e)})}"z"!==i&&"Z"!==i||s.push({command:"z",points:[],start:void 0,pathLength:0})}return s}static calcLength(t,e,i,n){let s,r,a,o;const h=de;switch(i){case"L":return h.getLineLength(t,e,n[0],n[1]);case"C":return re([t,n[0],n[2],n[4]],[e,n[1],n[3],n[5]],1);case"Q":return ae([t,n[0],n[2]],[e,n[1],n[3]],1);case"A":s=0;const i=n[4],l=n[5],d=n[4]+l;let c=Math.PI/180;if(Math.abs(i-d)d;o-=c)a=h.getPointOnEllipticalArc(n[0],n[1],n[2],n[3],o,0),s+=h.getLineLength(r.x,r.y,a.x,a.y),r=a;else for(o=i+c;o1&&(a*=Math.sqrt(g),o*=Math.sqrt(g));let u=Math.sqrt((a*a*(o*o)-a*a*(c*c)-o*o*(d*d))/(a*a*(c*c)+o*o*(d*d)));s===r&&(u*=-1),isNaN(u)&&(u=0);const f=u*a*c/o,p=u*-o*d/a,m=(t+i)/2+Math.cos(l)*f-Math.sin(l)*p,_=(e+n)/2+Math.sin(l)*f+Math.cos(l)*p,y=function(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])},v=function(t,e){return(t[0]*e[0]+t[1]*e[1])/(y(t)*y(e))},x=function(t,e){return(t[0]*e[1]=1&&(C=0),0===r&&C>0&&(C-=2*Math.PI),1===r&&C<0&&(C+=2*Math.PI),[m,_,a,o,b,C,l,r]}}de.prototype.className="Path",de.prototype._attrsAffectingSize=["data"],n(de),E.addGetterSetter(de,"data");class ce extends ee{_sceneFunc(t){super._sceneFunc(t);const e=2*Math.PI,i=this.points();let n=i;const s=0!==this.tension()&&i.length>4;s&&(n=this.getTensionPoints());const r=this.pointerLength(),a=i.length;let o,h;if(s){const t=[n[n.length-4],n[n.length-3],n[n.length-2],n[n.length-1],i[a-2],i[a-1]],e=de.calcLength(n[n.length-4],n[n.length-3],"C",t),s=de.getPointOnQuadraticBezier(Math.min(1,1-r/e),t[0],t[1],t[2],t[3],t[4],t[5]);o=i[a-2]-s.x,h=i[a-1]-s.y}else o=i[a-2]-i[a-4],h=i[a-1]-i[a-3];const l=(Math.atan2(h,o)+e)%e,d=this.pointerWidth();this.pointerAtEnding()&&(t.save(),t.beginPath(),t.translate(i[a-2],i[a-1]),t.rotate(l),t.moveTo(0,0),t.lineTo(-r,d/2),t.lineTo(-r,-d/2),t.closePath(),t.restore(),this.__fillStroke(t)),this.pointerAtBeginning()&&(t.save(),t.beginPath(),t.translate(i[0],i[1]),s?(o=(n[0]+n[2])/2-i[0],h=(n[1]+n[3])/2-i[1]):(o=i[2]-i[0],h=i[3]-i[1]),t.rotate((Math.atan2(-h,-o)+e)%e),t.moveTo(0,0),t.lineTo(-r,d/2),t.lineTo(-r,-d/2),t.closePath(),t.restore(),this.__fillStroke(t))}__fillStroke(t){const e=this.dashEnabled();e&&(this.attrs.dashEnabled=!1,t.setLineDash([])),t.fillStrokeShape(this),e&&(this.attrs.dashEnabled=!0)}getSelfRect(){const t=super.getSelfRect(),e=this.pointerWidth()/2;return{x:t.x,y:t.y-e,width:t.width,height:t.height+2*e}}}ce.prototype.className="Arrow",n(ce),E.addGetterSetter(ce,"pointerLength",10,C()),E.addGetterSetter(ce,"pointerWidth",10,C()),E.addGetterSetter(ce,"pointerAtBeginning",!1),E.addGetterSetter(ce,"pointerAtEnding",!0);class ge extends Ft{_sceneFunc(t){t.beginPath(),t.arc(0,0,this.attrs.radius||0,0,2*Math.PI,!1),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.radius()}getHeight(){return 2*this.radius()}setWidth(t){this.radius()!==t/2&&this.radius(t/2)}setHeight(t){this.radius()!==t/2&&this.radius(t/2)}}ge.prototype._centroid=!0,ge.prototype.className="Circle",ge.prototype._attrsAffectingSize=["radius"],n(ge),E.addGetterSetter(ge,"radius",0,C());class ue extends Ft{_sceneFunc(t){const e=this.radiusX(),i=this.radiusY();t.beginPath(),t.save(),e!==i&&t.scale(1,i/e),t.arc(0,0,e,0,2*Math.PI,!1),t.restore(),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.radiusX()}getHeight(){return 2*this.radiusY()}setWidth(t){this.radiusX(t/2)}setHeight(t){this.radiusY(t/2)}}ue.prototype.className="Ellipse",ue.prototype._centroid=!0,ue.prototype._attrsAffectingSize=["radiusX","radiusY"],n(ue),E.addComponentsGetterSetter(ue,"radius",["x","y"]),E.addGetterSetter(ue,"radiusX",0,C()),E.addGetterSetter(ue,"radiusY",0,C());class fe extends Ft{constructor(t){super(t),this._loadListener=()=>{this._requestDraw()},this.on("imageChange.konva",(t=>{this._removeImageLoad(t.oldVal),this._setImageLoad()})),this._setImageLoad()}_setImageLoad(){const t=this.image();t&&t.complete||t&&4===t.readyState||t&&t.addEventListener&&t.addEventListener("load",this._loadListener)}_removeImageLoad(t){t&&t.removeEventListener&&t.removeEventListener("load",this._loadListener)}destroy(){return this._removeImageLoad(this.image()),super.destroy(),this}_useBufferCanvas(){const t=!!this.cornerRadius(),e=this.hasShadow();return!(!t||!e)||super._useBufferCanvas(!0)}_sceneFunc(t){const e=this.getWidth(),i=this.getHeight(),n=this.cornerRadius(),s=this.attrs.image;let r;if(s){const t=this.attrs.cropWidth,n=this.attrs.cropHeight;r=t&&n?[s,this.cropX(),this.cropY(),t,n,0,0,e,i]:[s,0,0,e,i]}(this.hasFill()||this.hasStroke()||n)&&(t.beginPath(),n?g.drawRoundedRectPath(t,e,i,n):t.rect(0,0,e,i),t.closePath(),t.fillStrokeShape(this)),s&&(n&&t.clip(),t.drawImage.apply(t,r))}_hitFunc(t){const e=this.width(),i=this.height(),n=this.cornerRadius();t.beginPath(),n?g.drawRoundedRectPath(t,e,i,n):t.rect(0,0,e,i),t.closePath(),t.fillStrokeShape(this)}getWidth(){var t,e;return null!==(t=this.attrs.width)&&void 0!==t?t:null===(e=this.image())||void 0===e?void 0:e.width}getHeight(){var t,e;return null!==(t=this.attrs.height)&&void 0!==t?t:null===(e=this.image())||void 0===e?void 0:e.height}static fromURL(t,e,i=null){const n=g.createImageElement();n.onload=function(){const t=new fe({image:n});e(t)},n.onerror=i,n.crossOrigin="Anonymous",n.src=t}}fe.prototype.className="Image",n(fe),E.addGetterSetter(fe,"cornerRadius",0,P(4)),E.addGetterSetter(fe,"image"),E.addComponentsGetterSetter(fe,"crop",["x","y","width","height"]),E.addGetterSetter(fe,"cropX",0,C()),E.addGetterSetter(fe,"cropY",0,C()),E.addGetterSetter(fe,"cropWidth",0,C()),E.addGetterSetter(fe,"cropHeight",0,C());const pe=["fontFamily","fontSize","fontStyle","padding","lineHeight","text","width","height","pointerDirection","pointerWidth","pointerHeight"],me="up",_e="right",ye="down",ve="left",xe=pe.length;class be extends Wt{constructor(t){super(t),this.on("add.konva",(function(t){this._addListeners(t.child),this._sync()}))}getText(){return this.find("Text")[0]}getTag(){return this.find("Tag")[0]}_addListeners(t){let e,i=this;const n=function(){i._sync()};for(e=0;e{e=Math.min(e,t.x),i=Math.max(i,t.x),n=Math.min(n,t.y),s=Math.max(s,t.y)})),{x:e,y:n,width:i-e,height:s-n}}getWidth(){return 2*this.radius()}getHeight(){return 2*this.radius()}setWidth(t){this.radius(t/2)}setHeight(t){this.radius(t/2)}}Ce.prototype.className="RegularPolygon",Ce.prototype._centroid=!0,Ce.prototype._attrsAffectingSize=["radius"],n(Ce),E.addGetterSetter(Ce,"radius",0,C()),E.addGetterSetter(Ce,"sides",0,C());const Pe=2*Math.PI;class ke extends Ft{_sceneFunc(t){t.beginPath(),t.arc(0,0,this.innerRadius(),0,Pe,!1),t.moveTo(this.outerRadius(),0),t.arc(0,0,this.outerRadius(),Pe,0,!0),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.outerRadius()}getHeight(){return 2*this.outerRadius()}setWidth(t){this.outerRadius(t/2)}setHeight(t){this.outerRadius(t/2)}}ke.prototype.className="Ring",ke.prototype._centroid=!0,ke.prototype._attrsAffectingSize=["innerRadius","outerRadius"],n(ke),E.addGetterSetter(ke,"innerRadius",0,C()),E.addGetterSetter(ke,"outerRadius",0,C());class Ae extends Ft{constructor(t){super(t),this._updated=!0,this.anim=new Xt((()=>{const t=this._updated;return this._updated=!1,t})),this.on("animationChange.konva",(function(){this.frameIndex(0)})),this.on("frameIndexChange.konva",(function(){this._updated=!0})),this.on("frameRateChange.konva",(function(){this.anim.isRunning()&&(clearInterval(this.interval),this._setInterval())}))}_sceneFunc(t){const e=this.animation(),i=this.frameIndex(),n=4*i,s=this.animations()[e],r=this.frameOffsets(),a=s[n+0],o=s[n+1],h=s[n+2],l=s[n+3],d=this.image();if((this.hasFill()||this.hasStroke())&&(t.beginPath(),t.rect(0,0,h,l),t.closePath(),t.fillStrokeShape(this)),d)if(r){const n=r[e],s=2*i;t.drawImage(d,a,o,h,l,n[s+0],n[s+1],h,l)}else t.drawImage(d,a,o,h,l,0,0,h,l)}_hitFunc(t){const e=this.animation(),i=this.frameIndex(),n=4*i,s=this.animations()[e],r=this.frameOffsets(),a=s[n+2],o=s[n+3];if(t.beginPath(),r){const n=r[e],s=2*i;t.rect(n[s+0],n[s+1],a,o)}else t.rect(0,0,a,o);t.closePath(),t.fillShape(this)}_useBufferCanvas(){return super._useBufferCanvas(!0)}_setInterval(){const t=this;this.interval=setInterval((function(){t._updateIndex()}),1e3/this.frameRate())}start(){if(this.isRunning())return;const t=this.getLayer();this.anim.setLayers(t),this._setInterval(),this.anim.start()}stop(){this.anim.stop(),clearInterval(this.interval)}isRunning(){return this.anim.isRunning()}_updateIndex(){const t=this.frameIndex(),e=this.animation();t{if(/\p{Emoji}/u.test(e)){const s=n[i+1];s&&/\p{Emoji_Modifier}|\u200D/u.test(s)?(t.push(e+s),n[i+1]=""):t.push(e)}else/\p{Regional_Indicator}{2}/u.test(e+(n[i+1]||""))?t.push(e+n[i+1]):i>0&&/\p{Mn}|\p{Me}|\p{Mc}/u.test(e)?t[t.length-1]+=e:e&&t.push(e);return t}),[])}Te.prototype.className="Star",Te.prototype._centroid=!0,Te.prototype._attrsAffectingSize=["innerRadius","outerRadius"],n(Te),E.addGetterSetter(Te,"numPoints",5,C()),E.addGetterSetter(Te,"innerRadius",0,C()),E.addGetterSetter(Te,"outerRadius",0,C());const Ge="auto",Re="inherit",Ee="justify",De="left",Le="middle",Oe="normal",Ie=" ",Fe="none",Be=["direction","fontFamily","fontSize","fontStyle","fontVariant","padding","align","verticalAlign","lineHeight","text","width","height","wrap","ellipsis","letterSpacing"],Ne=Be.length;let He;function ze(){return He||(He=g.createCanvasElement().getContext("2d"),He)}class We extends Ft{constructor(t){super(function(t){return(t=t||{}).fillLinearGradientColorStops||t.fillRadialGradientColorStops||t.fillPatternImage||(t.fill=t.fill||"black"),t}(t)),this._partialTextX=0,this._partialTextY=0;for(let t=0;t1&&(y+=o)}}_hitFunc(t){const e=this.getWidth(),i=this.getHeight();t.beginPath(),t.rect(0,0,e,i),t.closePath(),t.fillStrokeShape(this)}setText(t){const e=g._isString(t)?t:null==t?"":t+"";return this._setAttr("text",e),this}getWidth(){return this.attrs.width===Ge||void 0===this.attrs.width?this.getTextWidth()+2*this.padding():this.attrs.width}getHeight(){return this.attrs.height===Ge||void 0===this.attrs.height?this.fontSize()*this.textArr.length*this.lineHeight()+2*this.padding():this.attrs.height}getTextWidth(){return this.textWidth}getTextHeight(){return g.warn("text.getTextHeight() method is deprecated. Use text.height() - for full height and text.fontSize() - for one line height."),this.textHeight}measureSize(t){var e,i,n,s,r,a,o,h,l,d,c;let g,u=ze(),f=this.fontSize();u.save(),u.font=this._getContextFont(),g=u.measureText(t),u.restore();const p=f/100;return{actualBoundingBoxAscent:null!==(e=g.actualBoundingBoxAscent)&&void 0!==e?e:71.58203125*p,actualBoundingBoxDescent:null!==(i=g.actualBoundingBoxDescent)&&void 0!==i?i:0,actualBoundingBoxLeft:null!==(n=g.actualBoundingBoxLeft)&&void 0!==n?n:-7.421875*p,actualBoundingBoxRight:null!==(s=g.actualBoundingBoxRight)&&void 0!==s?s:75.732421875*p,alphabeticBaseline:null!==(r=g.alphabeticBaseline)&&void 0!==r?r:0,emHeightAscent:null!==(a=g.emHeightAscent)&&void 0!==a?a:100*p,emHeightDescent:null!==(o=g.emHeightDescent)&&void 0!==o?o:-20*p,fontBoundingBoxAscent:null!==(h=g.fontBoundingBoxAscent)&&void 0!==h?h:91*p,fontBoundingBoxDescent:null!==(l=g.fontBoundingBoxDescent)&&void 0!==l?l:21*p,hangingBaseline:null!==(d=g.hangingBaseline)&&void 0!==d?d:72.80000305175781*p,ideographicBaseline:null!==(c=g.ideographicBaseline)&&void 0!==c?c:-21*p,width:g.width,height:f}}_getContextFont(){return this.fontStyle()+Ie+this.fontVariant()+Ie+(this.fontSize()+"px ")+this.fontFamily().split(",").map((t=>{const e=(t=t.trim()).indexOf(" ")>=0,i=t.indexOf('"')>=0||t.indexOf("'")>=0;return e&&!i&&(t=`"${t}"`),t})).join(", ")}_addTextLine(t){this.align()===Ee&&(t=t.trim());const e=this._getTextWidth(t);return this.textArr.push({text:t,width:e,lastInParagraph:!1})}_getTextWidth(t){const e=this.letterSpacing(),i=t.length;return ze().measureText(t).width+e*i}_setTextData(){let t=this.text().split("\n"),e=+this.fontSize(),i=0,n=this.lineHeight()*e,s=this.attrs.width,r=this.attrs.height,a=s!==Ge&&void 0!==s,o=r!==Ge&&void 0!==r,h=this.padding(),l=s-2*h,d=r-2*h,c=0,g=this.wrap(),u="char"!==g&&g!==Fe,f=this.ellipsis();this.textArr=[],ze().font=this._getContextFont();const p=f?this._getTextWidth("…"):0;for(let e=0,s=t.length;el)for(;r.length>0;){let t=0,e=Me(r).length,s="",a=0;for(;t>>1,h=Me(r).slice(0,i+1).join(""),g=this._getTextWidth(h);(f&&o&&c+n>d?g+p:g)<=l?(t=i+1,s=h,a=g):e=i}if(!s)break;if(u){const e=Me(r),i=Me(s),n=e[i.length];let o;if((n===Ie||"-"===n)&&a<=l)o=i.length;else{const t=i.lastIndexOf(Ie),e=i.lastIndexOf("-");o=Math.max(t,e)+1}o>0&&(t=o,s=e.slice(0,t).join(""),a=this._getTextWidth(s))}s=s.trimRight(),this._addTextLine(s),i=Math.max(i,a),c+=n;if(this._shouldHandleEllipsis(c)){this._tryToAddEllipsisToLastLine();break}if(r=Me(r).slice(t).join("").trimLeft(),r.length>0&&(h=this._getTextWidth(r),h<=l)){this._addTextLine(r),c+=n,i=Math.max(i,h);break}}else this._addTextLine(r),c+=n,i=Math.max(i,h),this._shouldHandleEllipsis(c)&&ed)break}this.textHeight=e,this.textWidth=i}_shouldHandleEllipsis(t){const e=+this.fontSize(),i=this.lineHeight()*e,n=this.attrs.height,s=n!==Ge&&void 0!==n,r=n-2*this.padding();return!(this.wrap()!==Fe)||s&&t+i>r}_tryToAddEllipsisToLastLine(){const t=this.attrs.width,e=t!==Ge&&void 0!==t,i=t-2*this.padding(),n=this.ellipsis(),s=this.textArr[this.textArr.length-1];if(s&&n){if(e){this._getTextWidth(s.text+"…")this.pathLength?null:de.getPointAtLengthOfDataArray(t,this.dataArray)}_readDataAttribute(){this.dataArray=de.parsePathData(this.attrs.data),this.pathLength=this._getTextPathLength()}_sceneFunc(t){t.setAttr("font",this._getContextFont()),t.setAttr("textBaseline",this.textBaseline()),t.setAttr("textAlign","left"),t.save();const e=this.textDecoration(),i=this.fill(),n=this.fontSize(),s=this.glyphInfo;"underline"===e&&t.beginPath();for(let i=0;i=1){const i=e[0].p0;t.moveTo(i.x,i.y)}for(let i=0;it+`.${Ue}`)).join(" "),Ke="nodesRect",Qe=["widthChange","heightChange","scaleXChange","scaleYChange","skewXChange","skewYChange","rotationChange","offsetXChange","offsetYChange","transformsEnabledChange","strokeWidthChange"],Je={"top-left":-45,"top-center":0,"top-right":45,"middle-right":-90,"middle-left":90,"bottom-left":-135,"bottom-center":180,"bottom-right":135},$e="ontouchstart"in i._global;const Ze=["top-left","top-center","top-right","middle-right","middle-left","bottom-left","bottom-center","bottom-right"];function ti(t,e,i){const n=i.x+(t.x-i.x)*Math.cos(e)-(t.y-i.y)*Math.sin(e),s=i.y+(t.x-i.x)*Math.sin(e)+(t.y-i.y)*Math.cos(e);return{...t,rotation:t.rotation+e,x:n,y:s}}function ei(t,e){const i=function(t){return{x:t.x+t.width/2*Math.cos(t.rotation)+t.height/2*Math.sin(-t.rotation),y:t.y+t.height/2*Math.cos(t.rotation)+t.width/2*Math.sin(t.rotation)}}(t);return ti(t,e,i)}let ii=0;class ni extends Wt{constructor(t){super(t),this._movingAnchorName=null,this._transforming=!1,this._createElements(),this._handleMouseMove=this._handleMouseMove.bind(this),this._handleMouseUp=this._handleMouseUp.bind(this),this.update=this.update.bind(this),this.on(Ve,this.update),this.getNode()&&this.update()}attachTo(t){return this.setNode(t),this}setNode(t){return g.warn("tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead."),this.setNodes([t])}getNode(){return this._nodes&&this._nodes[0]}_getEventNamespace(){return Ue+this._id}setNodes(t=[]){this._nodes&&this._nodes.length&&this.detach();const e=t.filter((t=>!t.isAncestorOf(this)||(g.error("Konva.Transformer cannot be an a child of the node you are trying to attach"),!1)));this._nodes=t=e,1===t.length&&this.useSingleNodeRotation()?this.rotation(t[0].getAbsoluteRotation()):this.rotation(0),this._nodes.forEach((t=>{const e=()=>{1===this.nodes().length&&this.useSingleNodeRotation()&&this.rotation(this.nodes()[0].getAbsoluteRotation()),this._resetTransformCache(),this._transforming||this.isDragging()||this.update()};if(t._attrsAffectingSize.length){const i=t._attrsAffectingSize.map((t=>t+"Change."+this._getEventNamespace())).join(" ");t.on(i,e)}t.on(Qe.map((t=>t+`.${this._getEventNamespace()}`)).join(" "),e),t.on(`absoluteTransformChange.${this._getEventNamespace()}`,e),this._proxyDrag(t)})),this._resetTransformCache();return!!this.findOne(".top-left")&&this.update(),this}_proxyDrag(t){let e;t.on(`dragstart.${this._getEventNamespace()}`,(i=>{e=t.getAbsolutePosition(),this.isDragging()||t===this.findOne(".back")||this.startDrag(i,!1)})),t.on(`dragmove.${this._getEventNamespace()}`,(i=>{if(!e)return;const n=t.getAbsolutePosition(),s=n.x-e.x,r=n.y-e.y;this.nodes().forEach((e=>{if(e===t)return;if(e.isDragging())return;const n=e.getAbsolutePosition();e.setAbsolutePosition({x:n.x+s,y:n.y+r}),e.startDrag(i)})),e=null}))}getNodes(){return this._nodes||[]}getActiveAnchor(){return this._movingAnchorName}detach(){this._nodes&&this._nodes.forEach((t=>{t.off("."+this._getEventNamespace())})),this._nodes=[],this._resetTransformCache()}_resetTransformCache(){this._clearCache(Ke),this._clearCache("transform"),this._clearSelfAndDescendantCache("absoluteTransform")}_getNodeRect(){return this._getCache(Ke,this.__getNodeRect)}__getNodeShape(t,e=this.rotation(),n){const s=t.getClientRect({skipTransform:!0,skipShadow:!0,skipStroke:this.ignoreStroke()}),r=t.getAbsoluteScale(n),a=t.getAbsolutePosition(n),o=s.x*r.x-t.offsetX()*r.x,h=s.y*r.y-t.offsetY()*r.y,l=(i.getAngle(t.getAbsoluteRotation())+2*Math.PI)%(2*Math.PI);return ti({x:a.x+o*Math.cos(l)+h*Math.sin(-l),y:a.y+h*Math.cos(l)+o*Math.sin(l),width:s.width*r.x,height:s.height*r.y,rotation:l},-i.getAngle(e),{x:0,y:0})}__getNodeRect(){if(!this.getNode())return{x:-1e8,y:-1e8,width:0,height:0,rotation:0};const t=[];this.nodes().map((e=>{const i=e.getClientRect({skipTransform:!0,skipShadow:!0,skipStroke:this.ignoreStroke()}),n=[{x:i.x,y:i.y},{x:i.x+i.width,y:i.y},{x:i.x+i.width,y:i.y+i.height},{x:i.x,y:i.y+i.height}],s=e.getAbsoluteTransform();n.forEach((function(e){const i=s.point(e);t.push(i)}))}));const e=new s;e.rotate(-i.getAngle(this.rotation()));let n=1/0,r=1/0,a=-1/0,o=-1/0;t.forEach((function(t){const i=e.point(t);void 0===n&&(n=a=i.x,r=o=i.y),n=Math.min(n,i.x),r=Math.min(r,i.y),a=Math.max(a,i.x),o=Math.max(o,i.y)})),e.invert();const h=e.point({x:n,y:r});return{x:h.x,y:h.y,width:a-n,height:o-r,rotation:i.getAngle(this.rotation())}}getX(){return this._getNodeRect().x}getY(){return this._getNodeRect().y}getWidth(){return this._getNodeRect().width}getHeight(){return this._getNodeRect().height}_createElements(){this._createBack(),Ze.forEach((t=>{this._createAnchor(t)})),this._createAnchor("rotater")}_createAnchor(t){const e=new we({stroke:"rgb(0, 161, 255)",fill:"white",strokeWidth:1,name:t+" _anchor",dragDistance:0,draggable:!0,hitStrokeWidth:$e?10:"auto"}),n=this;e.on("mousedown touchstart",(function(t){n._handleMouseDown(t)})),e.on("dragstart",(t=>{e.stopDrag(),t.cancelBubble=!0})),e.on("dragend",(t=>{t.cancelBubble=!0})),e.on("mouseenter",(()=>{const n=i.getAngle(this.rotation()),s=this.rotateAnchorCursor(),r=function(t,e,i){if("rotater"===t)return i;e+=g.degToRad(Je[t]||0);const n=(g.radToDeg(e)%360+360)%360;return g._inRange(n,337.5,360)||g._inRange(n,0,22.5)?"ns-resize":g._inRange(n,22.5,67.5)?"nesw-resize":g._inRange(n,67.5,112.5)?"ew-resize":g._inRange(n,112.5,157.5)?"nwse-resize":g._inRange(n,157.5,202.5)?"ns-resize":g._inRange(n,202.5,247.5)?"nesw-resize":g._inRange(n,247.5,292.5)?"ew-resize":g._inRange(n,292.5,337.5)?"nwse-resize":(g.error("Transformer has unknown angle for cursor detection: "+n),"pointer")}(t,n,s);e.getStage().content&&(e.getStage().content.style.cursor=r),this._cursorChange=!0})),e.on("mouseout",(()=>{e.getStage().content&&(e.getStage().content.style.cursor=""),this._cursorChange=!1})),this.add(e)}_createBack(){const t=new Ft({name:"back",width:0,height:0,draggable:!0,sceneFunc(t,e){const i=e.getParent(),n=i.padding();t.beginPath(),t.rect(-n,-n,e.width()+2*n,e.height()+2*n),t.moveTo(e.width()/2,-n),i.rotateEnabled()&&i.rotateLineVisible()&&t.lineTo(e.width()/2,-i.rotateAnchorOffset()*g._sign(e.height())-n),t.fillStrokeShape(e)},hitFunc:(t,e)=>{if(!this.shouldOverdrawWholeArea())return;const i=this.padding();t.beginPath(),t.rect(-i,-i,e.width()+2*i,e.height()+2*i),t.fillStrokeShape(e)}});this.add(t),this._proxyDrag(t),t.on("dragstart",(t=>{t.cancelBubble=!0})),t.on("dragmove",(t=>{t.cancelBubble=!0})),t.on("dragend",(t=>{t.cancelBubble=!0})),this.on("dragmove",(t=>{this.update()}))}_handleMouseDown(t){if(this._transforming)return;this._movingAnchorName=t.target.name().split(" ")[0];const e=this._getNodeRect(),i=e.width,n=e.height,s=Math.sqrt(Math.pow(i,2)+Math.pow(n,2));this.sin=Math.abs(n/s),this.cos=Math.abs(i/s),"undefined"!=typeof window&&(window.addEventListener("mousemove",this._handleMouseMove),window.addEventListener("touchmove",this._handleMouseMove),window.addEventListener("mouseup",this._handleMouseUp,!0),window.addEventListener("touchend",this._handleMouseUp,!0)),this._transforming=!0;const r=t.target.getAbsolutePosition(),a=t.target.getStage().getPointerPosition();this._anchorDragOffset={x:a.x-r.x,y:a.y-r.y},ii++,this._fire("transformstart",{evt:t.evt,target:this.getNode()}),this._nodes.forEach((e=>{e._fire("transformstart",{evt:t.evt,target:e})}))}_handleMouseMove(t){let e,n,s;const r=this.findOne("."+this._movingAnchorName),a=r.getStage();a.setPointersPositions(t);const o=a.getPointerPosition();let h={x:o.x-this._anchorDragOffset.x,y:o.y-this._anchorDragOffset.y};const l=r.getAbsolutePosition();this.anchorDragBoundFunc()&&(h=this.anchorDragBoundFunc()(l,h,t)),r.setAbsolutePosition(h);const d=r.getAbsolutePosition();if(l.x===d.x&&l.y===d.y)return;if("rotater"===this._movingAnchorName){const s=this._getNodeRect();e=r.x()-s.width/2,n=-r.y()+s.height/2;let a=Math.atan2(-n,e)+Math.PI/2;s.height<0&&(a-=Math.PI);const o=i.getAngle(this.rotation())+a,h=i.getAngle(this.rotationSnapTolerance()),l=function(t,e,n){let s=e;for(let r=0;rt.x?-1:1,a=this.findOne(".top-left").y()>t.y?-1:1;e=s*this.cos*i,n=s*this.sin*a,this.findOne(".top-left").x(t.x-e),this.findOne(".top-left").y(t.y-n)}}else if("top-center"===this._movingAnchorName)this.findOne(".top-left").y(r.y());else if("top-right"===this._movingAnchorName){if(g){const t=u?{x:this.width()/2,y:this.height()/2}:{x:this.findOne(".bottom-left").x(),y:this.findOne(".bottom-left").y()};s=Math.sqrt(Math.pow(r.x()-t.x,2)+Math.pow(t.y-r.y(),2));const i=this.findOne(".top-right").x()t.y?-1:1;e=s*this.cos*i,n=s*this.sin*a,this.findOne(".top-right").x(t.x+e),this.findOne(".top-right").y(t.y-n)}var f=r.position();this.findOne(".top-left").y(f.y),this.findOne(".bottom-right").x(f.x)}else if("middle-left"===this._movingAnchorName)this.findOne(".top-left").x(r.x());else if("middle-right"===this._movingAnchorName)this.findOne(".bottom-right").x(r.x());else if("bottom-left"===this._movingAnchorName){if(g){const t=u?{x:this.width()/2,y:this.height()/2}:{x:this.findOne(".top-right").x(),y:this.findOne(".top-right").y()};s=Math.sqrt(Math.pow(t.x-r.x(),2)+Math.pow(r.y()-t.y,2));const i=t.x{var i;e._fire("transformend",{evt:t,target:e}),null===(i=e.getLayer())||void 0===i||i.batchDraw()})),this._movingAnchorName=null}}_fitNodesInto(t,e){const n=this._getNodeRect();if(g._inRange(t.width,2*-this.padding()-1,1))return void this.update();if(g._inRange(t.height,2*-this.padding()-1,1))return void this.update();const r=new s;if(r.rotate(i.getAngle(this.rotation())),this._movingAnchorName&&t.width<0&&this._movingAnchorName.indexOf("left")>=0){const e=r.point({x:2*-this.padding(),y:0});t.x+=e.x,t.y+=e.y,t.width+=2*this.padding(),this._movingAnchorName=this._movingAnchorName.replace("left","right"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y}else if(this._movingAnchorName&&t.width<0&&this._movingAnchorName.indexOf("right")>=0){const e=r.point({x:2*this.padding(),y:0});this._movingAnchorName=this._movingAnchorName.replace("right","left"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y,t.width+=2*this.padding()}if(this._movingAnchorName&&t.height<0&&this._movingAnchorName.indexOf("top")>=0){const e=r.point({x:0,y:2*-this.padding()});t.x+=e.x,t.y+=e.y,this._movingAnchorName=this._movingAnchorName.replace("top","bottom"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y,t.height+=2*this.padding()}else if(this._movingAnchorName&&t.height<0&&this._movingAnchorName.indexOf("bottom")>=0){const e=r.point({x:0,y:2*this.padding()});this._movingAnchorName=this._movingAnchorName.replace("bottom","top"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y,t.height+=2*this.padding()}if(this.boundBoxFunc()){const e=this.boundBoxFunc()(n,t);e?t=e:g.warn("boundBoxFunc returned falsy. You should return new bound rect from it!")}const a=1e7,o=new s;o.translate(n.x,n.y),o.rotate(n.rotation),o.scale(n.width/a,n.height/a);const h=new s,l=t.width/a,d=t.height/a;!1===this.flipEnabled()?(h.translate(t.x,t.y),h.rotate(t.rotation),h.translate(t.width<0?t.width:0,t.height<0?t.height:0),h.scale(Math.abs(l),Math.abs(d))):(h.translate(t.x,t.y),h.rotate(t.rotation),h.scale(l,d));const c=h.multiply(o.invert());this._nodes.forEach((t=>{var e;const i=t.getParent().getAbsoluteTransform(),n=t.getTransform().copy();n.translate(t.offsetX(),t.offsetY());const r=new s;r.multiply(i.copy().invert()).multiply(c).multiply(i).multiply(n);const a=r.decompose();t.setAttrs(a),null===(e=t.getLayer())||void 0===e||e.batchDraw()})),this.rotation(g._getRotation(t.rotation)),this._nodes.forEach((t=>{this._fire("transform",{evt:e,target:t}),t._fire("transform",{evt:e,target:t})})),this._resetTransformCache(),this.update(),this.getLayer().batchDraw()}forceUpdate(){this._resetTransformCache(),this.update()}_batchChangeChild(t,e){this.findOne(t).setAttrs(e)}update(){var t;const e=this._getNodeRect();this.rotation(g._getRotation(e.rotation));const i=e.width,n=e.height,s=this.enabledAnchors(),r=this.resizeEnabled(),a=this.padding(),o=this.anchorSize(),h=this.find("._anchor");h.forEach((t=>{t.setAttrs({width:o,height:o,offsetX:o/2,offsetY:o/2,stroke:this.anchorStroke(),strokeWidth:this.anchorStrokeWidth(),fill:this.anchorFill(),cornerRadius:this.anchorCornerRadius()})})),this._batchChangeChild(".top-left",{x:0,y:0,offsetX:o/2+a,offsetY:o/2+a,visible:r&&s.indexOf("top-left")>=0}),this._batchChangeChild(".top-center",{x:i/2,y:0,offsetY:o/2+a,visible:r&&s.indexOf("top-center")>=0}),this._batchChangeChild(".top-right",{x:i,y:0,offsetX:o/2-a,offsetY:o/2+a,visible:r&&s.indexOf("top-right")>=0}),this._batchChangeChild(".middle-left",{x:0,y:n/2,offsetX:o/2+a,visible:r&&s.indexOf("middle-left")>=0}),this._batchChangeChild(".middle-right",{x:i,y:n/2,offsetX:o/2-a,visible:r&&s.indexOf("middle-right")>=0}),this._batchChangeChild(".bottom-left",{x:0,y:n,offsetX:o/2+a,offsetY:o/2-a,visible:r&&s.indexOf("bottom-left")>=0}),this._batchChangeChild(".bottom-center",{x:i/2,y:n,offsetY:o/2-a,visible:r&&s.indexOf("bottom-center")>=0}),this._batchChangeChild(".bottom-right",{x:i,y:n,offsetX:o/2-a,offsetY:o/2-a,visible:r&&s.indexOf("bottom-right")>=0}),this._batchChangeChild(".rotater",{x:i/2,y:-this.rotateAnchorOffset()*g._sign(n)-a,visible:this.rotateEnabled()}),this._batchChangeChild(".back",{width:i,height:n,visible:this.borderEnabled(),stroke:this.borderStroke(),strokeWidth:this.borderStrokeWidth(),dash:this.borderDash(),x:0,y:0});const l=this.anchorStyleFunc();l&&h.forEach((t=>{l(t)})),null===(t=this.getLayer())||void 0===t||t.batchDraw()}isTransforming(){return this._transforming}stopTransform(){if(this._transforming){this._removeEvents();const t=this.findOne("."+this._movingAnchorName);t&&t.stopDrag()}}destroy(){return this.getStage()&&this._cursorChange&&this.getStage().content&&(this.getStage().content.style.cursor=""),Wt.prototype.destroy.call(this),this.detach(),this._removeEvents(),this}toObject(){return q.prototype.toObject.call(this)}clone(t){return q.prototype.clone.call(this,t)}getClientRect(){return this.nodes().length>0?super.getClientRect():{x:0,y:0,width:0,height:0}}}ni.isTransforming=()=>ii>0,ni.prototype.className="Transformer",n(ni),E.addGetterSetter(ni,"enabledAnchors",Ze,(function(t){return t instanceof Array||g.warn("enabledAnchors value should be an array"),t instanceof Array&&t.forEach((function(t){-1===Ze.indexOf(t)&&g.warn("Unknown anchor name: "+t+". Available names are: "+Ze.join(", "))})),t||[]})),E.addGetterSetter(ni,"flipEnabled",!0,M()),E.addGetterSetter(ni,"resizeEnabled",!0),E.addGetterSetter(ni,"anchorSize",10,C()),E.addGetterSetter(ni,"rotateEnabled",!0),E.addGetterSetter(ni,"rotateLineVisible",!0),E.addGetterSetter(ni,"rotationSnaps",[]),E.addGetterSetter(ni,"rotateAnchorOffset",50,C()),E.addGetterSetter(ni,"rotateAnchorCursor","crosshair"),E.addGetterSetter(ni,"rotationSnapTolerance",5,C()),E.addGetterSetter(ni,"borderEnabled",!0),E.addGetterSetter(ni,"anchorStroke","rgb(0, 161, 255)"),E.addGetterSetter(ni,"anchorStrokeWidth",1,C()),E.addGetterSetter(ni,"anchorFill","white"),E.addGetterSetter(ni,"anchorCornerRadius",0,C()),E.addGetterSetter(ni,"borderStroke","rgb(0, 161, 255)"),E.addGetterSetter(ni,"borderStrokeWidth",1,C()),E.addGetterSetter(ni,"borderDash"),E.addGetterSetter(ni,"keepRatio",!0),E.addGetterSetter(ni,"shiftBehavior","default"),E.addGetterSetter(ni,"centeredScaling",!1),E.addGetterSetter(ni,"ignoreStroke",!1),E.addGetterSetter(ni,"padding",0,C()),E.addGetterSetter(ni,"nodes"),E.addGetterSetter(ni,"node"),E.addGetterSetter(ni,"boundBoxFunc"),E.addGetterSetter(ni,"anchorDragBoundFunc"),E.addGetterSetter(ni,"anchorStyleFunc"),E.addGetterSetter(ni,"shouldOverdrawWholeArea",!1),E.addGetterSetter(ni,"useSingleNodeRotation",!0),E.backCompat(ni,{lineEnabled:"borderEnabled",rotateHandlerOffset:"rotateAnchorOffset",enabledHandlers:"enabledAnchors"});class si extends Ft{_sceneFunc(t){t.beginPath(),t.arc(0,0,this.radius(),0,i.getAngle(this.angle()),this.clockwise()),t.lineTo(0,0),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.radius()}getHeight(){return 2*this.radius()}setWidth(t){this.radius(t/2)}setHeight(t){this.radius(t/2)}}function ri(){this.r=0,this.g=0,this.b=0,this.a=0,this.next=null}si.prototype.className="Wedge",si.prototype._centroid=!0,si.prototype._attrsAffectingSize=["radius"],n(si),E.addGetterSetter(si,"radius",0,C()),E.addGetterSetter(si,"angle",0,C()),E.addGetterSetter(si,"clockwise",!1),E.backCompat(si,{angleDeg:"angle",getAngleDeg:"getAngle",setAngleDeg:"setAngle"});const ai=[512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259],oi=[9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24];E.addGetterSetter(q,"blurRadius",0,C(),E.afterSetFilter);E.addGetterSetter(q,"brightness",0,C(),E.afterSetFilter);E.addGetterSetter(q,"contrast",0,C(),E.afterSetFilter);function hi(t,e,i,n,s){const r=i-e,a=s-n;if(0===r)return n+a/2;if(0===a)return n;let o=(t-e)/r;return o=a*o+n,o}E.addGetterSetter(q,"embossStrength",.5,C(),E.afterSetFilter),E.addGetterSetter(q,"embossWhiteLevel",.5,C(),E.afterSetFilter),E.addGetterSetter(q,"embossDirection","top-left",void 0,E.afterSetFilter),E.addGetterSetter(q,"embossBlend",!1,void 0,E.afterSetFilter);E.addGetterSetter(q,"enhance",0,C(),E.afterSetFilter);E.addGetterSetter(q,"hue",0,C(),E.afterSetFilter),E.addGetterSetter(q,"saturation",0,C(),E.afterSetFilter),E.addGetterSetter(q,"luminance",0,C(),E.afterSetFilter);E.addGetterSetter(q,"hue",0,C(),E.afterSetFilter),E.addGetterSetter(q,"saturation",0,C(),E.afterSetFilter),E.addGetterSetter(q,"value",0,C(),E.afterSetFilter);function li(t,e,i){let n=4*(i*t.width+e);const s=[];return s.push(t.data[n++],t.data[n++],t.data[n++],t.data[n++]),s}function di(t,e){return Math.sqrt(Math.pow(t[0]-e[0],2)+Math.pow(t[1]-e[1],2)+Math.pow(t[2]-e[2],2))}E.addGetterSetter(q,"kaleidoscopePower",2,C(),E.afterSetFilter),E.addGetterSetter(q,"kaleidoscopeAngle",0,C(),E.afterSetFilter);E.addGetterSetter(q,"threshold",0,C(),E.afterSetFilter);E.addGetterSetter(q,"noise",.2,C(),E.afterSetFilter);E.addGetterSetter(q,"pixelSize",8,C(),E.afterSetFilter);E.addGetterSetter(q,"levels",.5,C(),E.afterSetFilter);E.addGetterSetter(q,"red",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),E.addGetterSetter(q,"green",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),E.addGetterSetter(q,"blue",0,w,E.afterSetFilter);E.addGetterSetter(q,"red",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),E.addGetterSetter(q,"green",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),E.addGetterSetter(q,"blue",0,w,E.afterSetFilter),E.addGetterSetter(q,"alpha",1,(function(t){return this._filterUpToDate=!1,t>1?1:t<0?0:t}));E.addGetterSetter(q,"threshold",.5,C(),E.afterSetFilter);return Jt.Util._assign(Jt,{Arc:$t,Arrow:ce,Circle:ge,Ellipse:ue,Image:fe,Label:be,Tag:Se,Line:ee,Path:de,Rect:we,RegularPolygon:Ce,Ring:ke,Sprite:Ae,Star:Te,Text:We,TextPath:qe,Transformer:ni,Wedge:si,Filters:{Blur:function(t){const e=Math.round(this.blurRadius());e>0&&function(t,e){const i=t.data,n=t.width,s=t.height;let r,a,o,h,l,d,c,g,u,f,p,m,_,y,v,x,b,S,w,C;const P=e+e+1,k=n-1,A=s-1,T=e+1,M=T*(T+1)/2,G=new ri,R=ai[e],E=oi[e];let D=null,L=G,O=null,I=null;for(let t=1;t>E,0!==w?(w=255/w,i[a]=(h*R>>E)*w,i[a+1]=(l*R>>E)*w,i[a+2]=(d*R>>E)*w):i[a]=i[a+1]=i[a+2]=0,h-=g,l-=u,d-=f,c-=p,g-=O.r,u-=O.g,f-=O.b,p-=O.a,r=o+((r=t+e+1)>E,w>0?(w=255/w,i[r]=(h*R>>E)*w,i[r+1]=(l*R>>E)*w,i[r+2]=(d*R>>E)*w):i[r]=i[r+1]=i[r+2]=0,h-=g,l-=u,d-=f,c-=p,g-=O.r,u-=O.g,f-=O.b,p-=O.a,r=t+((r=e+T)255?255:s,r=r<0?0:r>255?255:r,a=a<0?0:a>255?255:a,i[t]=s,i[t+1]=r,i[t+2]=a},Emboss:function(t){const e=10*this.embossStrength(),i=255*this.embossWhiteLevel(),n=this.embossDirection(),s=this.embossBlend(),r=t.data,a=t.width,o=t.height,h=4*a;let l=0,d=0,c=o;switch(n){case"top-left":l=-1,d=-1;break;case"top":l=-1,d=0;break;case"top-right":l=-1,d=1;break;case"right":l=0,d=1;break;case"bottom-right":l=1,d=1;break;case"bottom":l=1,d=0;break;case"bottom-left":l=1,d=-1;break;case"left":l=0,d=-1;break;default:g.error("Unknown emboss direction: "+n)}do{const t=(c-1)*h;let n=l;c+n<1&&(n=0),c+n>o&&(n=0);const g=(c-1+n)*a*4;let u=a;do{const n=t+4*(u-1);let o=d;u+o<1&&(o=0),u+o>a&&(o=0);const h=g+4*(u-1+o),l=r[n]-r[h],c=r[n+1]-r[h+1],f=r[n+2]-r[h+2];let p=l;const m=p>0?p:-p;if((c>0?c:-c)>m&&(p=c),(f>0?f:-f)>m&&(p=f),p*=e,s){const t=r[n]+p,e=r[n+1]+p,i=r[n+2]+p;r[n]=t>255?255:t<0?0:t,r[n+1]=e>255?255:e<0?0:e,r[n+2]=i>255?255:i<0?0:i}else{let t=i-p;t<0?t=0:t>255&&(t=255),r[n]=r[n+1]=r[n+2]=t}}while(--u)}while(--c)},Enhance:function(t){const e=t.data,i=e.length;let n,s,r,a=e[0],o=a,h=e[1],l=h,d=e[2],c=d;const g=this.enhance();if(0===g)return;for(let t=0;to&&(o=n),s=e[t+1],sl&&(l=s),r=e[t+2],rc&&(c=r);let u,f,p,m,_,y;if(o===a&&(o=255,a=0),l===h&&(l=255,h=0),c===d&&(c=255,d=0),g>0)u=o+g*(255-o),f=a-g*(a-0),p=l+g*(255-l),m=h-g*(h-0),_=c+g*(255-c),y=d-g*(d-0);else{const t=.5*(o+a);u=o+g*(o-t),f=a+g*(a-t);const e=.5*(l+h);p=l+g*(l-e),m=h+g*(h-e);const i=.5*(c+d);_=c+g*(c-i),y=d+g*(d-i)}for(let t=0;tl?g:l;const u=a,f=r,p=360/f*Math.PI/180;for(let t=0;te&&(b=x,S=0,w=-1),s=0;sl?g:l;const u=a,f=r;let p,m;for(d=0;d=0&&c=0&&g=0&&c=0&&g=1020?255:0}return a}(e,t.width,t.height),e=function(t,e,i){const n=[1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9],s=Math.round(Math.sqrt(n.length)),r=Math.floor(s/2),a=[];for(let o=0;o=0&&c=0&&g=i))for(let e=g;e=n)continue;const s=4*(i*e+t);r+=a[s+0],o+=a[s+1],h+=a[s+2],l+=a[s+3],f+=1}r/=f,o/=f,h/=f,l/=f;for(let t=d;t=i))for(let e=g;e=n)continue;const s=4*(i*e+t);a[s+0]=r,a[s+1]=o,a[s+2]=h,a[s+3]=l}}},Posterize:function(t){const e=Math.round(254*this.levels())+1,i=t.data,n=i.length,s=255/e;for(let t=0;t127&&(n=255-n),s>127&&(s=255-s),a>127&&(a=255-a),e[i]=n,e[i+1]=s,e[i+2]=a}while(--r)}while(--s)},Threshold:function(t){const e=255*this.threshold(),i=t.data,n=i.length;for(let t=0;tWorkflows +
  • + + Layouts + +
  • {% if current_user.is_admin() %}
  • + +{% if boards %} +
    + {% for board in boards %} +
    +
    + +
    +

    {{ board.host }}:{{ board.port }}

    + + +
    + {% for n in range(1, board.num_relays + 1) %} + {% set relay_key = "relay_" ~ n %} + {% set is_on = board.relay_states.get(relay_key, false) %} + {% set e = board.get_relay_entity(n) %} + + {% endfor %} +
    + + + {% if board.num_inputs > 0 %} +
    + {% for n in range(1, board.num_inputs + 1) %} + {% set input_key = "input_" ~ n %} + {% set raw_state = board.input_states.get(input_key, true) %} + {% set is_active = not raw_state %} + {% set e = board.get_input_entity(n) %} + + {{ e.name }} + + {% endfor %} +
    + {% endif %} +
    + +
    +
    + {% endfor %} +
    +{% endif %} + + {% if boards %}
    @@ -36,14 +109,27 @@ -
    {{ b.num_relays }} {{ b.num_inputs }} - + {% if b.is_online %}Online{% else %}Offline{% endif %} + {% if b.last_seen %}{{ b.last_seen.strftime('%Y-%m-%d %H:%M') }}{% else %}—{% endif %} + {% if not b.is_online %} + + {% else %} + + {% endif %} {% if current_user.is_admin() %} @@ -68,3 +154,137 @@ {% endif %} {% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/templates/dashboard/index.html b/app/templates/dashboard/index.html index c2780e7..038bc38 100644 --- a/app/templates/dashboard/index.html +++ b/app/templates/dashboard/index.html @@ -48,163 +48,4 @@ - -{% if boards %} -
    - {% for board in boards %} -
    -
    -
    -
    -
    {{ board.name }}
    - {{ board.board_type }} - - {% if board.is_online %}Online{% else %}Offline{% endif %} - -
    - - - -
    -
    -

    {{ board.host }}:{{ board.port }}

    - - -
    - {% for n in range(1, board.num_relays + 1) %} - {% set relay_key = "relay_" ~ n %} - {% set is_on = board.relay_states.get(relay_key, false) %} - {% set e = board.get_relay_entity(n) %} - - {% endfor %} -
    - - - {% if board.num_inputs > 0 %} -
    - {% for n in range(1, board.num_inputs + 1) %} - {% set input_key = "input_" ~ n %} - {% set raw_state = board.input_states.get(input_key, true) %} - {% set is_active = not raw_state %} - {% set e = board.get_input_entity(n) %} - - {{ e.name }} - - {% endfor %} -
    - {% endif %} -
    - -
    -
    - {% endfor %} -
    -{% else %} -
    - -

    No boards added yet.

    - {% if current_user.is_admin() %} - - Add your first board - - {% endif %} -
    -{% endif %} -{% endblock %} - -{% block scripts %} - {% endblock %} diff --git a/app/templates/layouts/builder.html b/app/templates/layouts/builder.html new file mode 100644 index 0000000..17f2e48 --- /dev/null +++ b/app/templates/layouts/builder.html @@ -0,0 +1,333 @@ +{% extends "base.html" %} +{% block title %}Builder – {{ layout.name }}{% endblock %} + +{% block content %} + + +
    + + +
    +
    + + {{ layout.name }} +
    +
    + + +
    + + + + + + + + + + + + + + + + +
    + + + + + + + +
    +
    + + +
    + + +
    + Mode + + + + +
    + Zoom + + + + +
    + + +
    + + + Unsaved +
    + + +
    +
    +
    + +
    +
    + + +
    + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} diff --git a/app/templates/layouts/list.html b/app/templates/layouts/list.html new file mode 100644 index 0000000..0bfb436 --- /dev/null +++ b/app/templates/layouts/list.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} +{% block title %}Layouts – Location Management{% endblock %} + +{% block content %} +
    +

    Layouts

    + {% if current_user.is_admin() %} + + {% endif %} +
    + +{% if layouts %} +
    + {% for layout in layouts %} +
    +
    + +
    + {% if layout.thumbnail_b64 %} + thumbnail + {% else %} + + {% endif %} +
    +
    +
    {{ layout.name }}
    + {% if layout.description %} +

    {{ layout.description }}

    + {% endif %} +
    + +
    +
    + {% endfor %} +
    +{% else %} +
    + +

    No layouts yet.

    + {% if current_user.is_admin() %} + + {% endif %} +
    +{% endif %} + + +{% if current_user.is_admin() %} + +{% endif %} +{% endblock %} diff --git a/config.py b/config.py index e2d9fc3..3122668 100644 --- a/config.py +++ b/config.py @@ -14,8 +14,10 @@ class Config: f"sqlite:///{os.path.join(BASE_DIR, 'instance', 'location_mgmt.db')}" ) SQLALCHEMY_TRACK_MODIFICATIONS = False - # How often (seconds) the board poller updates relay states in the background + # How often (seconds) the fast poller updates ONLINE boards BOARD_POLL_INTERVAL = int(os.environ.get("BOARD_POLL_INTERVAL", 10)) + # How often (seconds) offline boards are rechecked to see if they came back + OFFLINE_RECHECK_INTERVAL = int(os.environ.get("OFFLINE_RECHECK_INTERVAL", 60)) # Base URL this server is reachable at (boards will POST webhooks here) SERVER_BASE_URL = os.environ.get("SERVER_BASE_URL", "http://localhost:5000") diff --git a/run.py b/run.py index 6a201cf..49b983e 100644 --- a/run.py +++ b/run.py @@ -11,31 +11,48 @@ import threading import time from app import create_app, socketio -from app.services.board_service import poll_all_boards +from app.services.board_service import poll_online_boards, recheck_offline_boards app = create_app(os.environ.get("FLASK_ENV", "development")) -def _background_poller(): - """Poll all boards in a loop every BOARD_POLL_INTERVAL seconds.""" +def _online_poller(): + """Fast loop — polls only online boards every BOARD_POLL_INTERVAL seconds.""" interval = app.config.get("BOARD_POLL_INTERVAL", 10) while True: try: - poll_all_boards(app) + poll_online_boards(app) except Exception as exc: - app.logger.warning("Poller error: %s", exc) + app.logger.warning("Online poller error: %s", exc) time.sleep(interval) +def _offline_recheck_poller(): + """Slow loop — probes offline boards every OFFLINE_RECHECK_INTERVAL seconds. + + Waits one full interval before the first check so startup is clean. + """ + interval = app.config.get("OFFLINE_RECHECK_INTERVAL", 60) + while True: + time.sleep(interval) # wait first, then check + try: + recheck_offline_boards(app) + except Exception as exc: + app.logger.warning("Offline recheck error: %s", exc) + + def create_socketio_app(): """WSGI callable for gunicorn / production.""" return socketio.middleware(app) if __name__ == "__main__": - # Start background board poller - t = threading.Thread(target=_background_poller, daemon=True) - t.start() + # Werkzeug debug-reloader starts two processes; only start the poller in + # the actual worker (WERKZEUG_RUN_MAIN=true) to avoid duplicate polling. + # In non-debug mode (WERKZEUG_RUN_MAIN is unset) we always start it. + if not app.debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true": + threading.Thread(target=_online_poller, daemon=True).start() + threading.Thread(target=_offline_recheck_poller, daemon=True).start() port = int(os.environ.get("PORT", 5000)) socketio.run(app, host="0.0.0.0", port=port, debug=app.debug, allow_unsafe_werkzeug=True)