APP.core.utils = {}; APP.core.utils.$ = (sel, root = document) => root.querySelector(sel); APP.core.utils.$$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); APP.core.utils.clamp = (x, a, b) => Math.min(b, Math.max(a, x)); APP.core.utils.lerp = (a, b, t) => a + (b - a) * t; APP.core.utils.now = () => performance.now(); APP.core.utils.escapeHtml = function (s) { return String(s).replace(/[&<>"']/g, m => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m])); }; APP.core.utils.canvasToBlob = function (canvas, quality = 0.88) { return new Promise((resolve, reject) => { if (!canvas.toBlob) { reject(new Error("Canvas.toBlob not supported")); return; } canvas.toBlob(blob => { if (!blob) { reject(new Error("Canvas toBlob failed")); return; } resolve(blob); }, "image/jpeg", quality); }); }; APP.core.utils.normBBox = function (bbox, w, h) { const [x, y, bw, bh] = bbox; return { x: APP.core.utils.clamp(x, 0, w - 1), y: APP.core.utils.clamp(y, 0, h - 1), w: APP.core.utils.clamp(bw, 1, w), h: APP.core.utils.clamp(bh, 1, h) }; }; APP.core.utils.loadedScripts = new Map(); APP.core.utils.loadScriptOnce = function (key, src) { return new Promise((resolve, reject) => { if (APP.core.utils.loadedScripts.get(key) === "loaded") { resolve(); return; } if (APP.core.utils.loadedScripts.get(key) === "loading") { const iv = setInterval(() => { if (APP.core.utils.loadedScripts.get(key) === "loaded") { clearInterval(iv); resolve(); } if (APP.core.utils.loadedScripts.get(key) === "failed") { clearInterval(iv); reject(new Error("Script failed earlier")); } }, 50); return; } APP.core.utils.loadedScripts.set(key, "loading"); const s = document.createElement("script"); s.src = src; s.async = true; s.onload = () => { APP.core.utils.loadedScripts.set(key, "loaded"); resolve(); }; s.onerror = () => { APP.core.utils.loadedScripts.set(key, "failed"); reject(new Error(`Failed to load ${src}`)); }; document.head.appendChild(s); }); };