Spaces:
Running on Zero
Running on Zero
ctrl shift bbox modifiers
Browse files
app.py
CHANGED
|
@@ -547,6 +547,20 @@ gradio-app { height: 100vh; overflow: hidden; }
|
|
| 547 |
.ib-sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
|
| 548 |
.ib-se { right: -6px; bottom: -6px; cursor: nwse-resize; }
|
| 549 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
/* ---- Right-side element cards (rendered by JS into #panel-root) ---- */
|
| 551 |
.ib-panel-head { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; }
|
| 552 |
.ib-add-wrap { margin-left: auto; display: flex; gap: 6px; align-items: center; }
|
|
@@ -714,7 +728,7 @@ CANVAS_JS = r"""
|
|
| 714 |
canvasRoot.appendChild(stage);
|
| 715 |
|
| 716 |
let els = []; // {id,type,ymin,xmin,ymax,xmax,desc,text,palette}
|
| 717 |
-
let
|
| 718 |
let drag = null;
|
| 719 |
let jsCounter = 0;
|
| 720 |
let emitTimer = null;
|
|
@@ -761,7 +775,7 @@ CANVAS_JS = r"""
|
|
| 761 |
text: e.text || "",
|
| 762 |
colors: e.colors || [],
|
| 763 |
})),
|
| 764 |
-
selected:
|
| 765 |
};
|
| 766 |
setNativeValue(el, JSON.stringify(payload));
|
| 767 |
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -777,10 +791,11 @@ CANVAS_JS = r"""
|
|
| 777 |
}
|
| 778 |
|
| 779 |
function renderBoxes() {
|
| 780 |
-
stage.querySelectorAll(".ib-box").forEach((n) => n.remove());
|
|
|
|
| 781 |
els.forEach((e, i) => {
|
| 782 |
const d = document.createElement("div");
|
| 783 |
-
d.className = "ib-box" + (e.id
|
| 784 |
d.style.left = e.xmin / 10 + "%";
|
| 785 |
d.style.top = e.ymin / 10 + "%";
|
| 786 |
d.style.width = (e.xmax - e.xmin) / 10 + "%";
|
|
@@ -790,20 +805,39 @@ CANVAS_JS = r"""
|
|
| 790 |
lab.className = "ib-label";
|
| 791 |
lab.textContent = labelText(e, i);
|
| 792 |
d.appendChild(lab);
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
|
|
|
|
|
|
| 799 |
stage.appendChild(d);
|
| 800 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
}
|
| 802 |
|
| 803 |
function cardHTML(e, i) {
|
| 804 |
const isText = e.type === "text";
|
| 805 |
return (
|
| 806 |
-
'<div class="ib-card' + (e.id
|
| 807 |
'<div class="ib-card-head">' +
|
| 808 |
'<span class="ib-badge">' + (i + 1) + "</span>" +
|
| 809 |
'<select class="ib-type" data-f="type">' +
|
|
@@ -1055,16 +1089,37 @@ CANVAS_JS = r"""
|
|
| 1055 |
renderPanel();
|
| 1056 |
}
|
| 1057 |
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1068 |
}
|
| 1069 |
|
| 1070 |
function toNorm(ev) {
|
|
@@ -1075,26 +1130,86 @@ CANVAS_JS = r"""
|
|
| 1075 |
};
|
| 1076 |
}
|
| 1077 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
// ---- canvas interaction ----
|
| 1079 |
stage.addEventListener("pointerdown", (ev) => {
|
| 1080 |
if (ev.button !== 0 || !img.src || eyedropSample) return;
|
|
|
|
|
|
|
| 1081 |
const boxEl = ev.target.closest(".ib-box");
|
| 1082 |
const handle = ev.target.classList.contains("ib-h") ? ev.target.dataset.h : null;
|
| 1083 |
-
const
|
| 1084 |
-
|
| 1085 |
-
|
| 1086 |
-
|
| 1087 |
-
} else if (boxEl) {
|
| 1088 |
const e = byId(boxEl.dataset.id);
|
| 1089 |
-
drag = { mode: "
|
| 1090 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1091 |
} else {
|
|
|
|
| 1092 |
const id = "j" + jsCounter++;
|
| 1093 |
const e = { id: id, type: "obj", ymin: p.y, xmin: p.x, ymax: p.y, xmax: p.x,
|
| 1094 |
desc: "", text: "", colors: [] };
|
| 1095 |
els.push(e);
|
|
|
|
| 1096 |
drag = { mode: "draw", e: e, start: p };
|
| 1097 |
-
selected = id;
|
| 1098 |
renderAll();
|
| 1099 |
}
|
| 1100 |
stage.setPointerCapture(ev.pointerId);
|
|
@@ -1104,45 +1219,54 @@ CANVAS_JS = r"""
|
|
| 1104 |
stage.addEventListener("pointermove", (ev) => {
|
| 1105 |
if (!drag) return;
|
| 1106 |
const p = toNorm(ev);
|
| 1107 |
-
const
|
| 1108 |
if (drag.mode === "move") {
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1118 |
} else if (drag.mode === "draw") {
|
|
|
|
| 1119 |
e.xmin = Math.min(drag.start.x, p.x);
|
| 1120 |
e.xmax = Math.max(drag.start.x, p.x);
|
| 1121 |
e.ymin = Math.min(drag.start.y, p.y);
|
| 1122 |
e.ymax = Math.max(drag.start.y, p.y);
|
| 1123 |
} else if (drag.mode === "resize") {
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
if (h.includes("n")) e.ymin = Math.min(p.y, e.ymax - 1);
|
| 1128 |
-
if (h.includes("s")) e.ymax = Math.max(p.y, e.ymin + 1);
|
| 1129 |
}
|
| 1130 |
renderBoxes();
|
| 1131 |
});
|
| 1132 |
|
| 1133 |
function endDrag() {
|
| 1134 |
if (!drag) return;
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
-
|
| 1138 |
-
|
|
|
|
|
|
|
| 1139 |
}
|
| 1140 |
const wasDraw = drag.mode === "draw";
|
| 1141 |
drag = null;
|
| 1142 |
renderAll();
|
|
|
|
| 1143 |
emitNow();
|
| 1144 |
if (wasDraw) {
|
| 1145 |
-
const
|
|
|
|
| 1146 |
if (card) card.focus();
|
| 1147 |
}
|
| 1148 |
}
|
|
@@ -1150,11 +1274,11 @@ CANVAS_JS = r"""
|
|
| 1150 |
stage.addEventListener("pointercancel", endDrag);
|
| 1151 |
|
| 1152 |
window.addEventListener("keydown", (ev) => {
|
| 1153 |
-
if (
|
| 1154 |
const t = document.activeElement;
|
| 1155 |
if (t && (t.tagName === "TEXTAREA" || t.tagName === "INPUT")) return;
|
| 1156 |
-
els = els.filter((x) => x.id
|
| 1157 |
-
|
| 1158 |
renderAll();
|
| 1159 |
emitNow();
|
| 1160 |
}
|
|
@@ -1184,7 +1308,7 @@ CANVAS_JS = r"""
|
|
| 1184 |
if (!e) return;
|
| 1185 |
e.type = ev.target.value;
|
| 1186 |
renderAll();
|
| 1187 |
-
|
| 1188 |
emitNow();
|
| 1189 |
});
|
| 1190 |
document.addEventListener("click", (ev) => {
|
|
@@ -1196,7 +1320,7 @@ CANVAS_JS = r"""
|
|
| 1196 |
const e = byId(card.dataset.id);
|
| 1197 |
if (ev.target.classList.contains("ib-del")) {
|
| 1198 |
els = els.filter((x) => x.id !== card.dataset.id);
|
| 1199 |
-
|
| 1200 |
renderAll();
|
| 1201 |
emitNow();
|
| 1202 |
return;
|
|
@@ -1229,16 +1353,18 @@ CANVAS_JS = r"""
|
|
| 1229 |
});
|
| 1230 |
return;
|
| 1231 |
}
|
| 1232 |
-
if (!ev.target.closest(".ib-f, .ib-type, .ib-pal"))
|
|
|
|
|
|
|
|
|
|
| 1233 |
});
|
| 1234 |
|
| 1235 |
function addElement(type) {
|
| 1236 |
const id = "j" + jsCounter++;
|
| 1237 |
els.push({ id: id, type: type, ymin: 350, xmin: 350, ymax: 650, xmax: 650,
|
| 1238 |
desc: "", text: "", colors: [] });
|
| 1239 |
-
selected = id;
|
| 1240 |
renderAll();
|
| 1241 |
-
|
| 1242 |
emitNow();
|
| 1243 |
}
|
| 1244 |
|
|
@@ -1251,7 +1377,7 @@ CANVAS_JS = r"""
|
|
| 1251 |
if (Array.isArray(p.elements)) {
|
| 1252 |
els = p.elements.map((e) => Object.assign({}, e));
|
| 1253 |
}
|
| 1254 |
-
|
| 1255 |
renderAll();
|
| 1256 |
} catch (e) {
|
| 1257 |
console.error("IB.load", e);
|
|
|
|
| 547 |
.ib-sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
|
| 548 |
.ib-se { right: -6px; bottom: -6px; cursor: nwse-resize; }
|
| 549 |
|
| 550 |
+
/* group-transform box (shown when 2+ boxes selected): outline is click-through, handles aren't */
|
| 551 |
+
.ib-group {
|
| 552 |
+
position: absolute; border: 1px dashed #ff8c00; box-sizing: border-box;
|
| 553 |
+
pointer-events: none; z-index: 6;
|
| 554 |
+
}
|
| 555 |
+
.ib-gh {
|
| 556 |
+
position: absolute; width: 11px; height: 11px; background: #fff; border: 1px solid #ff8c00;
|
| 557 |
+
box-sizing: border-box; pointer-events: auto;
|
| 558 |
+
}
|
| 559 |
+
.ib-gh.ib-n { left: 50%; top: -6px; transform: translateX(-50%); cursor: ns-resize; }
|
| 560 |
+
.ib-gh.ib-s { left: 50%; bottom: -6px; transform: translateX(-50%); cursor: ns-resize; }
|
| 561 |
+
.ib-gh.ib-e { right: -6px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }
|
| 562 |
+
.ib-gh.ib-w { left: -6px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }
|
| 563 |
+
|
| 564 |
/* ---- Right-side element cards (rendered by JS into #panel-root) ---- */
|
| 565 |
.ib-panel-head { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; }
|
| 566 |
.ib-add-wrap { margin-left: auto; display: flex; gap: 6px; align-items: center; }
|
|
|
|
| 728 |
canvasRoot.appendChild(stage);
|
| 729 |
|
| 730 |
let els = []; // {id,type,ymin,xmin,ymax,xmax,desc,text,palette}
|
| 731 |
+
let sel = new Set(); // selected ids (multi-select via ctrl/cmd-click)
|
| 732 |
let drag = null;
|
| 733 |
let jsCounter = 0;
|
| 734 |
let emitTimer = null;
|
|
|
|
| 775 |
text: e.text || "",
|
| 776 |
colors: e.colors || [],
|
| 777 |
})),
|
| 778 |
+
selected: Array.from(sel),
|
| 779 |
};
|
| 780 |
setNativeValue(el, JSON.stringify(payload));
|
| 781 |
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
|
| 791 |
}
|
| 792 |
|
| 793 |
function renderBoxes() {
|
| 794 |
+
stage.querySelectorAll(".ib-box, .ib-group").forEach((n) => n.remove());
|
| 795 |
+
const single = sel.size === 1;
|
| 796 |
els.forEach((e, i) => {
|
| 797 |
const d = document.createElement("div");
|
| 798 |
+
d.className = "ib-box" + (sel.has(e.id) ? " sel" : "");
|
| 799 |
d.style.left = e.xmin / 10 + "%";
|
| 800 |
d.style.top = e.ymin / 10 + "%";
|
| 801 |
d.style.width = (e.xmax - e.xmin) / 10 + "%";
|
|
|
|
| 805 |
lab.className = "ib-label";
|
| 806 |
lab.textContent = labelText(e, i);
|
| 807 |
d.appendChild(lab);
|
| 808 |
+
if (single && sel.has(e.id)) {
|
| 809 |
+
["nw", "ne", "sw", "se"].forEach((h) => {
|
| 810 |
+
const hd = document.createElement("div");
|
| 811 |
+
hd.className = "ib-h ib-" + h;
|
| 812 |
+
hd.dataset.h = h;
|
| 813 |
+
d.appendChild(hd);
|
| 814 |
+
});
|
| 815 |
+
}
|
| 816 |
stage.appendChild(d);
|
| 817 |
});
|
| 818 |
+
// A single group box with 8 handles when 2+ are selected — drag a handle to scale them together.
|
| 819 |
+
if (sel.size >= 2) {
|
| 820 |
+
const g = groupBBox();
|
| 821 |
+
const go = document.createElement("div");
|
| 822 |
+
go.className = "ib-group";
|
| 823 |
+
go.style.left = g.x0 / 10 + "%";
|
| 824 |
+
go.style.top = g.y0 / 10 + "%";
|
| 825 |
+
go.style.width = (g.x1 - g.x0) / 10 + "%";
|
| 826 |
+
go.style.height = (g.y1 - g.y0) / 10 + "%";
|
| 827 |
+
["nw", "n", "ne", "e", "se", "s", "sw", "w"].forEach((h) => {
|
| 828 |
+
const hd = document.createElement("div");
|
| 829 |
+
hd.className = "ib-gh ib-" + h;
|
| 830 |
+
hd.dataset.gh = h;
|
| 831 |
+
go.appendChild(hd);
|
| 832 |
+
});
|
| 833 |
+
stage.appendChild(go);
|
| 834 |
+
}
|
| 835 |
}
|
| 836 |
|
| 837 |
function cardHTML(e, i) {
|
| 838 |
const isText = e.type === "text";
|
| 839 |
return (
|
| 840 |
+
'<div class="ib-card' + (sel.has(e.id) ? " sel" : "") + '" data-id="' + e.id + '">' +
|
| 841 |
'<div class="ib-card-head">' +
|
| 842 |
'<span class="ib-badge">' + (i + 1) + "</span>" +
|
| 843 |
'<select class="ib-type" data-f="type">' +
|
|
|
|
| 1089 |
renderPanel();
|
| 1090 |
}
|
| 1091 |
|
| 1092 |
+
const selArr = () => els.filter((e) => sel.has(e.id));
|
| 1093 |
+
function groupBBox() {
|
| 1094 |
+
const s = selArr();
|
| 1095 |
+
if (!s.length) return null;
|
| 1096 |
+
let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
|
| 1097 |
+
s.forEach((e) => {
|
| 1098 |
+
x0 = Math.min(x0, e.xmin); y0 = Math.min(y0, e.ymin);
|
| 1099 |
+
x1 = Math.max(x1, e.xmax); y1 = Math.max(y1, e.ymax);
|
| 1100 |
+
});
|
| 1101 |
+
return { x0: x0, y0: y0, x1: x1, y1: y1 };
|
| 1102 |
+
}
|
| 1103 |
+
function applySelClasses() {
|
| 1104 |
+
stage.querySelectorAll(".ib-box").forEach((n) => n.classList.toggle("sel", sel.has(n.dataset.id)));
|
| 1105 |
+
const pr = document.getElementById("panel-root");
|
| 1106 |
+
if (pr) pr.querySelectorAll(".ib-card").forEach((n) => n.classList.toggle("sel", sel.has(n.dataset.id)));
|
| 1107 |
+
}
|
| 1108 |
+
function scrollToCard(id) {
|
| 1109 |
+
const pr = document.getElementById("panel-root");
|
| 1110 |
+
const c = pr && pr.querySelector('.ib-card[data-id="' + id + '"]');
|
| 1111 |
+
if (c) c.scrollIntoView({ block: "nearest" });
|
| 1112 |
+
}
|
| 1113 |
+
function selectOnly(id) {
|
| 1114 |
+
sel = new Set(id ? [id] : []);
|
| 1115 |
+
renderBoxes();
|
| 1116 |
+
applySelClasses();
|
| 1117 |
+
if (id) scrollToCard(id);
|
| 1118 |
+
}
|
| 1119 |
+
function toggleSelect(id) {
|
| 1120 |
+
if (sel.has(id)) sel.delete(id); else sel.add(id);
|
| 1121 |
+
renderBoxes();
|
| 1122 |
+
applySelClasses();
|
| 1123 |
}
|
| 1124 |
|
| 1125 |
function toNorm(ev) {
|
|
|
|
| 1130 |
};
|
| 1131 |
}
|
| 1132 |
|
| 1133 |
+
// snapshot the current geometry of the selected boxes (for move/group-resize deltas)
|
| 1134 |
+
const snapshot = () => {
|
| 1135 |
+
const o = {};
|
| 1136 |
+
selArr().forEach((e) => { o[e.id] = { xmin: e.xmin, ymin: e.ymin, xmax: e.xmax, ymax: e.ymax }; });
|
| 1137 |
+
return o;
|
| 1138 |
+
};
|
| 1139 |
+
|
| 1140 |
+
function resizeSingle(dr, p, shift) {
|
| 1141 |
+
const e = dr.e, o = dr.orig, h = dr.h;
|
| 1142 |
+
let xmin = o.xmin, ymin = o.ymin, xmax = o.xmax, ymax = o.ymax;
|
| 1143 |
+
if (h.includes("w")) xmin = Math.min(p.x, o.xmax - 1);
|
| 1144 |
+
if (h.includes("e")) xmax = Math.max(p.x, o.xmin + 1);
|
| 1145 |
+
if (h.includes("n")) ymin = Math.min(p.y, o.ymax - 1);
|
| 1146 |
+
if (h.includes("s")) ymax = Math.max(p.y, o.ymin + 1);
|
| 1147 |
+
if (shift) { // preserve aspect ratio, anchored at the opposite corner
|
| 1148 |
+
const ow = o.xmax - o.xmin, oh = o.ymax - o.ymin;
|
| 1149 |
+
const scale = Math.max((xmax - xmin) / ow, (ymax - ymin) / oh);
|
| 1150 |
+
const nw = ow * scale, nh = oh * scale;
|
| 1151 |
+
if (h.includes("w")) xmin = o.xmax - nw; else xmax = o.xmin + nw;
|
| 1152 |
+
if (h.includes("n")) ymin = o.ymax - nh; else ymax = o.ymin + nh;
|
| 1153 |
+
}
|
| 1154 |
+
e.xmin = clamp(xmin); e.ymin = clamp(ymin); e.xmax = clamp(xmax); e.ymax = clamp(ymax);
|
| 1155 |
+
}
|
| 1156 |
+
|
| 1157 |
+
function groupResize(dr, p, shift) {
|
| 1158 |
+
const h = dr.h, g = dr.g;
|
| 1159 |
+
let nx0 = g.x0, ny0 = g.y0, nx1 = g.x1, ny1 = g.y1;
|
| 1160 |
+
if (h.includes("w")) nx0 = Math.min(p.x, g.x1 - 1);
|
| 1161 |
+
if (h.includes("e")) nx1 = Math.max(p.x, g.x0 + 1);
|
| 1162 |
+
if (h.includes("n")) ny0 = Math.min(p.y, g.y1 - 1);
|
| 1163 |
+
if (h.includes("s")) ny1 = Math.max(p.y, g.y0 + 1);
|
| 1164 |
+
let sx = (nx1 - nx0) / (g.x1 - g.x0);
|
| 1165 |
+
let sy = (ny1 - ny0) / (g.y1 - g.y0);
|
| 1166 |
+
if (h === "n" || h === "s") sx = 1;
|
| 1167 |
+
if (h === "e" || h === "w") sy = 1;
|
| 1168 |
+
if (shift && h.length === 2) { const f = Math.max(sx, sy); sx = f; sy = f; }
|
| 1169 |
+
const ax = h.includes("w") ? g.x1 : g.x0; // anchor = opposite side
|
| 1170 |
+
const ay = h.includes("n") ? g.y1 : g.y0;
|
| 1171 |
+
selArr().forEach((e) => {
|
| 1172 |
+
const o = dr.orig[e.id];
|
| 1173 |
+
e.xmin = clamp(ax + (o.xmin - ax) * sx);
|
| 1174 |
+
e.xmax = clamp(ax + (o.xmax - ax) * sx);
|
| 1175 |
+
e.ymin = clamp(ay + (o.ymin - ay) * sy);
|
| 1176 |
+
e.ymax = clamp(ay + (o.ymax - ay) * sy);
|
| 1177 |
+
});
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
// ---- canvas interaction ----
|
| 1181 |
stage.addEventListener("pointerdown", (ev) => {
|
| 1182 |
if (ev.button !== 0 || !img.src || eyedropSample) return;
|
| 1183 |
+
const p = toNorm(ev);
|
| 1184 |
+
const gh = ev.target.classList.contains("ib-gh") ? ev.target.dataset.gh : null;
|
| 1185 |
const boxEl = ev.target.closest(".ib-box");
|
| 1186 |
const handle = ev.target.classList.contains("ib-h") ? ev.target.dataset.h : null;
|
| 1187 |
+
const additive = ev.ctrlKey || ev.metaKey;
|
| 1188 |
+
|
| 1189 |
+
if (gh) {
|
| 1190 |
+
drag = { mode: "gresize", h: gh, g: groupBBox(), orig: snapshot() };
|
| 1191 |
+
} else if (handle && boxEl) {
|
| 1192 |
const e = byId(boxEl.dataset.id);
|
| 1193 |
+
drag = { mode: "resize", h: handle, e: e,
|
| 1194 |
+
orig: { xmin: e.xmin, ymin: e.ymin, xmax: e.xmax, ymax: e.ymax } };
|
| 1195 |
+
} else if (boxEl) {
|
| 1196 |
+
const id = boxEl.dataset.id;
|
| 1197 |
+
if (additive) {
|
| 1198 |
+
toggleSelect(id);
|
| 1199 |
+
stage.setPointerCapture(ev.pointerId);
|
| 1200 |
+
ev.preventDefault();
|
| 1201 |
+
return;
|
| 1202 |
+
}
|
| 1203 |
+
if (!sel.has(id)) selectOnly(id);
|
| 1204 |
+
drag = { mode: "move", start: p, orig: snapshot(), ids: Array.from(sel) };
|
| 1205 |
} else {
|
| 1206 |
+
sel = new Set();
|
| 1207 |
const id = "j" + jsCounter++;
|
| 1208 |
const e = { id: id, type: "obj", ymin: p.y, xmin: p.x, ymax: p.y, xmax: p.x,
|
| 1209 |
desc: "", text: "", colors: [] };
|
| 1210 |
els.push(e);
|
| 1211 |
+
sel = new Set([id]);
|
| 1212 |
drag = { mode: "draw", e: e, start: p };
|
|
|
|
| 1213 |
renderAll();
|
| 1214 |
}
|
| 1215 |
stage.setPointerCapture(ev.pointerId);
|
|
|
|
| 1219 |
stage.addEventListener("pointermove", (ev) => {
|
| 1220 |
if (!drag) return;
|
| 1221 |
const p = toNorm(ev);
|
| 1222 |
+
const shift = ev.shiftKey;
|
| 1223 |
if (drag.mode === "move") {
|
| 1224 |
+
let dx = p.x - drag.start.x, dy = p.y - drag.start.y;
|
| 1225 |
+
let gx0 = 1e9, gy0 = 1e9, gx1 = -1e9, gy1 = -1e9;
|
| 1226 |
+
drag.ids.forEach((id) => {
|
| 1227 |
+
const o = drag.orig[id];
|
| 1228 |
+
gx0 = Math.min(gx0, o.xmin); gy0 = Math.min(gy0, o.ymin);
|
| 1229 |
+
gx1 = Math.max(gx1, o.xmax); gy1 = Math.max(gy1, o.ymax);
|
| 1230 |
+
});
|
| 1231 |
+
dx = Math.max(-gx0, Math.min(1000 - gx1, dx));
|
| 1232 |
+
dy = Math.max(-gy0, Math.min(1000 - gy1, dy));
|
| 1233 |
+
drag.ids.forEach((id) => {
|
| 1234 |
+
const o = drag.orig[id], e = byId(id);
|
| 1235 |
+
if (!e) return;
|
| 1236 |
+
e.xmin = o.xmin + dx; e.ymin = o.ymin + dy;
|
| 1237 |
+
e.xmax = o.xmax + dx; e.ymax = o.ymax + dy;
|
| 1238 |
+
});
|
| 1239 |
} else if (drag.mode === "draw") {
|
| 1240 |
+
const e = drag.e;
|
| 1241 |
e.xmin = Math.min(drag.start.x, p.x);
|
| 1242 |
e.xmax = Math.max(drag.start.x, p.x);
|
| 1243 |
e.ymin = Math.min(drag.start.y, p.y);
|
| 1244 |
e.ymax = Math.max(drag.start.y, p.y);
|
| 1245 |
} else if (drag.mode === "resize") {
|
| 1246 |
+
resizeSingle(drag, p, shift);
|
| 1247 |
+
} else if (drag.mode === "gresize") {
|
| 1248 |
+
groupResize(drag, p, shift);
|
|
|
|
|
|
|
| 1249 |
}
|
| 1250 |
renderBoxes();
|
| 1251 |
});
|
| 1252 |
|
| 1253 |
function endDrag() {
|
| 1254 |
if (!drag) return;
|
| 1255 |
+
if (drag.mode === "draw") {
|
| 1256 |
+
const e = drag.e;
|
| 1257 |
+
if (e.xmax - e.xmin < 8 || e.ymax - e.ymin < 8) {
|
| 1258 |
+
els = els.filter((x) => x !== e);
|
| 1259 |
+
sel = new Set();
|
| 1260 |
+
}
|
| 1261 |
}
|
| 1262 |
const wasDraw = drag.mode === "draw";
|
| 1263 |
drag = null;
|
| 1264 |
renderAll();
|
| 1265 |
+
applySelClasses();
|
| 1266 |
emitNow();
|
| 1267 |
if (wasDraw) {
|
| 1268 |
+
const pr = document.getElementById("panel-root");
|
| 1269 |
+
const card = pr && pr.querySelector(".ib-card.sel .ib-desc");
|
| 1270 |
if (card) card.focus();
|
| 1271 |
}
|
| 1272 |
}
|
|
|
|
| 1274 |
stage.addEventListener("pointercancel", endDrag);
|
| 1275 |
|
| 1276 |
window.addEventListener("keydown", (ev) => {
|
| 1277 |
+
if (ev.key === "Delete" && sel.size) {
|
| 1278 |
const t = document.activeElement;
|
| 1279 |
if (t && (t.tagName === "TEXTAREA" || t.tagName === "INPUT")) return;
|
| 1280 |
+
els = els.filter((x) => !sel.has(x.id));
|
| 1281 |
+
sel = new Set();
|
| 1282 |
renderAll();
|
| 1283 |
emitNow();
|
| 1284 |
}
|
|
|
|
| 1308 |
if (!e) return;
|
| 1309 |
e.type = ev.target.value;
|
| 1310 |
renderAll();
|
| 1311 |
+
selectOnly(e.id);
|
| 1312 |
emitNow();
|
| 1313 |
});
|
| 1314 |
document.addEventListener("click", (ev) => {
|
|
|
|
| 1320 |
const e = byId(card.dataset.id);
|
| 1321 |
if (ev.target.classList.contains("ib-del")) {
|
| 1322 |
els = els.filter((x) => x.id !== card.dataset.id);
|
| 1323 |
+
sel.delete(card.dataset.id);
|
| 1324 |
renderAll();
|
| 1325 |
emitNow();
|
| 1326 |
return;
|
|
|
|
| 1353 |
});
|
| 1354 |
return;
|
| 1355 |
}
|
| 1356 |
+
if (!ev.target.closest(".ib-f, .ib-type, .ib-pal")) {
|
| 1357 |
+
if (ev.ctrlKey || ev.metaKey) toggleSelect(card.dataset.id);
|
| 1358 |
+
else selectOnly(card.dataset.id);
|
| 1359 |
+
}
|
| 1360 |
});
|
| 1361 |
|
| 1362 |
function addElement(type) {
|
| 1363 |
const id = "j" + jsCounter++;
|
| 1364 |
els.push({ id: id, type: type, ymin: 350, xmin: 350, ymax: 650, xmax: 650,
|
| 1365 |
desc: "", text: "", colors: [] });
|
|
|
|
| 1366 |
renderAll();
|
| 1367 |
+
selectOnly(id);
|
| 1368 |
emitNow();
|
| 1369 |
}
|
| 1370 |
|
|
|
|
| 1377 |
if (Array.isArray(p.elements)) {
|
| 1378 |
els = p.elements.map((e) => Object.assign({}, e));
|
| 1379 |
}
|
| 1380 |
+
sel = new Set(Array.isArray(p.selected) ? p.selected : []);
|
| 1381 |
renderAll();
|
| 1382 |
} catch (e) {
|
| 1383 |
console.error("IB.load", e);
|