contactsheet 0.1.0
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/LICENSE +21 -0
- package/README.md +243 -0
- package/dist/canvas/app.js +2675 -0
- package/dist/canvas/app.js.map +7 -0
- package/dist/canvas/favicon.png +0 -0
- package/dist/canvas/index.html +59 -0
- package/dist/canvas/logo.png +0 -0
- package/dist/canvas/style-pins.css +104 -0
- package/dist/canvas/style-select.css +10 -0
- package/dist/canvas/style-sidebar.css +132 -0
- package/dist/canvas/style-wall.css +110 -0
- package/dist/canvas/style.css +1189 -0
- package/dist/cli.js +1783 -0
- package/dist/cli.js.map +7 -0
- package/package.json +53 -0
|
@@ -0,0 +1,2675 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/canvas/api.ts
|
|
4
|
+
var BASE = "/__cs";
|
|
5
|
+
async function req(url, init) {
|
|
6
|
+
const res = await fetch(url, init);
|
|
7
|
+
if (!res.ok) throw new Error(`${init?.method ?? "GET"} ${url} \u2192 HTTP ${res.status}`);
|
|
8
|
+
const text = await res.text();
|
|
9
|
+
return text ? JSON.parse(text) : null;
|
|
10
|
+
}
|
|
11
|
+
function jsonInit(method, body) {
|
|
12
|
+
return { method, headers: { "content-type": "application/json" }, body: JSON.stringify(body) };
|
|
13
|
+
}
|
|
14
|
+
function fetchState() {
|
|
15
|
+
return req(`${BASE}/api/state`);
|
|
16
|
+
}
|
|
17
|
+
function fetchRegistry() {
|
|
18
|
+
return req(`${BASE}/registry`);
|
|
19
|
+
}
|
|
20
|
+
function fetchAnnotations() {
|
|
21
|
+
return req(`${BASE}/api/annotations`);
|
|
22
|
+
}
|
|
23
|
+
function postSelection(sel) {
|
|
24
|
+
return req(`${BASE}/api/selection`, jsonInit("POST", sel));
|
|
25
|
+
}
|
|
26
|
+
function createAnnotation(body) {
|
|
27
|
+
return req(`${BASE}/api/annotations`, jsonInit("POST", body));
|
|
28
|
+
}
|
|
29
|
+
async function deleteAnnotation(id) {
|
|
30
|
+
const res = await fetch(`${BASE}/api/annotations/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
31
|
+
if (!res.ok) throw new Error(`DELETE annotation \u2192 HTTP ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
async function pushContext(pid) {
|
|
34
|
+
const token = document.querySelector('meta[name="cs-token"]')?.content ?? "";
|
|
35
|
+
const res = await fetch(`${BASE}/api/push`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "content-type": "application/json", "x-cs-token": token },
|
|
38
|
+
body: JSON.stringify(pid !== void 0 ? { pid } : {})
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) throw new Error(`POST push \u2192 HTTP ${res.status}`);
|
|
41
|
+
return res.json();
|
|
42
|
+
}
|
|
43
|
+
async function fetchContext() {
|
|
44
|
+
const res = await fetch(`${BASE}/api/context`);
|
|
45
|
+
if (!res.ok) throw new Error(`GET context \u2192 HTTP ${res.status}`);
|
|
46
|
+
return res.text();
|
|
47
|
+
}
|
|
48
|
+
function patchAnnotation(id, patch) {
|
|
49
|
+
return req(`${BASE}/api/annotations/${encodeURIComponent(id)}`, jsonInit("PATCH", patch));
|
|
50
|
+
}
|
|
51
|
+
function postRef(name, dataBase64) {
|
|
52
|
+
return req(`${BASE}/api/refs`, jsonInit("POST", { name, dataBase64 }));
|
|
53
|
+
}
|
|
54
|
+
function refUrl(path) {
|
|
55
|
+
return `${BASE}/api/refs/${path.split("/").map(encodeURIComponent).join("/")}`;
|
|
56
|
+
}
|
|
57
|
+
function boardUrl(entry, args) {
|
|
58
|
+
if (entry.kind === "screen") return entry.url || "/";
|
|
59
|
+
const base = `${BASE}/ab/${encodeURIComponent(entry.id)}`;
|
|
60
|
+
if (!args) return base;
|
|
61
|
+
return `${base}?args=${encodeURIComponent(JSON.stringify(args))}`;
|
|
62
|
+
}
|
|
63
|
+
function connectEvents(on) {
|
|
64
|
+
const es = new EventSource(`${BASE}/events`);
|
|
65
|
+
const dispatch = (raw) => {
|
|
66
|
+
if (typeof raw !== "string") return;
|
|
67
|
+
let ev;
|
|
68
|
+
try {
|
|
69
|
+
ev = JSON.parse(raw);
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (ev.type === "registry") on.registry(ev.entries ?? []);
|
|
74
|
+
else if (ev.type === "annotations") on.annotations(ev.annotations ?? []);
|
|
75
|
+
};
|
|
76
|
+
es.addEventListener("registry", (e) => dispatch(e.data));
|
|
77
|
+
es.addEventListener("annotations", (e) => dispatch(e.data));
|
|
78
|
+
es.onmessage = (e) => dispatch(e.data);
|
|
79
|
+
return es;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/canvas/dom.ts
|
|
83
|
+
function h(tag, cls, text) {
|
|
84
|
+
const el = document.createElement(tag);
|
|
85
|
+
if (cls) el.className = cls;
|
|
86
|
+
if (text !== void 0) el.textContent = text;
|
|
87
|
+
return el;
|
|
88
|
+
}
|
|
89
|
+
function qs(sel) {
|
|
90
|
+
const el = document.querySelector(sel);
|
|
91
|
+
if (!el) throw new Error(`canvas: \u7F3A\u5C11\u9AA8\u67B6\u5143\u7D20 ${sel}`);
|
|
92
|
+
return el;
|
|
93
|
+
}
|
|
94
|
+
function clamp(v, lo, hi) {
|
|
95
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
96
|
+
}
|
|
97
|
+
function debounce(ms, fn) {
|
|
98
|
+
let timer;
|
|
99
|
+
return (...args) => {
|
|
100
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
101
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function frameDoc(iframe) {
|
|
105
|
+
if (!iframe) return null;
|
|
106
|
+
try {
|
|
107
|
+
return iframe.contentDocument;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/canvas/state.ts
|
|
114
|
+
var state = {
|
|
115
|
+
info: null,
|
|
116
|
+
entries: [],
|
|
117
|
+
boards: /* @__PURE__ */ new Map(),
|
|
118
|
+
/** file → 该组内画板 id 的稳定顺序(已有 id 不动,新 id 排组尾) */
|
|
119
|
+
order: /* @__PURE__ */ new Map(),
|
|
120
|
+
annotations: [],
|
|
121
|
+
mode: "browse",
|
|
122
|
+
/** 进走查前的模式,Esc 逐级退回 */
|
|
123
|
+
modeBeforeReview: "browse",
|
|
124
|
+
/** 走查前的视图,退出时还原 */
|
|
125
|
+
viewBeforeReview: null,
|
|
126
|
+
/** 当前活动画板(hover/点击/双击都会更新),Enter 走查的就是它 */
|
|
127
|
+
activeId: null,
|
|
128
|
+
selection: null,
|
|
129
|
+
/** 按了 c、等着点元素落 pin */
|
|
130
|
+
pinPending: false,
|
|
131
|
+
pinnedThisRun: 0,
|
|
132
|
+
scale: 1,
|
|
133
|
+
tx: 284,
|
|
134
|
+
// 给左侧栏让位(侧栏 236 + 边距)
|
|
135
|
+
ty: 48
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/canvas/hud.ts
|
|
139
|
+
var modeEl;
|
|
140
|
+
var probeEl;
|
|
141
|
+
var MODE_LABEL = { browse: "\u6D4F\u89C8", interact: "\u4EA4\u4E92", review: "\u8D70\u67E5" };
|
|
142
|
+
function initHud() {
|
|
143
|
+
modeEl = qs("#cs-mode");
|
|
144
|
+
probeEl = qs("#cs-probe");
|
|
145
|
+
syncHud();
|
|
146
|
+
}
|
|
147
|
+
function syncHud() {
|
|
148
|
+
const n = state.pinPending ? state.pinnedThisRun : 0;
|
|
149
|
+
modeEl.textContent = state.pinPending ? n > 0 ? `\u6279\u6CE8\u4E2D \xB7 \u5DF2\u9489 ${n}` : "\u6279\u6CE8\u4E2D" : MODE_LABEL[state.mode];
|
|
150
|
+
document.body.dataset.mode = state.mode;
|
|
151
|
+
document.body.dataset.pin = state.pinPending ? "on" : "off";
|
|
152
|
+
}
|
|
153
|
+
function setProbe(text) {
|
|
154
|
+
probeEl.textContent = text;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/canvas/args-panel.ts
|
|
158
|
+
var panel;
|
|
159
|
+
var openId = null;
|
|
160
|
+
var readers = [];
|
|
161
|
+
function initArgsPanel() {
|
|
162
|
+
panel = qs("#cs-args");
|
|
163
|
+
}
|
|
164
|
+
function readMeta(board) {
|
|
165
|
+
const doc = frameDoc(board.iframe);
|
|
166
|
+
const node = doc?.getElementById("__cs_meta");
|
|
167
|
+
if (node?.textContent) {
|
|
168
|
+
try {
|
|
169
|
+
const meta = JSON.parse(node.textContent);
|
|
170
|
+
if (meta && typeof meta === "object" && meta.args && typeof meta.args === "object") return meta.args;
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return board.argsOverride ?? board.entry.args ?? null;
|
|
175
|
+
}
|
|
176
|
+
function closeArgsPanel() {
|
|
177
|
+
openId = null;
|
|
178
|
+
readers = [];
|
|
179
|
+
panel.hidden = true;
|
|
180
|
+
panel.textContent = "";
|
|
181
|
+
document.body.dataset.args = "closed";
|
|
182
|
+
}
|
|
183
|
+
function isArgsOpen(id) {
|
|
184
|
+
return openId === id;
|
|
185
|
+
}
|
|
186
|
+
function openArgsPanel(board) {
|
|
187
|
+
openId = board.entry.id;
|
|
188
|
+
readers = [];
|
|
189
|
+
panel.textContent = "";
|
|
190
|
+
panel.hidden = false;
|
|
191
|
+
document.body.dataset.args = "open";
|
|
192
|
+
const head = h("div", "cs-args-head");
|
|
193
|
+
const title = h("div", "cs-args-title", board.entry.exportName);
|
|
194
|
+
const close = h("button", "cs-x", "\u2715");
|
|
195
|
+
close.title = "\u5173\u95ED";
|
|
196
|
+
close.addEventListener("click", closeArgsPanel);
|
|
197
|
+
head.append(title, close);
|
|
198
|
+
const sub = h("div", "cs-args-sub", `${board.entry.file}
|
|
199
|
+
${board.entry.kind} \xB7 ${board.entry.id}`);
|
|
200
|
+
const body = h("div", "cs-args-body");
|
|
201
|
+
const args = readMeta(board);
|
|
202
|
+
const keys = args ? Object.keys(args) : [];
|
|
203
|
+
if (!args || keys.length === 0) {
|
|
204
|
+
body.appendChild(
|
|
205
|
+
h(
|
|
206
|
+
"div",
|
|
207
|
+
"cs-args-empty",
|
|
208
|
+
board.entry.kind === "screen" ? "screen \u753B\u677F\u6CA1\u6709 args(\u5B83\u76F4\u63A5\u6E32\u67D3\u76EE\u6807\u9875\u9762)\u3002" : "\u8FD9\u4E2A\u753B\u677F\u6CA1\u6709\u58F0\u660E args\u3002\u5728 .artboard.tsx \u91CC\u52A0 args: { \u2026 } \u5C31\u80FD\u5728\u8FD9\u91CC\u8C03\u3002"
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
for (const key of keys) body.appendChild(field(key, args[key]));
|
|
213
|
+
}
|
|
214
|
+
const foot = h("div", "cs-args-foot");
|
|
215
|
+
const save = h("button", void 0, "\u5B58\u4E3A\u753B\u677F");
|
|
216
|
+
save.disabled = true;
|
|
217
|
+
save.title = "v1 \u672A\u5B9E\u73B0:\u6539\u597D\u7684 args \u8BF7\u624B\u52A8\u5199\u56DE .artboard.tsx \u65B0\u589E\u4E00\u4E2A export";
|
|
218
|
+
foot.appendChild(save);
|
|
219
|
+
panel.append(head, sub, body, foot);
|
|
220
|
+
}
|
|
221
|
+
var applySoon = debounce(300, () => {
|
|
222
|
+
if (!openId) return;
|
|
223
|
+
const board = state.boards.get(openId);
|
|
224
|
+
if (!board) return;
|
|
225
|
+
const next = { ...readMeta(board) ?? {} };
|
|
226
|
+
for (const f of readers) {
|
|
227
|
+
const v = f.read();
|
|
228
|
+
if (v === void 0) return;
|
|
229
|
+
next[f.key] = v;
|
|
230
|
+
}
|
|
231
|
+
board.argsOverride = next;
|
|
232
|
+
const url = boardUrl(board.entry, next);
|
|
233
|
+
const win = board.iframe?.contentWindow;
|
|
234
|
+
if (win) win.location.replace(url);
|
|
235
|
+
else if (board.iframe) board.iframe.src = url;
|
|
236
|
+
if (board.iframe) board.iframe.dataset.csUrl = url;
|
|
237
|
+
setProbe(`args \u5DF2\u5E94\u7528:${url.slice(0, 70)}`);
|
|
238
|
+
});
|
|
239
|
+
function field(key, value) {
|
|
240
|
+
const wrap = h("div", "cs-field");
|
|
241
|
+
if (typeof value === "boolean") {
|
|
242
|
+
wrap.classList.add("is-bool");
|
|
243
|
+
const label = h("label");
|
|
244
|
+
const input = h("input");
|
|
245
|
+
input.type = "checkbox";
|
|
246
|
+
input.checked = value;
|
|
247
|
+
input.dataset.key = key;
|
|
248
|
+
label.append(input, h("span", void 0, key));
|
|
249
|
+
wrap.appendChild(label);
|
|
250
|
+
readers.push({ key, read: () => input.checked });
|
|
251
|
+
input.addEventListener("change", applySoon);
|
|
252
|
+
return wrap;
|
|
253
|
+
}
|
|
254
|
+
if (typeof value === "number") {
|
|
255
|
+
wrap.appendChild(h("label", void 0, key));
|
|
256
|
+
const input = h("input");
|
|
257
|
+
input.type = "number";
|
|
258
|
+
input.value = String(value);
|
|
259
|
+
wrap.appendChild(input);
|
|
260
|
+
readers.push({
|
|
261
|
+
key,
|
|
262
|
+
read: () => {
|
|
263
|
+
const n = Number(input.value);
|
|
264
|
+
return input.value.trim() === "" || Number.isNaN(n) ? void 0 : n;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
input.addEventListener("input", applySoon);
|
|
268
|
+
return wrap;
|
|
269
|
+
}
|
|
270
|
+
if (typeof value === "string") {
|
|
271
|
+
wrap.appendChild(h("label", void 0, key));
|
|
272
|
+
const input = h("input");
|
|
273
|
+
input.type = "text";
|
|
274
|
+
input.value = value;
|
|
275
|
+
wrap.appendChild(input);
|
|
276
|
+
readers.push({ key, read: () => input.value });
|
|
277
|
+
input.addEventListener("input", applySoon);
|
|
278
|
+
return wrap;
|
|
279
|
+
}
|
|
280
|
+
wrap.appendChild(h("label", void 0, `${key} (JSON)`));
|
|
281
|
+
const ta = h("textarea");
|
|
282
|
+
ta.value = JSON.stringify(value ?? null, null, 2);
|
|
283
|
+
wrap.appendChild(ta);
|
|
284
|
+
readers.push({
|
|
285
|
+
key,
|
|
286
|
+
read: () => {
|
|
287
|
+
try {
|
|
288
|
+
return JSON.parse(ta.value);
|
|
289
|
+
} catch {
|
|
290
|
+
ta.style.borderColor = "var(--cs-danger)";
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
ta.addEventListener("input", () => {
|
|
296
|
+
ta.style.borderColor = "";
|
|
297
|
+
applySoon();
|
|
298
|
+
});
|
|
299
|
+
return wrap;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/canvas/toast.ts
|
|
303
|
+
var host = null;
|
|
304
|
+
function ensureHost() {
|
|
305
|
+
if (host) return host;
|
|
306
|
+
host = document.getElementById("cs-toasts");
|
|
307
|
+
if (!host) {
|
|
308
|
+
host = h("div", "cs-toasts");
|
|
309
|
+
host.id = "cs-toasts";
|
|
310
|
+
qs("body").appendChild(host);
|
|
311
|
+
}
|
|
312
|
+
return host;
|
|
313
|
+
}
|
|
314
|
+
function toast(text, kind = "info") {
|
|
315
|
+
const el = h("div", `cs-toast is-${kind}`, text);
|
|
316
|
+
ensureHost().appendChild(el);
|
|
317
|
+
requestAnimationFrame(() => el.classList.add("is-in"));
|
|
318
|
+
const life = kind === "error" ? 6e3 : kind === "warn" ? 4500 : 3e3;
|
|
319
|
+
setTimeout(() => {
|
|
320
|
+
el.classList.remove("is-in");
|
|
321
|
+
el.addEventListener("transitionend", () => el.remove(), { once: true });
|
|
322
|
+
setTimeout(() => el.remove(), 400);
|
|
323
|
+
}, life);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/canvas/refs.ts
|
|
327
|
+
var dock;
|
|
328
|
+
var lightbox;
|
|
329
|
+
var dockHead = null;
|
|
330
|
+
var localSrc = /* @__PURE__ */ new Map();
|
|
331
|
+
var pasteTarget = null;
|
|
332
|
+
function setPasteTarget(target) {
|
|
333
|
+
pasteTarget = target;
|
|
334
|
+
}
|
|
335
|
+
function activeTarget() {
|
|
336
|
+
if (pasteTarget && !pasteTarget.el.isConnected) pasteTarget = null;
|
|
337
|
+
return pasteTarget;
|
|
338
|
+
}
|
|
339
|
+
function initRefs() {
|
|
340
|
+
dock = qs("#cs-refs");
|
|
341
|
+
dock.title = "\u5168\u5C40\u53C2\u8003\u56FE:\u7C98\u8D34\u65F6\u6CA1\u6709\u5F00\u7740\u6279\u6CE8\u8F93\u5165\u6846\u7684\u56FE\u843D\u5728\u8FD9\u91CC,\u4E0D\u6302\u5728\u4EFB\u4F55\u4E00\u6761\u6279\u6CE8\u4E0A";
|
|
342
|
+
lightbox = qs("#cs-lightbox");
|
|
343
|
+
lightbox.addEventListener("click", () => {
|
|
344
|
+
lightbox.hidden = true;
|
|
345
|
+
lightbox.textContent = "";
|
|
346
|
+
});
|
|
347
|
+
document.addEventListener("paste", onPaste);
|
|
348
|
+
}
|
|
349
|
+
function onPaste(e) {
|
|
350
|
+
const items = e.clipboardData?.items;
|
|
351
|
+
if (!items) return;
|
|
352
|
+
for (const item of Array.from(items)) {
|
|
353
|
+
if (item.kind !== "file" || !item.type.startsWith("image/")) continue;
|
|
354
|
+
const file = item.getAsFile();
|
|
355
|
+
if (!file) continue;
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
const target = activeTarget();
|
|
358
|
+
if (target) target.onFile(file);
|
|
359
|
+
else void uploadToDock(file);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function uploadRef(file) {
|
|
364
|
+
const dataUrl = await readDataUrl(file);
|
|
365
|
+
const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
|
|
366
|
+
const ext = (file.type.split("/")[1] || "png").replace("jpeg", "jpg");
|
|
367
|
+
const name = file.name || `paste-${Date.now()}.${ext}`;
|
|
368
|
+
setProbe(`\u4E0A\u4F20\u53C2\u8003\u56FE ${name}\u2026`);
|
|
369
|
+
const { path } = await postRef(name, base64);
|
|
370
|
+
localSrc.set(path, dataUrl);
|
|
371
|
+
return path;
|
|
372
|
+
}
|
|
373
|
+
function readDataUrl(file) {
|
|
374
|
+
return new Promise((resolve, reject) => {
|
|
375
|
+
const reader = new FileReader();
|
|
376
|
+
reader.onerror = () => reject(new Error("\u8BFB\u56FE\u5931\u8D25"));
|
|
377
|
+
reader.onload = () => resolve(String(reader.result));
|
|
378
|
+
reader.readAsDataURL(file);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function refSrc(path) {
|
|
382
|
+
return localSrc.get(path) ?? refUrl(path);
|
|
383
|
+
}
|
|
384
|
+
function refThumb(path) {
|
|
385
|
+
const card = h("div", "cs-ref-thumb");
|
|
386
|
+
card.title = path;
|
|
387
|
+
const img = h("img");
|
|
388
|
+
const broken = () => {
|
|
389
|
+
img.hidden = true;
|
|
390
|
+
card.classList.add("is-broken");
|
|
391
|
+
};
|
|
392
|
+
img.addEventListener("error", broken);
|
|
393
|
+
img.alt = path;
|
|
394
|
+
img.src = refSrc(path);
|
|
395
|
+
if (img.complete && img.naturalWidth === 0) broken();
|
|
396
|
+
card.append(img, h("span", "cs-ref-name", fileOf(path)));
|
|
397
|
+
card.addEventListener("click", (e) => {
|
|
398
|
+
e.stopPropagation();
|
|
399
|
+
openLightbox(refSrc(path), path);
|
|
400
|
+
});
|
|
401
|
+
return card;
|
|
402
|
+
}
|
|
403
|
+
function openLightbox(src, caption) {
|
|
404
|
+
lightbox.textContent = "";
|
|
405
|
+
const big = h("img");
|
|
406
|
+
big.src = src;
|
|
407
|
+
big.alt = caption;
|
|
408
|
+
lightbox.append(big, h("span", void 0, caption));
|
|
409
|
+
lightbox.hidden = false;
|
|
410
|
+
}
|
|
411
|
+
function fileOf(path) {
|
|
412
|
+
return path.split("/").pop() ?? path;
|
|
413
|
+
}
|
|
414
|
+
async function uploadToDock(file) {
|
|
415
|
+
try {
|
|
416
|
+
const path = await uploadRef(file);
|
|
417
|
+
addThumb(path);
|
|
418
|
+
toast(
|
|
419
|
+
`\u53C2\u8003\u56FE\u5DF2\u5B58:${path} \u2014\u2014 \u8FD9\u662F\u5168\u5C40\u53C2\u8003\u56FE,\u6CA1\u6302\u5728\u6279\u6CE8\u4E0A\u3002\u8981\u6302\u5230\u67D0\u6761\u6279\u6CE8:\u6309 c \u5199\u6279\u6CE8\u65F6\u7C98\u8D34,\u6216\u5728 pin \u6C14\u6CE1\u91CC\u70B9\u300C\u7F16\u8F91\u300D\u540E\u7C98\u8D34`,
|
|
420
|
+
"ok"
|
|
421
|
+
);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function addThumb(path) {
|
|
427
|
+
ensureDockHead();
|
|
428
|
+
const card = h("div", "cs-ref");
|
|
429
|
+
const img = h("img");
|
|
430
|
+
img.src = refSrc(path);
|
|
431
|
+
img.alt = path;
|
|
432
|
+
card.append(img, h("span", void 0, fileOf(path)));
|
|
433
|
+
card.title = `${path}(\u5168\u5C40\u53C2\u8003\u56FE,\u4E0D\u5C5E\u4E8E\u4EFB\u4F55\u6279\u6CE8)`;
|
|
434
|
+
card.addEventListener("click", () => openLightbox(refSrc(path), path));
|
|
435
|
+
dock.appendChild(card);
|
|
436
|
+
}
|
|
437
|
+
function ensureDockHead() {
|
|
438
|
+
if (dockHead) return;
|
|
439
|
+
dockHead = h("div", "cs-refs-head", "\u5168\u5C40\u53C2\u8003\u56FE \xB7 \u672A\u6302\u5230\u6279\u6CE8");
|
|
440
|
+
dockHead.title = "\u7C98\u8D34\u65F6\u6CA1\u6709\u5F00\u7740\u6279\u6CE8\u8F93\u5165\u6846\u7684\u56FE\u843D\u5728\u8FD9\u91CC\u3002\u8981\u6302\u5230\u67D0\u6761\u6279\u6CE8:\u6309 c \u5199\u6279\u6CE8\u65F6\u7C98\u8D34,\u6216\u5728 pin \u6C14\u6CE1\u91CC\u70B9\u300C\u7F16\u8F91\u300D\u540E\u7C98\u8D34";
|
|
441
|
+
dock.insertBefore(dockHead, dock.firstChild);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/canvas/select.ts
|
|
445
|
+
var fx;
|
|
446
|
+
var hoverBox;
|
|
447
|
+
var selectBox;
|
|
448
|
+
var hoverRef = null;
|
|
449
|
+
var selectRef = null;
|
|
450
|
+
var painted = /* @__PURE__ */ new Map();
|
|
451
|
+
function makeBox(kind) {
|
|
452
|
+
const box = h("div", "cs-box");
|
|
453
|
+
box.dataset.kind = kind;
|
|
454
|
+
box.appendChild(h("span", "cs-box-tag"));
|
|
455
|
+
fx.appendChild(box);
|
|
456
|
+
return box;
|
|
457
|
+
}
|
|
458
|
+
function initSelect() {
|
|
459
|
+
fx = qs("#cs-fx");
|
|
460
|
+
hoverBox = makeBox("hover");
|
|
461
|
+
selectBox = makeBox("select");
|
|
462
|
+
}
|
|
463
|
+
function elementAt(board, clientX, clientY) {
|
|
464
|
+
const doc = frameDoc(board.iframe);
|
|
465
|
+
if (!doc || !board.iframe) return null;
|
|
466
|
+
const r = board.iframe.getBoundingClientRect();
|
|
467
|
+
const s = liveScale(board, board.frameEl.getBoundingClientRect());
|
|
468
|
+
let stack;
|
|
469
|
+
try {
|
|
470
|
+
stack = doc.elementsFromPoint((clientX - r.left) / s, (clientY - r.top) / s);
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (board.entry.kind !== "component") return stack[0] ?? null;
|
|
475
|
+
for (const el of stack) {
|
|
476
|
+
const t = el.tagName;
|
|
477
|
+
if (t === "HTML" || t === "BODY" || el.hasAttribute("data-cs-artboard")) return null;
|
|
478
|
+
if (paintsSomething(el, doc)) return el;
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
var VISIBLE_TAGS = /* @__PURE__ */ new Set([
|
|
483
|
+
"IMG",
|
|
484
|
+
"VIDEO",
|
|
485
|
+
"CANVAS",
|
|
486
|
+
"INPUT",
|
|
487
|
+
"SELECT",
|
|
488
|
+
"TEXTAREA",
|
|
489
|
+
"BUTTON",
|
|
490
|
+
"HR",
|
|
491
|
+
"IFRAME",
|
|
492
|
+
"PICTURE",
|
|
493
|
+
"AUDIO",
|
|
494
|
+
"EMBED",
|
|
495
|
+
"OBJECT"
|
|
496
|
+
]);
|
|
497
|
+
function bgAlpha(bg) {
|
|
498
|
+
if (bg === "transparent") return 0;
|
|
499
|
+
const m = bg.match(/^rgba\([^)]*,\s*([\d.]+)\s*\)$/);
|
|
500
|
+
return m ? parseFloat(m[1]) : 1;
|
|
501
|
+
}
|
|
502
|
+
function paintsSomething(el, doc) {
|
|
503
|
+
if (VISIBLE_TAGS.has(el.tagName)) return true;
|
|
504
|
+
if (el.closest("svg")) return true;
|
|
505
|
+
for (const n of el.childNodes) {
|
|
506
|
+
if (n.nodeType === 3 && n.textContent && n.textContent.trim()) return true;
|
|
507
|
+
}
|
|
508
|
+
const win = doc.defaultView;
|
|
509
|
+
if (!win) return false;
|
|
510
|
+
const cs = win.getComputedStyle(el);
|
|
511
|
+
if (cs.backgroundImage !== "none") return true;
|
|
512
|
+
if (bgAlpha(cs.backgroundColor) > 0) return true;
|
|
513
|
+
if (cs.boxShadow !== "none") return true;
|
|
514
|
+
if ((parseFloat(cs.borderTopWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0) + (parseFloat(cs.borderBottomWidth) || 0) + (parseFloat(cs.borderLeftWidth) || 0) > 0) return true;
|
|
515
|
+
if (cs.outlineStyle !== "none" && (parseFloat(cs.outlineWidth) || 0) > 0) return true;
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
function screenRect(board, el) {
|
|
519
|
+
if (!board.iframe || !board.el.isConnected) return null;
|
|
520
|
+
const fr = board.iframe.getBoundingClientRect();
|
|
521
|
+
const s = liveScale(board, board.frameEl.getBoundingClientRect());
|
|
522
|
+
const b = el.getBoundingClientRect();
|
|
523
|
+
return new DOMRect(fr.left + b.left * s, fr.top + b.top * s, b.width * s, b.height * s);
|
|
524
|
+
}
|
|
525
|
+
function liveScale(board, frame) {
|
|
526
|
+
if (board.width > 0 && frame.width > 0) return frame.width / board.width;
|
|
527
|
+
return state.scale || 1;
|
|
528
|
+
}
|
|
529
|
+
function relPos(el, clientX, clientY, board) {
|
|
530
|
+
const r = screenRect(board, el);
|
|
531
|
+
if (!r || r.width === 0 || r.height === 0) return { x: 0, y: 0 };
|
|
532
|
+
const round = (v) => Math.round(clamp(v, 0, 1) * 1e3) / 1e3;
|
|
533
|
+
return { x: round((clientX - r.left) / r.width), y: round((clientY - r.top) / r.height) };
|
|
534
|
+
}
|
|
535
|
+
function isUnique(doc, sel) {
|
|
536
|
+
try {
|
|
537
|
+
return doc.querySelectorAll(sel).length === 1;
|
|
538
|
+
} catch {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function idSelector(el) {
|
|
543
|
+
const id = el.getAttribute("id");
|
|
544
|
+
if (!id) return null;
|
|
545
|
+
return `#${CSS.escape(id)}`;
|
|
546
|
+
}
|
|
547
|
+
function dataSelector(el) {
|
|
548
|
+
for (const attr of Array.from(el.attributes)) {
|
|
549
|
+
if (!attr.name.startsWith("data-") || !attr.value) continue;
|
|
550
|
+
const v = attr.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
551
|
+
return `[${attr.name}="${v}"]`;
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
function nthPart(el) {
|
|
556
|
+
const tag = el.localName;
|
|
557
|
+
const parent = el.parentElement;
|
|
558
|
+
if (!parent) return tag;
|
|
559
|
+
const sames = Array.from(parent.children).filter((c) => c.localName === tag);
|
|
560
|
+
if (sames.length < 2) return tag;
|
|
561
|
+
return `${tag}:nth-of-type(${sames.indexOf(el) + 1})`;
|
|
562
|
+
}
|
|
563
|
+
function buildSelector(el) {
|
|
564
|
+
const doc = el.ownerDocument;
|
|
565
|
+
const parts = [];
|
|
566
|
+
let cur = el;
|
|
567
|
+
while (cur && cur !== doc.documentElement && parts.length < 5) {
|
|
568
|
+
const byId = idSelector(cur);
|
|
569
|
+
if (byId && isUnique(doc, byId)) {
|
|
570
|
+
parts.unshift(byId);
|
|
571
|
+
return parts.join(" > ");
|
|
572
|
+
}
|
|
573
|
+
const byData = dataSelector(cur);
|
|
574
|
+
if (byData && isUnique(doc, byData)) {
|
|
575
|
+
parts.unshift(byData);
|
|
576
|
+
return parts.join(" > ");
|
|
577
|
+
}
|
|
578
|
+
parts.unshift(nthPart(cur));
|
|
579
|
+
const joined = parts.join(" > ");
|
|
580
|
+
if (isUnique(doc, joined)) return joined;
|
|
581
|
+
cur = cur.parentElement;
|
|
582
|
+
}
|
|
583
|
+
return parts.join(" > ") || el.localName;
|
|
584
|
+
}
|
|
585
|
+
function describe(el) {
|
|
586
|
+
const probe = el.closest("[data-probe]")?.getAttribute("data-probe");
|
|
587
|
+
const cls = typeof el.className === "string" ? el.className : "";
|
|
588
|
+
return `<${el.localName}>` + (probe ? ` probe=${probe}` : "") + (cls ? ` .${cls.trim().split(/\s+/).slice(0, 3).join(".")}` : "");
|
|
589
|
+
}
|
|
590
|
+
var TAG_GAP = 1;
|
|
591
|
+
function placeTag(tag, box, frame, st, label) {
|
|
592
|
+
const maxW = Math.max(24, Math.floor(frame.width) - 2);
|
|
593
|
+
const key = `${label}|${maxW}`;
|
|
594
|
+
if (st.tagKey !== key) {
|
|
595
|
+
tag.style.maxWidth = `${maxW}px`;
|
|
596
|
+
const t = tag.getBoundingClientRect();
|
|
597
|
+
st.tagKey = t.height > 0 ? key : "";
|
|
598
|
+
st.tagW = t.width;
|
|
599
|
+
st.tagH = t.height;
|
|
600
|
+
}
|
|
601
|
+
let x = box.left;
|
|
602
|
+
let y = box.top - st.tagH - TAG_GAP;
|
|
603
|
+
if (y < frame.top) y = box.top + TAG_GAP;
|
|
604
|
+
y = clamp(y, frame.top, Math.max(frame.top, frame.bottom - st.tagH));
|
|
605
|
+
x = clamp(x, frame.left, Math.max(frame.left, frame.right - st.tagW));
|
|
606
|
+
tag.style.left = `${x - box.left - 1}px`;
|
|
607
|
+
tag.style.top = `${y - box.top - 1}px`;
|
|
608
|
+
}
|
|
609
|
+
function paint(box, board, el, label) {
|
|
610
|
+
const r = screenRect(board, el);
|
|
611
|
+
if (!r || r.width + r.height === 0) {
|
|
612
|
+
const off = box.classList.contains("is-on");
|
|
613
|
+
box.classList.remove("is-on");
|
|
614
|
+
return off;
|
|
615
|
+
}
|
|
616
|
+
const frame = board.frameEl.getBoundingClientRect();
|
|
617
|
+
const sig = `${r.left},${r.top},${r.width},${r.height}|${frame.left},${frame.top},${frame.width},${frame.height}|${label}`;
|
|
618
|
+
let st = painted.get(box);
|
|
619
|
+
if (st && st.sig === sig && box.classList.contains("is-on")) return false;
|
|
620
|
+
if (!st) {
|
|
621
|
+
st = { sig, tagKey: "", tagW: 0, tagH: 0 };
|
|
622
|
+
painted.set(box, st);
|
|
623
|
+
}
|
|
624
|
+
st.sig = sig;
|
|
625
|
+
box.style.left = `${r.left}px`;
|
|
626
|
+
box.style.top = `${r.top}px`;
|
|
627
|
+
box.style.width = `${r.width}px`;
|
|
628
|
+
box.style.height = `${r.height}px`;
|
|
629
|
+
const tag = box.firstElementChild;
|
|
630
|
+
if (tag.textContent !== label) tag.textContent = label;
|
|
631
|
+
box.classList.add("is-on");
|
|
632
|
+
placeTag(tag, r, frame, st, label);
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
function resolveSelected() {
|
|
636
|
+
if (!selectRef) return null;
|
|
637
|
+
if (selectRef.el?.isConnected) return selectRef.el;
|
|
638
|
+
const doc = frameDoc(selectRef.board.iframe);
|
|
639
|
+
try {
|
|
640
|
+
selectRef.el = doc ? doc.querySelector(selectRef.selector) : null;
|
|
641
|
+
} catch {
|
|
642
|
+
selectRef.el = null;
|
|
643
|
+
}
|
|
644
|
+
return selectRef.el;
|
|
645
|
+
}
|
|
646
|
+
function paintAll() {
|
|
647
|
+
let changed = false;
|
|
648
|
+
if (hoverRef) {
|
|
649
|
+
if (hoverRef.el.isConnected) {
|
|
650
|
+
changed = paint(hoverBox, hoverRef.board, hoverRef.el, hoverRef.el.localName) || changed;
|
|
651
|
+
} else {
|
|
652
|
+
hideHover();
|
|
653
|
+
changed = true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (selectRef) {
|
|
657
|
+
const el = resolveSelected();
|
|
658
|
+
if (el) changed = paint(selectBox, selectRef.board, el, selectRef.selector) || changed;
|
|
659
|
+
else if (selectBox.classList.contains("is-on")) {
|
|
660
|
+
selectBox.classList.remove("is-on");
|
|
661
|
+
changed = true;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return changed;
|
|
665
|
+
}
|
|
666
|
+
var STILL_FRAMES = 24;
|
|
667
|
+
var followRaf = 0;
|
|
668
|
+
var stillFrames = 0;
|
|
669
|
+
function follow() {
|
|
670
|
+
followRaf = 0;
|
|
671
|
+
if (!hoverRef && !selectRef) return;
|
|
672
|
+
stillFrames = paintAll() ? 0 : stillFrames + 1;
|
|
673
|
+
if (stillFrames < STILL_FRAMES) followRaf = requestAnimationFrame(follow);
|
|
674
|
+
}
|
|
675
|
+
function wake() {
|
|
676
|
+
stillFrames = 0;
|
|
677
|
+
if (!followRaf && (hoverRef || selectRef)) followRaf = requestAnimationFrame(follow);
|
|
678
|
+
}
|
|
679
|
+
function showHover(board, el) {
|
|
680
|
+
hoverRef = { board, el };
|
|
681
|
+
paint(hoverBox, board, el, el.localName);
|
|
682
|
+
wake();
|
|
683
|
+
}
|
|
684
|
+
function hideHover() {
|
|
685
|
+
hoverRef = null;
|
|
686
|
+
hoverBox.classList.remove("is-on");
|
|
687
|
+
}
|
|
688
|
+
function showSelection(board, selector) {
|
|
689
|
+
selectRef = { board, selector, el: null };
|
|
690
|
+
refreshBoxes();
|
|
691
|
+
}
|
|
692
|
+
function clearSelection() {
|
|
693
|
+
selectRef = null;
|
|
694
|
+
selectBox.classList.remove("is-on");
|
|
695
|
+
}
|
|
696
|
+
function refreshBoxes() {
|
|
697
|
+
if (hoverRef && !hoverRef.el.isConnected) hideHover();
|
|
698
|
+
paintAll();
|
|
699
|
+
wake();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/canvas/pins.ts
|
|
703
|
+
var fx2;
|
|
704
|
+
var composer = null;
|
|
705
|
+
function initPins() {
|
|
706
|
+
fx2 = qs("#cs-fx");
|
|
707
|
+
}
|
|
708
|
+
function renderPins() {
|
|
709
|
+
for (const board of state.boards.values()) renderBoardPins(board);
|
|
710
|
+
}
|
|
711
|
+
function renderBoardPins(board) {
|
|
712
|
+
const layer = board.pinLayerEl;
|
|
713
|
+
layer.textContent = "";
|
|
714
|
+
const doc = frameDoc(board.iframe);
|
|
715
|
+
const list = state.annotations.filter(
|
|
716
|
+
(a) => a.artboardId === board.entry.id && a.status !== "verified"
|
|
717
|
+
);
|
|
718
|
+
const used = /* @__PURE__ */ new Map();
|
|
719
|
+
list.forEach((ann, i) => {
|
|
720
|
+
let left = 8;
|
|
721
|
+
let top = 8;
|
|
722
|
+
let orphan = true;
|
|
723
|
+
if (doc && ann.anchor) {
|
|
724
|
+
let el = null;
|
|
725
|
+
try {
|
|
726
|
+
el = doc.querySelector(ann.anchor.selector);
|
|
727
|
+
} catch {
|
|
728
|
+
el = null;
|
|
729
|
+
}
|
|
730
|
+
if (el) {
|
|
731
|
+
const r = el.getBoundingClientRect();
|
|
732
|
+
left = r.left + ann.anchor.x * r.width;
|
|
733
|
+
top = r.top + ann.anchor.y * r.height;
|
|
734
|
+
orphan = false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const key = `${Math.round(left / 12)}:${Math.round(top / 12)}`;
|
|
738
|
+
const dup = used.get(key) ?? 0;
|
|
739
|
+
used.set(key, dup + 1);
|
|
740
|
+
left += dup * 20;
|
|
741
|
+
const pin = h("div", "cs-pin", String(ann.seq ?? i + 1));
|
|
742
|
+
pin.dataset.annId = ann.id;
|
|
743
|
+
if (ann.status === "resolved") pin.classList.add("is-resolved");
|
|
744
|
+
if (ann.id === selectedPinId) pin.classList.add("is-selected");
|
|
745
|
+
if (orphan) {
|
|
746
|
+
pin.classList.add("is-orphan");
|
|
747
|
+
pin.title = "\u951A\u70B9\u5143\u7D20\u6CA1\u627E\u5230,pin \u843D\u5728\u753B\u677F\u5DE6\u4E0A\u89D2";
|
|
748
|
+
}
|
|
749
|
+
pin.style.left = `${left}px`;
|
|
750
|
+
pin.style.top = `${top}px`;
|
|
751
|
+
pin.addEventListener("click", (e) => {
|
|
752
|
+
e.stopPropagation();
|
|
753
|
+
selectPin(ann.id);
|
|
754
|
+
});
|
|
755
|
+
pin.appendChild(bubble(ann));
|
|
756
|
+
attachHoverGrace(pin);
|
|
757
|
+
layer.appendChild(pin);
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
var selectedPinId = null;
|
|
761
|
+
function selectedPin() {
|
|
762
|
+
return selectedPinId;
|
|
763
|
+
}
|
|
764
|
+
function selectPin(id) {
|
|
765
|
+
selectedPinId = id;
|
|
766
|
+
for (const p of document.querySelectorAll(".cs-pin")) {
|
|
767
|
+
p.classList.toggle("is-selected", !!id && p.dataset.annId === id);
|
|
768
|
+
if (id && p.dataset.annId === id) p.classList.add("is-open");
|
|
769
|
+
else p.classList.remove("is-open");
|
|
770
|
+
}
|
|
771
|
+
setProbe(id ? "\u6279\u6CE8\u5DF2\u9009\u4E2D:Enter \u7F16\u8F91 \xB7 Backspace \u5220\u9664 \xB7 Esc \u53D6\u6D88" : "\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
|
|
772
|
+
}
|
|
773
|
+
function editSelectedPin() {
|
|
774
|
+
if (!selectedPinId) return;
|
|
775
|
+
const pin = document.querySelector(`.cs-pin[data-ann-id="${CSS.escape(selectedPinId)}"]`);
|
|
776
|
+
if (!pin) return;
|
|
777
|
+
const box = pin.querySelector(".cs-pin-bubble");
|
|
778
|
+
if (!box) return;
|
|
779
|
+
const saveBtn = [...box.querySelectorAll("button")].find((b) => b.textContent === "\u4FDD\u5B58");
|
|
780
|
+
if (saveBtn) {
|
|
781
|
+
saveBtn.click();
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const ann = state.annotations.find((a) => a.id === selectedPinId);
|
|
785
|
+
const textEl = box.querySelector(".cs-pin-text");
|
|
786
|
+
if (ann && textEl) startEdit(box, textEl, ann);
|
|
787
|
+
}
|
|
788
|
+
function deleteSelectedPin() {
|
|
789
|
+
if (!selectedPinId) return;
|
|
790
|
+
const id = selectedPinId;
|
|
791
|
+
selectedPinId = null;
|
|
792
|
+
void remove(id);
|
|
793
|
+
}
|
|
794
|
+
function attachHoverGrace(pin) {
|
|
795
|
+
let timer = null;
|
|
796
|
+
pin.addEventListener("mouseenter", () => {
|
|
797
|
+
if (timer !== null) clearTimeout(timer);
|
|
798
|
+
timer = null;
|
|
799
|
+
pin.classList.add("is-open");
|
|
800
|
+
});
|
|
801
|
+
pin.addEventListener("mouseleave", () => {
|
|
802
|
+
timer = setTimeout(() => {
|
|
803
|
+
if (!pin.querySelector(".cs-pin-bubble.is-editing")) pin.classList.remove("is-open");
|
|
804
|
+
}, 250);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function refStrip(paths) {
|
|
808
|
+
const strip = h("div", "cs-ref-strip");
|
|
809
|
+
for (const p of paths) strip.appendChild(refThumb(p));
|
|
810
|
+
return strip;
|
|
811
|
+
}
|
|
812
|
+
function bubble(ann) {
|
|
813
|
+
const box = h("div", "cs-pin-bubble");
|
|
814
|
+
const textEl = h("div", "cs-pin-text", ann.text);
|
|
815
|
+
box.appendChild(textEl);
|
|
816
|
+
if (ann.refs?.length) box.appendChild(refStrip(ann.refs));
|
|
817
|
+
const meta = h("div", "cs-pin-meta");
|
|
818
|
+
meta.appendChild(h("span", void 0, ann.status === "open" ? "open" : "\u5F85\u6838\u9A8C"));
|
|
819
|
+
const act = (label, cls, fn) => {
|
|
820
|
+
const btn = h("button", cls, label);
|
|
821
|
+
btn.addEventListener("click", (e) => {
|
|
822
|
+
e.stopPropagation();
|
|
823
|
+
fn();
|
|
824
|
+
});
|
|
825
|
+
meta.appendChild(btn);
|
|
826
|
+
};
|
|
827
|
+
if (ann.status === "open") {
|
|
828
|
+
act(
|
|
829
|
+
"\u6807\u8BB0\u5B8C\u6210",
|
|
830
|
+
"cs-x",
|
|
831
|
+
() => void transition(ann.id, { status: "resolved", resolvedAt: (/* @__PURE__ */ new Date()).toISOString() }, "\u5DF2\u6807\u8BB0\u5B8C\u6210,\u5F85\u4EBA\u5DE5\u6838\u9A8C")
|
|
832
|
+
);
|
|
833
|
+
} else {
|
|
834
|
+
act(
|
|
835
|
+
"\u6838\u9A8C\u901A\u8FC7",
|
|
836
|
+
"cs-x",
|
|
837
|
+
() => void transition(ann.id, { status: "verified", verifiedAt: (/* @__PURE__ */ new Date()).toISOString() }, "\u6838\u9A8C\u901A\u8FC7,\u5DF2\u5F52\u6863\u8FDB\u5386\u53F2")
|
|
838
|
+
);
|
|
839
|
+
act("\u6253\u56DE", "cs-x", () => void transition(ann.id, { status: "open" }, "\u5DF2\u6253\u56DE,\u91CD\u65B0\u5F85\u5904\u7406"));
|
|
840
|
+
}
|
|
841
|
+
act("\u7F16\u8F91", "cs-x", () => startEdit(box, textEl, ann));
|
|
842
|
+
act("\u5220\u9664", "cs-x cs-x-danger", () => void remove(ann.id));
|
|
843
|
+
box.appendChild(meta);
|
|
844
|
+
return box;
|
|
845
|
+
}
|
|
846
|
+
async function transition(id, patch, msg) {
|
|
847
|
+
try {
|
|
848
|
+
const updated = await patchAnnotation(id, patch);
|
|
849
|
+
upsert(updated);
|
|
850
|
+
renderPins();
|
|
851
|
+
toast(msg, "ok");
|
|
852
|
+
} catch (err) {
|
|
853
|
+
toast(`\u6279\u6CE8\u72B6\u6001\u66F4\u65B0\u5931\u8D25:${err}`, "error");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function startEdit(box, textEl, ann) {
|
|
857
|
+
if (box.querySelector("textarea")) return;
|
|
858
|
+
box.classList.add("is-editing");
|
|
859
|
+
const ta = h("textarea", "cs-pin-edit");
|
|
860
|
+
ta.value = ann.text;
|
|
861
|
+
const refs = [...ann.refs ?? []];
|
|
862
|
+
const strip = box.querySelector(".cs-ref-strip") ?? refStrip([]);
|
|
863
|
+
if (!strip.isConnected) box.insertBefore(strip, box.querySelector(".cs-pin-meta"));
|
|
864
|
+
setPasteTarget({
|
|
865
|
+
el: box,
|
|
866
|
+
onFile: (file) => {
|
|
867
|
+
void uploadRef(file).then((path) => {
|
|
868
|
+
refs.push(path);
|
|
869
|
+
strip.appendChild(refThumb(path));
|
|
870
|
+
toast(`\u53C2\u8003\u56FE\u5DF2\u4E0A\u4F20:${path} \u2014\u2014 \u70B9\u300C\u4FDD\u5B58\u300D\u540E\u624D\u6302\u5230\u8FD9\u6761\u6279\u6CE8`, "ok");
|
|
871
|
+
}).catch((err) => toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error"));
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
ta.addEventListener("keydown", (e) => {
|
|
875
|
+
e.stopPropagation();
|
|
876
|
+
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
877
|
+
e.preventDefault();
|
|
878
|
+
save.click();
|
|
879
|
+
} else if (e.key === "Escape") {
|
|
880
|
+
e.preventDefault();
|
|
881
|
+
renderPins();
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
ta.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
885
|
+
const row2 = h("div", "cs-pin-meta");
|
|
886
|
+
const save = h("button", "cs-x", "\u4FDD\u5B58");
|
|
887
|
+
save.addEventListener("click", (e) => {
|
|
888
|
+
e.stopPropagation();
|
|
889
|
+
const text = ta.value.trim();
|
|
890
|
+
const textChanged = !!text && text !== ann.text;
|
|
891
|
+
const refsChanged = refs.length !== (ann.refs?.length ?? 0);
|
|
892
|
+
if (!textChanged && !refsChanged) return renderPins();
|
|
893
|
+
const patch = {};
|
|
894
|
+
if (textChanged) patch.text = text;
|
|
895
|
+
if (refsChanged) patch.refs = refs;
|
|
896
|
+
void patchAnnotation(ann.id, patch).then((updated) => {
|
|
897
|
+
upsert(updated);
|
|
898
|
+
renderPins();
|
|
899
|
+
toast("\u6279\u6CE8\u5DF2\u66F4\u65B0", "ok");
|
|
900
|
+
}).catch((err) => toast(`\u6279\u6CE8\u66F4\u65B0\u5931\u8D25:${err}`, "error"));
|
|
901
|
+
});
|
|
902
|
+
const cancel = h("button", "cs-x", "\u53D6\u6D88");
|
|
903
|
+
cancel.addEventListener("click", (e) => {
|
|
904
|
+
e.stopPropagation();
|
|
905
|
+
renderPins();
|
|
906
|
+
});
|
|
907
|
+
row2.appendChild(save);
|
|
908
|
+
row2.appendChild(cancel);
|
|
909
|
+
textEl.replaceWith(ta);
|
|
910
|
+
box.appendChild(row2);
|
|
911
|
+
ta.focus();
|
|
912
|
+
ta.setSelectionRange(ta.value.length, ta.value.length);
|
|
913
|
+
}
|
|
914
|
+
async function remove(id) {
|
|
915
|
+
try {
|
|
916
|
+
await deleteAnnotation(id);
|
|
917
|
+
state.annotations = state.annotations.filter((a) => a.id !== id);
|
|
918
|
+
renderPins();
|
|
919
|
+
toast("\u6279\u6CE8\u5DF2\u5220\u9664", "ok");
|
|
920
|
+
} catch (err) {
|
|
921
|
+
toast(`\u6279\u6CE8\u5220\u9664\u5931\u8D25:${err}`, "error");
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function legacyPushMemKey() {
|
|
925
|
+
return `cs-pushpid:${state.info?.projectRoot ?? "unknown"}`;
|
|
926
|
+
}
|
|
927
|
+
async function pushToClaudeCode(pid) {
|
|
928
|
+
setProbe("\u63A8\u9001\u4E2D\u2026");
|
|
929
|
+
try {
|
|
930
|
+
try {
|
|
931
|
+
localStorage.removeItem(legacyPushMemKey());
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
let r = await pushContext(pid);
|
|
935
|
+
if (!r.ok && "reason" in r && r.reason?.includes("\u5DF2\u4E0D\u5728") && pid !== void 0) {
|
|
936
|
+
r = await pushContext();
|
|
937
|
+
}
|
|
938
|
+
if (r.ok) {
|
|
939
|
+
toast(`\u5DF2\u6295\u9012\u5230\u300C${r.name}\u300D\u7684 inbox \u2014 \u8BE5\u4F1A\u8BDD\u53EF\u80FD\u9700\u8981\u4F60\u5728\u7EC8\u7AEF\u70B9\u4E00\u4E0B\u786E\u8BA4`, "ok");
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if ("choose" in r && r.choose?.length) {
|
|
943
|
+
showSessionPicker(r.choose);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
toast(`${"reason" in r && r.reason || "\u63A8\u9001\u5931\u8D25"} \u2014\u2014 \u5DF2\u56DE\u843D\u4E3A\u590D\u5236\u5230\u526A\u8D34\u677F`, "warn");
|
|
947
|
+
await copyContext();
|
|
948
|
+
} catch (err) {
|
|
949
|
+
toast(`\u63A8\u9001\u5931\u8D25:${err} \u2014\u2014 \u5DF2\u56DE\u843D\u4E3A\u590D\u5236\u5230\u526A\u8D34\u677F`, "warn");
|
|
950
|
+
await copyContext();
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function showSessionPicker(items) {
|
|
954
|
+
document.getElementById("cs-picker")?.remove();
|
|
955
|
+
const wrap = h("div", "cs-picker");
|
|
956
|
+
wrap.id = "cs-picker";
|
|
957
|
+
const card = h("div", "cs-card");
|
|
958
|
+
card.appendChild(h("h2", void 0, "\u63A8\u9001\u7ED9\u54EA\u4E2A\u4F1A\u8BDD?"));
|
|
959
|
+
const cleanup = () => {
|
|
960
|
+
document.removeEventListener("keydown", onKey, true);
|
|
961
|
+
wrap.remove();
|
|
962
|
+
};
|
|
963
|
+
items.slice(0, 9).forEach((it, i) => {
|
|
964
|
+
const row2 = h(
|
|
965
|
+
"button",
|
|
966
|
+
"cs-picker-row",
|
|
967
|
+
`${i + 1} ${it.name}`
|
|
968
|
+
);
|
|
969
|
+
const badge = h("span", `cs-picker-status is-${it.status}`, it.status);
|
|
970
|
+
row2.appendChild(badge);
|
|
971
|
+
row2.addEventListener("click", () => {
|
|
972
|
+
cleanup();
|
|
973
|
+
void pushToClaudeCode(it.pid);
|
|
974
|
+
});
|
|
975
|
+
card.appendChild(row2);
|
|
976
|
+
});
|
|
977
|
+
card.appendChild(h("p", "cs-dim", "\u6570\u5B57\u952E\u9009\u62E9 \xB7 Esc \u53D6\u6D88"));
|
|
978
|
+
wrap.appendChild(card);
|
|
979
|
+
const onKey = (e) => {
|
|
980
|
+
const n = Number(e.key);
|
|
981
|
+
if (Number.isInteger(n) && n >= 1 && n <= Math.min(items.length, 9)) {
|
|
982
|
+
e.stopPropagation();
|
|
983
|
+
cleanup();
|
|
984
|
+
void pushToClaudeCode(items[n - 1].pid);
|
|
985
|
+
} else if (e.key === "Escape") {
|
|
986
|
+
e.stopPropagation();
|
|
987
|
+
cleanup();
|
|
988
|
+
toast("\u5DF2\u53D6\u6D88\u63A8\u9001");
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
document.addEventListener("keydown", onKey, true);
|
|
992
|
+
wrap.addEventListener("click", (e) => {
|
|
993
|
+
if (e.target === wrap) cleanup();
|
|
994
|
+
});
|
|
995
|
+
document.body.appendChild(wrap);
|
|
996
|
+
}
|
|
997
|
+
async function copyContext() {
|
|
998
|
+
try {
|
|
999
|
+
const text = await fetchContext();
|
|
1000
|
+
if (!text.trim()) {
|
|
1001
|
+
toast("\u6CA1\u6709\u5F85\u5904\u7406\u7684\u6279\u6CE8\u6216\u9009\u4E2D,\u5148\u6309 c \u9489\u4E00\u6761", "warn");
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
await navigator.clipboard.writeText(text);
|
|
1005
|
+
const n = state.annotations.filter((a) => a.status === "open").length;
|
|
1006
|
+
toast(`\u5DF2\u590D\u5236 ${n} \u6761\u6279\u6CE8(\u542B\u9009\u4E2D),\u7C98\u5230\u4EFB\u4F55 Claude Code \u7A97\u53E3\u5373\u53EF`, "ok");
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
toast(`\u590D\u5236\u5931\u8D25:${err}`, "error");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function upsert(ann) {
|
|
1012
|
+
const i = state.annotations.findIndex((a) => a.id === ann.id);
|
|
1013
|
+
if (i >= 0) state.annotations[i] = ann;
|
|
1014
|
+
else state.annotations.push(ann);
|
|
1015
|
+
}
|
|
1016
|
+
function openComposer(board, el, clientX, clientY) {
|
|
1017
|
+
closeComposer();
|
|
1018
|
+
const selector = buildSelector(el);
|
|
1019
|
+
const rel = relPos(el, clientX, clientY, board);
|
|
1020
|
+
const box = h("div", "cs-pin-input");
|
|
1021
|
+
box.style.left = `${Math.min(clientX + 10, window.innerWidth - 250)}px`;
|
|
1022
|
+
box.style.top = `${Math.min(clientY + 10, window.innerHeight - 170)}px`;
|
|
1023
|
+
const ta = h("textarea");
|
|
1024
|
+
ta.placeholder = `\u6279\u6CE8 ${selector}`;
|
|
1025
|
+
const strip = refStrip([]);
|
|
1026
|
+
const tip = h("div", "cs-pin-tip", "Enter \u63D0\u4EA4 \xB7 Shift+Enter \u6362\u884C \xB7 Cmd/Ctrl+V \u8D34\u53C2\u8003\u56FE \xB7 Esc \u53D6\u6D88");
|
|
1027
|
+
box.append(ta, strip, tip);
|
|
1028
|
+
fx2.appendChild(box);
|
|
1029
|
+
composer = box;
|
|
1030
|
+
ta.focus();
|
|
1031
|
+
const refs = [];
|
|
1032
|
+
setPasteTarget({
|
|
1033
|
+
el: box,
|
|
1034
|
+
onFile: (file) => {
|
|
1035
|
+
void uploadRef(file).then((path) => {
|
|
1036
|
+
refs.push(path);
|
|
1037
|
+
strip.appendChild(refThumb(path));
|
|
1038
|
+
setProbe(`\u53C2\u8003\u56FE\u5DF2\u6302\u5230\u8FD9\u6761\u6279\u6CE8:${path}`);
|
|
1039
|
+
}).catch((err) => toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error"));
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
ta.addEventListener("keydown", (e) => {
|
|
1043
|
+
e.stopPropagation();
|
|
1044
|
+
if (e.key === "Escape") {
|
|
1045
|
+
closeComposer();
|
|
1046
|
+
exitPinMode();
|
|
1047
|
+
} else if (e.key === "Enter" && !e.shiftKey) {
|
|
1048
|
+
e.preventDefault();
|
|
1049
|
+
const text = ta.value.trim();
|
|
1050
|
+
if (!text) {
|
|
1051
|
+
closeComposer();
|
|
1052
|
+
exitPinMode();
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
void submit(board, selector, rel, text, refs);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
async function submit(board, selector, rel, text, refs) {
|
|
1060
|
+
closeComposer();
|
|
1061
|
+
try {
|
|
1062
|
+
const ann = await createAnnotation({
|
|
1063
|
+
artboardId: board.entry.id,
|
|
1064
|
+
anchor: { selector, x: rel.x, y: rel.y },
|
|
1065
|
+
text,
|
|
1066
|
+
refs,
|
|
1067
|
+
status: "open"
|
|
1068
|
+
});
|
|
1069
|
+
upsert(ann);
|
|
1070
|
+
renderBoardPins(board);
|
|
1071
|
+
state.pinnedThisRun++;
|
|
1072
|
+
syncHud();
|
|
1073
|
+
const withRefs = refs.length ? ` \xB7 \u5E26 ${refs.length} \u5F20\u53C2\u8003\u56FE` : "";
|
|
1074
|
+
toast(`\u5DF2\u8BB0\u6279\u6CE8:${text.slice(0, 30)}${text.length > 30 ? "\u2026" : ""}${withRefs}`, "ok");
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
toast(`\u6279\u6CE8\u5931\u8D25:${String(err)}`, "error");
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
function closeComposer() {
|
|
1080
|
+
composer?.remove();
|
|
1081
|
+
composer = null;
|
|
1082
|
+
setPasteTarget(null);
|
|
1083
|
+
}
|
|
1084
|
+
function isComposing() {
|
|
1085
|
+
return composer !== null;
|
|
1086
|
+
}
|
|
1087
|
+
function enterPinMode() {
|
|
1088
|
+
state.pinPending = true;
|
|
1089
|
+
state.pinnedThisRun = 0;
|
|
1090
|
+
syncHud();
|
|
1091
|
+
setProbe("\u6279\u6CE8\u6A21\u5F0F:\u70B9\u5143\u7D20\u843D pin,\u53EF\u8FDE\u9489\u591A\u6761,Esc \u9000\u51FA");
|
|
1092
|
+
}
|
|
1093
|
+
function exitPinMode() {
|
|
1094
|
+
if (!state.pinPending) return;
|
|
1095
|
+
state.pinPending = false;
|
|
1096
|
+
if (state.pinnedThisRun > 0)
|
|
1097
|
+
toast(`\u672C\u8F6E\u9489\u4E86 ${state.pinnedThisRun} \u6761\u6279\u6CE8 \xB7 \u6309 p \u63A8\u7ED9 Claude`, "info");
|
|
1098
|
+
state.pinnedThisRun = 0;
|
|
1099
|
+
syncHud();
|
|
1100
|
+
setProbe("\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/canvas/view.ts
|
|
1104
|
+
var MIN_SCALE = 0.05;
|
|
1105
|
+
var MAX_SCALE = 2;
|
|
1106
|
+
var viewport;
|
|
1107
|
+
var world;
|
|
1108
|
+
var zoomLabel;
|
|
1109
|
+
var listeners = [];
|
|
1110
|
+
function onViewChange(fn) {
|
|
1111
|
+
listeners.push(fn);
|
|
1112
|
+
}
|
|
1113
|
+
function viewKey() {
|
|
1114
|
+
return `cs-view:${state.info?.projectRoot ?? "unknown"}`;
|
|
1115
|
+
}
|
|
1116
|
+
var saveTimer = null;
|
|
1117
|
+
function persistView() {
|
|
1118
|
+
const v = state.mode === "review" && state.viewBeforeReview ? state.viewBeforeReview : { tx: state.tx, ty: state.ty, scale: state.scale };
|
|
1119
|
+
if (saveTimer !== null) clearTimeout(saveTimer);
|
|
1120
|
+
saveTimer = setTimeout(() => {
|
|
1121
|
+
try {
|
|
1122
|
+
localStorage.setItem(viewKey(), JSON.stringify(v));
|
|
1123
|
+
} catch {
|
|
1124
|
+
}
|
|
1125
|
+
}, 300);
|
|
1126
|
+
}
|
|
1127
|
+
function restoreView() {
|
|
1128
|
+
try {
|
|
1129
|
+
const raw = localStorage.getItem(viewKey());
|
|
1130
|
+
if (!raw) return false;
|
|
1131
|
+
const v = JSON.parse(raw);
|
|
1132
|
+
if (![v.tx, v.ty, v.scale].every(Number.isFinite)) return false;
|
|
1133
|
+
setView(v.tx, v.ty, v.scale);
|
|
1134
|
+
return true;
|
|
1135
|
+
} catch {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
var raf = 0;
|
|
1140
|
+
function applyView() {
|
|
1141
|
+
world.style.transform = `translate(${state.tx}px, ${state.ty}px) scale(${state.scale})`;
|
|
1142
|
+
world.style.setProperty("--cs-inv", String(1 / state.scale));
|
|
1143
|
+
const grid = 24 * state.scale;
|
|
1144
|
+
viewport.style.backgroundSize = `${grid}px ${grid}px`;
|
|
1145
|
+
viewport.style.backgroundPosition = `${state.tx}px ${state.ty}px`;
|
|
1146
|
+
zoomLabel.textContent = `${Math.round(state.scale * 100)}%`;
|
|
1147
|
+
persistView();
|
|
1148
|
+
if (raf) return;
|
|
1149
|
+
raf = requestAnimationFrame(() => {
|
|
1150
|
+
raf = 0;
|
|
1151
|
+
for (const fn of listeners) fn();
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
function setView(tx, ty, scale) {
|
|
1155
|
+
state.scale = clamp(scale, MIN_SCALE, MAX_SCALE);
|
|
1156
|
+
state.tx = tx;
|
|
1157
|
+
state.ty = ty;
|
|
1158
|
+
applyView();
|
|
1159
|
+
}
|
|
1160
|
+
function worldBounds() {
|
|
1161
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1162
|
+
for (const b of state.boards.values()) {
|
|
1163
|
+
if (b.el.hidden) continue;
|
|
1164
|
+
const x = parseFloat(b.el.style.left || "0");
|
|
1165
|
+
const y = parseFloat(b.el.style.top || "0");
|
|
1166
|
+
minX = Math.min(minX, x);
|
|
1167
|
+
minY = Math.min(minY, y);
|
|
1168
|
+
maxX = Math.max(maxX, x + b.width);
|
|
1169
|
+
maxY = Math.max(maxY, y + b.height + 40);
|
|
1170
|
+
}
|
|
1171
|
+
if (!Number.isFinite(minX)) return null;
|
|
1172
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1173
|
+
}
|
|
1174
|
+
function animateTo(tx, ty, scale) {
|
|
1175
|
+
const world3 = qs("#cs-world");
|
|
1176
|
+
world3.style.transition = `transform var(--cs-dur-3) var(--cs-ease-out)`;
|
|
1177
|
+
setView(tx, ty, scale);
|
|
1178
|
+
window.setTimeout(() => {
|
|
1179
|
+
world3.style.transition = "";
|
|
1180
|
+
applyView();
|
|
1181
|
+
}, 260);
|
|
1182
|
+
}
|
|
1183
|
+
function usableViewport() {
|
|
1184
|
+
const vp = qs("#cs-viewport").getBoundingClientRect();
|
|
1185
|
+
const sb = document.getElementById("cs-sidebar");
|
|
1186
|
+
const off = sb && document.body.dataset.sidebar !== "off" ? sb.getBoundingClientRect().width : 0;
|
|
1187
|
+
return { x: vp.x + off, y: vp.y, width: vp.width - off, height: vp.height };
|
|
1188
|
+
}
|
|
1189
|
+
function fitAll(pad = 64) {
|
|
1190
|
+
const b = worldBounds();
|
|
1191
|
+
if (!b) return;
|
|
1192
|
+
const vp = usableViewport();
|
|
1193
|
+
const scale = clamp(
|
|
1194
|
+
Math.min((vp.width - pad * 2) / b.w, (vp.height - pad * 2) / b.h),
|
|
1195
|
+
MIN_SCALE,
|
|
1196
|
+
MAX_SCALE
|
|
1197
|
+
);
|
|
1198
|
+
animateTo(
|
|
1199
|
+
vp.x + vp.width / 2 - (b.x + b.w / 2) * scale,
|
|
1200
|
+
vp.y + vp.height / 2 - (b.y + b.h / 2) * scale,
|
|
1201
|
+
scale
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
function fitBoard(id, pad = 80) {
|
|
1205
|
+
const board = state.boards.get(id ?? state.activeId ?? "");
|
|
1206
|
+
if (!board) return fitAll();
|
|
1207
|
+
const x = parseFloat(board.el.style.left || "0");
|
|
1208
|
+
const y = parseFloat(board.el.style.top || "0");
|
|
1209
|
+
const vp = usableViewport();
|
|
1210
|
+
const scale = clamp(
|
|
1211
|
+
Math.min((vp.width - pad * 2) / board.width, (vp.height - pad * 2) / (board.height + 40)),
|
|
1212
|
+
MIN_SCALE,
|
|
1213
|
+
MAX_SCALE
|
|
1214
|
+
);
|
|
1215
|
+
animateTo(
|
|
1216
|
+
vp.x + vp.width / 2 - (x + board.width / 2) * scale,
|
|
1217
|
+
vp.y + vp.height / 2 - (y + (board.height + 40) / 2) * scale,
|
|
1218
|
+
scale
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
function zoomReset() {
|
|
1222
|
+
const vp = usableViewport();
|
|
1223
|
+
const cx = vp.x + vp.width / 2;
|
|
1224
|
+
const cy = vp.y + vp.height / 2;
|
|
1225
|
+
const k = 1 / state.scale;
|
|
1226
|
+
animateTo(cx - (cx - state.tx) * k, cy - (cy - state.ty) * k, 1);
|
|
1227
|
+
}
|
|
1228
|
+
function zoomStep(factor) {
|
|
1229
|
+
const vp = usableViewport();
|
|
1230
|
+
zoomAt(vp.x + vp.width / 2, vp.y + vp.height / 2, factor);
|
|
1231
|
+
}
|
|
1232
|
+
function zoomAt(clientX, clientY, factor) {
|
|
1233
|
+
const next = clamp(state.scale * factor, MIN_SCALE, MAX_SCALE);
|
|
1234
|
+
if (next === state.scale) return;
|
|
1235
|
+
const r = viewport.getBoundingClientRect();
|
|
1236
|
+
const cx = clientX - r.left;
|
|
1237
|
+
const cy = clientY - r.top;
|
|
1238
|
+
const k = next / state.scale;
|
|
1239
|
+
state.tx = cx - (cx - state.tx) * k;
|
|
1240
|
+
state.ty = cy - (cy - state.ty) * k;
|
|
1241
|
+
state.scale = next;
|
|
1242
|
+
applyView();
|
|
1243
|
+
}
|
|
1244
|
+
function isBlank(target) {
|
|
1245
|
+
const el = target;
|
|
1246
|
+
if (!el) return false;
|
|
1247
|
+
return !el.closest(".cs-board");
|
|
1248
|
+
}
|
|
1249
|
+
function initView() {
|
|
1250
|
+
viewport = qs("#cs-viewport");
|
|
1251
|
+
world = qs("#cs-world");
|
|
1252
|
+
zoomLabel = qs("#cs-zoom");
|
|
1253
|
+
viewport.addEventListener(
|
|
1254
|
+
"wheel",
|
|
1255
|
+
(e) => {
|
|
1256
|
+
e.preventDefault();
|
|
1257
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1258
|
+
zoomAt(e.clientX, e.clientY, Math.exp(-e.deltaY / 260));
|
|
1259
|
+
} else {
|
|
1260
|
+
state.tx -= e.deltaX;
|
|
1261
|
+
state.ty -= e.deltaY;
|
|
1262
|
+
applyView();
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
{ passive: false }
|
|
1266
|
+
);
|
|
1267
|
+
let panId = null;
|
|
1268
|
+
let lastX = 0;
|
|
1269
|
+
let lastY = 0;
|
|
1270
|
+
viewport.addEventListener("pointerdown", (e) => {
|
|
1271
|
+
if (!(e.button === 1 || e.button === 0 && isBlank(e.target))) return;
|
|
1272
|
+
panId = e.pointerId;
|
|
1273
|
+
lastX = e.clientX;
|
|
1274
|
+
lastY = e.clientY;
|
|
1275
|
+
viewport.classList.add("is-panning");
|
|
1276
|
+
viewport.setPointerCapture(e.pointerId);
|
|
1277
|
+
});
|
|
1278
|
+
viewport.addEventListener("pointermove", (e) => {
|
|
1279
|
+
if (panId !== e.pointerId) return;
|
|
1280
|
+
state.tx += e.clientX - lastX;
|
|
1281
|
+
state.ty += e.clientY - lastY;
|
|
1282
|
+
lastX = e.clientX;
|
|
1283
|
+
lastY = e.clientY;
|
|
1284
|
+
applyView();
|
|
1285
|
+
});
|
|
1286
|
+
const endPan = (e) => {
|
|
1287
|
+
if (panId !== e.pointerId) return;
|
|
1288
|
+
panId = null;
|
|
1289
|
+
viewport.classList.remove("is-panning");
|
|
1290
|
+
};
|
|
1291
|
+
viewport.addEventListener("pointerup", endPan);
|
|
1292
|
+
viewport.addEventListener("pointercancel", endPan);
|
|
1293
|
+
applyView();
|
|
1294
|
+
}
|
|
1295
|
+
function isPanning() {
|
|
1296
|
+
return viewport.classList.contains("is-panning");
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// src/canvas/modes.ts
|
|
1300
|
+
var viewport2;
|
|
1301
|
+
function boardOf(target) {
|
|
1302
|
+
const el = target?.closest?.(".cs-board");
|
|
1303
|
+
if (!el?.dataset.id) return null;
|
|
1304
|
+
return state.boards.get(el.dataset.id) ?? null;
|
|
1305
|
+
}
|
|
1306
|
+
function setActive(id) {
|
|
1307
|
+
if (state.activeId === id) return;
|
|
1308
|
+
state.activeId = id;
|
|
1309
|
+
for (const b of state.boards.values()) b.el.classList.toggle("is-active", b.entry.id === id);
|
|
1310
|
+
}
|
|
1311
|
+
function setMode(mode, boardId) {
|
|
1312
|
+
if (mode !== "browse" && boardId) setActive(boardId);
|
|
1313
|
+
if (mode === "review" && !state.activeId) return;
|
|
1314
|
+
const from = state.mode;
|
|
1315
|
+
state.mode = mode;
|
|
1316
|
+
hideHover();
|
|
1317
|
+
if (mode !== "browse") clearSelection();
|
|
1318
|
+
syncHud();
|
|
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);
|
|
1328
|
+
}
|
|
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
|
+
}
|
|
1333
|
+
function fitActiveBoard() {
|
|
1334
|
+
const board = state.activeId ? state.boards.get(state.activeId) : null;
|
|
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);
|
|
1343
|
+
}
|
|
1344
|
+
function escape() {
|
|
1345
|
+
if (isComposing()) {
|
|
1346
|
+
closeComposer();
|
|
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);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (state.mode === "interact") {
|
|
1363
|
+
setMode("browse");
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
clearSelection();
|
|
1367
|
+
closeArgsPanel();
|
|
1368
|
+
setProbe("\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
|
|
1369
|
+
}
|
|
1370
|
+
function isEditable(target) {
|
|
1371
|
+
const el = target;
|
|
1372
|
+
if (!el) return false;
|
|
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;
|
|
1376
|
+
}
|
|
1377
|
+
function onKeyDown(e) {
|
|
1378
|
+
if (e.key === "Escape") {
|
|
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
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
function attachFrameKeys(doc) {
|
|
1445
|
+
doc.addEventListener("keydown", onKeyDown);
|
|
1446
|
+
}
|
|
1447
|
+
function onMouseMove(e) {
|
|
1448
|
+
if (isPanning()) return;
|
|
1449
|
+
const overlay = e.target?.closest?.(".cs-ovl");
|
|
1450
|
+
const board = overlay ? boardOf(e.target) : null;
|
|
1451
|
+
if (!board) {
|
|
1452
|
+
hideHover();
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
if (state.mode !== "browse") return;
|
|
1456
|
+
setActive(board.entry.id);
|
|
1457
|
+
const el = elementAt(board, e.clientX, e.clientY);
|
|
1458
|
+
if (!el) {
|
|
1459
|
+
hideHover();
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
showHover(board, el);
|
|
1463
|
+
setProbe(`${board.entry.exportName} ${describe(el)}`);
|
|
1464
|
+
}
|
|
1465
|
+
function onClick(e) {
|
|
1466
|
+
const target = e.target;
|
|
1467
|
+
const title = target?.closest?.(".cs-board-title");
|
|
1468
|
+
if (title) {
|
|
1469
|
+
const board2 = boardOf(target);
|
|
1470
|
+
if (!board2) return;
|
|
1471
|
+
setActive(board2.entry.id);
|
|
1472
|
+
if (isArgsOpen(board2.entry.id)) closeArgsPanel();
|
|
1473
|
+
else openArgsPanel(board2);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const overlay = target?.closest?.(".cs-ovl");
|
|
1477
|
+
if (!overlay) return;
|
|
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;
|
|
1487
|
+
}
|
|
1488
|
+
if (state.mode !== "browse") return;
|
|
1489
|
+
const selector = buildSelector(el);
|
|
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)}`));
|
|
1495
|
+
}
|
|
1496
|
+
function onDblClick(e) {
|
|
1497
|
+
const board = boardOf(e.target);
|
|
1498
|
+
if (!board || !e.target.closest(".cs-ovl")) return;
|
|
1499
|
+
setMode("interact", board.entry.id);
|
|
1500
|
+
}
|
|
1501
|
+
function initModes() {
|
|
1502
|
+
viewport2 = qs("#cs-viewport");
|
|
1503
|
+
viewport2.addEventListener("mousemove", onMouseMove);
|
|
1504
|
+
viewport2.addEventListener("mouseleave", hideHover);
|
|
1505
|
+
viewport2.addEventListener("click", onClick);
|
|
1506
|
+
viewport2.addEventListener("dblclick", onDblClick);
|
|
1507
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1508
|
+
window.addEventListener("resize", () => {
|
|
1509
|
+
if (state.mode === "review") fitActiveBoard();
|
|
1510
|
+
else applyView();
|
|
1511
|
+
});
|
|
1512
|
+
syncHud();
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// src/canvas/wall.ts
|
|
1516
|
+
var PAD = 48;
|
|
1517
|
+
var BOARD_GAP = 24;
|
|
1518
|
+
var TITLE_H = 24;
|
|
1519
|
+
var SECTION_HEAD_H = 34;
|
|
1520
|
+
var SECTION_GAP = 40;
|
|
1521
|
+
var ROW_MAX_W = 5200;
|
|
1522
|
+
var MIN_H = 88;
|
|
1523
|
+
var MAX_H = 1200;
|
|
1524
|
+
var DEFAULT_W = 480;
|
|
1525
|
+
var MOUNT_MIN_SCALE = 0.35;
|
|
1526
|
+
var world2;
|
|
1527
|
+
var io;
|
|
1528
|
+
var groups = /* @__PURE__ */ new Map();
|
|
1529
|
+
var measureCount = /* @__PURE__ */ new Map();
|
|
1530
|
+
var lastBodyH = /* @__PURE__ */ new Map();
|
|
1531
|
+
var heightWatchers = /* @__PURE__ */ new Map();
|
|
1532
|
+
function initWall() {
|
|
1533
|
+
world2 = qs("#cs-world");
|
|
1534
|
+
initWallTools();
|
|
1535
|
+
io = new IntersectionObserver(
|
|
1536
|
+
(records) => {
|
|
1537
|
+
for (const r of records) {
|
|
1538
|
+
if (!r.isIntersecting) continue;
|
|
1539
|
+
if (state.scale < MOUNT_MIN_SCALE) continue;
|
|
1540
|
+
const id = r.target.dataset.id;
|
|
1541
|
+
const board = id ? state.boards.get(id) : null;
|
|
1542
|
+
if (board) requestMount(board);
|
|
1543
|
+
io.unobserve(r.target);
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
// 视口外 1.5 屏就开始挂
|
|
1547
|
+
{ root: qs("#cs-viewport"), rootMargin: "150%" }
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
function syncEntries(entries) {
|
|
1551
|
+
state.entries = entries;
|
|
1552
|
+
syncWallTools();
|
|
1553
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1554
|
+
for (const e of entries) {
|
|
1555
|
+
const list = byFile.get(e.file);
|
|
1556
|
+
if (list) list.push(e);
|
|
1557
|
+
else byFile.set(e.file, [e]);
|
|
1558
|
+
}
|
|
1559
|
+
const alive = new Set(entries.map((e) => e.id));
|
|
1560
|
+
for (const [id, board] of state.boards) {
|
|
1561
|
+
if (alive.has(id)) continue;
|
|
1562
|
+
io.unobserve(board.el);
|
|
1563
|
+
board.el.remove();
|
|
1564
|
+
state.boards.delete(id);
|
|
1565
|
+
measureCount.delete(id);
|
|
1566
|
+
lastBodyH.delete(id);
|
|
1567
|
+
bgSyncs.delete(id);
|
|
1568
|
+
heightWatchers.get(id)?.disconnect();
|
|
1569
|
+
heightWatchers.delete(id);
|
|
1570
|
+
}
|
|
1571
|
+
for (const [file, list] of byFile) {
|
|
1572
|
+
const ids = list.map((e) => e.id);
|
|
1573
|
+
const kept = (state.order.get(file) ?? []).filter((id) => ids.includes(id));
|
|
1574
|
+
state.order.set(file, kept.concat(ids.filter((id) => !kept.includes(id))));
|
|
1575
|
+
for (const entry of list) {
|
|
1576
|
+
const exist = state.boards.get(entry.id);
|
|
1577
|
+
if (exist) updateBoard(exist, entry);
|
|
1578
|
+
else state.boards.set(entry.id, createBoard(entry));
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
for (const file of [...state.order.keys()]) if (!byFile.has(file)) state.order.delete(file);
|
|
1582
|
+
if (firstSync && !hasAnyPos()) {
|
|
1583
|
+
autoArrange();
|
|
1584
|
+
settleDeadline = Date.now() + SETTLE_WINDOW;
|
|
1585
|
+
} else {
|
|
1586
|
+
const n = placeNewBoards();
|
|
1587
|
+
applyPositions();
|
|
1588
|
+
if (n && !firstSync) toast(`\u65B0\u753B\u677F ${n} \u5757 \xB7 \u5DF2\u653E\u5230\u5899\u5E95\u90E8`, "info");
|
|
1589
|
+
}
|
|
1590
|
+
if (state.boards.size) firstSync = false;
|
|
1591
|
+
renderSidebar();
|
|
1592
|
+
}
|
|
1593
|
+
function groupLabel(file) {
|
|
1594
|
+
let s = file;
|
|
1595
|
+
const dir = state.info?.designDir;
|
|
1596
|
+
if (dir && s.startsWith(`${dir}/`)) s = s.slice(dir.length + 1);
|
|
1597
|
+
return s.replace(/\.artboard\.tsx?$/, "");
|
|
1598
|
+
}
|
|
1599
|
+
function ensureSection(key) {
|
|
1600
|
+
const found = groups.get(key);
|
|
1601
|
+
if (found) return found.titleEl;
|
|
1602
|
+
const titleEl = h("div", "cs-section-title");
|
|
1603
|
+
titleEl.dataset.section = key;
|
|
1604
|
+
world2.appendChild(titleEl);
|
|
1605
|
+
groups.set(key, { el: titleEl, titleEl });
|
|
1606
|
+
return titleEl;
|
|
1607
|
+
}
|
|
1608
|
+
function createBoard(entry) {
|
|
1609
|
+
const el = h("div", "cs-board");
|
|
1610
|
+
el.dataset.id = entry.id;
|
|
1611
|
+
el.dataset.kind = entry.kind;
|
|
1612
|
+
const titleEl = h("div", "cs-board-title");
|
|
1613
|
+
const nameEl = h("span", "cs-name", entry.exportName);
|
|
1614
|
+
const badgeEl = h("span", "cs-badge", entry.kind);
|
|
1615
|
+
badgeEl.dataset.kind = entry.kind;
|
|
1616
|
+
const argsCountEl = h("span", "cs-args-n");
|
|
1617
|
+
titleEl.append(nameEl, badgeEl, argsCountEl);
|
|
1618
|
+
const frameEl = h("div", "cs-frame");
|
|
1619
|
+
const placeholderEl = h("div", "cs-ph", entry.exportName);
|
|
1620
|
+
const overlayEl = h("div", "cs-ovl");
|
|
1621
|
+
const pinLayerEl = h("div", "cs-pins");
|
|
1622
|
+
frameEl.append(placeholderEl, overlayEl);
|
|
1623
|
+
el.append(titleEl, frameEl, pinLayerEl);
|
|
1624
|
+
world2.appendChild(el);
|
|
1625
|
+
const board = {
|
|
1626
|
+
entry,
|
|
1627
|
+
el,
|
|
1628
|
+
titleEl,
|
|
1629
|
+
nameEl,
|
|
1630
|
+
badgeEl,
|
|
1631
|
+
argsCountEl,
|
|
1632
|
+
frameEl,
|
|
1633
|
+
overlayEl,
|
|
1634
|
+
pinLayerEl,
|
|
1635
|
+
placeholderEl,
|
|
1636
|
+
iframe: null,
|
|
1637
|
+
width: boardWidth(entry),
|
|
1638
|
+
// 高度优先级:手动拖过的 > 声明的 > 上次测出来缓存的 > 兜底 320。
|
|
1639
|
+
// 用缓存值开局,懒挂载的画板挂上来时高度就已经是对的,不会挂一块动一次墙。
|
|
1640
|
+
height: clamp(getSize(entry.id).h ?? entry.env?.height ?? getFit(entry.id).h ?? 320, MIN_H, MAX_DRAG_H),
|
|
1641
|
+
argsOverride: null
|
|
1642
|
+
};
|
|
1643
|
+
if (entry.kind === "screen") titleEl.appendChild(widthSwitcher(board));
|
|
1644
|
+
if (entry.kind === "component") {
|
|
1645
|
+
titleEl.appendChild(bgToggle(board));
|
|
1646
|
+
board.el.dataset.bg = effectiveBg(entry.id);
|
|
1647
|
+
}
|
|
1648
|
+
attachResizeHandles(board);
|
|
1649
|
+
attachBoardDrag(board);
|
|
1650
|
+
applySize(board);
|
|
1651
|
+
updateBoard(board, entry);
|
|
1652
|
+
io.observe(el);
|
|
1653
|
+
return board;
|
|
1654
|
+
}
|
|
1655
|
+
var WIDTH_PRESETS = [390, 768, 1024, 1280, 1440];
|
|
1656
|
+
var MIN_SIZE = 120;
|
|
1657
|
+
var MAX_DRAG_H = 2400;
|
|
1658
|
+
var wsels = /* @__PURE__ */ new Map();
|
|
1659
|
+
function sizeKey(id) {
|
|
1660
|
+
return `cs-size:${state.info?.projectRoot ?? "unknown"}:${id}`;
|
|
1661
|
+
}
|
|
1662
|
+
function getSize(id) {
|
|
1663
|
+
try {
|
|
1664
|
+
return JSON.parse(localStorage.getItem(sizeKey(id)) ?? "{}") ?? {};
|
|
1665
|
+
} catch {
|
|
1666
|
+
return {};
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
function setSize(id, patch) {
|
|
1670
|
+
try {
|
|
1671
|
+
localStorage.setItem(sizeKey(id), JSON.stringify({ ...getSize(id), ...patch }));
|
|
1672
|
+
} catch {
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function clearSize(id, dims) {
|
|
1676
|
+
const cur = getSize(id);
|
|
1677
|
+
if (dims.w) delete cur.w;
|
|
1678
|
+
if (dims.h) delete cur.h;
|
|
1679
|
+
try {
|
|
1680
|
+
localStorage.setItem(sizeKey(id), JSON.stringify(cur));
|
|
1681
|
+
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function boardWidth(entry) {
|
|
1685
|
+
const o = getSize(entry.id).w;
|
|
1686
|
+
if (typeof o === "number" && o >= MIN_SIZE) return o;
|
|
1687
|
+
if (entry.env?.width) return entry.env.width;
|
|
1688
|
+
return getFit(entry.id).w ?? DEFAULT_W;
|
|
1689
|
+
}
|
|
1690
|
+
function fitKey(id) {
|
|
1691
|
+
return `cs-fit:${state.info?.projectRoot ?? "unknown"}:${id}`;
|
|
1692
|
+
}
|
|
1693
|
+
function getFit(id) {
|
|
1694
|
+
try {
|
|
1695
|
+
return JSON.parse(localStorage.getItem(fitKey(id)) ?? "{}") ?? {};
|
|
1696
|
+
} catch {
|
|
1697
|
+
return {};
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
function setFit(id, patch) {
|
|
1701
|
+
try {
|
|
1702
|
+
localStorage.setItem(fitKey(id), JSON.stringify({ ...getFit(id), ...patch }));
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
function posKey(id) {
|
|
1707
|
+
return `cs-pos:${state.info?.projectRoot ?? "unknown"}:${id}`;
|
|
1708
|
+
}
|
|
1709
|
+
function getPos(id) {
|
|
1710
|
+
try {
|
|
1711
|
+
const raw = localStorage.getItem(posKey(id));
|
|
1712
|
+
if (!raw) return null;
|
|
1713
|
+
const p = JSON.parse(raw);
|
|
1714
|
+
return Number.isFinite(p?.x) && Number.isFinite(p?.y) ? p : null;
|
|
1715
|
+
} catch {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
function setPos(id, p) {
|
|
1720
|
+
try {
|
|
1721
|
+
localStorage.setItem(posKey(id), JSON.stringify(p));
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function hasAnyPos() {
|
|
1726
|
+
for (const id of state.boards.keys()) if (getPos(id)) return true;
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
function hiddenKey() {
|
|
1730
|
+
return `cs-hidden:${state.info?.projectRoot ?? "unknown"}`;
|
|
1731
|
+
}
|
|
1732
|
+
function hiddenSet() {
|
|
1733
|
+
try {
|
|
1734
|
+
return new Set(JSON.parse(localStorage.getItem(hiddenKey()) ?? "[]"));
|
|
1735
|
+
} catch {
|
|
1736
|
+
return /* @__PURE__ */ new Set();
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
function isHidden(id) {
|
|
1740
|
+
return hiddenSet().has(id);
|
|
1741
|
+
}
|
|
1742
|
+
function toggleHidden(id) {
|
|
1743
|
+
setHidden([id], !hiddenSet().has(id));
|
|
1744
|
+
}
|
|
1745
|
+
function setHidden(ids, hidden) {
|
|
1746
|
+
const s = hiddenSet();
|
|
1747
|
+
for (const id of ids) {
|
|
1748
|
+
if (hidden) s.add(id);
|
|
1749
|
+
else s.delete(id);
|
|
1750
|
+
}
|
|
1751
|
+
try {
|
|
1752
|
+
localStorage.setItem(hiddenKey(), JSON.stringify([...s]));
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
cancelSettleArrange();
|
|
1756
|
+
placeNewBoards();
|
|
1757
|
+
applyPositions();
|
|
1758
|
+
}
|
|
1759
|
+
function widthSwitcher(board) {
|
|
1760
|
+
const sel = document.createElement("select");
|
|
1761
|
+
sel.className = "cs-wsel";
|
|
1762
|
+
sel.title = "\u89C6\u53E3\u5BBD\u5EA6:\u5207\u6362\u4E0D\u91CD\u8F7D,\u54CD\u5E94\u5F0F\u5F53\u573A\u91CD\u6392;\u9009\u8FC7\u7684\u8BB0\u5728\u672C\u673A";
|
|
1763
|
+
const declared = board.entry.env?.width;
|
|
1764
|
+
const opts = [.../* @__PURE__ */ new Set([...declared ? [declared] : [], ...WIDTH_PRESETS])].sort((a, b) => a - b);
|
|
1765
|
+
for (const w of opts) {
|
|
1766
|
+
const o = document.createElement("option");
|
|
1767
|
+
o.value = String(w);
|
|
1768
|
+
o.textContent = `${w}${w === declared ? " (\u58F0\u660E)" : ""}`;
|
|
1769
|
+
sel.appendChild(o);
|
|
1770
|
+
}
|
|
1771
|
+
const custom = document.createElement("option");
|
|
1772
|
+
custom.value = "custom";
|
|
1773
|
+
custom.textContent = "\u81EA\u5B9A\u4E49\u2026";
|
|
1774
|
+
sel.appendChild(custom);
|
|
1775
|
+
sel.value = String(board.width);
|
|
1776
|
+
if (sel.value !== String(board.width)) {
|
|
1777
|
+
const o = document.createElement("option");
|
|
1778
|
+
o.value = String(board.width);
|
|
1779
|
+
o.textContent = String(board.width);
|
|
1780
|
+
sel.insertBefore(o, custom);
|
|
1781
|
+
sel.value = String(board.width);
|
|
1782
|
+
}
|
|
1783
|
+
sel.addEventListener("click", (e) => e.stopPropagation());
|
|
1784
|
+
sel.addEventListener("change", (e) => {
|
|
1785
|
+
e.stopPropagation();
|
|
1786
|
+
let w;
|
|
1787
|
+
if (sel.value === "custom") {
|
|
1788
|
+
const input = prompt("\u89C6\u53E3\u5BBD\u5EA6(px):", String(board.width));
|
|
1789
|
+
w = Number(input);
|
|
1790
|
+
if (!Number.isFinite(w) || w < MIN_SIZE) {
|
|
1791
|
+
sel.value = String(board.width);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
} else {
|
|
1795
|
+
w = Number(sel.value);
|
|
1796
|
+
}
|
|
1797
|
+
syncWsel(sel, w);
|
|
1798
|
+
board.width = w;
|
|
1799
|
+
setSize(board.entry.id, { w });
|
|
1800
|
+
cancelSettleArrange();
|
|
1801
|
+
applySize(board);
|
|
1802
|
+
applyPositions();
|
|
1803
|
+
});
|
|
1804
|
+
wsels.set(board.entry.id, sel);
|
|
1805
|
+
return sel;
|
|
1806
|
+
}
|
|
1807
|
+
function syncWsel(sel, w) {
|
|
1808
|
+
if (![...sel.options].some((o) => o.value === String(w))) {
|
|
1809
|
+
const custom = [...sel.options].find((o2) => o2.value === "custom") ?? null;
|
|
1810
|
+
const o = document.createElement("option");
|
|
1811
|
+
o.value = String(w);
|
|
1812
|
+
o.textContent = String(w);
|
|
1813
|
+
sel.insertBefore(o, custom);
|
|
1814
|
+
}
|
|
1815
|
+
sel.value = String(w);
|
|
1816
|
+
}
|
|
1817
|
+
function attachBoardDrag(board) {
|
|
1818
|
+
board.titleEl.addEventListener("pointerdown", (e) => {
|
|
1819
|
+
if (e.button !== 0) return;
|
|
1820
|
+
cancelSettleArrange();
|
|
1821
|
+
if (e.target.closest("select,button")) return;
|
|
1822
|
+
const startX = e.clientX;
|
|
1823
|
+
const startY = e.clientY;
|
|
1824
|
+
const x0 = parseFloat(board.el.style.left || "0");
|
|
1825
|
+
const y0 = parseFloat(board.el.style.top || "0");
|
|
1826
|
+
let moved = false;
|
|
1827
|
+
board.titleEl.setPointerCapture(e.pointerId);
|
|
1828
|
+
const move = (ev) => {
|
|
1829
|
+
const dx = (ev.clientX - startX) / state.scale;
|
|
1830
|
+
const dy = (ev.clientY - startY) / state.scale;
|
|
1831
|
+
if (!moved && Math.hypot(ev.clientX - startX, ev.clientY - startY) < 4) return;
|
|
1832
|
+
if (!moved) {
|
|
1833
|
+
moved = true;
|
|
1834
|
+
board.el.classList.add("is-dragging");
|
|
1835
|
+
}
|
|
1836
|
+
board.el.style.left = `${Math.round(x0 + dx)}px`;
|
|
1837
|
+
board.el.style.top = `${Math.round(y0 + dy)}px`;
|
|
1838
|
+
setProbe(`${Math.round(x0 + dx)}, ${Math.round(y0 + dy)}`);
|
|
1839
|
+
};
|
|
1840
|
+
const up = () => {
|
|
1841
|
+
board.titleEl.removeEventListener("pointermove", move);
|
|
1842
|
+
board.titleEl.removeEventListener("pointerup", up);
|
|
1843
|
+
board.titleEl.releasePointerCapture?.(e.pointerId);
|
|
1844
|
+
if (!moved) return;
|
|
1845
|
+
board.el.classList.remove("is-dragging");
|
|
1846
|
+
setPos(board.entry.id, {
|
|
1847
|
+
x: parseFloat(board.el.style.left || "0"),
|
|
1848
|
+
y: parseFloat(board.el.style.top || "0")
|
|
1849
|
+
});
|
|
1850
|
+
applyPositions();
|
|
1851
|
+
renderBoardPins(board);
|
|
1852
|
+
};
|
|
1853
|
+
board.titleEl.addEventListener("pointermove", move);
|
|
1854
|
+
board.titleEl.addEventListener("pointerup", up);
|
|
1855
|
+
});
|
|
1856
|
+
board.titleEl.addEventListener("dblclick", (e) => {
|
|
1857
|
+
if (e.target.closest("select,button")) return;
|
|
1858
|
+
e.stopPropagation();
|
|
1859
|
+
const p = computeAutoPositions().get(board.entry.id);
|
|
1860
|
+
if (!p) return;
|
|
1861
|
+
setPos(board.entry.id, p);
|
|
1862
|
+
applyPositions();
|
|
1863
|
+
setProbe("\u5DF2\u5F52\u4F4D\u5230\u81EA\u52A8\u6392\u5217\u7684\u4F4D\u7F6E");
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
var resizing = null;
|
|
1867
|
+
function attachResizeHandles(board) {
|
|
1868
|
+
const mk = (cls, dw, dh) => {
|
|
1869
|
+
const handle = h("div", `cs-rz ${cls}`);
|
|
1870
|
+
handle.title = "\u62D6\u52A8\u8C03\u6574 \xB7 \u53CC\u51FB\u8FD8\u539F\u4E3A\u58F0\u660E/\u81EA\u52A8\u5C3A\u5BF8";
|
|
1871
|
+
handle.addEventListener("dblclick", (e) => {
|
|
1872
|
+
e.stopPropagation();
|
|
1873
|
+
cancelSettleArrange();
|
|
1874
|
+
clearSize(board.entry.id, { w: dw, h: dh });
|
|
1875
|
+
board.width = boardWidth(board.entry);
|
|
1876
|
+
const sel = wsels.get(board.entry.id);
|
|
1877
|
+
if (sel && dw) syncWsel(sel, board.width);
|
|
1878
|
+
measureBoard(board);
|
|
1879
|
+
applySize(board);
|
|
1880
|
+
applyPositions();
|
|
1881
|
+
setProbe("\u5DF2\u8FD8\u539F\u5C3A\u5BF8");
|
|
1882
|
+
});
|
|
1883
|
+
handle.addEventListener("pointerdown", (e) => {
|
|
1884
|
+
e.stopPropagation();
|
|
1885
|
+
e.preventDefault();
|
|
1886
|
+
cancelSettleArrange();
|
|
1887
|
+
handle.setPointerCapture(e.pointerId);
|
|
1888
|
+
resizing = board.entry.id;
|
|
1889
|
+
const startX = e.clientX;
|
|
1890
|
+
const startY = e.clientY;
|
|
1891
|
+
const w0 = board.width;
|
|
1892
|
+
const h0 = board.frameEl.getBoundingClientRect().height / state.scale;
|
|
1893
|
+
const move = (ev) => {
|
|
1894
|
+
if (dw) board.width = Math.round(clamp(w0 + (ev.clientX - startX) / state.scale, MIN_SIZE, MAX_DRAG_H));
|
|
1895
|
+
if (dh) board.height = Math.round(clamp(h0 + (ev.clientY - startY) / state.scale, MIN_SIZE, MAX_DRAG_H));
|
|
1896
|
+
applySize(board);
|
|
1897
|
+
setProbe(`${board.width} \xD7 ${Math.round(board.height)}`);
|
|
1898
|
+
};
|
|
1899
|
+
const up = () => {
|
|
1900
|
+
handle.removeEventListener("pointermove", move);
|
|
1901
|
+
handle.removeEventListener("pointerup", up);
|
|
1902
|
+
const patch = {};
|
|
1903
|
+
if (dw) patch.w = board.width;
|
|
1904
|
+
if (dh) patch.h = Math.round(board.height);
|
|
1905
|
+
setSize(board.entry.id, patch);
|
|
1906
|
+
resizing = null;
|
|
1907
|
+
const sel = wsels.get(board.entry.id);
|
|
1908
|
+
if (sel && dw) syncWsel(sel, board.width);
|
|
1909
|
+
applyPositions();
|
|
1910
|
+
};
|
|
1911
|
+
handle.addEventListener("pointermove", move);
|
|
1912
|
+
handle.addEventListener("pointerup", up);
|
|
1913
|
+
});
|
|
1914
|
+
board.el.appendChild(handle);
|
|
1915
|
+
};
|
|
1916
|
+
mk("cs-rz-e", true, false);
|
|
1917
|
+
mk("cs-rz-s", false, true);
|
|
1918
|
+
mk("cs-rz-se", true, true);
|
|
1919
|
+
}
|
|
1920
|
+
function updateBoard(board, entry) {
|
|
1921
|
+
const urlChanged = board.entry.kind !== entry.kind || board.entry.url !== entry.url;
|
|
1922
|
+
board.entry = entry;
|
|
1923
|
+
board.nameEl.textContent = entry.exportName;
|
|
1924
|
+
board.nameEl.title = entry.exportName;
|
|
1925
|
+
board.badgeEl.textContent = entry.kind;
|
|
1926
|
+
board.badgeEl.title = entry.kind === "screen" ? "\u9875\u9762\u753B\u677F" : "\u7EC4\u4EF6\u753B\u677F";
|
|
1927
|
+
board.badgeEl.dataset.kind = entry.kind;
|
|
1928
|
+
board.el.dataset.kind = entry.kind;
|
|
1929
|
+
const n = entry.args ? Object.keys(entry.args).length : 0;
|
|
1930
|
+
if (entry.kind === "screen" && entry.url) {
|
|
1931
|
+
board.argsCountEl.textContent = entry.url;
|
|
1932
|
+
board.argsCountEl.classList.add("cs-route");
|
|
1933
|
+
board.argsCountEl.title = `\u8DEF\u7531 ${entry.url}`;
|
|
1934
|
+
} else {
|
|
1935
|
+
board.argsCountEl.textContent = n ? `${n} args` : "";
|
|
1936
|
+
board.argsCountEl.classList.remove("cs-route");
|
|
1937
|
+
board.argsCountEl.title = "";
|
|
1938
|
+
}
|
|
1939
|
+
board.width = boardWidth(entry);
|
|
1940
|
+
if (entry.env?.height) board.height = clamp(entry.env.height, MIN_H, MAX_H);
|
|
1941
|
+
applySize(board);
|
|
1942
|
+
if (urlChanged && board.iframe) {
|
|
1943
|
+
const url = boardUrl(entry, board.argsOverride);
|
|
1944
|
+
board.iframe.dataset.csUrl = url;
|
|
1945
|
+
board.iframe.src = url;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
function applySize(board) {
|
|
1949
|
+
board.el.style.width = `${board.width}px`;
|
|
1950
|
+
board.frameEl.style.width = `${board.width}px`;
|
|
1951
|
+
board.frameEl.style.height = `${board.height}px`;
|
|
1952
|
+
schedulePinSync(board);
|
|
1953
|
+
}
|
|
1954
|
+
var pinSyncPending = /* @__PURE__ */ new Set();
|
|
1955
|
+
var pinSyncRaf = 0;
|
|
1956
|
+
function schedulePinSync(board) {
|
|
1957
|
+
pinSyncPending.add(board.entry.id);
|
|
1958
|
+
if (pinSyncRaf) return;
|
|
1959
|
+
pinSyncRaf = requestAnimationFrame(() => {
|
|
1960
|
+
pinSyncRaf = 0;
|
|
1961
|
+
for (const id of pinSyncPending) {
|
|
1962
|
+
const b = state.boards.get(id);
|
|
1963
|
+
if (b) renderBoardPins(b);
|
|
1964
|
+
}
|
|
1965
|
+
pinSyncPending.clear();
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
var MAX_LOADING = 4;
|
|
1969
|
+
var loadingCount = 0;
|
|
1970
|
+
var mountQueue = [];
|
|
1971
|
+
function mountVisible() {
|
|
1972
|
+
if (state.scale < MOUNT_MIN_SCALE) return;
|
|
1973
|
+
const vp = qs("#cs-viewport").getBoundingClientRect();
|
|
1974
|
+
for (const board of state.boards.values()) {
|
|
1975
|
+
if (board.iframe) continue;
|
|
1976
|
+
const r = board.el.getBoundingClientRect();
|
|
1977
|
+
const visible = r.bottom > vp.top - vp.height && r.top < vp.bottom + vp.height && r.right > vp.left - vp.width && r.left < vp.right + vp.width;
|
|
1978
|
+
if (visible) requestMount(board);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function requestMount(board) {
|
|
1982
|
+
if (board.iframe || mountQueue.includes(board)) return;
|
|
1983
|
+
if (loadingCount >= MAX_LOADING) {
|
|
1984
|
+
mountQueue.push(board);
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
mount(board);
|
|
1988
|
+
}
|
|
1989
|
+
function drainMountQueue() {
|
|
1990
|
+
while (loadingCount < MAX_LOADING && mountQueue.length) {
|
|
1991
|
+
const next = mountQueue.shift();
|
|
1992
|
+
if (next && !next.iframe) mount(next);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function mount(board) {
|
|
1996
|
+
if (board.iframe) return;
|
|
1997
|
+
loadingCount++;
|
|
1998
|
+
const iframe = document.createElement("iframe");
|
|
1999
|
+
iframe.title = board.entry.exportName;
|
|
2000
|
+
const url = boardUrl(board.entry, board.argsOverride);
|
|
2001
|
+
iframe.dataset.csUrl = url;
|
|
2002
|
+
let settled = false;
|
|
2003
|
+
const settle = () => {
|
|
2004
|
+
if (settled) return;
|
|
2005
|
+
settled = true;
|
|
2006
|
+
loadingCount = Math.max(0, loadingCount - 1);
|
|
2007
|
+
drainMountQueue();
|
|
2008
|
+
};
|
|
2009
|
+
iframe.addEventListener("load", () => {
|
|
2010
|
+
settle();
|
|
2011
|
+
onFrameLoad(board);
|
|
2012
|
+
});
|
|
2013
|
+
setTimeout(settle, 1e4);
|
|
2014
|
+
board.frameEl.insertBefore(iframe, board.overlayEl);
|
|
2015
|
+
iframe.src = url;
|
|
2016
|
+
board.iframe = iframe;
|
|
2017
|
+
board.placeholderEl.hidden = true;
|
|
2018
|
+
}
|
|
2019
|
+
function onFrameLoad(board) {
|
|
2020
|
+
const doc = frameDoc(board.iframe);
|
|
2021
|
+
if (!doc) return;
|
|
2022
|
+
attachFrameKeys(doc);
|
|
2023
|
+
injectWallCss(board);
|
|
2024
|
+
measureBoard(board);
|
|
2025
|
+
applyPositions();
|
|
2026
|
+
scheduleSettleArrange();
|
|
2027
|
+
renderBoardPins(board);
|
|
2028
|
+
watchContentHeight(board);
|
|
2029
|
+
}
|
|
2030
|
+
function injectWallCss(board) {
|
|
2031
|
+
const doc = frameDoc(board.iframe);
|
|
2032
|
+
if (!doc?.head) return;
|
|
2033
|
+
if (!doc.getElementById("__cs_wall_css")) {
|
|
2034
|
+
const st = doc.createElement("style");
|
|
2035
|
+
st.id = "__cs_wall_css";
|
|
2036
|
+
st.textContent = "nextjs-portal{display:none !important}";
|
|
2037
|
+
doc.head.appendChild(st);
|
|
2038
|
+
}
|
|
2039
|
+
applyBgPref(board);
|
|
2040
|
+
}
|
|
2041
|
+
var BG_FALLBACK = "blend";
|
|
2042
|
+
var BG_LABEL = { blend: "\u878D\u5165\u753B\u5E03", real: "\u9879\u76EE\u771F\u5B9E\u5E95\u8272" };
|
|
2043
|
+
function bgGlobalKey() {
|
|
2044
|
+
return `cs-bg-default:${state.info?.projectRoot ?? "unknown"}`;
|
|
2045
|
+
}
|
|
2046
|
+
function globalBg() {
|
|
2047
|
+
try {
|
|
2048
|
+
return localStorage.getItem(bgGlobalKey()) === "real" ? "real" : BG_FALLBACK;
|
|
2049
|
+
} catch {
|
|
2050
|
+
return BG_FALLBACK;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function setGlobalBg(mode) {
|
|
2054
|
+
try {
|
|
2055
|
+
localStorage.setItem(bgGlobalKey(), mode);
|
|
2056
|
+
} catch {
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
function bgKey(id) {
|
|
2060
|
+
return `cs-bg:${state.info?.projectRoot ?? "unknown"}:${id}`;
|
|
2061
|
+
}
|
|
2062
|
+
function bgOverride(id) {
|
|
2063
|
+
try {
|
|
2064
|
+
const v = localStorage.getItem(bgKey(id));
|
|
2065
|
+
if (v === "blend") return "blend";
|
|
2066
|
+
if (v === "real" || v === "1") return "real";
|
|
2067
|
+
return null;
|
|
2068
|
+
} catch {
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
function setBgOverride(id, mode) {
|
|
2073
|
+
try {
|
|
2074
|
+
if (mode) localStorage.setItem(bgKey(id), mode);
|
|
2075
|
+
else localStorage.removeItem(bgKey(id));
|
|
2076
|
+
} catch {
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
function effectiveBg(id) {
|
|
2080
|
+
return bgOverride(id) ?? globalBg();
|
|
2081
|
+
}
|
|
2082
|
+
function canvasBg() {
|
|
2083
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--cs-bg").trim() || "#0a0b0d";
|
|
2084
|
+
}
|
|
2085
|
+
function applyBgPref(board) {
|
|
2086
|
+
if (board.entry.kind !== "component") return;
|
|
2087
|
+
const mode = effectiveBg(board.entry.id);
|
|
2088
|
+
board.el.dataset.bg = mode;
|
|
2089
|
+
const doc = frameDoc(board.iframe);
|
|
2090
|
+
if (!doc?.head) return;
|
|
2091
|
+
let st = doc.getElementById("__cs_bg");
|
|
2092
|
+
if (!st) {
|
|
2093
|
+
st = doc.createElement("style");
|
|
2094
|
+
st.id = "__cs_bg";
|
|
2095
|
+
doc.head.appendChild(st);
|
|
2096
|
+
}
|
|
2097
|
+
const noScrollbar = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{display:none}";
|
|
2098
|
+
st.textContent = (mode === "blend" ? `html,body{background:${canvasBg()} !important}` : "html,body{background:var(--background,#fff) !important}") + noScrollbar;
|
|
2099
|
+
}
|
|
2100
|
+
var bgSyncs = /* @__PURE__ */ new Map();
|
|
2101
|
+
function bgToggle(board) {
|
|
2102
|
+
const id = board.entry.id;
|
|
2103
|
+
const btn = document.createElement("button");
|
|
2104
|
+
btn.className = "cs-bgbtn";
|
|
2105
|
+
const sync = () => {
|
|
2106
|
+
const ov = bgOverride(id);
|
|
2107
|
+
const eff = ov ?? globalBg();
|
|
2108
|
+
btn.textContent = eff === "blend" ? "\u25FB" : "\u25A3";
|
|
2109
|
+
btn.classList.toggle("is-on", eff === "real");
|
|
2110
|
+
btn.classList.toggle("is-auto", ov === null);
|
|
2111
|
+
btn.title = `\u5E95\u8272:${BG_LABEL[eff]}(${ov ? "\u672C\u677F\u8986\u76D6" : "\u8DDF\u968F\u5168\u5C40"}) \xB7 \u70B9\u51FB\u5207\u6362`;
|
|
2112
|
+
btn.setAttribute("aria-label", btn.title);
|
|
2113
|
+
};
|
|
2114
|
+
sync();
|
|
2115
|
+
bgSyncs.set(id, sync);
|
|
2116
|
+
btn.addEventListener("click", (e) => {
|
|
2117
|
+
e.stopPropagation();
|
|
2118
|
+
const ov = bgOverride(id);
|
|
2119
|
+
const g = globalBg();
|
|
2120
|
+
setBgOverride(id, ov === null ? g === "blend" ? "real" : "blend" : ov === g ? null : g);
|
|
2121
|
+
sync();
|
|
2122
|
+
applyBgPref(board);
|
|
2123
|
+
});
|
|
2124
|
+
return btn;
|
|
2125
|
+
}
|
|
2126
|
+
var segBtns = [];
|
|
2127
|
+
function initWallTools() {
|
|
2128
|
+
const bar = h("div", "cs-wall-tools");
|
|
2129
|
+
bar.id = "cs-wall-tools";
|
|
2130
|
+
const label = h("span", "cs-wt-label", "\u7EC4\u4EF6\u5E95\u8272");
|
|
2131
|
+
label.title = "\u7EC4\u4EF6\u753B\u677F\u7684\u9ED8\u8BA4\u5E95\u8272(\u6309\u9879\u76EE\u8BB0\u5728\u672C\u673A)\u3002\u5355\u5757\u753B\u677F\u53EF\u4EE5\u7528\u6807\u9898\u6761\u4E0A\u7684 \u25FB/\u25A3 \u5355\u72EC\u8986\u76D6";
|
|
2132
|
+
const seg = h("div", "cs-seg");
|
|
2133
|
+
for (const mode of ["blend", "real"]) {
|
|
2134
|
+
const b = h("button", "cs-seg-btn", mode === "blend" ? "\u878D\u5165\u753B\u5E03" : "\u9879\u76EE\u5E95\u8272");
|
|
2135
|
+
b.title = mode === "blend" ? "\u6240\u6709\u7EC4\u4EF6\u753B\u677F\u9ED8\u8BA4\u628A\u5E95\u8272\u5237\u6210\u753B\u5E03\u540C\u8272 \u2014\u2014 \u767D\u5E95\u4E0D\u518D\u62A2\u955C" : "\u6240\u6709\u7EC4\u4EF6\u753B\u677F\u9ED8\u8BA4\u663E\u793A\u9879\u76EE\u81EA\u5DF1\u7684 --background";
|
|
2136
|
+
b.addEventListener("click", () => {
|
|
2137
|
+
setGlobalBg(mode);
|
|
2138
|
+
syncWallTools();
|
|
2139
|
+
for (const board of state.boards.values()) {
|
|
2140
|
+
applyBgPref(board);
|
|
2141
|
+
bgSyncs.get(board.entry.id)?.();
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
seg.appendChild(b);
|
|
2145
|
+
segBtns.push({ el: b, mode });
|
|
2146
|
+
}
|
|
2147
|
+
const arrange = h("button", "cs-wt-btn", "\u81EA\u52A8\u6392\u5217");
|
|
2148
|
+
arrange.title = "\u628A\u6240\u6709\u753B\u677F\u91CD\u65B0\u94FA\u5F00(\u9875\u9762 4/\u884C\u3001\u7EC4\u4EF6 15/\u884C)\u3002\u5E73\u65F6\u4E0D\u4F1A\u81EA\u52A8\u91CD\u6392 \u2014\u2014 \u4F60\u6446\u597D\u7684\u4F4D\u7F6E\u4E00\u76F4\u7559\u7740";
|
|
2149
|
+
arrange.addEventListener("click", () => {
|
|
2150
|
+
cancelSettleArrange();
|
|
2151
|
+
toast(`\u5DF2\u91CD\u65B0\u6392\u5217 ${autoArrange()} \u5757\u753B\u677F`, "ok");
|
|
2152
|
+
});
|
|
2153
|
+
bar.append(label, seg, arrange);
|
|
2154
|
+
document.body.appendChild(bar);
|
|
2155
|
+
syncWallTools();
|
|
2156
|
+
}
|
|
2157
|
+
function syncWallTools() {
|
|
2158
|
+
const g = globalBg();
|
|
2159
|
+
for (const s of segBtns) {
|
|
2160
|
+
s.el.classList.toggle("is-on", s.mode === g);
|
|
2161
|
+
s.el.setAttribute("aria-pressed", String(s.mode === g));
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
function watchContentHeight(board) {
|
|
2165
|
+
heightWatchers.get(board.entry.id)?.disconnect();
|
|
2166
|
+
const doc = frameDoc(board.iframe);
|
|
2167
|
+
const body = doc?.body;
|
|
2168
|
+
if (!body || typeof ResizeObserver === "undefined") return;
|
|
2169
|
+
const ro = new ResizeObserver(() => {
|
|
2170
|
+
const cur = body.getBoundingClientRect().height;
|
|
2171
|
+
const prev = lastBodyH.get(board.entry.id) ?? -1;
|
|
2172
|
+
schedulePinSync(board);
|
|
2173
|
+
if (Math.abs(cur - prev) < 2) return;
|
|
2174
|
+
lastBodyH.set(board.entry.id, cur);
|
|
2175
|
+
const times = (measureCount.get(board.entry.id) ?? 0) + 1;
|
|
2176
|
+
measureCount.set(board.entry.id, times);
|
|
2177
|
+
if (times > 30) {
|
|
2178
|
+
ro.disconnect();
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
measureBoard(board);
|
|
2182
|
+
applyPositions();
|
|
2183
|
+
scheduleSettleArrange();
|
|
2184
|
+
});
|
|
2185
|
+
ro.observe(body);
|
|
2186
|
+
heightWatchers.set(board.entry.id, ro);
|
|
2187
|
+
}
|
|
2188
|
+
function measureBoard(board) {
|
|
2189
|
+
if (resizing === board.entry.id) return;
|
|
2190
|
+
const manual = getSize(board.entry.id);
|
|
2191
|
+
if (manual.h) {
|
|
2192
|
+
board.height = clamp(manual.h, MIN_SIZE, MAX_DRAG_H);
|
|
2193
|
+
applySize(board);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
const fixed = board.entry.env?.height;
|
|
2197
|
+
if (fixed) {
|
|
2198
|
+
board.height = clamp(fixed, MIN_H, MAX_H);
|
|
2199
|
+
applySize(board);
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
const iframe = board.iframe;
|
|
2203
|
+
const doc = frameDoc(iframe);
|
|
2204
|
+
if (!iframe || !doc?.documentElement) return;
|
|
2205
|
+
const prev = iframe.style.height;
|
|
2206
|
+
iframe.style.height = `${MIN_H}px`;
|
|
2207
|
+
const raw = Math.max(doc.documentElement.scrollHeight, doc.body?.scrollHeight ?? 0);
|
|
2208
|
+
iframe.style.height = prev;
|
|
2209
|
+
board.height = clamp(raw, MIN_H, MAX_H);
|
|
2210
|
+
lastBodyH.set(board.entry.id, doc.body?.getBoundingClientRect().height ?? -1);
|
|
2211
|
+
setFit(board.entry.id, { h: Math.round(board.height) });
|
|
2212
|
+
if (board.entry.kind === "component" && !board.entry.env?.width && !manual.w) {
|
|
2213
|
+
const ab = doc.querySelector("[data-cs-artboard]");
|
|
2214
|
+
if (ab) {
|
|
2215
|
+
const w = Math.ceil(ab.getBoundingClientRect().width);
|
|
2216
|
+
if (w > 0) {
|
|
2217
|
+
board.width = clamp(w, 120, DEFAULT_W);
|
|
2218
|
+
setFit(board.entry.id, { w: board.width });
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
applySize(board);
|
|
2223
|
+
}
|
|
2224
|
+
function remeasureAll() {
|
|
2225
|
+
for (const board of state.boards.values()) {
|
|
2226
|
+
measureCount.set(board.entry.id, 0);
|
|
2227
|
+
if (board.iframe) measureBoard(board);
|
|
2228
|
+
}
|
|
2229
|
+
applyPositions();
|
|
2230
|
+
}
|
|
2231
|
+
var PER_ROW = { screen: 4, component: 15 };
|
|
2232
|
+
function arrangeable() {
|
|
2233
|
+
const out = [];
|
|
2234
|
+
for (const file of [...state.order.keys()].sort()) {
|
|
2235
|
+
for (const id of state.order.get(file) ?? []) {
|
|
2236
|
+
const b = state.boards.get(id);
|
|
2237
|
+
if (b && !isHidden(id)) out.push(b);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return out;
|
|
2241
|
+
}
|
|
2242
|
+
function flowRows(list, kind, startY, out) {
|
|
2243
|
+
let y = startY;
|
|
2244
|
+
let row2 = [];
|
|
2245
|
+
let rowW = 0;
|
|
2246
|
+
const flush = () => {
|
|
2247
|
+
if (!row2.length) return;
|
|
2248
|
+
let x = PAD;
|
|
2249
|
+
const rowH = Math.max(...row2.map((b) => TITLE_H + b.height));
|
|
2250
|
+
for (const b of row2) {
|
|
2251
|
+
out.set(b.entry.id, { x, y });
|
|
2252
|
+
x += b.width + BOARD_GAP;
|
|
2253
|
+
}
|
|
2254
|
+
y += rowH + BOARD_GAP;
|
|
2255
|
+
row2 = [];
|
|
2256
|
+
rowW = 0;
|
|
2257
|
+
};
|
|
2258
|
+
for (const b of list) {
|
|
2259
|
+
if (row2.length >= PER_ROW[kind] || row2.length && rowW + b.width > ROW_MAX_W) flush();
|
|
2260
|
+
row2.push(b);
|
|
2261
|
+
rowW += b.width + BOARD_GAP;
|
|
2262
|
+
}
|
|
2263
|
+
flush();
|
|
2264
|
+
return y;
|
|
2265
|
+
}
|
|
2266
|
+
function computeAutoPositions() {
|
|
2267
|
+
const out = /* @__PURE__ */ new Map();
|
|
2268
|
+
const ordered = arrangeable();
|
|
2269
|
+
let y = PAD;
|
|
2270
|
+
for (const kind of ["screen", "component"]) {
|
|
2271
|
+
const list = ordered.filter((b) => b.entry.kind === kind);
|
|
2272
|
+
if (!list.length) continue;
|
|
2273
|
+
y += SECTION_HEAD_H;
|
|
2274
|
+
y = flowRows(list, kind, y, out);
|
|
2275
|
+
y += SECTION_GAP;
|
|
2276
|
+
}
|
|
2277
|
+
return out;
|
|
2278
|
+
}
|
|
2279
|
+
function autoArrange() {
|
|
2280
|
+
const pos = computeAutoPositions();
|
|
2281
|
+
for (const [id, p] of pos) setPos(id, p);
|
|
2282
|
+
applyPositions();
|
|
2283
|
+
return pos.size;
|
|
2284
|
+
}
|
|
2285
|
+
function placeNewBoards() {
|
|
2286
|
+
const ordered = arrangeable();
|
|
2287
|
+
const fresh = ordered.filter((b) => !getPos(b.entry.id));
|
|
2288
|
+
if (!fresh.length) return 0;
|
|
2289
|
+
let bottom = -Infinity;
|
|
2290
|
+
for (const b of ordered) {
|
|
2291
|
+
const p = getPos(b.entry.id);
|
|
2292
|
+
if (p) bottom = Math.max(bottom, p.y + TITLE_H + b.height);
|
|
2293
|
+
}
|
|
2294
|
+
let y = Number.isFinite(bottom) ? bottom + SECTION_GAP : PAD;
|
|
2295
|
+
const out = /* @__PURE__ */ new Map();
|
|
2296
|
+
for (const kind of ["screen", "component"]) {
|
|
2297
|
+
const list = fresh.filter((b) => b.entry.kind === kind);
|
|
2298
|
+
if (!list.length) continue;
|
|
2299
|
+
y = flowRows(list, kind, y, out) + SECTION_GAP;
|
|
2300
|
+
}
|
|
2301
|
+
for (const [id, p] of out) setPos(id, p);
|
|
2302
|
+
return out.size;
|
|
2303
|
+
}
|
|
2304
|
+
function applyPositions() {
|
|
2305
|
+
for (const b of state.boards.values()) {
|
|
2306
|
+
const p = getPos(b.entry.id);
|
|
2307
|
+
if (p) {
|
|
2308
|
+
b.el.style.left = `${p.x}px`;
|
|
2309
|
+
b.el.style.top = `${p.y}px`;
|
|
2310
|
+
}
|
|
2311
|
+
b.el.hidden = isHidden(b.entry.id);
|
|
2312
|
+
}
|
|
2313
|
+
layoutSections();
|
|
2314
|
+
applyView();
|
|
2315
|
+
}
|
|
2316
|
+
function layoutSections() {
|
|
2317
|
+
for (const kind of ["screen", "component"]) {
|
|
2318
|
+
const titleEl = ensureSection(kind);
|
|
2319
|
+
let minX = Infinity;
|
|
2320
|
+
let minY = Infinity;
|
|
2321
|
+
let n = 0;
|
|
2322
|
+
for (const b of state.boards.values()) {
|
|
2323
|
+
if (b.entry.kind !== kind || isHidden(b.entry.id)) continue;
|
|
2324
|
+
n++;
|
|
2325
|
+
const p = getPos(b.entry.id);
|
|
2326
|
+
if (!p) continue;
|
|
2327
|
+
minX = Math.min(minX, p.x);
|
|
2328
|
+
minY = Math.min(minY, p.y);
|
|
2329
|
+
}
|
|
2330
|
+
if (!n || !Number.isFinite(minX)) {
|
|
2331
|
+
titleEl.hidden = true;
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
titleEl.hidden = false;
|
|
2335
|
+
titleEl.textContent = "";
|
|
2336
|
+
titleEl.append(
|
|
2337
|
+
h("span", void 0, kind === "screen" ? "\u9875\u9762" : "\u7EC4\u4EF6"),
|
|
2338
|
+
h("span", "cs-section-count", String(n))
|
|
2339
|
+
);
|
|
2340
|
+
titleEl.style.left = `${minX}px`;
|
|
2341
|
+
titleEl.style.top = `${minY - SECTION_HEAD_H}px`;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
var SETTLE_WINDOW = 8e3;
|
|
2345
|
+
var firstSync = true;
|
|
2346
|
+
var settleDeadline = 0;
|
|
2347
|
+
var settleTimer = null;
|
|
2348
|
+
function scheduleSettleArrange() {
|
|
2349
|
+
if (!settleDeadline || Date.now() > settleDeadline) return;
|
|
2350
|
+
if (settleTimer !== null) clearTimeout(settleTimer);
|
|
2351
|
+
settleTimer = setTimeout(() => {
|
|
2352
|
+
settleTimer = null;
|
|
2353
|
+
if (!settleDeadline) return;
|
|
2354
|
+
autoArrange();
|
|
2355
|
+
}, 350);
|
|
2356
|
+
}
|
|
2357
|
+
function cancelSettleArrange() {
|
|
2358
|
+
settleDeadline = 0;
|
|
2359
|
+
if (settleTimer !== null) clearTimeout(settleTimer);
|
|
2360
|
+
settleTimer = null;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// src/canvas/sidebar.ts
|
|
2364
|
+
var root;
|
|
2365
|
+
var listEl;
|
|
2366
|
+
var filter = "";
|
|
2367
|
+
var searchCollapsed = null;
|
|
2368
|
+
var uid = 0;
|
|
2369
|
+
function initSidebar() {
|
|
2370
|
+
root = qs("#cs-sidebar");
|
|
2371
|
+
root.textContent = "";
|
|
2372
|
+
const head = h("div", "cs-sb-head");
|
|
2373
|
+
const search = document.createElement("input");
|
|
2374
|
+
search.className = "cs-sb-search";
|
|
2375
|
+
search.type = "search";
|
|
2376
|
+
search.placeholder = "\u641C\u7D22\u753B\u677F\u2026";
|
|
2377
|
+
search.addEventListener("input", () => {
|
|
2378
|
+
filter = search.value.trim().toLowerCase();
|
|
2379
|
+
searchCollapsed = filter ? /* @__PURE__ */ new Set() : null;
|
|
2380
|
+
renderSidebar();
|
|
2381
|
+
});
|
|
2382
|
+
search.addEventListener("keydown", (e) => {
|
|
2383
|
+
e.stopPropagation();
|
|
2384
|
+
if (e.key === "Escape") {
|
|
2385
|
+
search.value = "";
|
|
2386
|
+
filter = "";
|
|
2387
|
+
searchCollapsed = null;
|
|
2388
|
+
renderSidebar();
|
|
2389
|
+
search.blur();
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
head.append(h("span", "cs-sb-title", "\u753B\u677F"), search);
|
|
2393
|
+
listEl = h("div", "cs-sb-list");
|
|
2394
|
+
listEl.addEventListener("keydown", (e) => {
|
|
2395
|
+
if ((e.key === "Enter" || e.key === " ") && e.target.closest("button")) {
|
|
2396
|
+
e.stopPropagation();
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
root.append(head, listEl);
|
|
2400
|
+
const toggle = qs("#cs-sb-toggle");
|
|
2401
|
+
toggle.addEventListener("click", () => {
|
|
2402
|
+
const closed = document.body.dataset.sidebar === "off";
|
|
2403
|
+
document.body.dataset.sidebar = closed ? "on" : "off";
|
|
2404
|
+
toggle.title = closed ? "\u6536\u8D77\u5217\u8868" : "\u5C55\u5F00\u5217\u8868";
|
|
2405
|
+
});
|
|
2406
|
+
renderSidebar();
|
|
2407
|
+
}
|
|
2408
|
+
function match(text) {
|
|
2409
|
+
return !filter || text.toLowerCase().includes(filter);
|
|
2410
|
+
}
|
|
2411
|
+
function collapseKey() {
|
|
2412
|
+
return `cs-collapsed:${state.info?.projectRoot ?? "unknown"}`;
|
|
2413
|
+
}
|
|
2414
|
+
function collapsedSet() {
|
|
2415
|
+
try {
|
|
2416
|
+
return new Set(JSON.parse(localStorage.getItem(collapseKey()) ?? "[]"));
|
|
2417
|
+
} catch {
|
|
2418
|
+
return /* @__PURE__ */ new Set();
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
function isCollapsed(key) {
|
|
2422
|
+
return searchCollapsed ? searchCollapsed.has(key) : collapsedSet().has(key);
|
|
2423
|
+
}
|
|
2424
|
+
function toggleCollapsed(key) {
|
|
2425
|
+
if (searchCollapsed) {
|
|
2426
|
+
if (!searchCollapsed.delete(key)) searchCollapsed.add(key);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
const s = collapsedSet();
|
|
2430
|
+
if (s.has(key)) s.delete(key);
|
|
2431
|
+
else s.add(key);
|
|
2432
|
+
try {
|
|
2433
|
+
localStorage.setItem(collapseKey(), JSON.stringify([...s]));
|
|
2434
|
+
} catch {
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
function bulkMode(ids) {
|
|
2438
|
+
const shown = ids.filter((id) => !isHidden(id)).length;
|
|
2439
|
+
return { mode: shown === ids.length ? "all" : shown === 0 ? "none" : "some", shown };
|
|
2440
|
+
}
|
|
2441
|
+
function setGroupHidden(ids, hide) {
|
|
2442
|
+
setHidden(ids, hide);
|
|
2443
|
+
}
|
|
2444
|
+
function focusedKey() {
|
|
2445
|
+
const el = document.activeElement;
|
|
2446
|
+
return el && listEl.contains(el) ? el.dataset.fk ?? null : null;
|
|
2447
|
+
}
|
|
2448
|
+
function restoreFocus(key) {
|
|
2449
|
+
if (!key) return;
|
|
2450
|
+
const el = listEl.querySelector(`[data-fk="${CSS.escape(key)}"]`);
|
|
2451
|
+
el?.focus({ preventScroll: true });
|
|
2452
|
+
}
|
|
2453
|
+
function renderSidebar() {
|
|
2454
|
+
if (!listEl) return;
|
|
2455
|
+
const keepFocus = focusedKey();
|
|
2456
|
+
listEl.textContent = "";
|
|
2457
|
+
uid = 0;
|
|
2458
|
+
const screens = state.entries.filter((e) => e.kind === "screen");
|
|
2459
|
+
const comps = state.entries.filter((e) => e.kind !== "screen");
|
|
2460
|
+
const visScreens = screens.filter((e) => match(e.exportName) || match(e.url ?? ""));
|
|
2461
|
+
if (visScreens.length) {
|
|
2462
|
+
const g = group("sec:screen", "\u9875\u9762", visScreens.map((e) => e.id), "cs-sb-section");
|
|
2463
|
+
for (const e of visScreens) g.body.appendChild(row(e.id, e.exportName, e.url ?? "", "screen"));
|
|
2464
|
+
listEl.appendChild(g.el);
|
|
2465
|
+
}
|
|
2466
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2467
|
+
for (const e of comps) {
|
|
2468
|
+
if (!(match(e.exportName) || match(groupLabel(e.file)))) continue;
|
|
2469
|
+
const l = byFile.get(e.file);
|
|
2470
|
+
if (l) l.push(e);
|
|
2471
|
+
else byFile.set(e.file, [e]);
|
|
2472
|
+
}
|
|
2473
|
+
if (byFile.size) {
|
|
2474
|
+
const files = [...byFile.keys()].sort();
|
|
2475
|
+
const allIds = files.flatMap((f) => (byFile.get(f) ?? []).map((e) => e.id));
|
|
2476
|
+
const g = group("sec:component", "\u7EC4\u4EF6", allIds, "cs-sb-section");
|
|
2477
|
+
for (const file of files) {
|
|
2478
|
+
const list = byFile.get(file) ?? [];
|
|
2479
|
+
const fg = group(`file:${file}`, groupLabel(file), list.map((e) => e.id), "cs-sb-file");
|
|
2480
|
+
for (const e of list) fg.body.appendChild(row(e.id, e.exportName, "", "component"));
|
|
2481
|
+
g.body.appendChild(fg.el);
|
|
2482
|
+
}
|
|
2483
|
+
listEl.appendChild(g.el);
|
|
2484
|
+
}
|
|
2485
|
+
if (!listEl.childElementCount) {
|
|
2486
|
+
listEl.appendChild(h("div", "cs-sb-empty", filter ? "\u6CA1\u6709\u5339\u914D\u7684\u753B\u677F" : "\u8FD8\u6CA1\u6709\u753B\u677F"));
|
|
2487
|
+
}
|
|
2488
|
+
syncActive();
|
|
2489
|
+
restoreFocus(keepFocus);
|
|
2490
|
+
}
|
|
2491
|
+
function group(key, label, ids, headCls) {
|
|
2492
|
+
const el = h("div", "cs-sb-group");
|
|
2493
|
+
if (headCls === "cs-sb-file") el.classList.add("is-file");
|
|
2494
|
+
const headEl = h("div", headCls);
|
|
2495
|
+
const body = h("div", "cs-sb-items");
|
|
2496
|
+
body.id = `cs-sb-g${++uid}`;
|
|
2497
|
+
const collapsed = isCollapsed(key);
|
|
2498
|
+
body.hidden = collapsed;
|
|
2499
|
+
const { mode, shown } = bulkMode(ids);
|
|
2500
|
+
const caret = h("button", "cs-sb-caret");
|
|
2501
|
+
caret.type = "button";
|
|
2502
|
+
caret.dataset.fk = `caret:${key}`;
|
|
2503
|
+
caret.setAttribute("aria-expanded", String(!collapsed));
|
|
2504
|
+
caret.setAttribute("aria-controls", body.id);
|
|
2505
|
+
caret.title = collapsed ? `\u5C55\u5F00\u300C${label}\u300D` : `\u6298\u53E0\u300C${label}\u300D`;
|
|
2506
|
+
const arrow = h("span", "cs-sb-arrow", "\u25B8");
|
|
2507
|
+
arrow.setAttribute("aria-hidden", "true");
|
|
2508
|
+
const count = h("span", "cs-sb-count", mode === "all" ? String(ids.length) : `${shown}/${ids.length}`);
|
|
2509
|
+
caret.append(arrow, h("span", "cs-sb-label", label), count);
|
|
2510
|
+
caret.addEventListener("click", () => {
|
|
2511
|
+
toggleCollapsed(key);
|
|
2512
|
+
renderSidebar();
|
|
2513
|
+
});
|
|
2514
|
+
const bulk = h("button", "cs-sb-bulk", mode === "all" ? "\u25CF" : mode === "none" ? "\u25CC" : "\u25D0");
|
|
2515
|
+
bulk.type = "button";
|
|
2516
|
+
bulk.dataset.fk = `bulk:${key}`;
|
|
2517
|
+
bulk.dataset.state = mode;
|
|
2518
|
+
bulk.setAttribute("aria-pressed", mode === "all" ? "true" : mode === "none" ? "false" : "mixed");
|
|
2519
|
+
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`;
|
|
2520
|
+
bulk.setAttribute("aria-label", act);
|
|
2521
|
+
bulk.title = mode === "some" ? `${act}(\u5F53\u524D ${shown}/${ids.length} \u663E\u793A\u4E2D)` : act;
|
|
2522
|
+
bulk.addEventListener("click", (e) => {
|
|
2523
|
+
e.stopPropagation();
|
|
2524
|
+
setGroupHidden(ids, mode === "all");
|
|
2525
|
+
renderSidebar();
|
|
2526
|
+
});
|
|
2527
|
+
headEl.append(caret, bulk);
|
|
2528
|
+
el.append(headEl, body);
|
|
2529
|
+
return { el, body };
|
|
2530
|
+
}
|
|
2531
|
+
function row(id, name, sub, kind) {
|
|
2532
|
+
const el = h("div", "cs-sb-row");
|
|
2533
|
+
el.dataset.id = id;
|
|
2534
|
+
const go = h("button", "cs-sb-go");
|
|
2535
|
+
go.type = "button";
|
|
2536
|
+
go.dataset.fk = `go:${id}`;
|
|
2537
|
+
go.dataset.kind = kind;
|
|
2538
|
+
go.title = sub ? `\u805A\u7126 ${name} (${sub})` : `\u805A\u7126 ${name}`;
|
|
2539
|
+
go.append(h("span", "cs-sb-dot"), h("span", "cs-sb-name", name));
|
|
2540
|
+
if (sub) go.appendChild(h("span", "cs-sb-route", sub));
|
|
2541
|
+
go.addEventListener("click", () => {
|
|
2542
|
+
if (isHidden(id)) toggleHidden(id);
|
|
2543
|
+
fitBoard(id);
|
|
2544
|
+
state.activeId = id;
|
|
2545
|
+
syncActive();
|
|
2546
|
+
});
|
|
2547
|
+
const eye = h("button", "cs-sb-eye", isHidden(id) ? "\u25CC" : "\u25CF");
|
|
2548
|
+
eye.type = "button";
|
|
2549
|
+
eye.dataset.fk = `eye:${id}`;
|
|
2550
|
+
eye.title = isHidden(id) ? "\u663E\u793A\u8FD9\u5757\u753B\u677F" : "\u4ECE\u5899\u4E0A\u9690\u85CF(\u4E0D\u5220,\u968F\u65F6\u70B9\u56DE\u6765)";
|
|
2551
|
+
eye.setAttribute("aria-label", isHidden(id) ? `\u663E\u793A ${name}` : `\u9690\u85CF ${name}`);
|
|
2552
|
+
eye.setAttribute("aria-pressed", String(!isHidden(id)));
|
|
2553
|
+
eye.addEventListener("click", (e) => {
|
|
2554
|
+
e.stopPropagation();
|
|
2555
|
+
toggleHidden(id);
|
|
2556
|
+
renderSidebar();
|
|
2557
|
+
});
|
|
2558
|
+
el.append(go, eye);
|
|
2559
|
+
if (isHidden(id)) el.classList.add("is-hidden-board");
|
|
2560
|
+
return el;
|
|
2561
|
+
}
|
|
2562
|
+
function syncActive() {
|
|
2563
|
+
if (!listEl) return;
|
|
2564
|
+
for (const r of listEl.querySelectorAll(".cs-sb-row")) {
|
|
2565
|
+
r.classList.toggle("is-active", r.dataset.id === state.activeId);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// src/canvas/app.ts
|
|
2570
|
+
function showBootError(err) {
|
|
2571
|
+
const boot2 = qs("#cs-boot");
|
|
2572
|
+
boot2.textContent = "";
|
|
2573
|
+
const card = h("div", "cs-card");
|
|
2574
|
+
const compileErr = /HTTP 5\d\d/.test(String(err));
|
|
2575
|
+
if (compileErr) {
|
|
2576
|
+
card.appendChild(h("h2", void 0, "\u753B\u677F\u6587\u4EF6\u7F16\u8BD1\u9519\u8BEF"));
|
|
2577
|
+
card.appendChild(
|
|
2578
|
+
h("p", void 0, "next dev \u5728\u8DD1,\u4F46\u753B\u677F\u6CE8\u518C\u8868\u6A21\u5757\u6E32\u67D3\u5931\u8D25 \u2014\u2014 \u901A\u5E38\u662F\u67D0\u4E2A *.artboard.tsx \u6709\u8BED\u6CD5\u6216 import \u9519\u8BEF\u3002\u53BB next dev \u7684\u7EC8\u7AEF\u770B\u5177\u4F53\u62A5\u9519,\u4FEE\u597D\u540E\u8FD9\u9762\u5899\u4F1A\u81EA\u52A8\u6062\u590D\u3002")
|
|
2579
|
+
);
|
|
2580
|
+
} else {
|
|
2581
|
+
card.appendChild(h("h2", void 0, "\u76EE\u6807 dev server \u672A\u542F\u52A8"));
|
|
2582
|
+
card.appendChild(h("p", void 0, "\u753B\u5E03\u8FDE\u4E0D\u4E0A\u753B\u677F\u6570\u636E\u3002\u5148\u5728\u9879\u76EE\u91CC\u8D77 next dev,\u5899\u4F1A\u81EA\u52A8\u6062\u590D\u3002"));
|
|
2583
|
+
const p = h("p");
|
|
2584
|
+
p.appendChild(h("code", void 0, "pnpm dev"));
|
|
2585
|
+
card.appendChild(p);
|
|
2586
|
+
}
|
|
2587
|
+
card.appendChild(h("div", "cs-err", String(err)));
|
|
2588
|
+
card.appendChild(h("p", "cs-dim", "\u6BCF 3 \u79D2\u81EA\u52A8\u91CD\u8BD5\u4E2D\u2026"));
|
|
2589
|
+
const btn = h("button", void 0, "\u7ACB\u5373\u91CD\u8BD5");
|
|
2590
|
+
btn.addEventListener("click", () => void tryLoadRegistry());
|
|
2591
|
+
card.appendChild(btn);
|
|
2592
|
+
boot2.appendChild(card);
|
|
2593
|
+
boot2.hidden = false;
|
|
2594
|
+
}
|
|
2595
|
+
var retryTimer = null;
|
|
2596
|
+
async function tryLoadRegistry() {
|
|
2597
|
+
try {
|
|
2598
|
+
const reg = await fetchRegistry();
|
|
2599
|
+
syncEntries(reg);
|
|
2600
|
+
remeasureAll();
|
|
2601
|
+
renderPins();
|
|
2602
|
+
qs("#cs-boot").hidden = true;
|
|
2603
|
+
if (retryTimer !== null) {
|
|
2604
|
+
clearInterval(retryTimer);
|
|
2605
|
+
retryTimer = null;
|
|
2606
|
+
}
|
|
2607
|
+
return true;
|
|
2608
|
+
} catch (err) {
|
|
2609
|
+
showBootError(err);
|
|
2610
|
+
if (retryTimer === null) retryTimer = setInterval(() => void tryLoadRegistry(), 3e3);
|
|
2611
|
+
return false;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
function showTarget() {
|
|
2615
|
+
const info = state.info;
|
|
2616
|
+
const el = qs("#cs-target");
|
|
2617
|
+
el.textContent = info ? `${info.target} \xB7 ${info.designDir}` : "";
|
|
2618
|
+
el.title = info ? `v${info.version} \xB7 ${info.projectRoot}` : "";
|
|
2619
|
+
}
|
|
2620
|
+
function onRegistryEvent(entries) {
|
|
2621
|
+
void reloadUntilMatches(entries.map((e) => e.id));
|
|
2622
|
+
}
|
|
2623
|
+
async function reloadUntilMatches(wantIds, tries = 4) {
|
|
2624
|
+
const want = new Set(wantIds);
|
|
2625
|
+
for (let i = 0; i < tries; i++) {
|
|
2626
|
+
if (!await tryLoadRegistry()) return;
|
|
2627
|
+
const got = new Set(state.entries.map((e) => e.id));
|
|
2628
|
+
if (got.size === want.size && [...want].every((id) => got.has(id))) return;
|
|
2629
|
+
await new Promise((r) => setTimeout(r, 700));
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
function onAnnotations(list) {
|
|
2633
|
+
state.annotations = list;
|
|
2634
|
+
renderPins();
|
|
2635
|
+
}
|
|
2636
|
+
async function boot() {
|
|
2637
|
+
initView();
|
|
2638
|
+
initSelect();
|
|
2639
|
+
initHud();
|
|
2640
|
+
initPins();
|
|
2641
|
+
initArgsPanel();
|
|
2642
|
+
initWall();
|
|
2643
|
+
initSidebar();
|
|
2644
|
+
initModes();
|
|
2645
|
+
initRefs();
|
|
2646
|
+
onViewChange(refreshBoxes);
|
|
2647
|
+
let mountTimer = null;
|
|
2648
|
+
onViewChange(() => {
|
|
2649
|
+
if (mountTimer !== null) clearTimeout(mountTimer);
|
|
2650
|
+
mountTimer = setTimeout(mountVisible, 180);
|
|
2651
|
+
});
|
|
2652
|
+
qs("#cs-copy").addEventListener("click", () => void copyContext());
|
|
2653
|
+
qs("#cs-push").addEventListener("click", () => void pushToClaudeCode());
|
|
2654
|
+
try {
|
|
2655
|
+
state.info = await fetchState();
|
|
2656
|
+
} catch (err) {
|
|
2657
|
+
showBootError(err);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
showTarget();
|
|
2661
|
+
restoreView();
|
|
2662
|
+
await tryLoadRegistry();
|
|
2663
|
+
try {
|
|
2664
|
+
state.annotations = await fetchAnnotations();
|
|
2665
|
+
renderPins();
|
|
2666
|
+
} catch {
|
|
2667
|
+
}
|
|
2668
|
+
connectEvents({
|
|
2669
|
+
registry: onRegistryEvent,
|
|
2670
|
+
annotations: onAnnotations
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
void boot();
|
|
2674
|
+
})();
|
|
2675
|
+
//# sourceMappingURL=app.js.map
|