@fenglimg/cocos-state-controller 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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/assets/script/controller/Capability.ts +100 -0
  4. package/assets/script/controller/Capability.ts.meta +10 -0
  5. package/assets/script/controller/CapabilityRegistry.ts +116 -0
  6. package/assets/script/controller/CapabilityRegistry.ts.meta +10 -0
  7. package/assets/script/controller/EnumPropRefMap.ts +232 -0
  8. package/assets/script/controller/EnumPropRefMap.ts.meta +10 -0
  9. package/assets/script/controller/NestedCtrlData.ts +199 -0
  10. package/assets/script/controller/NestedCtrlData.ts.meta +10 -0
  11. package/assets/script/controller/PrefabIntrospection.ts +151 -0
  12. package/assets/script/controller/PrefabIntrospection.ts.meta +10 -0
  13. package/assets/script/controller/Props.meta +13 -0
  14. package/assets/script/controller/StateControllerV2.ts +1957 -0
  15. package/assets/script/controller/StateControllerV2.ts.meta +10 -0
  16. package/assets/script/controller/StateEnumV2.ts +165 -0
  17. package/assets/script/controller/StateEnumV2.ts.meta +10 -0
  18. package/assets/script/controller/StateErrorManagerV2.ts +217 -0
  19. package/assets/script/controller/StateErrorManagerV2.ts.meta +10 -0
  20. package/assets/script/controller/StatePropHandlerV2.ts +316 -0
  21. package/assets/script/controller/StatePropHandlerV2.ts.meta +10 -0
  22. package/assets/script/controller/StatePropertyControlService.ts +148 -0
  23. package/assets/script/controller/StatePropertyControlService.ts.meta +10 -0
  24. package/assets/script/controller/StateSelectV2.ts +4542 -0
  25. package/assets/script/controller/StateSelectV2.ts.meta +10 -0
  26. package/assets/script/controller/capabilities/AutoSyncCapability.ts +30 -0
  27. package/assets/script/controller/capabilities/AutoSyncCapability.ts.meta +10 -0
  28. package/assets/script/controller/capabilities/EventCapability.ts +144 -0
  29. package/assets/script/controller/capabilities/EventCapability.ts.meta +10 -0
  30. package/assets/script/controller/capabilities/MigrationCapability.ts +94 -0
  31. package/assets/script/controller/capabilities/MigrationCapability.ts.meta +10 -0
  32. package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts +157 -0
  33. package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts.meta +10 -0
  34. package/assets/script/controller/capabilities/PropertyControlCapability.ts +124 -0
  35. package/assets/script/controller/capabilities/PropertyControlCapability.ts.meta +10 -0
  36. package/assets/script/controller/capabilities/RecordingCapability.ts +69 -0
  37. package/assets/script/controller/capabilities/RecordingCapability.ts.meta +10 -0
  38. package/assets/script/controller/capabilities/SelectedPageIdCapability.ts +88 -0
  39. package/assets/script/controller/capabilities/SelectedPageIdCapability.ts.meta +10 -0
  40. package/assets/script/controller/capabilities.meta +13 -0
  41. package/assets/script/controller/props/CtrlInspectorGroups.ts +138 -0
  42. package/assets/script/controller/props/CtrlInspectorGroups.ts.meta +10 -0
  43. package/assets/script/controller/props/SelectInspectorGroups.ts +104 -0
  44. package/assets/script/controller/props/SelectInspectorGroups.ts.meta +10 -0
  45. package/bin/csc.js +286 -0
  46. package/package.json +60 -0
  47. package/packages/state-controller-v2-panel/README.md +80 -0
  48. package/packages/state-controller-v2-panel/inspector-inject.js +917 -0
  49. package/packages/state-controller-v2-panel/inspector-probe.json +3767 -0
  50. package/packages/state-controller-v2-panel/lib/handlers.js +534 -0
  51. package/packages/state-controller-v2-panel/main.js +149 -0
  52. package/packages/state-controller-v2-panel/package.json +32 -0
  53. package/packages/state-controller-v2-panel/panel/build.js +23 -0
  54. package/packages/state-controller-v2-panel/panel/logic.js +1207 -0
  55. package/packages/state-controller-v2-panel/panel/styles.css +454 -0
  56. package/packages/state-controller-v2-panel/panel/template.html +296 -0
  57. package/packages/state-controller-v2-panel/scene-accessor.js +657 -0
  58. package/skills/cocos-state-controller/SKILL.md +28 -0
  59. package/skills/cocos-state-controller/refs/cli-usage.md +78 -0
  60. package/skills/cocos-state-controller/refs/editor-guide.md +127 -0
  61. package/skills/cocos-state-controller/refs/migrate.md +106 -0
  62. package/skills/cocos-state-controller/refs/upstream-pr.md +66 -0
  63. package/tools/migration/migrate-prefab-v1-to-v2.js +608 -0
  64. package/tools/state-controller-sync-manifest.json +33 -0
@@ -0,0 +1,917 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * state-controller-v2-panel · inspector 注入层
5
+ *
6
+ * 当前阶段 = P0 探针: 注入编辑器渲染进程, 探测属性检查器 (inspector) 面板的真实 DOM,
7
+ * 找出 "属性行 DOM → propRef (compName.propKey)" 这座桥. 桥确认后才铺 P1–P3.
8
+ *
9
+ * 设计约束 (照搬 hierarchy-plus 教训):
10
+ * - 走 require('electron').webContents.getAllWebContents() + executeJavaScript 注入
11
+ * - 注入脚本必须自包含 (字符串化后在渲染进程跑, 不能引用外部变量)
12
+ * - 主进程侧只做同步活, 不阻塞
13
+ *
14
+ * 本文件当前只导出 probeInspector(); P1–P3 的 inject/apply/clear/withdraw 后续追加在此.
15
+ */
16
+
17
+ // ===== P0 探针: 在渲染进程执行的自包含脚本 =====
18
+ // 返回一个结构化对象: { found, inspectorPanel, panels, candidateCounts, samples, skeleton }
19
+ function buildProbeScript() {
20
+ const fn = function () {
21
+ const CAP_TRAVERSE = 8000; // 深度遍历节点上限 (防卡)
22
+ const CAP_SAMPLE = 18; // 采样属性行数量
23
+
24
+ // ---- 工具: 穿透 shadowRoot 按 id 找 ----
25
+ function deepFindById(root, id) {
26
+ if (!root) return null;
27
+ try {
28
+ const d = root.getElementById ? root.getElementById(id)
29
+ : (root.querySelector ? root.querySelector('#' + id) : null);
30
+ if (d) return d;
31
+ } catch (e) {}
32
+ const all = root.querySelectorAll ? root.querySelectorAll('*') : [];
33
+ for (let i = 0; i < all.length; i++) {
34
+ if (all[i].shadowRoot) {
35
+ const f = deepFindById(all[i].shadowRoot, id);
36
+ if (f) return f;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+
42
+ // ---- 工具: 深度遍历 (穿透 shadowRoot), 收集所有元素 ----
43
+ function deepCollect(root, out) {
44
+ if (!root || out.length >= CAP_TRAVERSE) return;
45
+ const all = root.querySelectorAll ? root.querySelectorAll('*') : [];
46
+ for (let i = 0; i < all.length; i++) {
47
+ if (out.length >= CAP_TRAVERSE) return;
48
+ const el = all[i];
49
+ out.push(el);
50
+ if (el.shadowRoot) deepCollect(el.shadowRoot, out);
51
+ }
52
+ }
53
+
54
+ // ---- 工具: 元素简述 ----
55
+ function brief(el) {
56
+ if (!el || !el.tagName) return null;
57
+ let cls = '';
58
+ try { cls = (el.className && el.className.toString ? el.className.toString() : '').slice(0, 50); } catch (e) {}
59
+ let text = '';
60
+ try { text = (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 28); } catch (e) {}
61
+ return { tag: el.tagName.toLowerCase(), id: el.id || '', cls: cls, text: text };
62
+ }
63
+
64
+ // ---- 工具: dump 元素的关键属性 (找桥用) ----
65
+ function dumpAttrs(el) {
66
+ const out = {};
67
+ try {
68
+ const at = el.attributes || [];
69
+ for (let i = 0; i < at.length; i++) {
70
+ const n = at[i].name;
71
+ // 只留可能是桥的属性, 跳过样式噪音
72
+ if (n === 'class' || n === 'style') continue;
73
+ out[n] = (at[i].value || '').slice(0, 80);
74
+ }
75
+ } catch (e) {}
76
+ return out;
77
+ }
78
+
79
+ // ---- 工具: 浅 dump 一个对象的字段 (primitive + 一层引用名), 找真 propKey ----
80
+ function dumpObjShallow(o, cap) {
81
+ const out = {};
82
+ if (!o || typeof o !== 'object') return out;
83
+ let keys = [];
84
+ try { keys = Object.keys(o); } catch (e) { return out; }
85
+ out.__keys = keys.slice(0, cap || 30);
86
+ for (let i = 0; i < keys.length && i < (cap || 30); i++) {
87
+ const k = keys[i];
88
+ try {
89
+ const val = o[k];
90
+ const t = typeof val;
91
+ if (val === null || t === 'string' || t === 'number' || t === 'boolean') {
92
+ out[k] = String(val).slice(0, 90);
93
+ } else if (t === 'object') {
94
+ // 标注引用类型 (找 cc.Component / path 节点)
95
+ let tag = '<obj>';
96
+ try { tag = '<' + (val.__classname__ || (val.constructor && val.constructor.name) || 'obj') + '>'; } catch (e) {}
97
+ out[k] = tag;
98
+ } else if (t === 'function') {
99
+ out[k] = '<fn>';
100
+ }
101
+ } catch (e) {}
102
+ }
103
+ return out;
104
+ }
105
+
106
+ // ---- 工具: 探测元素上挂的框架数据 (Vue / Polymer) ----
107
+ function dumpFrameworkData(el) {
108
+ const out = {};
109
+ try {
110
+ if (el.__vue__) {
111
+ out.hasVue = true;
112
+ const v = el.__vue__;
113
+ out.vueKeys = Object.keys(v).filter(function (k) { return k[0] !== '_' || k === '_props'; }).slice(0, 25);
114
+ ['path', 'value', 'name', 'compName', 'type', 'attrs'].forEach(function (k) {
115
+ try {
116
+ if (v[k] !== undefined && (typeof v[k] !== 'object' || v[k] === null)) out['vue.' + k] = String(v[k]).slice(0, 80);
117
+ } catch (e) {}
118
+ });
119
+ // 深挖 target: 真 propKey / path / 组件引用可能在此
120
+ try {
121
+ if (v.target && typeof v.target === 'object') {
122
+ out['vue.target'] = dumpObjShallow(v.target, 30);
123
+ }
124
+ } catch (e) {}
125
+ // 深挖 _props (Vue prop bindings)
126
+ try {
127
+ if (v._props && typeof v._props === 'object') {
128
+ out['vue._props'] = dumpObjShallow(v._props, 30);
129
+ }
130
+ } catch (e) {}
131
+ // 有些版本把 attrs/path 放在 $attrs / $options.propsData
132
+ try {
133
+ if (v.$attrs) out['vue.$attrs'] = dumpObjShallow(v.$attrs, 20);
134
+ } catch (e) {}
135
+ }
136
+ } catch (e) {}
137
+ // Polymer 风格的属性
138
+ ['path', '_propPath', '_path', '_value', 'name', '_name'].forEach(function (k) {
139
+ try {
140
+ if (el[k] !== undefined && (typeof el[k] !== 'object' || el[k] === null)) out['prop.' + k] = String(el[k]).slice(0, 80);
141
+ } catch (e) {}
142
+ });
143
+ return out;
144
+ }
145
+
146
+ // ---- 工具: 向上爬祖先链 (穿透 shadow 边界), 找 component 分组/名字 ----
147
+ function climb(el, levels) {
148
+ const chain = [];
149
+ let cur = el;
150
+ for (let i = 0; i < levels && cur; i++) {
151
+ let p = cur.parentElement;
152
+ if (!p) {
153
+ const root = cur.getRootNode && cur.getRootNode();
154
+ p = (root && root.host) ? root.host : null;
155
+ }
156
+ if (!p) break;
157
+ const b = brief(p);
158
+ // 顺带把祖先的 path/comp 类属性也带上 (component header 常在此)
159
+ const fw = dumpFrameworkData(p);
160
+ if (Object.keys(fw).length) b.fw = fw;
161
+ chain.push(b);
162
+ cur = p;
163
+ }
164
+ return chain;
165
+ }
166
+
167
+ // ====== 主流程 ======
168
+ // 1) 定位 inspector 面板: 先试常见 id, 再枚举所有面板兜底
169
+ const tryIds = ['inspector', 'node', 'cc-inspector'];
170
+ let inspectorPanel = null;
171
+ for (let i = 0; i < tryIds.length; i++) {
172
+ const p = deepFindById(document, tryIds[i]);
173
+ if (p && p.shadowRoot) { inspectorPanel = p; break; }
174
+ }
175
+
176
+ // 枚举所有带 shadowRoot 的 *panel* frame (报 id/pkg, 帮我们认面板)
177
+ const panels = [];
178
+ (function () {
179
+ const all = [];
180
+ deepCollect(document, all);
181
+ for (let i = 0; i < all.length; i++) {
182
+ const el = all[i];
183
+ if (!el.shadowRoot) continue;
184
+ const tag = el.tagName.toLowerCase();
185
+ if (tag.indexOf('panel') >= 0 || tag.indexOf('dock') >= 0 || el.id) {
186
+ let pkg = '';
187
+ try { pkg = el.getAttribute('package') || el.getAttribute('name') || el.getAttribute('panel-id') || ''; } catch (e) {}
188
+ if (tag.indexOf('panel') >= 0 || pkg || el.id) {
189
+ panels.push({ tag: tag, id: el.id || '', pkg: pkg });
190
+ }
191
+ }
192
+ }
193
+ })();
194
+
195
+ // 兜底: 若没找到 inspector, 从 panels 里挑 id/pkg 含 inspector 的
196
+ if (!inspectorPanel) {
197
+ const all = [];
198
+ deepCollect(document, all);
199
+ for (let i = 0; i < all.length; i++) {
200
+ const el = all[i];
201
+ if (!el.shadowRoot) continue;
202
+ const hay = ((el.id || '') + ' ' + (el.getAttribute && (el.getAttribute('package') || '') || '')).toLowerCase();
203
+ if (hay.indexOf('inspector') >= 0) { inspectorPanel = el; break; }
204
+ }
205
+ }
206
+
207
+ if (!inspectorPanel || !inspectorPanel.shadowRoot) {
208
+ return { found: false, reason: 'no inspector panel', panels: panels };
209
+ }
210
+
211
+ // 2) 在 inspector shadowRoot 内深度收集所有元素
212
+ const elems = [];
213
+ deepCollect(inspectorPanel.shadowRoot, elems);
214
+
215
+ // 3) 按 4 种策略找 "属性行" 候选, 报命中数
216
+ const byTag = []; // <ui-prop>
217
+ const byPathAttr = []; // 带 path 属性
218
+ const byVue = []; // 挂 __vue__ 且含 path/value
219
+ const byClass = []; // class 含 prop
220
+ for (let i = 0; i < elems.length; i++) {
221
+ const el = elems[i];
222
+ const tag = el.tagName ? el.tagName.toLowerCase() : '';
223
+ if (tag === 'ui-prop' || tag === 'cc-prop' || tag.indexOf('-prop') >= 0) byTag.push(el);
224
+ let hasPath = false;
225
+ try { hasPath = el.hasAttribute && el.hasAttribute('path'); } catch (e) {}
226
+ if (hasPath) byPathAttr.push(el);
227
+ if (el.__vue__) {
228
+ try {
229
+ const v = el.__vue__;
230
+ if (v.path !== undefined || v.value !== undefined || v.attrs !== undefined) byVue.push(el);
231
+ } catch (e) {}
232
+ }
233
+ let cls = '';
234
+ try { cls = (el.className && el.className.toString) ? el.className.toString().toLowerCase() : ''; } catch (e) {}
235
+ if (cls.indexOf('prop') >= 0) byClass.push(el);
236
+ }
237
+
238
+ const candidateCounts = {
239
+ totalElemsInInspector: elems.length,
240
+ byTag_uiProp: byTag.length,
241
+ byPathAttr: byPathAttr.length,
242
+ byVuePathOrValue: byVue.length,
243
+ byClassProp: byClass.length,
244
+ };
245
+
246
+ // 4) 选命中最多的策略采样 (优先 ui-prop, 再 pathAttr, 再 vue, 再 class)
247
+ let pick = byTag.length ? byTag : (byPathAttr.length ? byPathAttr : (byVue.length ? byVue : byClass));
248
+ const samples = [];
249
+ for (let i = 0; i < pick.length && samples.length < CAP_SAMPLE; i++) {
250
+ const el = pick[i];
251
+ samples.push({
252
+ self: brief(el),
253
+ attrs: dumpAttrs(el),
254
+ fw: dumpFrameworkData(el),
255
+ ancestors: climb(el, 6),
256
+ });
257
+ }
258
+
259
+ // 5) inspector 顶层骨架 (前几层, 帮我们看 component 分组结构)
260
+ const skeleton = [];
261
+ (function () {
262
+ const top = inspectorPanel.shadowRoot.children || [];
263
+ function walk(el, depth) {
264
+ if (depth > 4 || skeleton.length > 120) return;
265
+ const b = brief(el);
266
+ b.depth = depth;
267
+ const fw = dumpFrameworkData(el);
268
+ if (Object.keys(fw).length) b.fw = fw;
269
+ skeleton.push(b);
270
+ const kids = el.children || [];
271
+ for (let i = 0; i < kids.length; i++) walk(kids[i], depth + 1);
272
+ if (el.shadowRoot) {
273
+ const sk = el.shadowRoot.children || [];
274
+ for (let i = 0; i < sk.length; i++) walk(sk[i], depth + 1);
275
+ }
276
+ }
277
+ for (let i = 0; i < top.length; i++) walk(top[i], 0);
278
+ })();
279
+
280
+ return {
281
+ found: true,
282
+ inspectorPanel: brief(inspectorPanel),
283
+ panels: panels,
284
+ candidateCounts: candidateCounts,
285
+ pickedStrategy: byTag.length ? 'ui-prop tag' : (byPathAttr.length ? 'path attr' : (byVue.length ? 'vue path/value' : 'class prop')),
286
+ samples: samples,
287
+ skeleton: skeleton,
288
+ };
289
+ };
290
+ return '(' + fn.toString() + ')()';
291
+ }
292
+
293
+ /**
294
+ * P0: 在所有 webContents 跑探针, 把命中 inspector 的那个结果写成 JSON 文件 + 控制台摘要.
295
+ * 操作前提: 编辑器里先选中一个挂了组件的节点 (inspector 才有内容可探).
296
+ */
297
+ function probeInspector() {
298
+ const script = buildProbeScript();
299
+ let wrote = false;
300
+ try {
301
+ const all = require('electron').webContents.getAllWebContents();
302
+ if (!all || !all.length) {
303
+ Editor.warn('[sc-inspector-probe] 拿不到 webContents');
304
+ return;
305
+ }
306
+ all.forEach(function (wc) {
307
+ try {
308
+ wc.executeJavaScript(script).then(function (r) {
309
+ if (!r) return;
310
+ if (!r.found) {
311
+ // 只在没有任何 wc 写成功时, 留个失败线索
312
+ return;
313
+ }
314
+ if (wrote) return;
315
+ wrote = true;
316
+ try {
317
+ const fs = require('fs');
318
+ const path = require('path');
319
+ const out = path.join(__dirname, 'inspector-probe.json');
320
+ fs.writeFileSync(out, JSON.stringify(r, null, 2), 'utf8');
321
+ Editor.log('[sc-inspector-probe] ✅ DOM 探测完成 → ' + out
322
+ + '\n inspector 面板: <' + (r.inspectorPanel && r.inspectorPanel.tag) + '> #' + (r.inspectorPanel && r.inspectorPanel.id)
323
+ + '\n 命中策略: ' + r.pickedStrategy
324
+ + '\n 候选计数: ' + JSON.stringify(r.candidateCounts)
325
+ + '\n 把这个 json 发我即可确认 "行↔propRef" 桥');
326
+ } catch (e) {
327
+ Editor.warn('[sc-inspector-probe] 写文件失败: ' + e.message
328
+ + '\n--- 直接 dump (截断) ---\n' + JSON.stringify(r).slice(0, 4000));
329
+ }
330
+ }).catch(function () {});
331
+ } catch (e) {}
332
+ });
333
+ // 兜底提示: 1.2s 后若没写成功, 多半是没选中节点
334
+ setTimeout(function () {
335
+ if (!wrote) {
336
+ Editor.warn('[sc-inspector-probe] 未探到 inspector 内容 — 请先在场景里【选中一个挂了组件的节点】(最好带 StateSelectV2 + cc.Sprite 等), 再跑一次探针');
337
+ }
338
+ }, 1200);
339
+ } catch (e) {
340
+ Editor.warn('[sc-inspector-probe] 注入失败: ' + e.message);
341
+ }
342
+ }
343
+
344
+ // ===== P1/P2a: 常驻注入脚本 — 给属性行按"状态机身份"贴标记 =====
345
+ // 版本号机制 (照搬 hierarchy-plus): 改注入逻辑时 VER+1, reload 扩展即覆盖旧常驻脚本, 免重启编辑器.
346
+ // P2a: 不再无脑贴 ◆; 注入侧解析每行 propRef → 发主进程 → 场景按 tracked/excluded 分类回传 → 按身份着色.
347
+ function buildResidentScript() {
348
+ const fn = function () {
349
+ const VER = 13;
350
+ const old = window.__SCI;
351
+ if (old && old.version === VER) { old.apply(); return 'same-version'; }
352
+ if (old && old.observer) { try { old.observer.disconnect(); } catch (e) {} }
353
+ if (old && old.heartbeat) { try { clearInterval(old.heartbeat); } catch (e) {} }
354
+ const SCI = window.__SCI = old || {};
355
+ SCI.version = VER;
356
+ SCI.observer = null;
357
+ SCI.enabled = true;
358
+ SCI.data = SCI.data || null;
359
+ SCI.dataSV = SCI.dataSV || null;
360
+ SCI.reqUuid = null;
361
+ SCI.lastReq = 0;
362
+
363
+ const injectedFlags = (typeof window.__SCI_FLAGS === 'object' && window.__SCI_FLAGS) ? window.__SCI_FLAGS : null;
364
+ SCI.flags = (old && old.flags) || injectedFlags || { master: true, viz: true, dirty: true, exclude: true };
365
+ SCI.setFlags = function (f) {
366
+ if (f && typeof f === 'object') {
367
+ for (const k in f) SCI.flags[k] = !!f[k];
368
+ }
369
+ SCI.apply();
370
+ };
371
+
372
+ SCI.deepFindById = function (root, id) {
373
+ if (!root) return null;
374
+ try {
375
+ const d = root.getElementById ? root.getElementById(id)
376
+ : (root.querySelector ? root.querySelector('#' + id) : null);
377
+ if (d) return d;
378
+ } catch (e) {}
379
+ const all = root.querySelectorAll ? root.querySelectorAll('*') : [];
380
+ for (let i = 0; i < all.length; i++) {
381
+ if (all[i].shadowRoot) {
382
+ const f = SCI.deepFindById(all[i].shadowRoot, id);
383
+ if (f) return f;
384
+ }
385
+ }
386
+ return null;
387
+ };
388
+
389
+ SCI.getPanel = function () {
390
+ const p = SCI.deepFindById(document, 'inspector');
391
+ return (p && p.shadowRoot) ? p : null;
392
+ };
393
+
394
+ SCI.getSelUuid = function () {
395
+ try {
396
+ const sel = Editor.Selection.curSelection('node');
397
+ return (sel && sel.length) ? sel[0] : null;
398
+ } catch (e) { return null; }
399
+ };
400
+
401
+ SCI.getSelCount = function () {
402
+ try {
403
+ const sel = Editor.Selection.curSelection('node');
404
+ return (sel && sel.length) ? sel.length : 0;
405
+ } catch (e) { return 0; }
406
+ };
407
+
408
+ SCI.rowDisplay = function (uiProp) {
409
+ try { return uiProp._name || (uiProp.__vue__ && uiProp.__vue__.name) || ''; } catch (e) { return ''; }
410
+ };
411
+
412
+ SCI.compIndexOfRow = function (uiProp) {
413
+ try {
414
+ const v = uiProp.__vue__;
415
+ if (v && v.target && typeof v.target.path === 'string') {
416
+ const m = v.target.path.match(/__comps__\.(\d+)\./);
417
+ if (m) return parseInt(m[1], 10);
418
+ }
419
+ } catch (e) {}
420
+ return -1;
421
+ };
422
+
423
+ SCI.sectionOf = function (el) {
424
+ let cur = el;
425
+ for (let i = 0; i < 14 && cur; i++) {
426
+ let p = cur.parentElement;
427
+ if (!p) { const r = cur.getRootNode && cur.getRootNode(); p = (r && r.host) ? r.host : null; }
428
+ if (!p) break;
429
+ if (p.tagName && p.tagName.toLowerCase() === 'ui-section') return p;
430
+ cur = p;
431
+ }
432
+ return null;
433
+ };
434
+
435
+ SCI.maybeRequest = function (uuid) {
436
+ const now = Date.now();
437
+ if (uuid !== SCI.reqUuid || (now - SCI.lastReq) > 600) {
438
+ SCI.reqUuid = uuid;
439
+ SCI.lastReq = now;
440
+ SCI.request(uuid);
441
+ SCI.requestSV(uuid);
442
+ }
443
+ };
444
+
445
+ SCI.requestSV = function (uuid) {
446
+ try {
447
+ Editor.Ipc.sendToMain('state-controller-v2-panel:inspector-req-state-values', { uuid: uuid },
448
+ function (err, res) {
449
+ const map = {};
450
+ let props = null, states = null, selectedIndex = -1;
451
+ if (!err && res && res.ok && res.hasSelect) {
452
+ props = res.props || {};
453
+ states = res.states || [];
454
+ selectedIndex = (typeof res.selectedIndex === 'number') ? res.selectedIndex : -1;
455
+ const rows = res.rows || [];
456
+ for (let i = 0; i < rows.length; i++) {
457
+ const it = rows[i];
458
+ map[it.scope + '|' + it.display] = { varies: !!it.variesAcrossStates, override: !!it.overriddenAtCurrent, dirty: !!it.dirty, refs: it.refs };
459
+ }
460
+ }
461
+ SCI.dataSV = { uuid: uuid, map: map, props: props, states: states, selectedIndex: selectedIndex };
462
+ SCI.apply();
463
+ });
464
+ } catch (e) {}
465
+ };
466
+
467
+ SCI.request = function (uuid) {
468
+ try {
469
+ Editor.Ipc.sendToMain('state-controller-v2-panel:inspector-req-status', { uuid: uuid },
470
+ function (err, res) {
471
+ const map = {};
472
+ if (!err && res && res.ok && res.items) {
473
+ for (let i = 0; i < res.items.length; i++) {
474
+ const it = res.items[i];
475
+ map[it.scope + '|' + it.display] = { kind: it.kind, refs: it.refs };
476
+ }
477
+ }
478
+ SCI.data = { uuid: uuid, map: map };
479
+ SCI.apply();
480
+ });
481
+ } catch (e) {}
482
+ };
483
+
484
+ SCI.fmtVal = function (v) {
485
+ if (v === null || v === undefined) return '—';
486
+ const t = typeof v;
487
+ if (t === 'number') return (Math.round(v * 1000) / 1000) + '';
488
+ if (t === 'boolean' || t === 'string') return v + '';
489
+ if (t === 'object') {
490
+ if (v._t === 'Color') return 'rgba(' + v.r + ',' + v.g + ',' + v.b + ',' + v.a + ')';
491
+ if (v._t === 'Vec2') return '(' + v.x + ', ' + v.y + ')';
492
+ if (v._t === 'Vec3') return '(' + v.x + ', ' + v.y + ', ' + v.z + ')';
493
+ if (v._t === 'Size') return v.width + '×' + v.height;
494
+ if (v._t === 'Quat') return '(' + v.x + ',' + v.y + ',' + v.z + ',' + v.w + ')';
495
+ if (v._t === 'Asset') return '' + v.id;
496
+ }
497
+ return '?';
498
+ };
499
+
500
+ SCI.fmtValHTML = function (v) {
501
+ if (v && typeof v === 'object' && v._t === 'Color') {
502
+ const c = 'rgba(' + v.r + ',' + v.g + ',' + v.b + ',' + (v.a / 255).toFixed(2) + ')';
503
+ return '<span style="display:inline-block;width:10px;height:10px;border-radius:2px;border:1px solid #555;'
504
+ + 'vertical-align:middle;margin-right:4px;background:' + c + '"></span>'
505
+ + '<span style="vertical-align:middle">rgba(' + v.r + ',' + v.g + ',' + v.b + ',' + v.a + ')</span>';
506
+ }
507
+ return '<span style="vertical-align:middle">' + String(SCI.fmtVal(v)).replace(/</g, '&lt;') + '</span>';
508
+ };
509
+
510
+ SCI.ensureTip = function () {
511
+ if (SCI.tip && SCI.tip.isConnected) return SCI.tip;
512
+ const t = document.createElement('div');
513
+ t.className = '__sci-sv-tip';
514
+ t.style.cssText = 'position:fixed;z-index:2147483647;pointer-events:none;'
515
+ + 'background:#252525;border:1px solid #444;border-radius:6px;'
516
+ + 'box-shadow:0 8px 24px rgba(0,0,0,0.6);font-family:Menlo,Monaco,"Courier New",monospace;'
517
+ + 'font-size:12px;line-height:1.4;display:none;opacity:0;transition:opacity 0.1s ease-in-out;'
518
+ + 'width:360px;flex-direction:column;overflow:hidden;';
519
+ document.body.appendChild(t);
520
+ SCI.tip = t;
521
+ return t;
522
+ };
523
+
524
+ SCI.buildTipHTML = function (badgeData) {
525
+ const isExcluded = badgeData.kind === 'excluded';
526
+ const isLoose = badgeData.kind === 'loose';
527
+ const isMixed = badgeData.kind === 'mixed';
528
+
529
+ let badgeHTML = '';
530
+ const PILL = 'border:2px solid #000;padding:1px 6px;font-size:10px;font-weight:700;margin-left:8px;';
531
+ if (isExcluded) badgeHTML = '<span style="background:#9a9a9a;color:#000;' + PILL + '">已排除</span>';
532
+ else if (isLoose) badgeHTML = '<span style="background:#FF6B6B;color:#000;' + PILL + '">未受控</span>';
533
+ else if (isMixed) badgeHTML = '<span style="background:#FFDE4D;color:#000;' + PILL + '">部分未受控</span>';
534
+ else if (badgeData.override) badgeHTML = '<span style="background:#FFDE4D;color:#000;' + PILL + '">已覆盖 Default</span>';
535
+ else if (badgeData.varies) badgeHTML = '<span style="background:#4D96FF;color:#fff;' + PILL + '">状态驱动</span>';
536
+
537
+ const display = badgeData.display || (badgeData.refs && badgeData.refs[0]) || 'Property';
538
+
539
+ let html = '<div style="background:#333;padding:8px 12px;border-bottom:1px solid #444;display:flex;justify-content:space-between;align-items:center;">'
540
+ + '<div style="font-weight:bold;color:#fff;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + display + '</div>'
541
+ + '<div style="flex-shrink:0;">' + badgeHTML + '</div>'
542
+ + '</div>'
543
+ + '<div style="padding:8px 12px;max-height:300px;overflow-y:auto;display:block;">';
544
+
545
+ if (!badgeData.refs || !badgeData.refs.length) {
546
+ html += '<div style="color:#888;font-size:12px;">无状态数据</div></div>';
547
+ return html;
548
+ }
549
+
550
+ const props = (SCI.dataSV && SCI.dataSV.props) || null;
551
+ const states = (SCI.dataSV && SCI.dataSV.states) || null;
552
+ const selIdx = (SCI.dataSV && typeof SCI.dataSV.selectedIndex === 'number') ? SCI.dataSV.selectedIndex : -1;
553
+
554
+ if (!props || !states || !states.length) {
555
+ html += '<div style="color:#888;font-size:12px;">无状态数据</div></div>';
556
+ return html;
557
+ }
558
+
559
+ html += '<table style="width:100%;border-collapse:collapse;table-layout:fixed;font-size:12px;margin:0;padding:0;">';
560
+ for (let s = 0; s < states.length; s++) {
561
+ const idx = states[s].index;
562
+ const isCur = idx === selIdx;
563
+ const nm = states[s].name || ('S' + idx);
564
+
565
+ let valHTML = '<div style="display:flex;flex-wrap:wrap;gap:4px 8px;">';
566
+ let hasVal = false;
567
+ for (let r = 0; r < badgeData.refs.length; r++) {
568
+ const p = props[badgeData.refs[r]];
569
+ if (!p) continue;
570
+ const rawVal = p.valueByState ? p.valueByState[idx] : undefined;
571
+ const vStr = SCI.fmtValHTML(rawVal);
572
+ const keyPrefix = badgeData.refs.length > 1 ? '<span style="color:#777;">' + badgeData.refs[r].split('.').pop() + '=</span>' : '';
573
+ valHTML += '<span style="display:inline-flex;align-items:center;line-height:1.4;">' + keyPrefix + vStr + '</span>';
574
+ hasVal = true;
575
+ }
576
+ if (!hasVal) valHTML += '<span style="color:#777;line-height:1.4;">—</span>';
577
+ valHTML += '</div>';
578
+
579
+ html += '<tr>'
580
+ + '<td style="width:120px;padding:6px 8px 6px 0;vertical-align:top;border-bottom:1px solid #333;">'
581
+ + '<div style="display:flex;align-items:flex-start;">'
582
+ + '<span style="width:14px;flex-shrink:0;color:#5ab1ef;font-weight:bold;line-height:1.4;">' + (isCur ? '▸' : '') + '</span>'
583
+ + '<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;color:' + (isCur ? '#fff' : '#999') + ';font-weight:' + (isCur ? 'bold' : 'normal') + ';" title="' + nm.replace(/"/g, '&quot;') + '">' + nm + '</span>'
584
+ + '</div>'
585
+ + '</td>'
586
+ + '<td style="padding:6px 0;vertical-align:top;color:#9cdcfe;border-bottom:1px solid #333;word-wrap:break-word;">'
587
+ + valHTML
588
+ + '</td>'
589
+ + '</tr>';
590
+ }
591
+ html += '</table></div>';
592
+ return html;
593
+ };
594
+
595
+ SCI.showTip = function (container) {
596
+ if (!container || !container.__badgeData) return;
597
+ const t = SCI.ensureTip();
598
+ SCI.tipFor = container;
599
+ t.innerHTML = SCI.buildTipHTML(container.__badgeData);
600
+ t.style.display = 'flex';
601
+ const r = container.getBoundingClientRect();
602
+ const tw = t.offsetWidth, th = t.offsetHeight;
603
+ let left = r.right + 6, top = r.top;
604
+ if (left + tw > window.innerWidth - 8) left = Math.max(8, r.left - tw - 6);
605
+ if (top + th > window.innerHeight - 8) top = Math.max(8, window.innerHeight - th - 8);
606
+ t.style.left = left + 'px';
607
+ t.style.top = top + 'px';
608
+ t.style.opacity = '1';
609
+ };
610
+
611
+ SCI.hideTip = function () {
612
+ SCI.tipFor = null;
613
+ if (SCI.tip) {
614
+ SCI.tip.style.opacity = '0';
615
+ setTimeout(function() {
616
+ if (!SCI.tipFor && SCI.tip) SCI.tip.style.display = 'none';
617
+ }, 120);
618
+ }
619
+ };
620
+
621
+ SCI.updateRowStatus = function(row, kind, refs, svHit, flags) {
622
+ let container = null;
623
+ const kids = row.children;
624
+ for (let k = 0; k < kids.length; k++) {
625
+ if (kids[k].className === '__sci-indicator') { container = kids[k]; break; }
626
+ }
627
+
628
+ const isExcluded = flags.exclude && kind === 'excluded';
629
+ const isLoose = flags.exclude && kind === 'loose';
630
+ const isMixed = flags.exclude && kind === 'mixed';
631
+
632
+ const varies = flags.viz && svHit && svHit.varies;
633
+ const override = flags.viz && svHit && svHit.override;
634
+ const isDirty = flags.dirty && svHit && svHit.dirty;
635
+
636
+ if (!isExcluded && !isLoose && !isMixed && !varies && !override && !isDirty) {
637
+ if (container) {
638
+ if (SCI.tipFor === container) SCI.hideTip();
639
+ container.parentNode.removeChild(container);
640
+ }
641
+ row.style.opacity = '';
642
+ row.removeAttribute('data-sci-dirty');
643
+ return;
644
+ }
645
+
646
+ if (!container) {
647
+ container = document.createElement('div');
648
+ container.className = '__sci-indicator';
649
+ container.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;'
650
+ + 'width:18px;align-self:stretch;position:relative;'
651
+ + 'flex:0 0 auto;cursor:pointer;user-select:none;'
652
+ + 'box-sizing:border-box;margin-right:2px;';
653
+ row.insertBefore(container, row.firstChild);
654
+ }
655
+
656
+ container.setAttribute('data-refs', (refs || []).join(','));
657
+ container.setAttribute('data-kind', kind || '');
658
+
659
+ container.__badgeData = {
660
+ kind: kind,
661
+ refs: refs || (svHit && svHit.refs) || [],
662
+ display: SCI.rowDisplay(row),
663
+ varies: varies,
664
+ override: override,
665
+ dirty: isDirty
666
+ };
667
+
668
+ let svg = '';
669
+ if (isExcluded) {
670
+ svg = '<svg viewBox="0 0 16 16" width="14" height="14"><rect x="2.5" y="2.5" width="11" height="11" fill="#9a9a9a" stroke="#000" stroke-width="1.5"/><line x1="4.5" y1="11.5" x2="11.5" y2="4.5" stroke="#000" stroke-width="2"/></svg>';
671
+ row.style.opacity = '0.5';
672
+ } else if (isLoose) {
673
+ svg = '<svg viewBox="0 0 16 16" width="14" height="14"><path d="M8 1.5 L14.8 13.5 L1.2 13.5 Z" fill="#FF6B6B" stroke="#000" stroke-width="1.5" stroke-linejoin="miter"/><rect x="7.15" y="6" width="1.7" height="3.4" fill="#000"/><rect x="7.15" y="10.4" width="1.7" height="1.7" fill="#000"/></svg>';
674
+ row.style.opacity = '';
675
+ } else if (isMixed) {
676
+ svg = '<svg viewBox="0 0 16 16" width="14" height="14"><rect x="2.5" y="2.5" width="11" height="11" fill="#2b2b2b"/><path d="M2.5 2.5 L13.5 2.5 L2.5 13.5 Z" fill="#FFDE4D"/><line x1="13.5" y1="2.5" x2="2.5" y2="13.5" stroke="#000" stroke-width="1.5"/><rect x="2.5" y="2.5" width="11" height="11" fill="none" stroke="#000" stroke-width="1.5"/></svg>';
677
+ row.style.opacity = '';
678
+ } else if (varies || override) {
679
+ if (override) {
680
+ svg = '<svg viewBox="0 0 16 16" width="14" height="14"><path d="M8 0.8 L15.2 8 L8 15.2 L0.8 8 Z" fill="#FFDE4D" stroke="#000" stroke-width="1.5" stroke-linejoin="miter"/><path d="M8 4.2 L11.8 8 L8 11.8 L4.2 8 Z" fill="#4D96FF" stroke="#000" stroke-width="1" stroke-linejoin="miter"/></svg>';
681
+ } else {
682
+ svg = '<svg viewBox="0 0 16 16" width="14" height="14"><path d="M8 1.5 L14.5 8 L8 14.5 L1.5 8 Z" fill="#4D96FF" stroke="#000" stroke-width="1.5" stroke-linejoin="miter"/></svg>';
683
+ }
684
+ row.style.opacity = '';
685
+ } else {
686
+ row.style.opacity = '';
687
+ }
688
+
689
+ container.innerHTML = svg;
690
+
691
+ if (isDirty) {
692
+ container.style.boxShadow = 'inset 3px 0 0 #FFDE4D';
693
+ row.setAttribute('data-sci-dirty', '1');
694
+ } else {
695
+ container.style.boxShadow = 'none';
696
+ row.removeAttribute('data-sci-dirty');
697
+ }
698
+ };
699
+
700
+ SCI.onClick = function (e) {
701
+ let el = e.target, badge = null;
702
+ for (let i = 0; i < 6 && el; i++) {
703
+ if (el.className === '__sci-indicator') { badge = el; break; }
704
+ el = el.parentElement;
705
+ }
706
+ if (!badge) return;
707
+ e.stopPropagation();
708
+ if (e.preventDefault) e.preventDefault();
709
+ const refs = (badge.getAttribute('data-refs') || '').split(',').filter(Boolean);
710
+ const kind = badge.getAttribute('data-kind');
711
+ if (!refs.length || !kind || kind === 'null') return;
712
+ const action = (kind === 'excluded') ? 'unexclude' : 'exclude';
713
+ const uuid = SCI.getSelUuid();
714
+ if (!uuid) return;
715
+ try {
716
+ Editor.Ipc.sendToMain('state-controller-v2-panel:inspector-toggle-exclude',
717
+ { uuid: uuid, refs: refs, action: action },
718
+ function () { SCI.reqUuid = null; SCI.lastReq = 0; SCI.apply(); });
719
+ } catch (err) {}
720
+ };
721
+
722
+ SCI.wipeMarks = function (panel) {
723
+ SCI.hideTip();
724
+ const old = panel.shadowRoot.querySelectorAll('.__sci-indicator');
725
+ for (let i = 0; i < old.length; i++) old[i].remove();
726
+ const rws = panel.shadowRoot.querySelectorAll('ui-prop');
727
+ for (let j = 0; j < rws.length; j++) {
728
+ if (rws[j].style.opacity) rws[j].style.opacity = '';
729
+ if (rws[j].getAttribute('data-sci-dirty')) { rws[j].style.boxShadow = ''; rws[j].removeAttribute('data-sci-dirty'); }
730
+ }
731
+ };
732
+
733
+ SCI.apply = function () {
734
+ const panel = SCI.getPanel();
735
+ if (!panel || !SCI.enabled) return;
736
+ if (!SCI.flags.master || SCI.getSelCount() > 1) {
737
+ SCI.wipeMarks(panel);
738
+ SCI.connect();
739
+ return;
740
+ }
741
+ if (SCI.observer) SCI.observer.disconnect();
742
+ try {
743
+ const uuid = SCI.getSelUuid();
744
+ if (uuid) SCI.maybeRequest(uuid);
745
+ const map = (SCI.data && SCI.data.uuid === uuid) ? SCI.data.map : null;
746
+ const svMap = (SCI.dataSV && SCI.dataSV.uuid === uuid) ? SCI.dataSV.map : null;
747
+ const lis = panel.shadowRoot.querySelectorAll('ui-prop');
748
+ const secScope = new Map();
749
+ for (let i = 0; i < lis.length; i++) {
750
+ const ci = SCI.compIndexOfRow(lis[i]);
751
+ if (ci >= 0) {
752
+ const sec = SCI.sectionOf(lis[i]);
753
+ if (sec && !secScope.has(sec)) secScope.set(sec, ci);
754
+ }
755
+ }
756
+ for (let i = 0; i < lis.length; i++) {
757
+ const row = lis[i];
758
+ let kind = null, refs = null, svHit = null;
759
+ const display = SCI.rowDisplay(row);
760
+ if (display && (map || svMap)) {
761
+ const sec = SCI.sectionOf(row);
762
+ const scope = (sec && secScope.has(sec)) ? secScope.get(sec) : 'node';
763
+ const key = scope + '|' + display;
764
+ if (map) { const hit = map[key]; if (hit) { kind = hit.kind; refs = hit.refs; } }
765
+ if (svMap) svHit = svMap[key] || null;
766
+ }
767
+ SCI.updateRowStatus(row, kind, refs, svHit, SCI.flags);
768
+ }
769
+ } finally {
770
+ SCI.connect();
771
+ }
772
+ };
773
+
774
+ SCI.connect = function () {
775
+ const panel = SCI.getPanel();
776
+ if (!panel) return;
777
+ const sr = panel.shadowRoot;
778
+ // 记下当前接上的 panel/shadowRoot, 供 ensureConnected 检测是否被换掉.
779
+ SCI.panel = panel;
780
+ SCI.sr = sr;
781
+ if (!sr.__sciClick) {
782
+ sr.addEventListener('click', function (e) {
783
+ const f = window.__SCI && window.__SCI.onClick; if (f) f(e);
784
+ }, true);
785
+ sr.__sciClick = true;
786
+ }
787
+ if (!sr.__sciHover) {
788
+ sr.addEventListener('mouseover', function (e) {
789
+ const S = window.__SCI; if (!S) return;
790
+ let el = e.target, badge = null;
791
+ for (let i = 0; i < 5 && el; i++) {
792
+ if (el.className === '__sci-indicator') { badge = el; break; }
793
+ el = el.parentElement;
794
+ }
795
+ if (badge) S.showTip(badge);
796
+ }, true);
797
+ sr.addEventListener('mouseout', function (e) {
798
+ const S = window.__SCI; if (!S) return;
799
+ let el = e.target, badge = null;
800
+ for (let i = 0; i < 5 && el; i++) {
801
+ if (el.className === '__sci-indicator') { badge = el; break; }
802
+ el = el.parentElement;
803
+ }
804
+ if (badge && S.tipFor === badge) S.hideTip();
805
+ }, true);
806
+ sr.__sciHover = true;
807
+ }
808
+ if (!SCI.observer) {
809
+ SCI.observer = new MutationObserver(function () {
810
+ if (SCI.raf) cancelAnimationFrame(SCI.raf);
811
+ SCI.raf = requestAnimationFrame(function () { SCI.apply(); });
812
+ });
813
+ }
814
+ SCI.observer.observe(sr, {
815
+ childList: true, subtree: true,
816
+ attributes: true, attributeFilter: ['style', 'class'],
817
+ });
818
+ };
819
+
820
+ // 重发现: inspector dock 被 Cocos 重建后, observer/hover/click 会挂在死的旧 shadowRoot 上
821
+ // (项目开久后标记/hover 失效、需重载项目的根因). 心跳周期调用此函数, 检测当前 panel/shadowRoot
822
+ // 是否被换掉或监听丢失, 一旦发现就丢弃旧 observer 并在新 shadowRoot 上重连 + 重绘.
823
+ SCI.ensureConnected = function () {
824
+ if (!SCI.enabled) return;
825
+ const p = SCI.getPanel();
826
+ if (!p) return;
827
+ const sr = p.shadowRoot;
828
+ if (p !== SCI.panel || sr !== SCI.sr || !sr.__sciHover) {
829
+ if (SCI.observer) { try { SCI.observer.disconnect(); } catch (e) {} SCI.observer = null; }
830
+ SCI.connect();
831
+ SCI.apply();
832
+ }
833
+ };
834
+
835
+ SCI.clear = function () {
836
+ SCI.enabled = false;
837
+ if (SCI.heartbeat) { try { clearInterval(SCI.heartbeat); } catch (e) {} SCI.heartbeat = null; }
838
+ SCI.panel = null;
839
+ SCI.sr = null;
840
+ if (SCI.observer) { try { SCI.observer.disconnect(); } catch (e) {} }
841
+ SCI.hideTip();
842
+ if (SCI.tip && SCI.tip.parentNode) { try { SCI.tip.parentNode.removeChild(SCI.tip); } catch (e) {} SCI.tip = null; }
843
+ const panel = SCI.getPanel();
844
+ if (panel) {
845
+ const bs = panel.shadowRoot.querySelectorAll('.__sci-indicator');
846
+ for (let i = 0; i < bs.length; i++) bs[i].remove();
847
+ const rows = panel.shadowRoot.querySelectorAll('ui-prop');
848
+ for (let j = 0; j < rows.length; j++) {
849
+ if (rows[j].style.opacity) rows[j].style.opacity = '';
850
+ if (rows[j].getAttribute('data-sci-dirty')) { rows[j].style.boxShadow = ''; rows[j].removeAttribute('data-sci-dirty'); }
851
+ }
852
+ }
853
+ SCI.reqUuid = null;
854
+ SCI.data = null;
855
+ SCI.dataSV = null;
856
+ };
857
+
858
+ let tries = 0;
859
+ (function wait() {
860
+ if (SCI.getPanel()) { SCI.enabled = true; SCI.connect(); SCI.apply(); return; }
861
+ if (tries++ < 20) setTimeout(wait, 300);
862
+ })();
863
+ // 心跳重发现 (要点1 修复): 即使初次 wait 已接上, dock 重建后也能自动重连, 无需重载项目.
864
+ SCI.heartbeat = setInterval(function () { SCI.ensureConnected(); }, 1500);
865
+
866
+ return 'inited';
867
+ };
868
+ return '(' + fn.toString() + ')()';
869
+ }
870
+
871
+ const RESIDENT_SCRIPT = buildResidentScript();
872
+
873
+ function forEachWCSimple(script) {
874
+ try {
875
+ const all = require('electron').webContents.getAllWebContents();
876
+ if (!all) return;
877
+ all.forEach(function (wc) {
878
+ try { wc.executeJavaScript(script).catch(function () {}); } catch (e) {}
879
+ });
880
+ } catch (e) {}
881
+ }
882
+
883
+ const DEFAULT_FLAGS = { master: true, viz: true, dirty: true, exclude: true };
884
+ function normalizeFlags(flags) {
885
+ const f = {};
886
+ for (const k in DEFAULT_FLAGS) f[k] = (flags && typeof flags[k] === 'boolean') ? flags[k] : DEFAULT_FLAGS[k];
887
+ return f;
888
+ }
889
+
890
+ function enableInspectorMark(flags) {
891
+ const f = normalizeFlags(flags);
892
+ forEachWCSimple('window.__SCI_FLAGS = ' + JSON.stringify(f) + ';');
893
+ forEachWCSimple(RESIDENT_SCRIPT);
894
+ forEachWCSimple('window.__SCI && window.__SCI.setFlags && window.__SCI.setFlags(' + JSON.stringify(f) + ');');
895
+ Editor.log('[sc-inspector] Inspector 增强已开 (M1+M2a+P2b). flags=' + JSON.stringify(f)
896
+ + '\n 视觉系统重构: 统一左侧提示容器, 支持 Hover 呈现多状态值表。'
897
+ + '\n ◆ 蓝菱 = 状态机驱动; 蓝菱套黄菱 = 当前 state 覆盖 default;'
898
+ + '\n 黄色左边框 = 录制脏值;'
899
+ + '\n ■ 灰方块带斜杠 = 已排除; ▲ 粉三角 = 掉出控制; ◧ 黄半方块 = 部分子项未受控。');
900
+ }
901
+
902
+ function setInspectorFlags(flags) {
903
+ const f = normalizeFlags(flags);
904
+ forEachWCSimple('window.__SCI && window.__SCI.setFlags && window.__SCI.setFlags(' + JSON.stringify(f) + ');');
905
+ }
906
+
907
+ function disableInspectorMark() {
908
+ forEachWCSimple('window.__SCI && window.__SCI.clear && window.__SCI.clear()');
909
+ Editor.log('[sc-inspector] Inspector 增强已关 (徽标已撤)');
910
+ }
911
+
912
+ module.exports = {
913
+ probeInspector: probeInspector,
914
+ enableInspectorMark: enableInspectorMark,
915
+ setInspectorFlags: setInspectorFlags,
916
+ disableInspectorMark: disableInspectorMark,
917
+ };