contactsheet 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/canvas/app.js +525 -459
- package/dist/canvas/app.js.map +4 -4
- package/dist/canvas/style.css +31 -0
- package/dist/cli.js +95 -8
- package/dist/cli.js.map +4 -4
- package/package.json +1 -1
package/dist/canvas/app.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
return req(`${BASE}/api/state`);
|
|
16
16
|
}
|
|
17
17
|
function fetchRegistry() {
|
|
18
|
-
return req(`${BASE}/registry
|
|
18
|
+
return req(`${BASE}/registry`, { signal: AbortSignal.timeout(15e3) });
|
|
19
19
|
}
|
|
20
20
|
function fetchAnnotations() {
|
|
21
21
|
return req(`${BASE}/api/annotations`);
|
|
@@ -72,24 +72,26 @@
|
|
|
72
72
|
}
|
|
73
73
|
if (ev.type === "registry") on.registry(ev.entries ?? []);
|
|
74
74
|
else if (ev.type === "annotations") on.annotations(ev.annotations ?? []);
|
|
75
|
+
else if (ev.type === "health") on.health(ev.ok, ev.detail);
|
|
75
76
|
};
|
|
76
77
|
es.addEventListener("registry", (e) => dispatch(e.data));
|
|
77
78
|
es.addEventListener("annotations", (e) => dispatch(e.data));
|
|
79
|
+
es.addEventListener("health", (e) => dispatch(e.data));
|
|
78
80
|
es.onmessage = (e) => dispatch(e.data);
|
|
79
81
|
return es;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
// src/canvas/dom.ts
|
|
83
85
|
function h(tag, cls, text) {
|
|
84
|
-
const
|
|
85
|
-
if (cls)
|
|
86
|
-
if (text !== void 0)
|
|
87
|
-
return
|
|
86
|
+
const el2 = document.createElement(tag);
|
|
87
|
+
if (cls) el2.className = cls;
|
|
88
|
+
if (text !== void 0) el2.textContent = text;
|
|
89
|
+
return el2;
|
|
88
90
|
}
|
|
89
91
|
function qs(sel) {
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
92
|
-
return
|
|
92
|
+
const el2 = document.querySelector(sel);
|
|
93
|
+
if (!el2) throw new Error(`canvas: \u7F3A\u5C11\u9AA8\u67B6\u5143\u7D20 ${sel}`);
|
|
94
|
+
return el2;
|
|
93
95
|
}
|
|
94
96
|
function clamp(v, lo, hi) {
|
|
95
97
|
return v < lo ? lo : v > hi ? hi : v;
|
|
@@ -312,14 +314,14 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
312
314
|
return host;
|
|
313
315
|
}
|
|
314
316
|
function toast(text, kind = "info") {
|
|
315
|
-
const
|
|
316
|
-
ensureHost().appendChild(
|
|
317
|
-
requestAnimationFrame(() =>
|
|
317
|
+
const el2 = h("div", `cs-toast is-${kind}`, text);
|
|
318
|
+
ensureHost().appendChild(el2);
|
|
319
|
+
requestAnimationFrame(() => el2.classList.add("is-in"));
|
|
318
320
|
const life = kind === "error" ? 6e3 : kind === "warn" ? 4500 : 3e3;
|
|
319
321
|
setTimeout(() => {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
setTimeout(() =>
|
|
322
|
+
el2.classList.remove("is-in");
|
|
323
|
+
el2.addEventListener("transitionend", () => el2.remove(), { once: true });
|
|
324
|
+
setTimeout(() => el2.remove(), 400);
|
|
323
325
|
}, life);
|
|
324
326
|
}
|
|
325
327
|
|
|
@@ -472,10 +474,10 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
472
474
|
return null;
|
|
473
475
|
}
|
|
474
476
|
if (board.entry.kind !== "component") return stack[0] ?? null;
|
|
475
|
-
for (const
|
|
476
|
-
const t =
|
|
477
|
-
if (t === "HTML" || t === "BODY" ||
|
|
478
|
-
if (paintsSomething(
|
|
477
|
+
for (const el2 of stack) {
|
|
478
|
+
const t = el2.tagName;
|
|
479
|
+
if (t === "HTML" || t === "BODY" || el2.hasAttribute("data-cs-artboard")) return null;
|
|
480
|
+
if (paintsSomething(el2, doc)) return el2;
|
|
479
481
|
}
|
|
480
482
|
return null;
|
|
481
483
|
}
|
|
@@ -499,15 +501,15 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
499
501
|
const m = bg.match(/^rgba\([^)]*,\s*([\d.]+)\s*\)$/);
|
|
500
502
|
return m ? parseFloat(m[1]) : 1;
|
|
501
503
|
}
|
|
502
|
-
function paintsSomething(
|
|
503
|
-
if (VISIBLE_TAGS.has(
|
|
504
|
-
if (
|
|
505
|
-
for (const n of
|
|
504
|
+
function paintsSomething(el2, doc) {
|
|
505
|
+
if (VISIBLE_TAGS.has(el2.tagName)) return true;
|
|
506
|
+
if (el2.closest("svg")) return true;
|
|
507
|
+
for (const n of el2.childNodes) {
|
|
506
508
|
if (n.nodeType === 3 && n.textContent && n.textContent.trim()) return true;
|
|
507
509
|
}
|
|
508
510
|
const win = doc.defaultView;
|
|
509
511
|
if (!win) return false;
|
|
510
|
-
const cs = win.getComputedStyle(
|
|
512
|
+
const cs = win.getComputedStyle(el2);
|
|
511
513
|
if (cs.backgroundImage !== "none") return true;
|
|
512
514
|
if (bgAlpha(cs.backgroundColor) > 0) return true;
|
|
513
515
|
if (cs.boxShadow !== "none") return true;
|
|
@@ -515,19 +517,19 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
515
517
|
if (cs.outlineStyle !== "none" && (parseFloat(cs.outlineWidth) || 0) > 0) return true;
|
|
516
518
|
return false;
|
|
517
519
|
}
|
|
518
|
-
function screenRect(board,
|
|
520
|
+
function screenRect(board, el2) {
|
|
519
521
|
if (!board.iframe || !board.el.isConnected) return null;
|
|
520
522
|
const fr = board.iframe.getBoundingClientRect();
|
|
521
523
|
const s = liveScale(board, board.frameEl.getBoundingClientRect());
|
|
522
|
-
const b =
|
|
524
|
+
const b = el2.getBoundingClientRect();
|
|
523
525
|
return new DOMRect(fr.left + b.left * s, fr.top + b.top * s, b.width * s, b.height * s);
|
|
524
526
|
}
|
|
525
527
|
function liveScale(board, frame) {
|
|
526
528
|
if (board.width > 0 && frame.width > 0) return frame.width / board.width;
|
|
527
529
|
return state.scale || 1;
|
|
528
530
|
}
|
|
529
|
-
function relPos(
|
|
530
|
-
const r = screenRect(board,
|
|
531
|
+
function relPos(el2, clientX, clientY, board) {
|
|
532
|
+
const r = screenRect(board, el2);
|
|
531
533
|
if (!r || r.width === 0 || r.height === 0) return { x: 0, y: 0 };
|
|
532
534
|
const round = (v) => Math.round(clamp(v, 0, 1) * 1e3) / 1e3;
|
|
533
535
|
return { x: round((clientX - r.left) / r.width), y: round((clientY - r.top) / r.height) };
|
|
@@ -539,31 +541,31 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
539
541
|
return false;
|
|
540
542
|
}
|
|
541
543
|
}
|
|
542
|
-
function idSelector(
|
|
543
|
-
const id =
|
|
544
|
+
function idSelector(el2) {
|
|
545
|
+
const id = el2.getAttribute("id");
|
|
544
546
|
if (!id) return null;
|
|
545
547
|
return `#${CSS.escape(id)}`;
|
|
546
548
|
}
|
|
547
|
-
function dataSelector(
|
|
548
|
-
for (const attr of Array.from(
|
|
549
|
+
function dataSelector(el2) {
|
|
550
|
+
for (const attr of Array.from(el2.attributes)) {
|
|
549
551
|
if (!attr.name.startsWith("data-") || !attr.value) continue;
|
|
550
552
|
const v = attr.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
551
553
|
return `[${attr.name}="${v}"]`;
|
|
552
554
|
}
|
|
553
555
|
return null;
|
|
554
556
|
}
|
|
555
|
-
function nthPart(
|
|
556
|
-
const tag =
|
|
557
|
-
const parent =
|
|
557
|
+
function nthPart(el2) {
|
|
558
|
+
const tag = el2.localName;
|
|
559
|
+
const parent = el2.parentElement;
|
|
558
560
|
if (!parent) return tag;
|
|
559
561
|
const sames = Array.from(parent.children).filter((c) => c.localName === tag);
|
|
560
562
|
if (sames.length < 2) return tag;
|
|
561
|
-
return `${tag}:nth-of-type(${sames.indexOf(
|
|
563
|
+
return `${tag}:nth-of-type(${sames.indexOf(el2) + 1})`;
|
|
562
564
|
}
|
|
563
|
-
function buildSelector(
|
|
564
|
-
const doc =
|
|
565
|
+
function buildSelector(el2) {
|
|
566
|
+
const doc = el2.ownerDocument;
|
|
565
567
|
const parts = [];
|
|
566
|
-
let cur =
|
|
568
|
+
let cur = el2;
|
|
567
569
|
while (cur && cur !== doc.documentElement && parts.length < 5) {
|
|
568
570
|
const byId = idSelector(cur);
|
|
569
571
|
if (byId && isUnique(doc, byId)) {
|
|
@@ -580,12 +582,12 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
580
582
|
if (isUnique(doc, joined)) return joined;
|
|
581
583
|
cur = cur.parentElement;
|
|
582
584
|
}
|
|
583
|
-
return parts.join(" > ") ||
|
|
585
|
+
return parts.join(" > ") || el2.localName;
|
|
584
586
|
}
|
|
585
|
-
function describe(
|
|
586
|
-
const probe =
|
|
587
|
-
const cls = typeof
|
|
588
|
-
return `<${
|
|
587
|
+
function describe(el2) {
|
|
588
|
+
const probe = el2.closest("[data-probe]")?.getAttribute("data-probe");
|
|
589
|
+
const cls = typeof el2.className === "string" ? el2.className : "";
|
|
590
|
+
return `<${el2.localName}>` + (probe ? ` probe=${probe}` : "") + (cls ? ` .${cls.trim().split(/\s+/).slice(0, 3).join(".")}` : "");
|
|
589
591
|
}
|
|
590
592
|
var TAG_GAP = 1;
|
|
591
593
|
function placeTag(tag, box, frame, st, label) {
|
|
@@ -606,8 +608,8 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
606
608
|
tag.style.left = `${x - box.left - 1}px`;
|
|
607
609
|
tag.style.top = `${y - box.top - 1}px`;
|
|
608
610
|
}
|
|
609
|
-
function paint(box, board,
|
|
610
|
-
const r = screenRect(board,
|
|
611
|
+
function paint(box, board, el2, label) {
|
|
612
|
+
const r = screenRect(board, el2);
|
|
611
613
|
if (!r || r.width + r.height === 0) {
|
|
612
614
|
const off = box.classList.contains("is-on");
|
|
613
615
|
box.classList.remove("is-on");
|
|
@@ -654,8 +656,8 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
654
656
|
}
|
|
655
657
|
}
|
|
656
658
|
if (selectRef) {
|
|
657
|
-
const
|
|
658
|
-
if (
|
|
659
|
+
const el2 = resolveSelected();
|
|
660
|
+
if (el2) changed = paint(selectBox, selectRef.board, el2, selectRef.selector) || changed;
|
|
659
661
|
else if (selectBox.classList.contains("is-on")) {
|
|
660
662
|
selectBox.classList.remove("is-on");
|
|
661
663
|
changed = true;
|
|
@@ -676,9 +678,9 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
676
678
|
stillFrames = 0;
|
|
677
679
|
if (!followRaf && (hoverRef || selectRef)) followRaf = requestAnimationFrame(follow);
|
|
678
680
|
}
|
|
679
|
-
function showHover(board,
|
|
680
|
-
hoverRef = { board, el };
|
|
681
|
-
paint(hoverBox, board,
|
|
681
|
+
function showHover(board, el2) {
|
|
682
|
+
hoverRef = { board, el: el2 };
|
|
683
|
+
paint(hoverBox, board, el2, el2.localName);
|
|
682
684
|
wake();
|
|
683
685
|
}
|
|
684
686
|
function hideHover() {
|
|
@@ -721,14 +723,14 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
721
723
|
let top = 8;
|
|
722
724
|
let orphan = true;
|
|
723
725
|
if (doc && ann.anchor) {
|
|
724
|
-
let
|
|
726
|
+
let el2 = null;
|
|
725
727
|
try {
|
|
726
|
-
|
|
728
|
+
el2 = doc.querySelector(ann.anchor.selector);
|
|
727
729
|
} catch {
|
|
728
|
-
|
|
730
|
+
el2 = null;
|
|
729
731
|
}
|
|
730
|
-
if (
|
|
731
|
-
const r =
|
|
732
|
+
if (el2) {
|
|
733
|
+
const r = el2.getBoundingClientRect();
|
|
732
734
|
left = r.left + ann.anchor.x * r.width;
|
|
733
735
|
top = r.top + ann.anchor.y * r.height;
|
|
734
736
|
orphan = false;
|
|
@@ -1013,10 +1015,10 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
1013
1015
|
if (i >= 0) state.annotations[i] = ann;
|
|
1014
1016
|
else state.annotations.push(ann);
|
|
1015
1017
|
}
|
|
1016
|
-
function openComposer(board,
|
|
1018
|
+
function openComposer(board, el2, clientX, clientY) {
|
|
1017
1019
|
closeComposer();
|
|
1018
|
-
const selector = buildSelector(
|
|
1019
|
-
const rel = relPos(
|
|
1020
|
+
const selector = buildSelector(el2);
|
|
1021
|
+
const rel = relPos(el2, clientX, clientY, board);
|
|
1020
1022
|
const box = h("div", "cs-pin-input");
|
|
1021
1023
|
box.style.left = `${Math.min(clientX + 10, window.innerWidth - 250)}px`;
|
|
1022
1024
|
box.style.top = `${Math.min(clientY + 10, window.innerHeight - 170)}px`;
|
|
@@ -1242,9 +1244,9 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
1242
1244
|
applyView();
|
|
1243
1245
|
}
|
|
1244
1246
|
function isBlank(target) {
|
|
1245
|
-
const
|
|
1246
|
-
if (!
|
|
1247
|
-
return !
|
|
1247
|
+
const el2 = target;
|
|
1248
|
+
if (!el2) return false;
|
|
1249
|
+
return !el2.closest(".cs-board");
|
|
1248
1250
|
}
|
|
1249
1251
|
function initView() {
|
|
1250
1252
|
viewport = qs("#cs-viewport");
|
|
@@ -1296,220 +1298,210 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
1296
1298
|
return viewport.classList.contains("is-panning");
|
|
1297
1299
|
}
|
|
1298
1300
|
|
|
1299
|
-
// src/canvas/
|
|
1300
|
-
var
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1301
|
+
// src/canvas/sidebar.ts
|
|
1302
|
+
var root;
|
|
1303
|
+
var listEl;
|
|
1304
|
+
var filter = "";
|
|
1305
|
+
var searchCollapsed = null;
|
|
1306
|
+
var uid = 0;
|
|
1307
|
+
function initSidebar() {
|
|
1308
|
+
root = qs("#cs-sidebar");
|
|
1309
|
+
root.textContent = "";
|
|
1310
|
+
const head = h("div", "cs-sb-head");
|
|
1311
|
+
const search = document.createElement("input");
|
|
1312
|
+
search.className = "cs-sb-search";
|
|
1313
|
+
search.type = "search";
|
|
1314
|
+
search.placeholder = "\u641C\u7D22\u753B\u677F\u2026";
|
|
1315
|
+
search.addEventListener("input", () => {
|
|
1316
|
+
filter = search.value.trim().toLowerCase();
|
|
1317
|
+
searchCollapsed = filter ? /* @__PURE__ */ new Set() : null;
|
|
1318
|
+
renderSidebar();
|
|
1319
|
+
});
|
|
1320
|
+
search.addEventListener("keydown", (e) => {
|
|
1321
|
+
e.stopPropagation();
|
|
1322
|
+
if (e.key === "Escape") {
|
|
1323
|
+
search.value = "";
|
|
1324
|
+
filter = "";
|
|
1325
|
+
searchCollapsed = null;
|
|
1326
|
+
renderSidebar();
|
|
1327
|
+
search.blur();
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
head.append(h("span", "cs-sb-title", "\u753B\u677F"), search);
|
|
1331
|
+
listEl = h("div", "cs-sb-list");
|
|
1332
|
+
listEl.addEventListener("keydown", (e) => {
|
|
1333
|
+
if ((e.key === "Enter" || e.key === " ") && e.target.closest("button")) {
|
|
1334
|
+
e.stopPropagation();
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
root.append(head, listEl);
|
|
1338
|
+
const toggle = qs("#cs-sb-toggle");
|
|
1339
|
+
toggle.addEventListener("click", () => {
|
|
1340
|
+
const closed = document.body.dataset.sidebar === "off";
|
|
1341
|
+
document.body.dataset.sidebar = closed ? "on" : "off";
|
|
1342
|
+
toggle.title = closed ? "\u6536\u8D77\u5217\u8868" : "\u5C55\u5F00\u5217\u8868";
|
|
1343
|
+
});
|
|
1344
|
+
renderSidebar();
|
|
1305
1345
|
}
|
|
1306
|
-
function
|
|
1307
|
-
|
|
1308
|
-
state.activeId = id;
|
|
1309
|
-
for (const b of state.boards.values()) b.el.classList.toggle("is-active", b.entry.id === id);
|
|
1346
|
+
function match(text) {
|
|
1347
|
+
return !filter || text.toLowerCase().includes(filter);
|
|
1310
1348
|
}
|
|
1311
|
-
function
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
if (mode === "review" && from !== "review") {
|
|
1320
|
-
state.modeBeforeReview = from;
|
|
1321
|
-
state.viewBeforeReview = { scale: state.scale, tx: state.tx, ty: state.ty };
|
|
1322
|
-
closeArgsPanel();
|
|
1323
|
-
fitActiveBoard();
|
|
1324
|
-
} else if (mode !== "review" && from === "review" && state.viewBeforeReview) {
|
|
1325
|
-
const v = state.viewBeforeReview;
|
|
1326
|
-
state.viewBeforeReview = null;
|
|
1327
|
-
setView(v.tx, v.ty, v.scale);
|
|
1349
|
+
function collapseKey() {
|
|
1350
|
+
return `cs-collapsed:${state.info?.projectRoot ?? "unknown"}`;
|
|
1351
|
+
}
|
|
1352
|
+
function collapsedSet() {
|
|
1353
|
+
try {
|
|
1354
|
+
return new Set(JSON.parse(localStorage.getItem(collapseKey()) ?? "[]"));
|
|
1355
|
+
} catch {
|
|
1356
|
+
return /* @__PURE__ */ new Set();
|
|
1328
1357
|
}
|
|
1329
|
-
setProbe(
|
|
1330
|
-
mode === "interact" ? "\u4EA4\u4E92\u6A21\u5F0F:\u76F4\u63A5\u64CD\u4F5C\u8FD9\u5757\u753B\u677F,Esc \u56DE\u6D4F\u89C8" : mode === "review" ? "\u8D70\u67E5\u6A21\u5F0F:Esc \u9000\u51FA" : "\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20"
|
|
1331
|
-
);
|
|
1332
1358
|
}
|
|
1333
|
-
function
|
|
1334
|
-
|
|
1335
|
-
if (!board) return;
|
|
1336
|
-
const r = board.frameEl.getBoundingClientRect();
|
|
1337
|
-
const worldX = (r.left - state.tx) / state.scale;
|
|
1338
|
-
const worldY = (r.top - state.ty) / state.scale;
|
|
1339
|
-
const vw = window.innerWidth;
|
|
1340
|
-
const vh = window.innerHeight;
|
|
1341
|
-
const s = clamp(Math.min(vw / board.width, vh / board.height), MIN_SCALE, MAX_SCALE);
|
|
1342
|
-
setView((vw - board.width * s) / 2 - worldX * s, (vh - board.height * s) / 2 - worldY * s, s);
|
|
1359
|
+
function isCollapsed(key) {
|
|
1360
|
+
return searchCollapsed ? searchCollapsed.has(key) : collapsedSet().has(key);
|
|
1343
1361
|
}
|
|
1344
|
-
function
|
|
1345
|
-
if (
|
|
1346
|
-
|
|
1347
|
-
exitPinMode();
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
if (state.pinPending) {
|
|
1351
|
-
exitPinMode();
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
if (selectedPin()) {
|
|
1355
|
-
selectPin(null);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
if (state.mode === "review") {
|
|
1359
|
-
setMode(state.modeBeforeReview === "review" ? "browse" : state.modeBeforeReview);
|
|
1362
|
+
function toggleCollapsed(key) {
|
|
1363
|
+
if (searchCollapsed) {
|
|
1364
|
+
if (!searchCollapsed.delete(key)) searchCollapsed.add(key);
|
|
1360
1365
|
return;
|
|
1361
1366
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1367
|
+
const s = collapsedSet();
|
|
1368
|
+
if (s.has(key)) s.delete(key);
|
|
1369
|
+
else s.add(key);
|
|
1370
|
+
try {
|
|
1371
|
+
localStorage.setItem(collapseKey(), JSON.stringify([...s]));
|
|
1372
|
+
} catch {
|
|
1365
1373
|
}
|
|
1366
|
-
clearSelection();
|
|
1367
|
-
closeArgsPanel();
|
|
1368
|
-
setProbe("\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
|
|
1369
1374
|
}
|
|
1370
|
-
function
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
const tag = el.tagName;
|
|
1374
|
-
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable === true) return true;
|
|
1375
|
-
return el.closest("#cs-sidebar, #cs-args, .cs-picker, #cs-topbar") !== null;
|
|
1375
|
+
function bulkMode(ids) {
|
|
1376
|
+
const shown = ids.filter((id) => !isHidden(id)).length;
|
|
1377
|
+
return { mode: shown === ids.length ? "all" : shown === 0 ? "none" : "some", shown };
|
|
1376
1378
|
}
|
|
1377
|
-
function
|
|
1378
|
-
|
|
1379
|
-
escape();
|
|
1380
|
-
return;
|
|
1381
|
-
}
|
|
1382
|
-
if (isEditable(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
1383
|
-
if (e.key === "Enter") {
|
|
1384
|
-
if (selectedPin()) {
|
|
1385
|
-
e.preventDefault();
|
|
1386
|
-
editSelectedPin();
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
if (state.mode !== "review" && state.activeId) {
|
|
1390
|
-
e.preventDefault();
|
|
1391
|
-
setMode("review", state.activeId);
|
|
1392
|
-
}
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
if (e.key === "Backspace" || e.key === "Delete") {
|
|
1396
|
-
if (selectedPin()) {
|
|
1397
|
-
e.preventDefault();
|
|
1398
|
-
deleteSelectedPin();
|
|
1399
|
-
}
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
if (e.key === "c" || e.key === "C") {
|
|
1403
|
-
if (state.mode === "browse" && !state.pinPending) {
|
|
1404
|
-
e.preventDefault();
|
|
1405
|
-
enterPinMode();
|
|
1406
|
-
}
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
if (e.key === "y" || e.key === "Y") {
|
|
1410
|
-
e.preventDefault();
|
|
1411
|
-
void copyContext();
|
|
1412
|
-
return;
|
|
1413
|
-
}
|
|
1414
|
-
if (e.key === "p") {
|
|
1415
|
-
e.preventDefault();
|
|
1416
|
-
void pushToClaudeCode();
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
if (e.key === "1") {
|
|
1420
|
-
e.preventDefault();
|
|
1421
|
-
fitAll();
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
if (e.key === "2") {
|
|
1425
|
-
e.preventDefault();
|
|
1426
|
-
fitBoard();
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
if (e.key === "0") {
|
|
1430
|
-
e.preventDefault();
|
|
1431
|
-
zoomReset();
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
if (e.key === "=" || e.key === "+") {
|
|
1435
|
-
e.preventDefault();
|
|
1436
|
-
zoomStep(1.25);
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
if (e.key === "-" || e.key === "_") {
|
|
1440
|
-
e.preventDefault();
|
|
1441
|
-
zoomStep(0.8);
|
|
1442
|
-
}
|
|
1379
|
+
function setGroupHidden(ids, hide) {
|
|
1380
|
+
setHidden(ids, hide);
|
|
1443
1381
|
}
|
|
1444
|
-
function
|
|
1445
|
-
|
|
1382
|
+
function focusedKey() {
|
|
1383
|
+
const el2 = document.activeElement;
|
|
1384
|
+
return el2 && listEl.contains(el2) ? el2.dataset.fk ?? null : null;
|
|
1446
1385
|
}
|
|
1447
|
-
function
|
|
1448
|
-
if (
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1386
|
+
function restoreFocus(key) {
|
|
1387
|
+
if (!key) return;
|
|
1388
|
+
const el2 = listEl.querySelector(`[data-fk="${CSS.escape(key)}"]`);
|
|
1389
|
+
el2?.focus({ preventScroll: true });
|
|
1390
|
+
}
|
|
1391
|
+
function renderSidebar() {
|
|
1392
|
+
if (!listEl) return;
|
|
1393
|
+
const keepFocus = focusedKey();
|
|
1394
|
+
listEl.textContent = "";
|
|
1395
|
+
uid = 0;
|
|
1396
|
+
const screens = state.entries.filter((e) => e.kind === "screen");
|
|
1397
|
+
const comps = state.entries.filter((e) => e.kind !== "screen");
|
|
1398
|
+
const visScreens = screens.filter((e) => match(e.exportName) || match(e.url ?? ""));
|
|
1399
|
+
if (visScreens.length) {
|
|
1400
|
+
const g = group("sec:screen", "\u9875\u9762", visScreens.map((e) => e.id), "cs-sb-section");
|
|
1401
|
+
for (const e of visScreens) g.body.appendChild(row(e.id, e.exportName, e.url ?? "", "screen"));
|
|
1402
|
+
listEl.appendChild(g.el);
|
|
1454
1403
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1404
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1405
|
+
for (const e of comps) {
|
|
1406
|
+
if (!(match(e.exportName) || match(groupLabel(e.file)))) continue;
|
|
1407
|
+
const l = byFile.get(e.file);
|
|
1408
|
+
if (l) l.push(e);
|
|
1409
|
+
else byFile.set(e.file, [e]);
|
|
1461
1410
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
else openArgsPanel(board2);
|
|
1474
|
-
return;
|
|
1411
|
+
if (byFile.size) {
|
|
1412
|
+
const files = [...byFile.keys()].sort();
|
|
1413
|
+
const allIds = files.flatMap((f) => (byFile.get(f) ?? []).map((e) => e.id));
|
|
1414
|
+
const g = group("sec:component", "\u7EC4\u4EF6", allIds, "cs-sb-section");
|
|
1415
|
+
for (const file of files) {
|
|
1416
|
+
const list = byFile.get(file) ?? [];
|
|
1417
|
+
const fg = group(`file:${file}`, groupLabel(file), list.map((e) => e.id), "cs-sb-file");
|
|
1418
|
+
for (const e of list) fg.body.appendChild(row(e.id, e.exportName, "", "component"));
|
|
1419
|
+
g.body.appendChild(fg.el);
|
|
1420
|
+
}
|
|
1421
|
+
listEl.appendChild(g.el);
|
|
1475
1422
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
const board = boardOf(target);
|
|
1479
|
-
if (!board) return;
|
|
1480
|
-
if (state.mode === "interact") return;
|
|
1481
|
-
setActive(board.entry.id);
|
|
1482
|
-
const el = elementAt(board, e.clientX, e.clientY);
|
|
1483
|
-
if (!el) return;
|
|
1484
|
-
if (state.pinPending) {
|
|
1485
|
-
openComposer(board, el, e.clientX, e.clientY);
|
|
1486
|
-
return;
|
|
1423
|
+
if (!listEl.childElementCount) {
|
|
1424
|
+
listEl.appendChild(h("div", "cs-sb-empty", filter ? "\u6CA1\u6709\u5339\u914D\u7684\u753B\u677F" : "\u8FD8\u6CA1\u6709\u753B\u677F"));
|
|
1487
1425
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
const rel = relPos(el, e.clientX, e.clientY, board);
|
|
1491
|
-
showSelection(board, selector);
|
|
1492
|
-
state.selection = { artboardId: board.entry.id, selector, x: rel.x, y: rel.y, ts: Date.now() };
|
|
1493
|
-
setProbe(`\u5DF2\u9009\u4E2D ${board.entry.exportName} \u2192 ${selector}`);
|
|
1494
|
-
postSelection(state.selection).catch((err) => setProbe(`selection \u53D1\u9001\u5931\u8D25:${String(err)}`));
|
|
1426
|
+
syncActive();
|
|
1427
|
+
restoreFocus(keepFocus);
|
|
1495
1428
|
}
|
|
1496
|
-
function
|
|
1497
|
-
const
|
|
1498
|
-
if (
|
|
1499
|
-
|
|
1429
|
+
function group(key, label, ids, headCls) {
|
|
1430
|
+
const el2 = h("div", "cs-sb-group");
|
|
1431
|
+
if (headCls === "cs-sb-file") el2.classList.add("is-file");
|
|
1432
|
+
const headEl = h("div", headCls);
|
|
1433
|
+
const body = h("div", "cs-sb-items");
|
|
1434
|
+
body.id = `cs-sb-g${++uid}`;
|
|
1435
|
+
const collapsed = isCollapsed(key);
|
|
1436
|
+
body.hidden = collapsed;
|
|
1437
|
+
const { mode, shown } = bulkMode(ids);
|
|
1438
|
+
const caret = h("button", "cs-sb-caret");
|
|
1439
|
+
caret.type = "button";
|
|
1440
|
+
caret.dataset.fk = `caret:${key}`;
|
|
1441
|
+
caret.setAttribute("aria-expanded", String(!collapsed));
|
|
1442
|
+
caret.setAttribute("aria-controls", body.id);
|
|
1443
|
+
caret.title = collapsed ? `\u5C55\u5F00\u300C${label}\u300D` : `\u6298\u53E0\u300C${label}\u300D`;
|
|
1444
|
+
const arrow = h("span", "cs-sb-arrow", "\u25B8");
|
|
1445
|
+
arrow.setAttribute("aria-hidden", "true");
|
|
1446
|
+
const count = h("span", "cs-sb-count", mode === "all" ? String(ids.length) : `${shown}/${ids.length}`);
|
|
1447
|
+
caret.append(arrow, h("span", "cs-sb-label", label), count);
|
|
1448
|
+
caret.addEventListener("click", () => {
|
|
1449
|
+
toggleCollapsed(key);
|
|
1450
|
+
renderSidebar();
|
|
1451
|
+
});
|
|
1452
|
+
const bulk = h("button", "cs-sb-bulk", mode === "all" ? "\u25CF" : mode === "none" ? "\u25CC" : "\u25D0");
|
|
1453
|
+
bulk.type = "button";
|
|
1454
|
+
bulk.dataset.fk = `bulk:${key}`;
|
|
1455
|
+
bulk.dataset.state = mode;
|
|
1456
|
+
bulk.setAttribute("aria-pressed", mode === "all" ? "true" : mode === "none" ? "false" : "mixed");
|
|
1457
|
+
const act = mode === "all" ? `\u9690\u85CF\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F` : `\u663E\u793A\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F`;
|
|
1458
|
+
bulk.setAttribute("aria-label", act);
|
|
1459
|
+
bulk.title = mode === "some" ? `${act}(\u5F53\u524D ${shown}/${ids.length} \u663E\u793A\u4E2D)` : act;
|
|
1460
|
+
bulk.addEventListener("click", (e) => {
|
|
1461
|
+
e.stopPropagation();
|
|
1462
|
+
setGroupHidden(ids, mode === "all");
|
|
1463
|
+
renderSidebar();
|
|
1464
|
+
});
|
|
1465
|
+
headEl.append(caret, bulk);
|
|
1466
|
+
el2.append(headEl, body);
|
|
1467
|
+
return { el: el2, body };
|
|
1500
1468
|
}
|
|
1501
|
-
function
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1469
|
+
function row(id, name, sub, kind) {
|
|
1470
|
+
const el2 = h("div", "cs-sb-row");
|
|
1471
|
+
el2.dataset.id = id;
|
|
1472
|
+
const go = h("button", "cs-sb-go");
|
|
1473
|
+
go.type = "button";
|
|
1474
|
+
go.dataset.fk = `go:${id}`;
|
|
1475
|
+
go.dataset.kind = kind;
|
|
1476
|
+
go.title = sub ? `\u805A\u7126 ${name} (${sub})` : `\u805A\u7126 ${name}`;
|
|
1477
|
+
go.append(h("span", "cs-sb-dot"), h("span", "cs-sb-name", name));
|
|
1478
|
+
if (sub) go.appendChild(h("span", "cs-sb-route", sub));
|
|
1479
|
+
go.addEventListener("click", () => {
|
|
1480
|
+
if (isHidden(id)) toggleHidden(id);
|
|
1481
|
+
fitBoard(id);
|
|
1482
|
+
state.activeId = id;
|
|
1483
|
+
syncActive();
|
|
1511
1484
|
});
|
|
1512
|
-
|
|
1485
|
+
const eye = h("button", "cs-sb-eye", isHidden(id) ? "\u25CC" : "\u25CF");
|
|
1486
|
+
eye.type = "button";
|
|
1487
|
+
eye.dataset.fk = `eye:${id}`;
|
|
1488
|
+
eye.title = isHidden(id) ? "\u663E\u793A\u8FD9\u5757\u753B\u677F" : "\u4ECE\u5899\u4E0A\u9690\u85CF(\u4E0D\u5220,\u968F\u65F6\u70B9\u56DE\u6765)";
|
|
1489
|
+
eye.setAttribute("aria-label", isHidden(id) ? `\u663E\u793A ${name}` : `\u9690\u85CF ${name}`);
|
|
1490
|
+
eye.setAttribute("aria-pressed", String(!isHidden(id)));
|
|
1491
|
+
eye.addEventListener("click", (e) => {
|
|
1492
|
+
e.stopPropagation();
|
|
1493
|
+
toggleHidden(id);
|
|
1494
|
+
renderSidebar();
|
|
1495
|
+
});
|
|
1496
|
+
el2.append(go, eye);
|
|
1497
|
+
if (isHidden(id)) el2.classList.add("is-hidden-board");
|
|
1498
|
+
return el2;
|
|
1499
|
+
}
|
|
1500
|
+
function syncActive() {
|
|
1501
|
+
if (!listEl) return;
|
|
1502
|
+
for (const r of listEl.querySelectorAll(".cs-sb-row")) {
|
|
1503
|
+
r.classList.toggle("is-active", r.dataset.id === state.activeId);
|
|
1504
|
+
}
|
|
1513
1505
|
}
|
|
1514
1506
|
|
|
1515
1507
|
// src/canvas/wall.ts
|
|
@@ -1609,9 +1601,9 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
1609
1601
|
return titleEl;
|
|
1610
1602
|
}
|
|
1611
1603
|
function createBoard(entry) {
|
|
1612
|
-
const
|
|
1613
|
-
|
|
1614
|
-
|
|
1604
|
+
const el2 = h("div", "cs-board");
|
|
1605
|
+
el2.dataset.id = entry.id;
|
|
1606
|
+
el2.dataset.kind = entry.kind;
|
|
1615
1607
|
const titleEl = h("div", "cs-board-title");
|
|
1616
1608
|
const nameEl = h("span", "cs-name", entry.exportName);
|
|
1617
1609
|
const badgeEl = h("span", "cs-badge", entry.kind);
|
|
@@ -1623,11 +1615,11 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
1623
1615
|
const overlayEl = h("div", "cs-ovl");
|
|
1624
1616
|
const pinLayerEl = h("div", "cs-pins");
|
|
1625
1617
|
frameEl.append(placeholderEl, overlayEl);
|
|
1626
|
-
|
|
1627
|
-
world2.appendChild(
|
|
1618
|
+
el2.append(titleEl, frameEl, pinLayerEl);
|
|
1619
|
+
world2.appendChild(el2);
|
|
1628
1620
|
const board = {
|
|
1629
1621
|
entry,
|
|
1630
|
-
el,
|
|
1622
|
+
el: el2,
|
|
1631
1623
|
titleEl,
|
|
1632
1624
|
nameEl,
|
|
1633
1625
|
badgeEl,
|
|
@@ -1650,10 +1642,10 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
1650
1642
|
}
|
|
1651
1643
|
attachResizeHandles(board);
|
|
1652
1644
|
attachBoardDrag(board);
|
|
1653
|
-
|
|
1645
|
+
el2.hidden = isHidden(entry.id);
|
|
1654
1646
|
applySize(board);
|
|
1655
1647
|
updateBoard(board, entry);
|
|
1656
|
-
io.observe(
|
|
1648
|
+
io.observe(el2);
|
|
1657
1649
|
return board;
|
|
1658
1650
|
}
|
|
1659
1651
|
var WIDTH_PRESETS = [390, 768, 1024, 1280, 1440];
|
|
@@ -2393,210 +2385,269 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
2393
2385
|
settleTimer = null;
|
|
2394
2386
|
}
|
|
2395
2387
|
|
|
2396
|
-
// src/canvas/
|
|
2397
|
-
var
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
const
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2388
|
+
// src/canvas/modes.ts
|
|
2389
|
+
var viewport2;
|
|
2390
|
+
function boardOf(target) {
|
|
2391
|
+
const el2 = target?.closest?.(".cs-board");
|
|
2392
|
+
if (!el2?.dataset.id) return null;
|
|
2393
|
+
return state.boards.get(el2.dataset.id) ?? null;
|
|
2394
|
+
}
|
|
2395
|
+
function setActive(id) {
|
|
2396
|
+
if (state.activeId === id) return;
|
|
2397
|
+
state.activeId = id;
|
|
2398
|
+
for (const b of state.boards.values()) b.el.classList.toggle("is-active", b.entry.id === id);
|
|
2399
|
+
}
|
|
2400
|
+
function setMode(mode, boardId) {
|
|
2401
|
+
if (mode !== "browse" && boardId) setActive(boardId);
|
|
2402
|
+
if (mode === "review" && !state.activeId) return;
|
|
2403
|
+
const from = state.mode;
|
|
2404
|
+
state.mode = mode;
|
|
2405
|
+
hideHover();
|
|
2406
|
+
if (mode !== "browse") clearSelection();
|
|
2407
|
+
syncHud();
|
|
2408
|
+
if (mode === "review" && from !== "review") {
|
|
2409
|
+
state.modeBeforeReview = from;
|
|
2410
|
+
state.viewBeforeReview = { scale: state.scale, tx: state.tx, ty: state.ty };
|
|
2411
|
+
closeArgsPanel();
|
|
2412
|
+
fitActiveBoard();
|
|
2413
|
+
} else if (mode !== "review" && from === "review" && state.viewBeforeReview) {
|
|
2414
|
+
const v = state.viewBeforeReview;
|
|
2415
|
+
state.viewBeforeReview = null;
|
|
2416
|
+
setView(v.tx, v.ty, v.scale);
|
|
2417
|
+
}
|
|
2418
|
+
setProbe(
|
|
2419
|
+
mode === "interact" ? "\u4EA4\u4E92\u6A21\u5F0F:\u76F4\u63A5\u64CD\u4F5C\u8FD9\u5757\u753B\u677F,Esc \u56DE\u6D4F\u89C8" : mode === "review" ? "\u8D70\u67E5\u6A21\u5F0F:Esc \u9000\u51FA" : "\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20"
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
function fitActiveBoard() {
|
|
2423
|
+
const board = state.activeId ? state.boards.get(state.activeId) : null;
|
|
2424
|
+
if (!board) return;
|
|
2425
|
+
const r = board.frameEl.getBoundingClientRect();
|
|
2426
|
+
const worldX = (r.left - state.tx) / state.scale;
|
|
2427
|
+
const worldY = (r.top - state.ty) / state.scale;
|
|
2428
|
+
const vw = window.innerWidth;
|
|
2429
|
+
const vh = window.innerHeight;
|
|
2430
|
+
const s = clamp(Math.min(vw / board.width, vh / board.height), MIN_SCALE, MAX_SCALE);
|
|
2431
|
+
setView((vw - board.width * s) / 2 - worldX * s, (vh - board.height * s) / 2 - worldY * s, s);
|
|
2432
|
+
}
|
|
2433
|
+
function escape() {
|
|
2434
|
+
if (isComposing()) {
|
|
2435
|
+
closeComposer();
|
|
2436
|
+
exitPinMode();
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
if (state.pinPending) {
|
|
2440
|
+
exitPinMode();
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
if (selectedPin()) {
|
|
2444
|
+
selectPin(null);
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
if (state.mode === "review") {
|
|
2448
|
+
setMode(state.modeBeforeReview === "review" ? "browse" : state.modeBeforeReview);
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
if (state.mode === "interact") {
|
|
2452
|
+
setMode("browse");
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
clearSelection();
|
|
2456
|
+
closeArgsPanel();
|
|
2457
|
+
setProbe("\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
|
|
2458
|
+
}
|
|
2459
|
+
function isEditable(target) {
|
|
2460
|
+
const el2 = target;
|
|
2461
|
+
if (!el2) return false;
|
|
2462
|
+
const tag = el2.tagName;
|
|
2463
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el2.isContentEditable === true) return true;
|
|
2464
|
+
return el2.closest("#cs-sidebar, #cs-args, .cs-picker, #cs-topbar") !== null;
|
|
2465
|
+
}
|
|
2466
|
+
function onKeyDown(e) {
|
|
2467
|
+
if (e.key === "Escape") {
|
|
2468
|
+
escape();
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
if (isEditable(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
2472
|
+
if (e.key === "Enter") {
|
|
2473
|
+
if (selectedPin()) {
|
|
2474
|
+
e.preventDefault();
|
|
2475
|
+
editSelectedPin();
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
if (state.mode !== "review" && state.activeId) {
|
|
2479
|
+
e.preventDefault();
|
|
2480
|
+
setMode("review", state.activeId);
|
|
2481
|
+
}
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
if (e.key === "Backspace" || e.key === "Delete") {
|
|
2485
|
+
if (selectedPin()) {
|
|
2486
|
+
e.preventDefault();
|
|
2487
|
+
deleteSelectedPin();
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
if (state.mode === "browse" && state.activeId) {
|
|
2491
|
+
e.preventDefault();
|
|
2492
|
+
const b = state.boards.get(state.activeId);
|
|
2493
|
+
setHidden([state.activeId], true);
|
|
2494
|
+
clearSelection();
|
|
2495
|
+
setActive(null);
|
|
2496
|
+
toast(`\u5DF2\u6536\u8D77\u300C${b?.entry.exportName ?? state.activeId}\u300D\xB7 \u5DE6\u4FA7\u5217\u8868\u968F\u65F6\u70B9\u56DE\u6765`, "info");
|
|
2423
2497
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2500
|
+
if (e.key === "c" || e.key === "C") {
|
|
2501
|
+
if (state.mode === "browse" && !state.pinPending) {
|
|
2502
|
+
e.preventDefault();
|
|
2503
|
+
enterPinMode();
|
|
2430
2504
|
}
|
|
2431
|
-
|
|
2432
|
-
root.append(head, listEl);
|
|
2433
|
-
const toggle = qs("#cs-sb-toggle");
|
|
2434
|
-
toggle.addEventListener("click", () => {
|
|
2435
|
-
const closed = document.body.dataset.sidebar === "off";
|
|
2436
|
-
document.body.dataset.sidebar = closed ? "on" : "off";
|
|
2437
|
-
toggle.title = closed ? "\u6536\u8D77\u5217\u8868" : "\u5C55\u5F00\u5217\u8868";
|
|
2438
|
-
});
|
|
2439
|
-
renderSidebar();
|
|
2440
|
-
}
|
|
2441
|
-
function match(text) {
|
|
2442
|
-
return !filter || text.toLowerCase().includes(filter);
|
|
2443
|
-
}
|
|
2444
|
-
function collapseKey() {
|
|
2445
|
-
return `cs-collapsed:${state.info?.projectRoot ?? "unknown"}`;
|
|
2446
|
-
}
|
|
2447
|
-
function collapsedSet() {
|
|
2448
|
-
try {
|
|
2449
|
-
return new Set(JSON.parse(localStorage.getItem(collapseKey()) ?? "[]"));
|
|
2450
|
-
} catch {
|
|
2451
|
-
return /* @__PURE__ */ new Set();
|
|
2505
|
+
return;
|
|
2452
2506
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
}
|
|
2457
|
-
function toggleCollapsed(key) {
|
|
2458
|
-
if (searchCollapsed) {
|
|
2459
|
-
if (!searchCollapsed.delete(key)) searchCollapsed.add(key);
|
|
2507
|
+
if (e.key === "y" || e.key === "Y") {
|
|
2508
|
+
e.preventDefault();
|
|
2509
|
+
void copyContext();
|
|
2460
2510
|
return;
|
|
2461
2511
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2512
|
+
if (e.key === "p") {
|
|
2513
|
+
e.preventDefault();
|
|
2514
|
+
void pushToClaudeCode();
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
if (e.key === "1") {
|
|
2518
|
+
e.preventDefault();
|
|
2519
|
+
fitAll();
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
if (e.key === "2") {
|
|
2523
|
+
e.preventDefault();
|
|
2524
|
+
fitBoard();
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
if (e.key === "0") {
|
|
2528
|
+
e.preventDefault();
|
|
2529
|
+
zoomReset();
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
if (e.key === "=" || e.key === "+") {
|
|
2533
|
+
e.preventDefault();
|
|
2534
|
+
zoomStep(1.25);
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
if (e.key === "-" || e.key === "_") {
|
|
2538
|
+
e.preventDefault();
|
|
2539
|
+
zoomStep(0.8);
|
|
2468
2540
|
}
|
|
2469
2541
|
}
|
|
2470
|
-
function
|
|
2471
|
-
|
|
2472
|
-
return { mode: shown === ids.length ? "all" : shown === 0 ? "none" : "some", shown };
|
|
2473
|
-
}
|
|
2474
|
-
function setGroupHidden(ids, hide) {
|
|
2475
|
-
setHidden(ids, hide);
|
|
2476
|
-
}
|
|
2477
|
-
function focusedKey() {
|
|
2478
|
-
const el = document.activeElement;
|
|
2479
|
-
return el && listEl.contains(el) ? el.dataset.fk ?? null : null;
|
|
2480
|
-
}
|
|
2481
|
-
function restoreFocus(key) {
|
|
2482
|
-
if (!key) return;
|
|
2483
|
-
const el = listEl.querySelector(`[data-fk="${CSS.escape(key)}"]`);
|
|
2484
|
-
el?.focus({ preventScroll: true });
|
|
2542
|
+
function attachFrameKeys(doc) {
|
|
2543
|
+
doc.addEventListener("keydown", onKeyDown);
|
|
2485
2544
|
}
|
|
2486
|
-
function
|
|
2487
|
-
if (
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
const visScreens = screens.filter((e) => match(e.exportName) || match(e.url ?? ""));
|
|
2494
|
-
if (visScreens.length) {
|
|
2495
|
-
const g = group("sec:screen", "\u9875\u9762", visScreens.map((e) => e.id), "cs-sb-section");
|
|
2496
|
-
for (const e of visScreens) g.body.appendChild(row(e.id, e.exportName, e.url ?? "", "screen"));
|
|
2497
|
-
listEl.appendChild(g.el);
|
|
2545
|
+
function onMouseMove(e) {
|
|
2546
|
+
if (isPanning()) return;
|
|
2547
|
+
const overlay = e.target?.closest?.(".cs-ovl");
|
|
2548
|
+
const board = overlay ? boardOf(e.target) : null;
|
|
2549
|
+
if (!board) {
|
|
2550
|
+
hideHover();
|
|
2551
|
+
return;
|
|
2498
2552
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2553
|
+
if (state.mode !== "browse") return;
|
|
2554
|
+
setActive(board.entry.id);
|
|
2555
|
+
const el2 = elementAt(board, e.clientX, e.clientY);
|
|
2556
|
+
if (!el2) {
|
|
2557
|
+
hideHover();
|
|
2558
|
+
return;
|
|
2505
2559
|
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2560
|
+
showHover(board, el2);
|
|
2561
|
+
setProbe(`${board.entry.exportName} ${describe(el2)}`);
|
|
2562
|
+
}
|
|
2563
|
+
function onClick(e) {
|
|
2564
|
+
const target = e.target;
|
|
2565
|
+
const title = target?.closest?.(".cs-board-title");
|
|
2566
|
+
if (title) {
|
|
2567
|
+
const board2 = boardOf(target);
|
|
2568
|
+
if (!board2) return;
|
|
2569
|
+
setActive(board2.entry.id);
|
|
2570
|
+
if (isArgsOpen(board2.entry.id)) closeArgsPanel();
|
|
2571
|
+
else openArgsPanel(board2);
|
|
2572
|
+
return;
|
|
2517
2573
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2574
|
+
const overlay = target?.closest?.(".cs-ovl");
|
|
2575
|
+
if (!overlay) return;
|
|
2576
|
+
const board = boardOf(target);
|
|
2577
|
+
if (!board) return;
|
|
2578
|
+
if (state.mode === "interact") {
|
|
2579
|
+
if (board.entry.id !== state.activeId) hintInactive(board);
|
|
2580
|
+
return;
|
|
2520
2581
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2582
|
+
setActive(board.entry.id);
|
|
2583
|
+
const el2 = elementAt(board, e.clientX, e.clientY);
|
|
2584
|
+
if (!el2) return;
|
|
2585
|
+
if (state.pinPending) {
|
|
2586
|
+
openComposer(board, el2, e.clientX, e.clientY);
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
if (state.mode !== "browse") return;
|
|
2590
|
+
const selector = buildSelector(el2);
|
|
2591
|
+
const rel = relPos(el2, e.clientX, e.clientY, board);
|
|
2592
|
+
showSelection(board, selector);
|
|
2593
|
+
state.selection = { artboardId: board.entry.id, selector, x: rel.x, y: rel.y, ts: Date.now() };
|
|
2594
|
+
setProbe(`\u5DF2\u9009\u4E2D ${board.entry.exportName} \u2192 ${selector}`);
|
|
2595
|
+
postSelection(state.selection).catch((err) => setProbe(`selection \u53D1\u9001\u5931\u8D25:${String(err)}`));
|
|
2523
2596
|
}
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
const
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
caret.type = "button";
|
|
2535
|
-
caret.dataset.fk = `caret:${key}`;
|
|
2536
|
-
caret.setAttribute("aria-expanded", String(!collapsed));
|
|
2537
|
-
caret.setAttribute("aria-controls", body.id);
|
|
2538
|
-
caret.title = collapsed ? `\u5C55\u5F00\u300C${label}\u300D` : `\u6298\u53E0\u300C${label}\u300D`;
|
|
2539
|
-
const arrow = h("span", "cs-sb-arrow", "\u25B8");
|
|
2540
|
-
arrow.setAttribute("aria-hidden", "true");
|
|
2541
|
-
const count = h("span", "cs-sb-count", mode === "all" ? String(ids.length) : `${shown}/${ids.length}`);
|
|
2542
|
-
caret.append(arrow, h("span", "cs-sb-label", label), count);
|
|
2543
|
-
caret.addEventListener("click", () => {
|
|
2544
|
-
toggleCollapsed(key);
|
|
2545
|
-
renderSidebar();
|
|
2546
|
-
});
|
|
2547
|
-
const bulk = h("button", "cs-sb-bulk", mode === "all" ? "\u25CF" : mode === "none" ? "\u25CC" : "\u25D0");
|
|
2548
|
-
bulk.type = "button";
|
|
2549
|
-
bulk.dataset.fk = `bulk:${key}`;
|
|
2550
|
-
bulk.dataset.state = mode;
|
|
2551
|
-
bulk.setAttribute("aria-pressed", mode === "all" ? "true" : mode === "none" ? "false" : "mixed");
|
|
2552
|
-
const act = mode === "all" ? `\u9690\u85CF\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F` : `\u663E\u793A\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F`;
|
|
2553
|
-
bulk.setAttribute("aria-label", act);
|
|
2554
|
-
bulk.title = mode === "some" ? `${act}(\u5F53\u524D ${shown}/${ids.length} \u663E\u793A\u4E2D)` : act;
|
|
2555
|
-
bulk.addEventListener("click", (e) => {
|
|
2556
|
-
e.stopPropagation();
|
|
2557
|
-
setGroupHidden(ids, mode === "all");
|
|
2558
|
-
renderSidebar();
|
|
2559
|
-
});
|
|
2560
|
-
headEl.append(caret, bulk);
|
|
2561
|
-
el.append(headEl, body);
|
|
2562
|
-
return { el, body };
|
|
2597
|
+
var lastHint = "";
|
|
2598
|
+
var lastHintAt = 0;
|
|
2599
|
+
function hintInactive(board) {
|
|
2600
|
+
const cur = state.boards.get(state.activeId ?? "")?.entry.exportName ?? "?";
|
|
2601
|
+
setProbe(`\u4EA4\u4E92\u4E2D\u7684\u662F\u300C${cur}\u300D\u2014\u2014 \u8981\u4EA4\u4E92\u8FD9\u5757\u8BF7\u53CC\u51FB\u5B83,Esc \u9000\u51FA\u4EA4\u4E92`);
|
|
2602
|
+
const now = Date.now();
|
|
2603
|
+
if (lastHint === board.entry.id && now - lastHintAt < 4e3) return;
|
|
2604
|
+
lastHint = board.entry.id;
|
|
2605
|
+
lastHintAt = now;
|
|
2606
|
+
toast(`\u300C${board.entry.exportName}\u300D\u672A\u6FC0\u6D3B \xB7 \u53CC\u51FB\u5B83\u5207\u6362\u4EA4\u4E92\u76EE\u6807`, "info");
|
|
2563
2607
|
}
|
|
2564
|
-
function
|
|
2565
|
-
const
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
state.
|
|
2578
|
-
|
|
2579
|
-
});
|
|
2580
|
-
const eye = h("button", "cs-sb-eye", isHidden(id) ? "\u25CC" : "\u25CF");
|
|
2581
|
-
eye.type = "button";
|
|
2582
|
-
eye.dataset.fk = `eye:${id}`;
|
|
2583
|
-
eye.title = isHidden(id) ? "\u663E\u793A\u8FD9\u5757\u753B\u677F" : "\u4ECE\u5899\u4E0A\u9690\u85CF(\u4E0D\u5220,\u968F\u65F6\u70B9\u56DE\u6765)";
|
|
2584
|
-
eye.setAttribute("aria-label", isHidden(id) ? `\u663E\u793A ${name}` : `\u9690\u85CF ${name}`);
|
|
2585
|
-
eye.setAttribute("aria-pressed", String(!isHidden(id)));
|
|
2586
|
-
eye.addEventListener("click", (e) => {
|
|
2587
|
-
e.stopPropagation();
|
|
2588
|
-
toggleHidden(id);
|
|
2589
|
-
renderSidebar();
|
|
2608
|
+
function onDblClick(e) {
|
|
2609
|
+
const board = boardOf(e.target);
|
|
2610
|
+
if (!board || !e.target.closest(".cs-ovl")) return;
|
|
2611
|
+
setMode("interact", board.entry.id);
|
|
2612
|
+
}
|
|
2613
|
+
function initModes() {
|
|
2614
|
+
viewport2 = qs("#cs-viewport");
|
|
2615
|
+
viewport2.addEventListener("mousemove", onMouseMove);
|
|
2616
|
+
viewport2.addEventListener("mouseleave", hideHover);
|
|
2617
|
+
viewport2.addEventListener("click", onClick);
|
|
2618
|
+
viewport2.addEventListener("dblclick", onDblClick);
|
|
2619
|
+
document.addEventListener("keydown", onKeyDown);
|
|
2620
|
+
window.addEventListener("resize", () => {
|
|
2621
|
+
if (state.mode === "review") fitActiveBoard();
|
|
2622
|
+
else applyView();
|
|
2590
2623
|
});
|
|
2591
|
-
|
|
2592
|
-
if (isHidden(id)) el.classList.add("is-hidden-board");
|
|
2593
|
-
return el;
|
|
2624
|
+
syncHud();
|
|
2594
2625
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2626
|
+
|
|
2627
|
+
// src/canvas/health-banner.ts
|
|
2628
|
+
var el = null;
|
|
2629
|
+
var lastOkAt;
|
|
2630
|
+
function setLastOk(ts) {
|
|
2631
|
+
lastOkAt = ts;
|
|
2632
|
+
}
|
|
2633
|
+
function showUnhealthy(detail) {
|
|
2634
|
+
hideUnhealthy();
|
|
2635
|
+
const at = lastOkAt ? new Date(lastOkAt).toLocaleTimeString("zh-CN", { hour12: false }) : null;
|
|
2636
|
+
el = h("div", "cs-health");
|
|
2637
|
+
el.setAttribute("role", "alert");
|
|
2638
|
+
el.appendChild(h("b", void 0, "\u4F60\u7684 dev server \u6CA1\u54CD\u5E94\u4E86"));
|
|
2639
|
+
el.appendChild(
|
|
2640
|
+
h(
|
|
2641
|
+
"span",
|
|
2642
|
+
void 0,
|
|
2643
|
+
`${detail ?? "SSR \u63A2\u6D4B\u5931\u8D25"}${at ? ` \xB7 \u6700\u540E\u6B63\u5E38 ${at}` : ""} \u2014\u2014 \u753B\u5E03\u6CA1\u574F,\u753B\u677F\u7834\u56FE\u662F\u5B83\u6E32\u67D3\u4E0D\u51FA\u6765\u3002\u901A\u5E38\u91CD\u542F next dev \u5373\u6108\u3002`
|
|
2644
|
+
)
|
|
2645
|
+
);
|
|
2646
|
+
document.body.appendChild(el);
|
|
2647
|
+
}
|
|
2648
|
+
function hideUnhealthy() {
|
|
2649
|
+
el?.remove();
|
|
2650
|
+
el = null;
|
|
2600
2651
|
}
|
|
2601
2652
|
|
|
2602
2653
|
// src/canvas/app.ts
|
|
@@ -2655,9 +2706,9 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
2655
2706
|
}
|
|
2656
2707
|
function showTarget() {
|
|
2657
2708
|
const info = state.info;
|
|
2658
|
-
const
|
|
2659
|
-
|
|
2660
|
-
|
|
2709
|
+
const el2 = qs("#cs-target");
|
|
2710
|
+
el2.textContent = info ? `${info.target} \xB7 ${info.designDir}` : "";
|
|
2711
|
+
el2.title = info ? `v${info.version} \xB7 ${info.projectRoot}` : "";
|
|
2661
2712
|
}
|
|
2662
2713
|
function onRegistryEvent(entries) {
|
|
2663
2714
|
void reloadUntilMatches(entries.map((e) => e.id));
|
|
@@ -2701,16 +2752,31 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
|
2701
2752
|
}
|
|
2702
2753
|
showTarget();
|
|
2703
2754
|
restoreView();
|
|
2704
|
-
await tryLoadRegistry();
|
|
2705
2755
|
try {
|
|
2706
|
-
|
|
2707
|
-
|
|
2756
|
+
const hres = await fetch("/__cs/api/health");
|
|
2757
|
+
const hstate = await hres.json();
|
|
2758
|
+
setLastOk(hstate.lastOkAt);
|
|
2759
|
+
if (!hstate.ok) showUnhealthy(hstate.detail);
|
|
2708
2760
|
} catch {
|
|
2709
2761
|
}
|
|
2710
2762
|
connectEvents({
|
|
2711
2763
|
registry: onRegistryEvent,
|
|
2712
|
-
annotations: onAnnotations
|
|
2764
|
+
annotations: onAnnotations,
|
|
2765
|
+
health: (ok, detail) => {
|
|
2766
|
+
if (ok) {
|
|
2767
|
+
setLastOk(Date.now());
|
|
2768
|
+
hideUnhealthy();
|
|
2769
|
+
} else {
|
|
2770
|
+
showUnhealthy(detail);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2713
2773
|
});
|
|
2774
|
+
await tryLoadRegistry();
|
|
2775
|
+
try {
|
|
2776
|
+
state.annotations = await fetchAnnotations();
|
|
2777
|
+
renderPins();
|
|
2778
|
+
} catch {
|
|
2779
|
+
}
|
|
2714
2780
|
}
|
|
2715
2781
|
void boot();
|
|
2716
2782
|
})();
|