@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.
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/assets/script/controller/Capability.ts +100 -0
- package/assets/script/controller/Capability.ts.meta +10 -0
- package/assets/script/controller/CapabilityRegistry.ts +116 -0
- package/assets/script/controller/CapabilityRegistry.ts.meta +10 -0
- package/assets/script/controller/EnumPropRefMap.ts +232 -0
- package/assets/script/controller/EnumPropRefMap.ts.meta +10 -0
- package/assets/script/controller/NestedCtrlData.ts +199 -0
- package/assets/script/controller/NestedCtrlData.ts.meta +10 -0
- package/assets/script/controller/PrefabIntrospection.ts +151 -0
- package/assets/script/controller/PrefabIntrospection.ts.meta +10 -0
- package/assets/script/controller/Props.meta +13 -0
- package/assets/script/controller/StateControllerV2.ts +1957 -0
- package/assets/script/controller/StateControllerV2.ts.meta +10 -0
- package/assets/script/controller/StateEnumV2.ts +165 -0
- package/assets/script/controller/StateEnumV2.ts.meta +10 -0
- package/assets/script/controller/StateErrorManagerV2.ts +217 -0
- package/assets/script/controller/StateErrorManagerV2.ts.meta +10 -0
- package/assets/script/controller/StatePropHandlerV2.ts +316 -0
- package/assets/script/controller/StatePropHandlerV2.ts.meta +10 -0
- package/assets/script/controller/StatePropertyControlService.ts +148 -0
- package/assets/script/controller/StatePropertyControlService.ts.meta +10 -0
- package/assets/script/controller/StateSelectV2.ts +4542 -0
- package/assets/script/controller/StateSelectV2.ts.meta +10 -0
- package/assets/script/controller/capabilities/AutoSyncCapability.ts +30 -0
- package/assets/script/controller/capabilities/AutoSyncCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/EventCapability.ts +144 -0
- package/assets/script/controller/capabilities/EventCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/MigrationCapability.ts +94 -0
- package/assets/script/controller/capabilities/MigrationCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts +157 -0
- package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/PropertyControlCapability.ts +124 -0
- package/assets/script/controller/capabilities/PropertyControlCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/RecordingCapability.ts +69 -0
- package/assets/script/controller/capabilities/RecordingCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/SelectedPageIdCapability.ts +88 -0
- package/assets/script/controller/capabilities/SelectedPageIdCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities.meta +13 -0
- package/assets/script/controller/props/CtrlInspectorGroups.ts +138 -0
- package/assets/script/controller/props/CtrlInspectorGroups.ts.meta +10 -0
- package/assets/script/controller/props/SelectInspectorGroups.ts +104 -0
- package/assets/script/controller/props/SelectInspectorGroups.ts.meta +10 -0
- package/bin/csc.js +286 -0
- package/package.json +60 -0
- package/packages/state-controller-v2-panel/README.md +80 -0
- package/packages/state-controller-v2-panel/inspector-inject.js +917 -0
- package/packages/state-controller-v2-panel/inspector-probe.json +3767 -0
- package/packages/state-controller-v2-panel/lib/handlers.js +534 -0
- package/packages/state-controller-v2-panel/main.js +149 -0
- package/packages/state-controller-v2-panel/package.json +32 -0
- package/packages/state-controller-v2-panel/panel/build.js +23 -0
- package/packages/state-controller-v2-panel/panel/logic.js +1207 -0
- package/packages/state-controller-v2-panel/panel/styles.css +454 -0
- package/packages/state-controller-v2-panel/panel/template.html +296 -0
- package/packages/state-controller-v2-panel/scene-accessor.js +657 -0
- package/skills/cocos-state-controller/SKILL.md +28 -0
- package/skills/cocos-state-controller/refs/cli-usage.md +78 -0
- package/skills/cocos-state-controller/refs/editor-guide.md +127 -0
- package/skills/cocos-state-controller/refs/migrate.md +106 -0
- package/skills/cocos-state-controller/refs/upstream-pr.md +66 -0
- package/tools/migration/migrate-prefab-v1-to-v2.js +608 -0
- package/tools/state-controller-sync-manifest.json +33 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* state-controller-v2-panel — Pure IPC handler 层.
|
|
3
|
+
*
|
|
4
|
+
* 所有面板操作的纯函数实现, 不依赖 Cocos Editor 全局.
|
|
5
|
+
* 接收 ctrl 实例 (+ 必要时 select 实例) 与参数, 返回结果.
|
|
6
|
+
*
|
|
7
|
+
* scene-accessor.js 上层负责:
|
|
8
|
+
* - 通过 uuid 把 message 路由到对应 ctrl/select 实例
|
|
9
|
+
* - 包 event.reply 回包
|
|
10
|
+
* - 把 installBroadcastBridge 的 send 接到 Editor.Ipc.sendToPanel
|
|
11
|
+
*
|
|
12
|
+
* 设计原则:
|
|
13
|
+
* - 所有读写都走 ctrl/select 实例的 field/setter, 不依赖 plugin 侧 require 项目源
|
|
14
|
+
* - capability 本体在 game runtime 里通过 ctrl 加载时自注册 + selectedIndex.setter
|
|
15
|
+
* 自动联动 (录制 / 事件 / tween 等), plugin 侧不重复维护一份
|
|
16
|
+
* - 唯一例外: installBroadcastBridge 要监听 game runtime 内 state 切换, 需要
|
|
17
|
+
* EventCapability 单例. 它仅在 jest (node CommonJS) 路径下可达; cocos 编辑器
|
|
18
|
+
* scene-script 上下文里 require 项目源会抛, 此时降级到 noop bridge —— panel
|
|
19
|
+
* 操作触发的刷新走 scene-accessor.js 主动 broadcast 路径, 用户在编辑器外手改
|
|
20
|
+
* ctrl 时需手动重选 ctrl. (具体见 feedback memory: editor-e2e-required)
|
|
21
|
+
* - 错误兜底: ctrl 为 null/undefined 不抛, 返回安全值
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// 项目源 require — 仅 jest 路径用得到, cocos 编辑器侧 try 失败也无害.
|
|
27
|
+
let _CapabilityRegistry = null;
|
|
28
|
+
try {
|
|
29
|
+
// 逐个 require 内置 capability 触发 side-effect 自注册 (原 capabilities/index barrel 已删:
|
|
30
|
+
// cocos 2.x 构建会无条件包含 assets 下所有脚本, barrel 仅 jest/此处显式加载需要).
|
|
31
|
+
require('../../../assets/script/controller/capabilities/AutoSyncCapability');
|
|
32
|
+
require('../../../assets/script/controller/capabilities/EventCapability');
|
|
33
|
+
require('../../../assets/script/controller/capabilities/MigrationCapability');
|
|
34
|
+
require('../../../assets/script/controller/capabilities/MultiCtrlBindingCapability');
|
|
35
|
+
require('../../../assets/script/controller/capabilities/PropertyControlCapability');
|
|
36
|
+
require('../../../assets/script/controller/capabilities/RecordingCapability');
|
|
37
|
+
require('../../../assets/script/controller/capabilities/SelectedPageIdCapability');
|
|
38
|
+
_CapabilityRegistry = require('../../../assets/script/controller/CapabilityRegistry').CapabilityRegistry;
|
|
39
|
+
} catch (_) {
|
|
40
|
+
// cocos editor scene-script 上下文 — 项目 ts 源不可达, installBroadcastBridge 降级
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function safeCtrlId(ctrl) {
|
|
44
|
+
return (ctrl && typeof ctrl.ctrlId === 'number') ? ctrl.ctrlId : -1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findIndexByStateId(ctrl, stateId) {
|
|
48
|
+
const states = ctrl && ctrl._states;
|
|
49
|
+
if (!states) return -1;
|
|
50
|
+
for (let i = 0; i < states.length; i++) {
|
|
51
|
+
const s = states[i];
|
|
52
|
+
if (s && s.stateId === stateId) return i;
|
|
53
|
+
}
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function listAllStates(ctrl) {
|
|
58
|
+
const out = [];
|
|
59
|
+
if (!ctrl || !ctrl._states) return out;
|
|
60
|
+
for (let i = 0; i < ctrl._states.length; i++) {
|
|
61
|
+
const s = ctrl._states[i];
|
|
62
|
+
if (!s) continue;
|
|
63
|
+
out.push({ index: i, stateId: s.stateId, name: s.name });
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 完整快照, 供 Panel 渲染. 一次性读取避免多次 IPC round-trip.
|
|
70
|
+
*/
|
|
71
|
+
function getCtrlSnapshot(ctrl) {
|
|
72
|
+
if (!ctrl) return null;
|
|
73
|
+
const states = listAllStates(ctrl);
|
|
74
|
+
const idx = ctrl.selectedIndex;
|
|
75
|
+
const sel = states[idx];
|
|
76
|
+
return {
|
|
77
|
+
ctrlId: ctrl.ctrlId,
|
|
78
|
+
ctrlName: ctrl.ctrlName || '',
|
|
79
|
+
selectedIndex: idx,
|
|
80
|
+
selectedStateId: sel ? sel.stateId : -1,
|
|
81
|
+
isRecording: !!ctrl.isRecording,
|
|
82
|
+
states: states,
|
|
83
|
+
deletedStates: listDeletedStates(ctrl),
|
|
84
|
+
previewingStateId: (typeof ctrl.previewingStateId === 'number') ? ctrl.previewingStateId : -1,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function setSelectedIndex(ctrl, index) {
|
|
89
|
+
if (!ctrl) return false;
|
|
90
|
+
if (typeof index !== 'number') return false;
|
|
91
|
+
if (!ctrl._states || index < 0 || index >= ctrl._states.length) return false;
|
|
92
|
+
ctrl.selectedIndex = index;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function setStateById(ctrl, stateId) {
|
|
97
|
+
if (!ctrl) return false;
|
|
98
|
+
const idx = findIndexByStateId(ctrl, stateId);
|
|
99
|
+
if (idx < 0) return false;
|
|
100
|
+
ctrl.selectedIndex = idx;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setRecording(ctrl, isRecording) {
|
|
105
|
+
if (!ctrl) return false;
|
|
106
|
+
if (isRecording) {
|
|
107
|
+
if (typeof ctrl.startRecording === 'function') ctrl.startRecording();
|
|
108
|
+
} else if (typeof ctrl.stopRecording === 'function') ctrl.stopRecording();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 撤销本次录制 (TASK-002): 调 ctrl.cancelRecording, ctrlData 回滚到录制开始前.
|
|
114
|
+
* 非录制态调是 no-op (ctrl 端幂等).
|
|
115
|
+
*/
|
|
116
|
+
function cancelRecording(ctrl) {
|
|
117
|
+
if (!ctrl) return false;
|
|
118
|
+
if (typeof ctrl.cancelRecording === 'function') ctrl.cancelRecording();
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 新增 state. 走 StateControllerV2.states setter (复用 smart-name + stateId 分配逻辑).
|
|
124
|
+
* 返回新 stateId, 失败返回 -1.
|
|
125
|
+
*
|
|
126
|
+
* 不 require StateControllerV2 源: 用现有 state 的 constructor 当工厂.
|
|
127
|
+
*/
|
|
128
|
+
function addState(ctrl, name) {
|
|
129
|
+
if (!ctrl || !ctrl._states) return -1;
|
|
130
|
+
const protoState = ctrl._states[0];
|
|
131
|
+
if (!protoState || typeof protoState.constructor !== 'function') return -1;
|
|
132
|
+
const StateValue = protoState.constructor;
|
|
133
|
+
if (typeof StateValue.create !== 'function') return -1;
|
|
134
|
+
|
|
135
|
+
const beforeIds = Object.create(null);
|
|
136
|
+
for (let i = 0; i < ctrl._states.length; i++) {
|
|
137
|
+
beforeIds[ctrl._states[i].stateId] = true;
|
|
138
|
+
}
|
|
139
|
+
const newState = StateValue.create(name || ('S' + ctrl._states.length), ctrl.stateIdAuto++);
|
|
140
|
+
const newStates = ctrl._states.slice();
|
|
141
|
+
newStates.push(newState);
|
|
142
|
+
ctrl.states = newStates;
|
|
143
|
+
|
|
144
|
+
// setter 内部可能改写 newState 引用; 用 id diff 找到新加入的 stateId
|
|
145
|
+
let newId = -1;
|
|
146
|
+
for (let i = 0; i < ctrl._states.length; i++) {
|
|
147
|
+
if (!beforeIds[ctrl._states[i].stateId]) {
|
|
148
|
+
newId = ctrl._states[i].stateId;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return newId;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 删除指定 index 的 state. 至少保留 1 个 (与 removeSelectedState 一致).
|
|
157
|
+
*/
|
|
158
|
+
function removeState(ctrl, index) {
|
|
159
|
+
if (!ctrl || !ctrl._states) return false;
|
|
160
|
+
if (typeof index !== 'number' || index < 0 || index >= ctrl._states.length) return false;
|
|
161
|
+
if (ctrl._states.length <= 1) return false;
|
|
162
|
+
|
|
163
|
+
const newStates = ctrl._states.slice();
|
|
164
|
+
newStates.splice(index, 1);
|
|
165
|
+
const beforeLen = ctrl._states.length;
|
|
166
|
+
ctrl.states = newStates;
|
|
167
|
+
return ctrl._states.length === beforeLen - 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function restoreLastDeletedState(ctrl) {
|
|
171
|
+
if (!ctrl || typeof ctrl.restoreLastDeletedState !== 'function') return false;
|
|
172
|
+
return !!ctrl.restoreLastDeletedState();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** 回收站: 列出暂存的已删除 state [{name, stateId}]. */
|
|
176
|
+
function listDeletedStates(ctrl) {
|
|
177
|
+
if (!ctrl || typeof ctrl.listDeletedStates !== 'function') return [];
|
|
178
|
+
const list = ctrl.listDeletedStates();
|
|
179
|
+
return Array.isArray(list) ? list : [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 回收站: 恢复指定 stateId 的暂存 state (追加到尾部, 数据自动接回). */
|
|
183
|
+
function restoreDeletedState(ctrl, stateId) {
|
|
184
|
+
if (!ctrl || typeof ctrl.restoreDeletedState !== 'function') return false;
|
|
185
|
+
return !!ctrl.restoreDeletedState(stateId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 回收站硬删: 彻底删除指定 stateId 的页数据 (不可恢复). */
|
|
189
|
+
function purgeDeletedState(ctrl, stateId) {
|
|
190
|
+
if (!ctrl || typeof ctrl.purgeDeletedState !== 'function') return false;
|
|
191
|
+
return !!ctrl.purgeDeletedState(stateId);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 回收站: 清空 (对所有暂存项硬删, 不可恢复). */
|
|
195
|
+
function purgeAllDeletedStates(ctrl) {
|
|
196
|
+
if (!ctrl || typeof ctrl.purgeAllDeletedStates !== 'function') return false;
|
|
197
|
+
return !!ctrl.purgeAllDeletedStates();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** 回收站: 进入某 stateId 的只读预览 (叠加到节点, 不改选中). */
|
|
201
|
+
function previewDeletedState(ctrl, stateId) {
|
|
202
|
+
if (!ctrl || typeof ctrl.previewDeletedState !== 'function') return false;
|
|
203
|
+
return !!ctrl.previewDeletedState(stateId);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 回收站: 退出预览 (按快照还原节点). */
|
|
207
|
+
function exitPreview(ctrl) {
|
|
208
|
+
if (!ctrl || typeof ctrl.exitPreview !== 'function') return false;
|
|
209
|
+
return !!ctrl.exitPreview();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 手动添加 prop (panel "+ 添加属性" 按钮).
|
|
214
|
+
* 调 StateSelectV2.togglePropertyControl, 触发 PropertyControlService 写入 _ctrlData.
|
|
215
|
+
*/
|
|
216
|
+
function addProperty(ctrl, select, propType) {
|
|
217
|
+
if (!ctrl || !select) return false;
|
|
218
|
+
if (typeof select.togglePropertyControl !== 'function') return false;
|
|
219
|
+
// propType 可能是 EnumPropName 数字, 也可能是字符串 ("position"); 这里只透传
|
|
220
|
+
select.togglePropertyControl(propType, true);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 移除 prop (TASK-003 panel "☐ 取消跟随" 按钮).
|
|
226
|
+
* 调 StateSelectV2.togglePropertyControl(false), 取消跟随 + 清 propData 中对应字段.
|
|
227
|
+
*/
|
|
228
|
+
function removeProperty(ctrl, select, propType) {
|
|
229
|
+
if (!ctrl || !select) return false;
|
|
230
|
+
if (typeof select.togglePropertyControl !== 'function') return false;
|
|
231
|
+
select.togglePropertyControl(propType, false);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* M1-1: 把一个存储值 (primitive 或 cc 类型) 序列化成 JSON-friendly 可显示形式.
|
|
237
|
+
*
|
|
238
|
+
* 不 require 项目源 (editor scene-script 路径不可达), 用 duck-type 识别 cc 类型:
|
|
239
|
+
* primitive (number/boolean/string) 原样; null/undefined → null;
|
|
240
|
+
* cc.Color {r,g,b,a} / cc.Vec3 {x,y,z} / cc.Vec2 {x,y} / cc.Size {width,height} /
|
|
241
|
+
* cc.Quat {x,y,z,w} → 带 _t 标签的纯对象; asset ({_uuid}|{name}) → {_t:'Asset', id}.
|
|
242
|
+
*/
|
|
243
|
+
function serializeStateValue(v) {
|
|
244
|
+
if (v === null || v === undefined) return null;
|
|
245
|
+
const t = typeof v;
|
|
246
|
+
if (t === 'number' || t === 'boolean' || t === 'string') return v;
|
|
247
|
+
if (t !== 'object') return null;
|
|
248
|
+
const num = function (x) { return typeof x === 'number'; };
|
|
249
|
+
if (num(v.r) && num(v.g) && num(v.b)) {
|
|
250
|
+
return { _t: 'Color', r: v.r, g: v.g, b: v.b, a: num(v.a) ? v.a : 255 };
|
|
251
|
+
}
|
|
252
|
+
if (num(v.width) && num(v.height)) return { _t: 'Size', width: v.width, height: v.height };
|
|
253
|
+
if (num(v.x) && num(v.y) && num(v.z) && num(v.w)) return { _t: 'Quat', x: v.x, y: v.y, z: v.z, w: v.w };
|
|
254
|
+
if (num(v.x) && num(v.y) && num(v.z)) return { _t: 'Vec3', x: v.x, y: v.y, z: v.z };
|
|
255
|
+
if (num(v.x) && num(v.y)) return { _t: 'Vec2', x: v.x, y: v.y };
|
|
256
|
+
if (v._uuid || v.name) return { _t: 'Asset', id: v._uuid || v.name };
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* M1-1: 读 select._ctrlData[currCtrlId] 里每个受控 propRef 在各 state 的存储值,
|
|
262
|
+
* 判定「是否跨状态有差异」并返回各状态值表 — 供注入侧标 ● + hover 各状态值.
|
|
263
|
+
*
|
|
264
|
+
* 数据形状 (W6-2c2 后): pageData = _ctrlData[ctrlId], key = '$$default$$' | stateIndex(number);
|
|
265
|
+
* 每个 propData 里 string propRef key → 存储值 (number/boolean/cc 类型);
|
|
266
|
+
* '$$' 前缀 key ($$controlledProps$$ 等) 与 legacy number-only key 跳过.
|
|
267
|
+
*
|
|
268
|
+
* variesAcrossStates: 各 state 的已定义值序列化后存在 ≥2 个不同 → true (真正受状态机驱动).
|
|
269
|
+
*
|
|
270
|
+
* @param select - StateSelectV2 实例 (读 _ctrlData / _ctrlsMap / currCtrlId)
|
|
271
|
+
* @param ctrl - 可选 StateControllerV2; 不传则从 select._ctrlsMap[currCtrlId] 推导
|
|
272
|
+
* @returns { ok, hasSelect, states:[{index,stateId,name}], props:{ [propRef]:{ variesAcrossStates, valueByState, defaultValue } } }
|
|
273
|
+
*/
|
|
274
|
+
function getPropStateValues(select, ctrl) {
|
|
275
|
+
const empty = { ok: true, hasSelect: false, states: [], props: {} };
|
|
276
|
+
if (!select) return empty;
|
|
277
|
+
const c = ctrl || (select._ctrlsMap && select._ctrlsMap[select.currCtrlId]) || null;
|
|
278
|
+
const ctrlId = (c && c.ctrlId != null) ? c.ctrlId
|
|
279
|
+
: (select.currCtrlId != null ? select.currCtrlId : null);
|
|
280
|
+
const data = select._ctrlData || {};
|
|
281
|
+
const pageData = (ctrlId != null && data[ctrlId]) ? data[ctrlId] : null;
|
|
282
|
+
const states = listAllStates(c);
|
|
283
|
+
const selectedIndex = (c && typeof c.selectedIndex === 'number') ? c.selectedIndex : -1;
|
|
284
|
+
const result = { ok: true, hasSelect: true, states: states, selectedIndex: selectedIndex, props: {} };
|
|
285
|
+
if (!pageData) return result;
|
|
286
|
+
|
|
287
|
+
function valueKeys(pd, sink) {
|
|
288
|
+
if (!pd) return;
|
|
289
|
+
for (const k in pd) {
|
|
290
|
+
if (k.indexOf('$$') === 0) continue; // 内部 key
|
|
291
|
+
if (/^\d+$/.test(k)) continue; // legacy number-only key (string twin 优先)
|
|
292
|
+
sink[k] = 1;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function pageOf(state) {
|
|
296
|
+
if (!state) return null;
|
|
297
|
+
if (pageData[state.stateId] != null) return pageData[state.stateId];
|
|
298
|
+
return pageData[state.index] != null ? pageData[state.index] : null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 收集所有受控 propRef (default + 各 state 的值 key 并集)
|
|
302
|
+
const refSet = {};
|
|
303
|
+
valueKeys(pageData.$$default$$, refSet);
|
|
304
|
+
for (let i = 0; i < states.length; i++) valueKeys(pageOf(states[i]), refSet);
|
|
305
|
+
|
|
306
|
+
for (const ref in refSet) {
|
|
307
|
+
const valueByState = {};
|
|
308
|
+
const distinct = {};
|
|
309
|
+
let definedCount = 0;
|
|
310
|
+
for (let i = 0; i < states.length; i++) {
|
|
311
|
+
const state = states[i];
|
|
312
|
+
const pd = pageOf(state);
|
|
313
|
+
const raw = pd ? pd[ref] : undefined;
|
|
314
|
+
const ser = serializeStateValue(raw);
|
|
315
|
+
valueByState[state.stateId] = ser;
|
|
316
|
+
if (raw !== undefined) {
|
|
317
|
+
definedCount++;
|
|
318
|
+
distinct[JSON.stringify(ser)] = 1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const defRaw = pageData.$$default$$ ? pageData.$$default$$[ref] : undefined;
|
|
322
|
+
const defSer = serializeStateValue(defRaw);
|
|
323
|
+
// M1-3: 当前 state 下值是否覆盖 default (两者均有定义且序列化后不等)
|
|
324
|
+
let overriddenAtCurrent = false;
|
|
325
|
+
if (selectedIndex >= 0 && defRaw !== undefined) {
|
|
326
|
+
const curPd = pageOf(states[selectedIndex]);
|
|
327
|
+
const curRaw = curPd ? curPd[ref] : undefined;
|
|
328
|
+
if (curRaw !== undefined) {
|
|
329
|
+
overriddenAtCurrent = JSON.stringify(serializeStateValue(curRaw)) !== JSON.stringify(defSer);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
result.props[ref] = {
|
|
333
|
+
variesAcrossStates: definedCount >= 2 && Object.keys(distinct).length >= 2,
|
|
334
|
+
overriddenAtCurrent: overriddenAtCurrent,
|
|
335
|
+
valueByState: valueByState,
|
|
336
|
+
defaultValue: defSer,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 把 ctrl 内部 state 切换 + setRecording 转成 IPC broadcast.
|
|
344
|
+
*
|
|
345
|
+
* jest 路径: 通过 EventCapability + ad-hoc capability 注册. 测试覆盖.
|
|
346
|
+
* cocos 编辑器路径: _CapabilityRegistry 为 null → noop. panel 触发的操作走
|
|
347
|
+
* scene-accessor.js 主动 broadcast 'on-data-changed' 自刷新.
|
|
348
|
+
*
|
|
349
|
+
* @param ctrl - 目标 controller
|
|
350
|
+
* @param send - (eventName, payload) => void, 上层接 Editor.Ipc.sendToPanel
|
|
351
|
+
* @returns unsubscribe() 卸载所有 listener
|
|
352
|
+
*/
|
|
353
|
+
function installBroadcastBridge(ctrl, send) {
|
|
354
|
+
if (!ctrl || typeof send !== 'function') return function () {};
|
|
355
|
+
if (!_CapabilityRegistry) return function () {};
|
|
356
|
+
|
|
357
|
+
const event = _CapabilityRegistry.get('event');
|
|
358
|
+
if (!event) return function () {};
|
|
359
|
+
|
|
360
|
+
const stateChangedCb = function (payload) {
|
|
361
|
+
send('onStateChanged', {
|
|
362
|
+
ctrlId: payload.ctrl ? payload.ctrl.ctrlId : safeCtrlId(ctrl),
|
|
363
|
+
fromState: payload.fromState,
|
|
364
|
+
toState: payload.toState,
|
|
365
|
+
fromName: payload.fromName,
|
|
366
|
+
toName: payload.toName,
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
event.on(ctrl, 'stateChanged', stateChangedCb);
|
|
370
|
+
|
|
371
|
+
const recBridgeName = '_panelRecordingBridge_' + ctrl.ctrlId;
|
|
372
|
+
const recBridge = {
|
|
373
|
+
name: recBridgeName,
|
|
374
|
+
onRecordingStart: function (ctx) {
|
|
375
|
+
if (ctx.ctrl === ctrl) send('onRecordingChanged', { ctrlId: safeCtrlId(ctrl), isRecording: true });
|
|
376
|
+
},
|
|
377
|
+
onRecordingStop: function (ctx) {
|
|
378
|
+
if (ctx.ctrl === ctrl) send('onRecordingChanged', { ctrlId: safeCtrlId(ctrl), isRecording: false });
|
|
379
|
+
},
|
|
380
|
+
// TASK-002: cancelRecording 也广播 onRecordingChanged → isRecording=false,
|
|
381
|
+
// 同时单独广播 onRecordingCancelled 让 panel 区分 "停止" 与 "撤销" (撤销需要刷数据)
|
|
382
|
+
onRecordingCancel: function (ctx) {
|
|
383
|
+
if (ctx.ctrl === ctrl) {
|
|
384
|
+
send('onRecordingChanged', { ctrlId: safeCtrlId(ctrl), isRecording: false });
|
|
385
|
+
send('onRecordingCancelled', { ctrlId: safeCtrlId(ctrl), fromState: ctx.fromState });
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
_CapabilityRegistry.register(recBridge);
|
|
390
|
+
|
|
391
|
+
return function unsubscribe() {
|
|
392
|
+
event.off(ctrl, 'stateChanged', stateChangedCb);
|
|
393
|
+
_CapabilityRegistry.unregister(recBridgeName);
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 聚合场景拓扑树
|
|
399
|
+
* @param {Array} ctrlsInfo [{ uuid, ctrl }]
|
|
400
|
+
* @param {Array} selectsInfo [{ nodeUuid, nodeName, nodePath, select, getRowsInfo }]
|
|
401
|
+
* @returns 拓扑树对象
|
|
402
|
+
*/
|
|
403
|
+
function buildTopology(ctrlsInfo, selectsInfo) {
|
|
404
|
+
const topology = { controllers: [] };
|
|
405
|
+
if (!ctrlsInfo) return topology;
|
|
406
|
+
|
|
407
|
+
for (let i = 0; i < ctrlsInfo.length; i++) {
|
|
408
|
+
const cInfo = ctrlsInfo[i];
|
|
409
|
+
const ctrl = cInfo.ctrl;
|
|
410
|
+
if (!ctrl) continue;
|
|
411
|
+
|
|
412
|
+
const snap = getCtrlSnapshot(ctrl);
|
|
413
|
+
if (!snap) continue;
|
|
414
|
+
|
|
415
|
+
const cNode = {
|
|
416
|
+
uuid: cInfo.uuid,
|
|
417
|
+
ctrlId: ctrl.ctrlId,
|
|
418
|
+
ctrlName: snap.ctrlName,
|
|
419
|
+
states: snap.states,
|
|
420
|
+
selectedIndex: snap.selectedIndex,
|
|
421
|
+
isRecording: snap.isRecording,
|
|
422
|
+
// 支柱 B: 该控制器的跨控制器联动声明 [{sourceStateId,targetCtrlId,targetStateId}].
|
|
423
|
+
bindings: (ctrl && typeof ctrl.getBindings === 'function') ? ctrl.getBindings() : [],
|
|
424
|
+
members: []
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
for (let j = 0; j < selectsInfo.length; j++) {
|
|
428
|
+
const sInfo = selectsInfo[j];
|
|
429
|
+
const select = sInfo.select;
|
|
430
|
+
if (!select || !select._ctrlsMap) continue;
|
|
431
|
+
|
|
432
|
+
// 成员节点是否接入了这个 controller
|
|
433
|
+
if (select._ctrlsMap[ctrl.ctrlId]) {
|
|
434
|
+
const sv = getPropStateValues(select, ctrl);
|
|
435
|
+
const rowsInfo = sInfo.getRowsInfo ? sInfo.getRowsInfo() : [];
|
|
436
|
+
const props = [];
|
|
437
|
+
|
|
438
|
+
for (let r = 0; r < rowsInfo.length; r++) {
|
|
439
|
+
const row = rowsInfo[r];
|
|
440
|
+
const refs = row.refs || [];
|
|
441
|
+
const kind = row.kind;
|
|
442
|
+
|
|
443
|
+
let varies = false, override = false, hasData = false;
|
|
444
|
+
const combinedValueByState = {};
|
|
445
|
+
let combinedDefault = undefined;
|
|
446
|
+
|
|
447
|
+
if (refs.length === 1) {
|
|
448
|
+
const p = sv.props && sv.props[refs[0]];
|
|
449
|
+
if (p) {
|
|
450
|
+
combinedDefault = p.defaultValue;
|
|
451
|
+
if (sv.states) {
|
|
452
|
+
for(let s = 0; s < sv.states.length; s++) {
|
|
453
|
+
const sId = sv.states[s].stateId;
|
|
454
|
+
combinedValueByState[sId] = p.valueByState ? p.valueByState[sId] : undefined;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (p) {
|
|
459
|
+
hasData = true;
|
|
460
|
+
if (p.variesAcrossStates) varies = true;
|
|
461
|
+
if (p.overriddenAtCurrent) override = true;
|
|
462
|
+
}
|
|
463
|
+
} else if (refs.length > 1) {
|
|
464
|
+
combinedDefault = {};
|
|
465
|
+
for (let k = 0; k < refs.length; k++) {
|
|
466
|
+
const ref = refs[k];
|
|
467
|
+
const p = sv.props && sv.props[ref];
|
|
468
|
+
const refName = ref.split('.').pop();
|
|
469
|
+
combinedDefault[refName] = p ? p.defaultValue : undefined;
|
|
470
|
+
|
|
471
|
+
if (sv.states) {
|
|
472
|
+
for(let s = 0; s < sv.states.length; s++) {
|
|
473
|
+
const sId = sv.states[s].stateId;
|
|
474
|
+
if (!combinedValueByState[sId]) combinedValueByState[sId] = {};
|
|
475
|
+
combinedValueByState[sId][refName] = p && p.valueByState ? p.valueByState[sId] : undefined;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (p) {
|
|
479
|
+
hasData = true;
|
|
480
|
+
if (p.variesAcrossStates) varies = true;
|
|
481
|
+
if (p.overriddenAtCurrent) override = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
props.push({
|
|
487
|
+
propRef: refs.join(','),
|
|
488
|
+
display: row.display,
|
|
489
|
+
compName: row.compName,
|
|
490
|
+
kind: kind,
|
|
491
|
+
variesAcrossStates: varies,
|
|
492
|
+
overriddenAtCurrent: override,
|
|
493
|
+
valueByState: combinedValueByState,
|
|
494
|
+
defaultValue: combinedDefault,
|
|
495
|
+
refs: refs
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
cNode.members.push({
|
|
500
|
+
nodeUuid: sInfo.nodeUuid,
|
|
501
|
+
nodeName: sInfo.nodeName,
|
|
502
|
+
nodePath: sInfo.nodePath,
|
|
503
|
+
props: props
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
topology.controllers.push(cNode);
|
|
509
|
+
}
|
|
510
|
+
return topology;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
module.exports = {
|
|
514
|
+
getCtrlSnapshot: getCtrlSnapshot,
|
|
515
|
+
setSelectedIndex: setSelectedIndex,
|
|
516
|
+
setStateById: setStateById,
|
|
517
|
+
setRecording: setRecording,
|
|
518
|
+
cancelRecording: cancelRecording,
|
|
519
|
+
addState: addState,
|
|
520
|
+
removeState: removeState,
|
|
521
|
+
restoreLastDeletedState: restoreLastDeletedState,
|
|
522
|
+
listDeletedStates: listDeletedStates,
|
|
523
|
+
restoreDeletedState: restoreDeletedState,
|
|
524
|
+
purgeDeletedState: purgeDeletedState,
|
|
525
|
+
purgeAllDeletedStates: purgeAllDeletedStates,
|
|
526
|
+
previewDeletedState: previewDeletedState,
|
|
527
|
+
exitPreview: exitPreview,
|
|
528
|
+
addProperty: addProperty,
|
|
529
|
+
removeProperty: removeProperty,
|
|
530
|
+
installBroadcastBridge: installBroadcastBridge,
|
|
531
|
+
getPropStateValues: getPropStateValues,
|
|
532
|
+
serializeStateValue: serializeStateValue,
|
|
533
|
+
buildTopology: buildTopology,
|
|
534
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* State Controller Panel — Editor 入口 (Wave 3 scaffold).
|
|
5
|
+
*
|
|
6
|
+
* 负责响应主菜单 + Inspector "跳转 Panel" 按钮的 open/close 消息.
|
|
7
|
+
* 实际面板 UI 在 panel/ 下 (Gemini 后续实装).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// inspector 注入层 (P0 探针起步, 后续 P1–P3 注入/撤销也在此文件)
|
|
11
|
+
// 热重载: 每次取用都清 require 缓存重新 require, 这样改 inspector-inject.js 后
|
|
12
|
+
// 只需重点一次菜单 "开" 即生效, 无需重启编辑器 (配合注入脚本内的 VER 版本号覆盖旧常驻脚本).
|
|
13
|
+
function freshInject() {
|
|
14
|
+
try { delete require.cache[require.resolve('./inspector-inject')]; } catch (e) {}
|
|
15
|
+
return require('./inspector-inject');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// M2a-3 硬化: 记住 inspector 增强是否开着, scene:ready (切/重载场景) 后自动重注入,
|
|
19
|
+
// 避免渲染进程重载丢失常驻脚本后标记不恢复 (注入侧 VER 机制保证幂等覆盖).
|
|
20
|
+
let _inspectorOn = false;
|
|
21
|
+
|
|
22
|
+
// 面板初次拉列表前先问"场景是否就绪": scene-script (scene-accessor) 仅在场景加载后才注册.
|
|
23
|
+
// 面板先于场景就绪打开时盲调 callSceneScript('list-ctrls') 会触发 Cocos 核心
|
|
24
|
+
// "Failed to send ipc message ...:list-ctrls to scene, message not found" 噪音告警.
|
|
25
|
+
// scene:ready 拉起后置 true, 面板据此决定立即拉取 / 等 scene:ready 兜底.
|
|
26
|
+
let _sceneReady = false;
|
|
27
|
+
|
|
28
|
+
// M2a-2: 各特性开关 (master 总开关 + viz/dirty/exclude), Editor.Profile 持久化 → 重启编辑器记忆.
|
|
29
|
+
const FLAGS_PROFILE_URL = 'profile://local/state-controller-inspector.json';
|
|
30
|
+
const DEFAULT_FLAGS = { master: true, viz: true, dirty: true, exclude: true };
|
|
31
|
+
let _flagsProfile = null;
|
|
32
|
+
function getFlags() {
|
|
33
|
+
if (!_flagsProfile) {
|
|
34
|
+
try { _flagsProfile = Editor.Profile.load(FLAGS_PROFILE_URL, { 'state-controller': DEFAULT_FLAGS }); }
|
|
35
|
+
catch (e) { _flagsProfile = null; }
|
|
36
|
+
}
|
|
37
|
+
const data = (_flagsProfile && _flagsProfile.data && _flagsProfile.data['state-controller']) || DEFAULT_FLAGS;
|
|
38
|
+
const f = {};
|
|
39
|
+
for (const k in DEFAULT_FLAGS) f[k] = (typeof data[k] === 'boolean') ? data[k] : DEFAULT_FLAGS[k];
|
|
40
|
+
return f;
|
|
41
|
+
}
|
|
42
|
+
function saveFlags(flags) {
|
|
43
|
+
const f = {};
|
|
44
|
+
for (const k in DEFAULT_FLAGS) f[k] = (flags && typeof flags[k] === 'boolean') ? flags[k] : DEFAULT_FLAGS[k];
|
|
45
|
+
try {
|
|
46
|
+
if (!_flagsProfile) _flagsProfile = Editor.Profile.load(FLAGS_PROFILE_URL, { 'state-controller': DEFAULT_FLAGS });
|
|
47
|
+
_flagsProfile.data['state-controller'] = f;
|
|
48
|
+
_flagsProfile.save();
|
|
49
|
+
} catch (e) { /* 静默, Profile 不可用时仅本次会话内存生效 */ }
|
|
50
|
+
return f;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
load() {
|
|
55
|
+
// 默认自动开启 (用户期望): 读持久化 flags, master 默认 true → 启动即注入, 无需手动点菜单.
|
|
56
|
+
// 用户经面板 master toggle 持久化关闭 (saveFlags master:false) 后, 下次启动不自动注入.
|
|
57
|
+
_inspectorOn = getFlags().master;
|
|
58
|
+
if (_inspectorOn) {
|
|
59
|
+
// 场景可能尚未 ready, 此次注入失败由 scene:ready 兜底 (其 _inspectorOn 已为 true).
|
|
60
|
+
try { freshInject().enableInspectorMark(getFlags()); } catch (e) { /* 静默, scene:ready 重注入 */ }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
unload() {
|
|
65
|
+
// 边界 #5: 同步立即返回, 绝不等 IPC (照搬 hierarchy-plus 教训 —
|
|
66
|
+
// 否则 Cocos Dashboard 检测不到进程退出会挂起).
|
|
67
|
+
// 撤销注入: 让常驻脚本 disconnect observer + 移除标记. executeJavaScript 是
|
|
68
|
+
// fire-and-forget (不 await), 同步派发不阻塞. 主要服务 "reload 扩展" 场景
|
|
69
|
+
// (webContents 不随之销毁, 残留 observer 必须清); 关编辑器场景 webContents 随之销毁, 无害.
|
|
70
|
+
try { freshInject().disableInspectorMark(); } catch (e) { /* 静默, 不阻塞退出 */ }
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
messages: {
|
|
74
|
+
'open'() {
|
|
75
|
+
Editor.Panel.open('state-controller-v2-panel');
|
|
76
|
+
},
|
|
77
|
+
'close'() {
|
|
78
|
+
Editor.Panel.close('state-controller-v2-panel');
|
|
79
|
+
},
|
|
80
|
+
// P0: 探测 inspector DOM, 找 "属性行 → propRef" 桥
|
|
81
|
+
'probe-inspector'() {
|
|
82
|
+
freshInject().probeInspector();
|
|
83
|
+
},
|
|
84
|
+
// P1/P2a: 开/关 inspector 属性行状态机标记 (带持久化的特性 flags)
|
|
85
|
+
'inspector-mark-on'() {
|
|
86
|
+
_inspectorOn = true;
|
|
87
|
+
freshInject().enableInspectorMark(getFlags());
|
|
88
|
+
},
|
|
89
|
+
'inspector-mark-off'() {
|
|
90
|
+
_inspectorOn = false;
|
|
91
|
+
freshInject().disableInspectorMark();
|
|
92
|
+
},
|
|
93
|
+
// M2a-3 硬化: 切/重载场景后, 若增强开着则重注入常驻脚本 (幂等, VER 覆盖旧脚本).
|
|
94
|
+
'scene:ready'() {
|
|
95
|
+
_sceneReady = true;
|
|
96
|
+
if (_inspectorOn) {
|
|
97
|
+
try { freshInject().enableInspectorMark(getFlags()); } catch (e) { /* 静默 */ }
|
|
98
|
+
}
|
|
99
|
+
// 显式转发给面板: 此刻 scene-script 已注册, 面板可安全拉 list-ctrls 无告警.
|
|
100
|
+
// (面板可能先于场景打开, 自身初次 is-scene-ready 查询为 false, 靠这条补拉.)
|
|
101
|
+
try { Editor.Ipc.sendToPanel('state-controller-v2-panel', 'state-controller-v2-panel:scene-ready'); } catch (e) { /* 静默, 面板未开时无害 */ }
|
|
102
|
+
},
|
|
103
|
+
// 面板初次拉取前查询场景就绪态 (见 _sceneReady 注释), 就绪才安全调 scene-script.
|
|
104
|
+
'is-scene-ready'(event) {
|
|
105
|
+
if (event && event.reply) event.reply(null, _sceneReady);
|
|
106
|
+
},
|
|
107
|
+
// M2a-2: 面板读当前特性开关 (渲染 toggle 勾选态)
|
|
108
|
+
'inspector-get-flags'(event) {
|
|
109
|
+
const f = getFlags();
|
|
110
|
+
if (event && event.reply) event.reply(null, f);
|
|
111
|
+
},
|
|
112
|
+
// M2a-2: 面板改特性开关 → 持久化 + 实时推给注入侧 (无需重注入). master 关也走 setFlags (注入侧清标记).
|
|
113
|
+
'inspector-set-flags'(event, payload) {
|
|
114
|
+
const f = saveFlags(payload);
|
|
115
|
+
try { freshInject().setInspectorFlags(f); } catch (e) { /* 静默 */ }
|
|
116
|
+
if (event && event.reply) event.reply(null, f);
|
|
117
|
+
},
|
|
118
|
+
// P2a: 注入侧 (渲染进程) 请求"这些 propRef 的状态机身份" → 转给 scene-script 分类 → 回包
|
|
119
|
+
'inspector-req-status'(event, payload) {
|
|
120
|
+
try {
|
|
121
|
+
Editor.Scene.callSceneScript('state-controller-v2-panel', 'inspector-prop-status', payload, function (err, res) {
|
|
122
|
+
if (event && event.reply) event.reply(err, res);
|
|
123
|
+
});
|
|
124
|
+
} catch (e) {
|
|
125
|
+
if (event && event.reply) event.reply(e.message, null);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
// M1-2: 注入侧请求"各受控 propRef 跨状态差异 + 各状态值表" → 转 scene-script → 回包 (标 ● + hover)
|
|
129
|
+
'inspector-req-state-values'(event, payload) {
|
|
130
|
+
try {
|
|
131
|
+
Editor.Scene.callSceneScript('state-controller-v2-panel', 'inspector-prop-state-values', payload, function (err, res) {
|
|
132
|
+
if (event && event.reply) event.reply(err, res);
|
|
133
|
+
});
|
|
134
|
+
} catch (e) {
|
|
135
|
+
if (event && event.reply) event.reply(e.message, null);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
// P2b: 注入侧点击标记 → 切换排除 (转 scene-script 写数据 + undo + 标脏)
|
|
139
|
+
'inspector-toggle-exclude'(event, payload) {
|
|
140
|
+
try {
|
|
141
|
+
Editor.Scene.callSceneScript('state-controller-v2-panel', 'inspector-toggle-exclude', payload, function (err, res) {
|
|
142
|
+
if (event && event.reply) event.reply(err, res);
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (event && event.reply) event.reply(e.message, null);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
};
|