@tmagic/editor 1.8.0-beta.22 → 1.8.0-beta.23
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/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/components/Resizer.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/initService.js +2 -1
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/services/codeBlock.js +4 -2
- package/dist/es/utils/dep/collect-worker-client.js +67 -27
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/type-match-rules.js +7 -27
- package/dist/tmagic-editor.umd.cjs +294 -265
- package/package.json +12 -12
- package/src/utils/dep/collect-worker-client.ts +97 -31
- package/src/utils/type-match-rules.ts +4 -30
- package/types/index.d.ts +86 -153
|
@@ -119,7 +119,8 @@ var CodeBlockEditor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
119
119
|
const closedHandler = () => {
|
|
120
120
|
changedValue.value = void 0;
|
|
121
121
|
};
|
|
122
|
-
const
|
|
122
|
+
const parentFloating = inject("parentFloating", ref(null));
|
|
123
|
+
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
|
123
124
|
const frameworkWidth = computed(() => uiService.get("frameworkRect")?.width || 0);
|
|
124
125
|
watch(boxVisible, (visible) => {
|
|
125
126
|
nextTick(() => {
|
|
@@ -7,7 +7,8 @@ var Resizer_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
|
|
|
7
7
|
emits: ["change"],
|
|
8
8
|
setup(__props, { emit: __emit }) {
|
|
9
9
|
const emit = __emit;
|
|
10
|
-
const
|
|
10
|
+
const targetEl = useTemplateRef("target");
|
|
11
|
+
const { isDragging } = useGetSo(targetEl, emit);
|
|
11
12
|
return (_ctx, _cache) => {
|
|
12
13
|
return openBlock(), createElementBlock("span", {
|
|
13
14
|
ref: "target",
|
|
@@ -30,7 +30,9 @@ var CondOpSelect_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
30
30
|
const optionComponent = getDesignConfig("components")?.option;
|
|
31
31
|
const options = computed(() => {
|
|
32
32
|
const [id, ...fieldNames] = [...props.config.parentFields || [], ...props.model.field];
|
|
33
|
-
|
|
33
|
+
const ds = dataSourceService.getDataSourceById(id);
|
|
34
|
+
const type = getFieldType(ds, fieldNames);
|
|
35
|
+
return getCondOpOptionsByFieldType(type);
|
|
34
36
|
});
|
|
35
37
|
const fieldChangeHandler = (v) => {
|
|
36
38
|
emit("change", v);
|
|
@@ -315,7 +315,8 @@ var DataSourceFields_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
315
315
|
const addDialogVisible = useModel(__props, "visible");
|
|
316
316
|
const addFromJsonDialogVisible = useModel(__props, "visible1");
|
|
317
317
|
const { height: editorHeight } = useEditorContentHeight();
|
|
318
|
-
const
|
|
318
|
+
const parentFloating = inject("parentFloating", ref(null));
|
|
319
|
+
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
|
319
320
|
const frameworkWidth = computed(() => uiService.get("frameworkRect")?.width || 0);
|
|
320
321
|
/**
|
|
321
322
|
* 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
|
|
@@ -128,7 +128,8 @@ var DataSourceInput_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
128
128
|
*/
|
|
129
129
|
const fieldQuerySearch = (queryString, leftAngleIndex, dotIndex, cb) => {
|
|
130
130
|
let result = [];
|
|
131
|
-
const
|
|
131
|
+
const dsKey = queryString.substring(leftAngleIndex + 1, dotIndex);
|
|
132
|
+
const keys = getKeysArray(dsKey);
|
|
132
133
|
const dsId = keys.shift();
|
|
133
134
|
const ds = dataSources.value.find((ds) => ds.id === dsId);
|
|
134
135
|
if (!ds) {
|
|
@@ -215,7 +215,8 @@ var DataSourceMocks_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
215
215
|
};
|
|
216
216
|
const addDialogVisible = useModel(__props, "visible");
|
|
217
217
|
const { height: editorHeight } = useEditorContentHeight();
|
|
218
|
-
const
|
|
218
|
+
const parentFloating = inject("parentFloating", ref(null));
|
|
219
|
+
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
|
219
220
|
const frameworkWidth = computed(() => uiService.get("frameworkRect")?.width || 0);
|
|
220
221
|
return (_ctx, _cache) => {
|
|
221
222
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
@@ -33,7 +33,8 @@ var DisplayConds_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
33
33
|
const parentFields = computed(() => filterFunction(mForm, props.config.parentFields, props) || []);
|
|
34
34
|
const fieldOnChange = (_formState, v, { model }) => {
|
|
35
35
|
const [id, ...fieldNames] = [...parentFields.value, ...v];
|
|
36
|
-
const
|
|
36
|
+
const ds = dataSourceService.getDataSourceById(id);
|
|
37
|
+
const type = getFieldType(ds, fieldNames);
|
|
37
38
|
if (type === "number") model.value = Number(model.value);
|
|
38
39
|
else if (type === "boolean") model.value = Boolean(model.value);
|
|
39
40
|
else if (type === "null") model.value = null;
|
|
@@ -131,7 +132,8 @@ var DisplayConds_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
131
132
|
name: "value",
|
|
132
133
|
type: (_mForm, { model }) => {
|
|
133
134
|
const [id, ...fieldNames] = [...parentFields.value, ...model.field];
|
|
134
|
-
const
|
|
135
|
+
const ds = dataSourceService.getDataSourceById(id);
|
|
136
|
+
const type = getFieldType(ds, fieldNames);
|
|
135
137
|
if (type === "number") return "number";
|
|
136
138
|
if (type === "boolean") return "select";
|
|
137
139
|
if (type === "null") return "display";
|
package/dist/es/initService.js
CHANGED
|
@@ -329,7 +329,8 @@ var initServiceEvents = (props, emit, { editorService, codeBlockService, dataSou
|
|
|
329
329
|
const root = editorService.get("root");
|
|
330
330
|
if (!root) return;
|
|
331
331
|
const handler = async () => {
|
|
332
|
-
const
|
|
332
|
+
const nodeIds = Object.keys(root.dataSourceDeps?.[id] || {});
|
|
333
|
+
const nodes = getNodes(nodeIds, root.items);
|
|
333
334
|
if (!(await Promise.all([
|
|
334
335
|
collectIdle(nodes, false, DepTargetType.DATA_SOURCE),
|
|
335
336
|
collectIdle(nodes, false, DepTargetType.DATA_SOURCE_COND),
|
|
@@ -145,7 +145,8 @@ var Sidebar_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
|
|
|
145
145
|
watch(() => props.data.status, (status) => {
|
|
146
146
|
activeTabName.value = status || "0";
|
|
147
147
|
});
|
|
148
|
-
const
|
|
148
|
+
const slideKeys = computed(() => sideBarItems.value.map((sideBarItem) => sideBarItem.$key));
|
|
149
|
+
const { dragstartHandler, dragendHandler, floatBoxStates, showingBoxKeys } = useFloatBox(slideKeys);
|
|
149
150
|
watch(() => showingBoxKeys.value.length, () => {
|
|
150
151
|
if (!showingBoxKeys.value.some((key) => activeTabName.value === sideBarItems.value.find((v) => v.$key === key)?.text) && activeTabName.value) return;
|
|
151
152
|
const nextSlideBarItem = sideBarItems.value.find((sideBarItem) => !showingBoxKeys.value.includes(sideBarItem.$key));
|
|
@@ -38,7 +38,8 @@ var DataSourceConfigPanel_vue_vue_type_script_setup_true_lang_default = /*@__PUR
|
|
|
38
38
|
const initValues = ref({});
|
|
39
39
|
const dataSourceConfig = ref([]);
|
|
40
40
|
const { height: editorHeight } = useEditorContentHeight();
|
|
41
|
-
const
|
|
41
|
+
const parentFloating = inject("parentFloating", ref(null));
|
|
42
|
+
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
|
42
43
|
const frameworkWidth = computed(() => uiService.get("frameworkRect")?.width || 0);
|
|
43
44
|
/** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
|
|
44
45
|
provide("editingDataSourceMethodName", computed(() => props.editMethodName));
|
|
@@ -51,7 +51,8 @@ var LayerPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
const { handleDragStart, handleDragEnd, handleDragLeave, handleDragOver } = useDrag(services, { canDropIn: (sourceIds, targetId) => props.canDropIn?.(sourceIds, targetId, "layer") });
|
|
54
|
-
const
|
|
54
|
+
const menuRef = useTemplateRef("menu");
|
|
55
|
+
const { nodeClickHandler, nodeDblclickHandler, nodeContentMenuHandler, highlightHandler: mouseenterHandler } = useClick(services, isCtrlKeyDown, nodeStatusMap, menuRef);
|
|
55
56
|
const dblclickHandler = async (event, data) => {
|
|
56
57
|
if (props.beforeNodeDblclick) {
|
|
57
58
|
if (await props.beforeNodeDblclick(event, data) === false) return;
|
|
@@ -141,7 +141,8 @@ var CodeBlock = class extends BaseService {
|
|
|
141
141
|
* @returns {CodeBlockDSL}
|
|
142
142
|
*/
|
|
143
143
|
getCodeDslByIds(ids) {
|
|
144
|
-
|
|
144
|
+
const codeDsl = this.getCodeDsl();
|
|
145
|
+
return pick(codeDsl, ids);
|
|
145
146
|
}
|
|
146
147
|
/**
|
|
147
148
|
* 获取编辑状态
|
|
@@ -371,7 +372,8 @@ var CodeBlock = class extends BaseService {
|
|
|
371
372
|
*/
|
|
372
373
|
async getUniqueId() {
|
|
373
374
|
const newId = `code_${Math.random().toString(10).substring(2).substring(0, 4)}`;
|
|
374
|
-
|
|
375
|
+
const dsl = await this.getCodeDsl();
|
|
376
|
+
if (!keys(dsl).includes(newId)) return newId;
|
|
375
377
|
return await this.getUniqueId();
|
|
376
378
|
}
|
|
377
379
|
/**
|
|
@@ -10,11 +10,17 @@ import serialize from "serialize-javascript";
|
|
|
10
10
|
*
|
|
11
11
|
* 全量收集与增量收集共用一个常驻 worker:增量收集调用频繁(每次节点更新都会触发),
|
|
12
12
|
* 按次创建 worker 的启动开销无法接受,也容易漏掉销毁导致线程泄漏。
|
|
13
|
+
*
|
|
14
|
+
* 请求在主线程侧排队后逐个投递(worker 本身也是串行处理),这样 abort 时能直接丢掉尚未投递的请求,
|
|
15
|
+
* 无需销毁线程:worker 只在真正异常或销毁时才重建。
|
|
13
16
|
*/
|
|
14
17
|
var CollectWorkerClient = class {
|
|
15
18
|
worker = null;
|
|
16
19
|
seed = 0;
|
|
17
|
-
|
|
20
|
+
/** 等待投递给 worker 的请求 */
|
|
21
|
+
queue = [];
|
|
22
|
+
/** 已投递、等待 worker 响应的请求,worker 串行处理,同一时刻最多一个 */
|
|
23
|
+
inflight = null;
|
|
18
24
|
get isSupported() {
|
|
19
25
|
return typeof Worker !== "undefined";
|
|
20
26
|
}
|
|
@@ -44,34 +50,63 @@ var CollectWorkerClient = class {
|
|
|
44
50
|
terminate() {
|
|
45
51
|
this.worker?.terminate();
|
|
46
52
|
this.worker = null;
|
|
47
|
-
this.
|
|
53
|
+
this.settleAll();
|
|
48
54
|
}
|
|
49
55
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
56
|
+
* 丢弃在途请求,但保留常驻 worker
|
|
57
|
+
*
|
|
58
|
+
* clearIdleTasks / reset 中断收集时调用。这里不能销毁 worker:worker 重建要重新加载并启动整份收集逻辑,
|
|
59
|
+
* 而 clearIdleTasks / reset 触发频繁(root 更新、数据源变更、历史回滚等),一旦下一次 abort 落在
|
|
60
|
+
* 上一个 worker 还没启动完的窗口内,就会陷入「加载中被销毁 → 重建 → 又被销毁」的循环,
|
|
61
|
+
* 表现为 worker 反复加载且依赖始终收集不完。
|
|
62
|
+
*
|
|
63
|
+
* 因此 abort 只做「不要这些结果」:尚未投递的请求直接丢掉,已投递的那一个标记为丢弃,
|
|
64
|
+
* 结果回来后忽略(不写回主线程),worker 继续复用。
|
|
52
65
|
*/
|
|
53
66
|
abort() {
|
|
54
|
-
this
|
|
67
|
+
const { queue } = this;
|
|
68
|
+
this.queue = [];
|
|
69
|
+
for (const task of queue) task.resolve(null);
|
|
70
|
+
if (this.inflight && !this.inflight.discarded) {
|
|
71
|
+
this.inflight.discarded = true;
|
|
72
|
+
this.inflight.resolve(null);
|
|
73
|
+
}
|
|
55
74
|
}
|
|
56
|
-
/**
|
|
57
|
-
* worker 按收到的顺序逐个处理请求,因此请求间不会互相打断,用 id 把结果分发回各自的调用方
|
|
58
|
-
*/
|
|
59
75
|
request(createRequest) {
|
|
60
|
-
|
|
61
|
-
if (!worker) return Promise.resolve(null);
|
|
76
|
+
if (!this.isSupported) return Promise.resolve(null);
|
|
62
77
|
this.seed += 1;
|
|
63
78
|
const id = this.seed;
|
|
64
79
|
return new Promise((resolve) => {
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
80
|
+
this.queue.push({
|
|
81
|
+
id,
|
|
82
|
+
createRequest,
|
|
83
|
+
resolve,
|
|
84
|
+
discarded: false
|
|
85
|
+
});
|
|
86
|
+
this.flush();
|
|
73
87
|
});
|
|
74
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* 投递队首请求,worker 串行处理,因此同一时刻只投递一个,用 id 把结果分发回各自的调用方
|
|
91
|
+
*/
|
|
92
|
+
flush() {
|
|
93
|
+
if (this.inflight || !this.queue.length) return;
|
|
94
|
+
const worker = this.getWorker();
|
|
95
|
+
if (!worker) {
|
|
96
|
+
this.settleAll();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const task = this.queue.shift();
|
|
100
|
+
this.inflight = task;
|
|
101
|
+
try {
|
|
102
|
+
worker.postMessage(task.createRequest(task.id));
|
|
103
|
+
} catch (e) {
|
|
104
|
+
error("magic editor: 依赖收集 worker 通信失败", e);
|
|
105
|
+
this.inflight = null;
|
|
106
|
+
if (!task.discarded) task.resolve(null);
|
|
107
|
+
this.flush();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
75
110
|
getWorker() {
|
|
76
111
|
if (!this.isSupported) return null;
|
|
77
112
|
if (this.worker) return this.worker;
|
|
@@ -94,20 +129,25 @@ var CollectWorkerClient = class {
|
|
|
94
129
|
return this.worker;
|
|
95
130
|
}
|
|
96
131
|
handleResponse(response) {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
this.
|
|
100
|
-
resolve(response.failed ? null : response);
|
|
132
|
+
const task = this.inflight;
|
|
133
|
+
if (task?.id !== response?.id) return;
|
|
134
|
+
this.inflight = null;
|
|
135
|
+
if (!task.discarded) task.resolve(response.failed ? null : response);
|
|
136
|
+
this.flush();
|
|
101
137
|
}
|
|
102
138
|
handleFatalError() {
|
|
103
139
|
this.worker?.terminate();
|
|
104
140
|
this.worker = null;
|
|
105
|
-
this.
|
|
141
|
+
this.settleAll();
|
|
106
142
|
}
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
this.
|
|
110
|
-
|
|
143
|
+
settleAll() {
|
|
144
|
+
const tasks = this.queue;
|
|
145
|
+
this.queue = [];
|
|
146
|
+
if (this.inflight) {
|
|
147
|
+
tasks.push(this.inflight);
|
|
148
|
+
this.inflight = null;
|
|
149
|
+
}
|
|
150
|
+
for (const task of tasks) if (!task.discarded) task.resolve(null);
|
|
111
151
|
}
|
|
112
152
|
};
|
|
113
153
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region packages/editor/src/utils/dep/worker.ts?worker&inline
|
|
2
|
-
var jsContent = "(function() {\n //#region packages/schema/src/index.ts\n const NODE_CONDS_KEY = \"displayConds\";\n const NODE_DISABLE_DATA_SOURCE_KEY = \"_tmagic_node_disabled_data_source\";\n const NODE_DISABLE_CODE_BLOCK_KEY = \"_tmagic_node_disabled_code_block\";\n let HookType = /* @__PURE__ */ function(HookType) {\n /** 代码块钩子标识 */\n HookType[\"CODE\"] = \"code\";\n return HookType;\n }({});\n //#endregion\n //#region packages/utils/src/const.ts\n const DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX = \"ds-field::\";\n //#endregion\n //#region packages/utils/src/index.ts\n const isObject = (obj) => Object.prototype.toString.call(obj) === \"[object Object]\";\n const getKeysArray = (keys) => `${keys}`.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n const dataSourceTemplateRegExp = /\\$\\{([\\s\\S]+?)\\}/g;\n //#endregion\n //#region packages/dep/src/types.ts\n /** 依赖收集的目标类型 */\n let DepTargetType = /* @__PURE__ */ function(DepTargetType) {\n DepTargetType[\"DEFAULT\"] = \"default\";\n /** 代码块 */\n DepTargetType[\"CODE_BLOCK\"] = \"code-block\";\n /** 数据源 */\n DepTargetType[\"DATA_SOURCE\"] = \"data-source\";\n /** 数据源方法 */\n DepTargetType[\"DATA_SOURCE_METHOD\"] = \"data-source-method\";\n /** 数据源条件 */\n DepTargetType[\"DATA_SOURCE_COND\"] = \"data-source-cond\";\n return DepTargetType;\n }({});\n //#endregion\n //#region packages/dep/src/Target.ts\n /**\n * 需要收集依赖的目标\n * 例如:一个代码块可以为一个目标\n */\n var Target = class {\n /**\n * 如何识别目标\n */\n isTarget;\n /**\n * 目标id,不可重复\n * 例如目标是代码块,则为代码块id\n */\n id;\n /**\n * 目标名称,用于显示在依赖列表中\n */\n name;\n /**\n * 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type\n */\n type = DepTargetType.DEFAULT;\n /**\n * 依赖详情\n * 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } }\n */\n deps = {};\n /**\n * 是否默认收集,默认为true,当值为false时需要传入type参数给collect方法才会被收集\n */\n isCollectByDefault;\n /**\n * 可序列化描述,有该描述的 target 可以在 worker 中重建,从而把依赖收集放到子线程执行\n */\n descriptor;\n constructor(options) {\n this.isTarget = options.isTarget;\n this.id = options.id;\n this.name = options.name;\n this.isCollectByDefault = options.isCollectByDefault ?? true;\n this.descriptor = options.descriptor;\n if (options.type) this.type = options.type;\n if (options.initialDeps) this.deps = options.initialDeps;\n }\n /**\n * 更新依赖\n * @param option 节点配置\n * @param key 哪个key配置了这个目标的id\n */\n updateDep({ id, name, key, data }) {\n const dep = this.deps[id] || {\n name,\n keys: []\n };\n dep.name = name;\n dep.data = data;\n this.deps[id] = dep;\n if (!dep.keys.includes(key)) dep.keys.push(key);\n }\n /**\n * 删除依赖\n * @param node 哪个节点的依赖需要移除,如果为空,则移除所有依赖\n * @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key\n * @returns void\n */\n removeDep(id, key) {\n if (typeof id === \"undefined\") {\n Object.keys(this.deps).forEach((depKey) => {\n delete this.deps[depKey];\n });\n return;\n }\n const dep = this.deps[id];\n if (!dep) return;\n if (key) {\n const index = dep.keys.indexOf(key);\n dep.keys.splice(index, 1);\n if (dep.keys.length === 0) delete this.deps[id];\n } else delete this.deps[id];\n }\n /**\n * 判断指定节点下的指定key是否存在在依赖列表中\n * @param node 哪个节点\n * @param key 哪个key\n * @returns boolean\n */\n hasDep(id, key) {\n return this.deps[id]?.keys.includes(key) ?? false;\n }\n destroy() {\n this.deps = {};\n }\n };\n //#endregion\n //#region packages/dep/src/utils.ts\n const INTEGER_REGEXP = /^\\d+$/;\n const createCodeBlockTarget = (id, codeBlock, initialDeps = {}) => new Target({\n type: DepTargetType.CODE_BLOCK,\n id,\n initialDeps,\n name: codeBlock.name,\n descriptor: {\n type: DepTargetType.CODE_BLOCK,\n id,\n codeBlock: { name: codeBlock.name }\n },\n isTarget: (_key, value) => {\n if (id === value) return true;\n if (value?.hookType === HookType.CODE && Array.isArray(value.hookData)) return value.hookData.some((item) => item.codeId === id);\n return false;\n }\n });\n /**\n * ['array'] ['array', '0'] ['array', '0', 'a'] 这种返回false\n * ['array', 'a'] 这种返回true\n * @param keys\n * @param fields\n * @returns boolean\n */\n const isIncludeArrayField = (keys, fields) => {\n let f = fields;\n return keys.some((key, index) => {\n const field = f.find(({ name }) => name === key);\n f = field?.fields || [];\n return field?.type === \"array\" && index < keys.length - 1 && !INTEGER_REGEXP.test(keys[index + 1]);\n });\n };\n /**\n * 判断模板(value)是不是使用数据源Id(dsId),如:`xxx${dsId.field}xxx${dsId.field}`\n * @param value any\n * @param dsId string | number\n * @param hasArray boolean true: 一定要包含有需要迭代的模板; false: 一定要包含普通模板;\n * @returns boolean\n */\n const isDataSourceTemplate = (value, ds, hasArray = false) => {\n const templates = value.match(dataSourceTemplateRegExp) || [];\n if (templates.length <= 0) return false;\n for (const tpl of templates) {\n const keys = getKeysArray(tpl.substring(2, tpl.length - 1));\n const dsId = keys.shift();\n if (!dsId || dsId !== ds.id) continue;\n if (hasArray === isIncludeArrayField(keys, ds.fields)) return true;\n }\n return false;\n };\n /**\n * 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}\n * @param value any\n * @param dsId string | number\n * @returns boolean\n */\n const isSpecificDataSourceTemplate = (value, dsId) => value?.isBindDataSourceField && value.dataSourceId && value.dataSourceId === dsId && typeof value.template === \"string\";\n /**\n * 关联数据源字段,格式为 [前缀+数据源ID, 字段名]\n * 使用data-source-field-select value: 'value' 可以配置出来\n * @param value any[]\n * @param id string | number\n * @returns boolean\n */\n const isUseDataSourceField = (value, id) => {\n if (!Array.isArray(value) || typeof value[0] !== \"string\") return false;\n const [prefixId] = value;\n const prefixIndex = prefixId.indexOf(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);\n if (prefixIndex === -1) return false;\n return prefixId.substring(prefixIndex + DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX.length) === id;\n };\n const isDataSourceTarget = (ds, key, value, hasArray = false) => {\n if (!value) return false;\n const valueType = typeof value;\n if (valueType !== \"string\" && valueType !== \"object\") return false;\n if (`${key}`.startsWith(\"displayConds\")) return false;\n if (valueType === \"string\") return isDataSourceTemplate(value, ds, hasArray);\n if (isObject(value) && value.isBindDataSource && value.dataSourceId === ds.id) return true;\n if (isSpecificDataSourceTemplate(value, ds.id)) return true;\n if (isUseDataSourceField(value, ds.id)) {\n const [, ...keys] = value;\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const isDataSourceCondTarget = (ds, key, value, hasArray = false) => {\n if (!Array.isArray(value) || !ds) return false;\n const [dsId, ...keys] = value;\n if (dsId !== ds.id || !`${key}`.startsWith(\"displayConds\")) return false;\n if (ds.fields?.some((field) => field.name === keys[0])) {\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const createDataSourceTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE,\n id: ds.id,\n initialDeps,\n descriptor: {\n type: DepTargetType.DATA_SOURCE,\n ds: {\n id: ds.id,\n fields: ds.fields || []\n }\n },\n isTarget: (key, value) => isDataSourceTarget(ds, key, value)\n });\n const createDataSourceCondTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_COND,\n id: ds.id,\n initialDeps,\n descriptor: {\n type: DepTargetType.DATA_SOURCE_COND,\n ds: {\n id: ds.id,\n fields: ds.fields || []\n }\n },\n isTarget: (key, value) => isDataSourceCondTarget(ds, key, value)\n });\n const createDataSourceMethodTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_METHOD,\n id: ds.id,\n initialDeps,\n descriptor: {\n type: DepTargetType.DATA_SOURCE_METHOD,\n ds: {\n id: ds.id,\n methods: (ds.methods || []).map((method) => ({ name: method.name })),\n fields: (ds.fields || []).map((field) => ({ name: field.name }))\n }\n },\n isTarget: (_key, value) => {\n if (!Array.isArray(value)) return false;\n const [dsId, methodName] = value;\n if (!methodName || dsId !== ds.id) return false;\n if (ds.methods?.some((method) => method.name === methodName)) return true;\n if (ds.fields?.some((field) => field.name === methodName)) return false;\n return true;\n }\n });\n /**\n * 用可序列化描述重建 target,使依赖收集可以在 worker 等无法传递函数的环境中进行\n * @param descriptor 由工厂函数写入 target 的描述\n * @param initialDeps 初始依赖\n * @returns Target\n */\n const createTargetByDescriptor = (descriptor, initialDeps = {}) => {\n switch (descriptor.type) {\n case DepTargetType.CODE_BLOCK: return createCodeBlockTarget(descriptor.id, descriptor.codeBlock, initialDeps);\n case DepTargetType.DATA_SOURCE: return createDataSourceTarget(descriptor.ds, initialDeps);\n case DepTargetType.DATA_SOURCE_COND: return createDataSourceCondTarget(descriptor.ds, initialDeps);\n case DepTargetType.DATA_SOURCE_METHOD: return createDataSourceMethodTarget(descriptor.ds, initialDeps);\n default: throw new Error(`unknown target descriptor: ${JSON.stringify(descriptor)}`);\n }\n };\n const traverseTarget = (targetsList, cb, type) => {\n if (type) {\n const targets = targetsList[type];\n if (targets) for (const target of Object.values(targets)) cb(target);\n return;\n }\n for (const targets of Object.values(targetsList)) for (const target of Object.values(targets)) cb(target);\n };\n //#endregion\n //#region packages/dep/src/Watcher.ts\n const DATA_SOURCE_TARGET_TYPES = /* @__PURE__ */ new Set([\n DepTargetType.DATA_SOURCE,\n DepTargetType.DATA_SOURCE_COND,\n DepTargetType.DATA_SOURCE_METHOD\n ]);\n var Watcher = class {\n targetsList = {};\n childrenProp = \"items\";\n idProp = \"id\";\n nameProp = \"name\";\n constructor(options) {\n if (options?.initialTargets) this.targetsList = options.initialTargets;\n if (options?.childrenProp) this.childrenProp = options.childrenProp;\n }\n getTargetsList() {\n return this.targetsList;\n }\n /**\n * 获取指定类型中的所有target\n * @param type 分类\n * @returns Target[]\n */\n getTargets(type = DepTargetType.DEFAULT) {\n return this.targetsList[type] || {};\n }\n /**\n * 添加新的目标\n * @param target Target\n */\n addTarget(target) {\n const targets = this.getTargets(target.type) || {};\n this.targetsList[target.type] = targets;\n targets[target.id] = target;\n }\n /**\n * 获取指定id的target\n * @param id target id\n * @returns Target\n */\n getTarget(id, type = DepTargetType.DEFAULT) {\n return this.getTargets(type)[id];\n }\n /**\n * 判断是否存在指定id的target\n * @param id target id\n * @returns boolean\n */\n hasTarget(id, type = DepTargetType.DEFAULT) {\n return Boolean(this.getTarget(id, type));\n }\n /**\n * 判断是否存在指定类型的target\n * @param type target type\n * @returns boolean\n */\n hasSpecifiedTypeTarget(type = DepTargetType.DEFAULT) {\n return Object.keys(this.getTargets(type)).length > 0;\n }\n /**\n * 删除指定id的target\n * @param id target id\n */\n removeTarget(id, type = DepTargetType.DEFAULT) {\n const targets = this.getTargets(type);\n if (targets[id]) {\n targets[id].destroy();\n delete targets[id];\n }\n }\n /**\n * 删除指定分类的所有target\n * @param type 分类\n * @returns void\n */\n removeTargets(type = DepTargetType.DEFAULT) {\n const targets = this.targetsList[type];\n if (!targets) return;\n for (const target of Object.values(targets)) target.destroy();\n delete this.targetsList[type];\n }\n /**\n * 删除所有target\n */\n clearTargets() {\n for (const key of Object.keys(this.targetsList)) delete this.targetsList[key];\n }\n /**\n * 收集依赖\n * @param nodes 需要收集的节点\n * @param deep 是否需要收集子节点\n * @param type 强制收集指定类型的依赖\n */\n collect(nodes, depExtendedData = {}, deep = false, type) {\n const targets = this.getCollectableTargets(type);\n if (!targets.length) return;\n for (const node of nodes) {\n this.removeTargetsDep(targets, node);\n this.collectItems(node, targets, depExtendedData, deep);\n }\n }\n /**\n * 获取本次需要参与收集的 target(过滤规则与 collectByCallback 一致)\n *\n * 注:供 editor 的 dep service / worker 跨包批量收集时复用,因此为 public。\n * @param type 强制收集指定类型的依赖\n */\n getCollectableTargets(type) {\n const targets = [];\n traverseTarget(this.targetsList, (target) => {\n if (!type && !target.isCollectByDefault) return;\n targets.push(target);\n }, type);\n return targets;\n }\n collectByCallback(nodes, type, cb) {\n traverseTarget(this.targetsList, (target) => {\n if (!type && !target.isCollectByDefault) return;\n for (const node of nodes) cb({\n node,\n target\n });\n }, type);\n }\n /**\n * 清除所有目标的依赖\n * @param nodes 需要清除依赖的节点\n */\n clear(nodes, type) {\n let { targetsList } = this;\n if (type) targetsList = { [type]: this.getTargets(type) };\n const clearedItemsNodeIds = /* @__PURE__ */ new Set();\n traverseTarget(targetsList, (target) => {\n if (nodes) for (const node of nodes) {\n target.removeDep(node[this.idProp]);\n if (Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length && !clearedItemsNodeIds.has(node[this.idProp])) {\n clearedItemsNodeIds.add(node[this.idProp]);\n this.clear(node[this.childrenProp]);\n }\n }\n else target.removeDep();\n });\n }\n /**\n * 清除指定类型的依赖\n * @param type 类型\n * @param nodes 需要清除依赖的节点\n */\n clearByType(type, nodes) {\n this.clear(nodes, type);\n }\n /**\n * 收集单个 target 的依赖,等价于 collectItems(node, [target], ...)\n */\n collectItem(node, target, depExtendedData = {}, deep = false) {\n this.collectItems(node, [target], depExtendedData, deep);\n }\n removeTargetDep(target, node, key) {\n target.removeDep(node[this.idProp], key);\n if (typeof key === \"undefined\" && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) for (const item of node[this.childrenProp]) this.removeTargetDep(target, item, key);\n }\n /**\n * 与 removeTargetDep 等价,但一次子树递归同时处理多个 target,\n * 把删除阶段的结构遍历从 ×targets 降到 ×1。\n *\n * 注:供 editor 的 dep service 跨包批量删除时复用,因此为 public。\n */\n removeTargetsDep(targets, node, key) {\n const id = node[this.idProp];\n for (const target of targets) target.removeDep(id, key);\n if (typeof key === \"undefined\" && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) for (const item of node[this.childrenProp]) this.removeTargetsDep(targets, item, key);\n }\n /**\n * 与 collectItem 等价,但一次遍历同时处理多个 target(不含删除阶段)。\n *\n * 关键优化:原实现对每个 target 都完整遍历一遍节点树(O(targets × 树规模)),大页面 + 大量数据源时,\n * 结构遍历(Object.entries / 递归 / fullKey 字符串拼接)会被重复 targets 次。这里改为「整棵树只遍历一次,\n * 在每个属性上检查所有 target」,把结构遍历开销从 ×targets 降到 ×1,isTarget 调用次数不变,收集结果完全一致。\n *\n * 注:供 editor 的 dep service / worker 跨包批量收集时复用,因此为 public。\n */\n collectItems(node, targets, depExtendedData = {}, deep = false) {\n const activeTargets = this.filterTargetsByNode(node, targets);\n if (!activeTargets.length) return;\n this.collectTargetForTargets(node, node, \"\", activeTargets, depExtendedData, deep);\n }\n filterTargetsByNode(node, targets) {\n const disableDataSource = Boolean(node[NODE_DISABLE_DATA_SOURCE_KEY]);\n const disableCodeBlock = Boolean(node[NODE_DISABLE_CODE_BLOCK_KEY]);\n if (!disableDataSource && !disableCodeBlock) return targets;\n return targets.filter((target) => {\n if (disableDataSource && DATA_SOURCE_TARGET_TYPES.has(target.type)) return false;\n if (disableCodeBlock && target.type === DepTargetType.CODE_BLOCK) return false;\n return true;\n });\n }\n collectTargetForTargets(node, config, prop, targets, depExtendedData, deep) {\n const id = node[this.idProp];\n const name = `${node[this.nameProp] || node[this.idProp]}`;\n for (const [key, value] of Object.entries(config)) {\n if (typeof value === \"undefined\" || value === \"\") continue;\n const keyIsItems = key === this.childrenProp;\n const fullKey = prop ? `${prop}.${key}` : key;\n let notMatched = null;\n for (let i = 0, l = targets.length; i < l; i++) {\n const target = targets[i];\n if (target.isTarget(fullKey, value, config)) target.updateDep({\n id,\n name,\n data: depExtendedData,\n key: fullKey\n });\n else (notMatched || (notMatched = [])).push(target);\n }\n if (notMatched) {\n if (!keyIsItems && Array.isArray(value)) for (let i = 0, l = value.length; i < l; i++) {\n const item = value[i];\n if (isObject(item)) this.collectTargetForTargets(node, item, `${fullKey}[${i}]`, notMatched, depExtendedData, deep);\n }\n else if (isObject(value)) this.collectTargetForTargets(node, value, fullKey, notMatched, depExtendedData, deep);\n }\n if (keyIsItems && deep && Array.isArray(value)) for (const child of value) this.collectItems(child, targets, depExtendedData, deep);\n }\n }\n };\n //#endregion\n //#region packages/editor/src/utils/logger.ts\n const error = (...args) => {\n if (process.env.NODE_ENV === \"development\") console.error(\"magic editor: \", ...args);\n };\n //#endregion\n //#region packages/editor/src/utils/dep/worker.ts\n /**\n * 收集本次会覆盖到的节点 id\n * 必须与 deep 一致:deep=false 时不能带上子孙 id,否则写回阶段 removeDep 会清掉未重收的子节点依赖\n */\n const getNodeIds = (nodes, deep, ids = []) => {\n for (const node of nodes) {\n ids.push(node.id);\n if (deep && Array.isArray(node.items) && node.items.length) getNodeIds(node.items, deep, ids);\n }\n return ids;\n };\n const collectDsl = ({ id, dsl }) => {\n try {\n const mApp = eval(`(${dsl})`);\n if (!mApp) {\n postMessage({\n id,\n deps: {}\n });\n return;\n }\n const watcher = new Watcher({ initialTargets: {} });\n if (mApp.codeBlocks) for (const [id, code] of Object.entries(mApp.codeBlocks)) watcher.addTarget(createCodeBlockTarget(id, code));\n if (mApp.dataSources) for (const ds of mApp.dataSources) {\n watcher.addTarget(createDataSourceTarget(ds, {}));\n watcher.addTarget(createDataSourceMethodTarget(ds, {}));\n watcher.addTarget(createDataSourceCondTarget(ds, {}));\n }\n const targets = watcher.getCollectableTargets();\n for (const page of mApp.items) watcher.collectItems(page, targets, { pageId: page.id }, true);\n const deps = {\n [DepTargetType.DATA_SOURCE]: {},\n [DepTargetType.DATA_SOURCE_METHOD]: {},\n [DepTargetType.DATA_SOURCE_COND]: {},\n [DepTargetType.CODE_BLOCK]: {}\n };\n traverseTarget(watcher.getTargetsList(), (target) => {\n deps[target.type][target.id] = target.deps;\n });\n const response = {\n id,\n deps\n };\n postMessage(response);\n } catch (e) {\n error(e);\n postMessage({\n id,\n deps: {},\n failed: true\n });\n }\n };\n const collectNodes = ({ id, payload }) => {\n try {\n const { nodes, targets: descriptors, depExtendedData, deep } = eval(`(${payload})`);\n const watcher = new Watcher({ initialTargets: {} });\n const targets = descriptors.map((descriptor) => createTargetByDescriptor(descriptor));\n for (const node of nodes) watcher.collectItems(node, targets, depExtendedData, deep);\n const deps = {};\n for (const target of targets) {\n if (!deps[target.type]) deps[target.type] = {};\n deps[target.type][target.id] = target.deps;\n }\n const response = {\n id,\n deps,\n nodeIds: getNodeIds(nodes, deep)\n };\n postMessage(response);\n } catch (e) {\n error(e);\n postMessage({\n id,\n deps: {},\n nodeIds: [],\n failed: true\n });\n }\n };\n /**\n * 依赖收集 worker\n *\n * 依赖收集需要深度遍历整棵节点树并对每个属性做 target 匹配,节点/数据源多时会长时间占用主线程导致页面卡死,\n * 因此把遍历匹配放到 worker 中执行。\n * 全量与增量收集共用一个常驻 worker:收集逻辑只打进一份 inline worker 产物,\n * 也不会因为频繁收集而反复创建线程,每个请求用 id 与调用方对应。\n */\n onmessage = (e) => {\n if (\"dsl\" in e.data) {\n collectDsl(e.data);\n return;\n }\n collectNodes(e.data);\n };\n //#endregion\n})();\n";
|
|
2
|
+
var jsContent = "(function() {\n //#region packages/schema/src/index.ts\n const NODE_CONDS_KEY = \"displayConds\";\n const NODE_DISABLE_DATA_SOURCE_KEY = \"_tmagic_node_disabled_data_source\";\n const NODE_DISABLE_CODE_BLOCK_KEY = \"_tmagic_node_disabled_code_block\";\n let HookType = /* @__PURE__ */ function(HookType) {\n /** 代码块钩子标识 */\n HookType[\"CODE\"] = \"code\";\n return HookType;\n }({});\n //#endregion\n //#region packages/utils/src/const.ts\n const DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX = \"ds-field::\";\n //#endregion\n //#region packages/utils/src/index.ts\n const isObject = (obj) => Object.prototype.toString.call(obj) === \"[object Object]\";\n const getKeysArray = (keys) => `${keys}`.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n const dataSourceTemplateRegExp = /\\$\\{([\\s\\S]+?)\\}/g;\n //#endregion\n //#region packages/dep/src/types.ts\n /** 依赖收集的目标类型 */\n let DepTargetType = /* @__PURE__ */ function(DepTargetType) {\n DepTargetType[\"DEFAULT\"] = \"default\";\n /** 代码块 */\n DepTargetType[\"CODE_BLOCK\"] = \"code-block\";\n /** 数据源 */\n DepTargetType[\"DATA_SOURCE\"] = \"data-source\";\n /** 数据源方法 */\n DepTargetType[\"DATA_SOURCE_METHOD\"] = \"data-source-method\";\n /** 数据源条件 */\n DepTargetType[\"DATA_SOURCE_COND\"] = \"data-source-cond\";\n return DepTargetType;\n }({});\n //#endregion\n //#region packages/dep/src/Target.ts\n /**\n * 需要收集依赖的目标\n * 例如:一个代码块可以为一个目标\n */\n var Target = class {\n /**\n * 如何识别目标\n */\n isTarget;\n /**\n * 目标id,不可重复\n * 例如目标是代码块,则为代码块id\n */\n id;\n /**\n * 目标名称,用于显示在依赖列表中\n */\n name;\n /**\n * 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type\n */\n type = DepTargetType.DEFAULT;\n /**\n * 依赖详情\n * 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } }\n */\n deps = {};\n /**\n * 是否默认收集,默认为true,当值为false时需要传入type参数给collect方法才会被收集\n */\n isCollectByDefault;\n /**\n * 可序列化描述,有该描述的 target 可以在 worker 中重建,从而把依赖收集放到子线程执行\n */\n descriptor;\n constructor(options) {\n this.isTarget = options.isTarget;\n this.id = options.id;\n this.name = options.name;\n this.isCollectByDefault = options.isCollectByDefault ?? true;\n this.descriptor = options.descriptor;\n if (options.type) this.type = options.type;\n if (options.initialDeps) this.deps = options.initialDeps;\n }\n /**\n * 更新依赖\n * @param option 节点配置\n * @param key 哪个key配置了这个目标的id\n */\n updateDep({ id, name, key, data }) {\n const dep = this.deps[id] || {\n name,\n keys: []\n };\n dep.name = name;\n dep.data = data;\n this.deps[id] = dep;\n if (!dep.keys.includes(key)) dep.keys.push(key);\n }\n /**\n * 删除依赖\n * @param node 哪个节点的依赖需要移除,如果为空,则移除所有依赖\n * @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key\n * @returns void\n */\n removeDep(id, key) {\n if (typeof id === \"undefined\") {\n Object.keys(this.deps).forEach((depKey) => {\n delete this.deps[depKey];\n });\n return;\n }\n const dep = this.deps[id];\n if (!dep) return;\n if (key) {\n const index = dep.keys.indexOf(key);\n dep.keys.splice(index, 1);\n if (dep.keys.length === 0) delete this.deps[id];\n } else delete this.deps[id];\n }\n /**\n * 判断指定节点下的指定key是否存在在依赖列表中\n * @param node 哪个节点\n * @param key 哪个key\n * @returns boolean\n */\n hasDep(id, key) {\n return this.deps[id]?.keys.includes(key) ?? false;\n }\n destroy() {\n this.deps = {};\n }\n };\n //#endregion\n //#region packages/dep/src/utils.ts\n const INTEGER_REGEXP = /^\\d+$/;\n const createCodeBlockTarget = (id, codeBlock, initialDeps = {}) => new Target({\n type: DepTargetType.CODE_BLOCK,\n id,\n initialDeps,\n name: codeBlock.name,\n descriptor: {\n type: DepTargetType.CODE_BLOCK,\n id,\n codeBlock: { name: codeBlock.name }\n },\n isTarget: (_key, value) => {\n if (id === value) return true;\n if (value?.hookType === HookType.CODE && Array.isArray(value.hookData)) return value.hookData.some((item) => item.codeId === id);\n return false;\n }\n });\n /**\n * ['array'] ['array', '0'] ['array', '0', 'a'] 这种返回false\n * ['array', 'a'] 这种返回true\n * @param keys\n * @param fields\n * @returns boolean\n */\n const isIncludeArrayField = (keys, fields) => {\n let f = fields;\n return keys.some((key, index) => {\n const field = f.find(({ name }) => name === key);\n f = field?.fields || [];\n return field?.type === \"array\" && index < keys.length - 1 && !INTEGER_REGEXP.test(keys[index + 1]);\n });\n };\n /**\n * 判断模板(value)是不是使用数据源Id(dsId),如:`xxx${dsId.field}xxx${dsId.field}`\n * @param value any\n * @param dsId string | number\n * @param hasArray boolean true: 一定要包含有需要迭代的模板; false: 一定要包含普通模板;\n * @returns boolean\n */\n const isDataSourceTemplate = (value, ds, hasArray = false) => {\n const templates = value.match(dataSourceTemplateRegExp) || [];\n if (templates.length <= 0) return false;\n for (const tpl of templates) {\n const expression = tpl.substring(2, tpl.length - 1);\n const keys = getKeysArray(expression);\n const dsId = keys.shift();\n if (!dsId || dsId !== ds.id) continue;\n if (hasArray === isIncludeArrayField(keys, ds.fields)) return true;\n }\n return false;\n };\n /**\n * 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}\n * @param value any\n * @param dsId string | number\n * @returns boolean\n */\n const isSpecificDataSourceTemplate = (value, dsId) => value?.isBindDataSourceField && value.dataSourceId && value.dataSourceId === dsId && typeof value.template === \"string\";\n /**\n * 关联数据源字段,格式为 [前缀+数据源ID, 字段名]\n * 使用data-source-field-select value: 'value' 可以配置出来\n * @param value any[]\n * @param id string | number\n * @returns boolean\n */\n const isUseDataSourceField = (value, id) => {\n if (!Array.isArray(value) || typeof value[0] !== \"string\") return false;\n const [prefixId] = value;\n const prefixIndex = prefixId.indexOf(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);\n if (prefixIndex === -1) return false;\n return prefixId.substring(prefixIndex + DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX.length) === id;\n };\n const isDataSourceTarget = (ds, key, value, hasArray = false) => {\n if (!value) return false;\n const valueType = typeof value;\n if (valueType !== \"string\" && valueType !== \"object\") return false;\n if (`${key}`.startsWith(\"displayConds\")) return false;\n if (valueType === \"string\") return isDataSourceTemplate(value, ds, hasArray);\n if (isObject(value) && value.isBindDataSource && value.dataSourceId === ds.id) return true;\n if (isSpecificDataSourceTemplate(value, ds.id)) return true;\n if (isUseDataSourceField(value, ds.id)) {\n const [, ...keys] = value;\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const isDataSourceCondTarget = (ds, key, value, hasArray = false) => {\n if (!Array.isArray(value) || !ds) return false;\n const [dsId, ...keys] = value;\n if (dsId !== ds.id || !`${key}`.startsWith(\"displayConds\")) return false;\n if (ds.fields?.some((field) => field.name === keys[0])) {\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const createDataSourceTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE,\n id: ds.id,\n initialDeps,\n descriptor: {\n type: DepTargetType.DATA_SOURCE,\n ds: {\n id: ds.id,\n fields: ds.fields || []\n }\n },\n isTarget: (key, value) => isDataSourceTarget(ds, key, value)\n });\n const createDataSourceCondTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_COND,\n id: ds.id,\n initialDeps,\n descriptor: {\n type: DepTargetType.DATA_SOURCE_COND,\n ds: {\n id: ds.id,\n fields: ds.fields || []\n }\n },\n isTarget: (key, value) => isDataSourceCondTarget(ds, key, value)\n });\n const createDataSourceMethodTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_METHOD,\n id: ds.id,\n initialDeps,\n descriptor: {\n type: DepTargetType.DATA_SOURCE_METHOD,\n ds: {\n id: ds.id,\n methods: (ds.methods || []).map((method) => ({ name: method.name })),\n fields: (ds.fields || []).map((field) => ({ name: field.name }))\n }\n },\n isTarget: (_key, value) => {\n if (!Array.isArray(value)) return false;\n const [dsId, methodName] = value;\n if (!methodName || dsId !== ds.id) return false;\n if (ds.methods?.some((method) => method.name === methodName)) return true;\n if (ds.fields?.some((field) => field.name === methodName)) return false;\n return true;\n }\n });\n /**\n * 用可序列化描述重建 target,使依赖收集可以在 worker 等无法传递函数的环境中进行\n * @param descriptor 由工厂函数写入 target 的描述\n * @param initialDeps 初始依赖\n * @returns Target\n */\n const createTargetByDescriptor = (descriptor, initialDeps = {}) => {\n switch (descriptor.type) {\n case DepTargetType.CODE_BLOCK: return createCodeBlockTarget(descriptor.id, descriptor.codeBlock, initialDeps);\n case DepTargetType.DATA_SOURCE: return createDataSourceTarget(descriptor.ds, initialDeps);\n case DepTargetType.DATA_SOURCE_COND: return createDataSourceCondTarget(descriptor.ds, initialDeps);\n case DepTargetType.DATA_SOURCE_METHOD: return createDataSourceMethodTarget(descriptor.ds, initialDeps);\n default: throw new Error(`unknown target descriptor: ${JSON.stringify(descriptor)}`);\n }\n };\n const traverseTarget = (targetsList, cb, type) => {\n if (type) {\n const targets = targetsList[type];\n if (targets) for (const target of Object.values(targets)) cb(target);\n return;\n }\n for (const targets of Object.values(targetsList)) for (const target of Object.values(targets)) cb(target);\n };\n //#endregion\n //#region packages/dep/src/Watcher.ts\n const DATA_SOURCE_TARGET_TYPES = /* @__PURE__ */ new Set([\n DepTargetType.DATA_SOURCE,\n DepTargetType.DATA_SOURCE_COND,\n DepTargetType.DATA_SOURCE_METHOD\n ]);\n var Watcher = class {\n targetsList = {};\n childrenProp = \"items\";\n idProp = \"id\";\n nameProp = \"name\";\n constructor(options) {\n if (options?.initialTargets) this.targetsList = options.initialTargets;\n if (options?.childrenProp) this.childrenProp = options.childrenProp;\n }\n getTargetsList() {\n return this.targetsList;\n }\n /**\n * 获取指定类型中的所有target\n * @param type 分类\n * @returns Target[]\n */\n getTargets(type = DepTargetType.DEFAULT) {\n return this.targetsList[type] || {};\n }\n /**\n * 添加新的目标\n * @param target Target\n */\n addTarget(target) {\n const targets = this.getTargets(target.type) || {};\n this.targetsList[target.type] = targets;\n targets[target.id] = target;\n }\n /**\n * 获取指定id的target\n * @param id target id\n * @returns Target\n */\n getTarget(id, type = DepTargetType.DEFAULT) {\n return this.getTargets(type)[id];\n }\n /**\n * 判断是否存在指定id的target\n * @param id target id\n * @returns boolean\n */\n hasTarget(id, type = DepTargetType.DEFAULT) {\n return Boolean(this.getTarget(id, type));\n }\n /**\n * 判断是否存在指定类型的target\n * @param type target type\n * @returns boolean\n */\n hasSpecifiedTypeTarget(type = DepTargetType.DEFAULT) {\n return Object.keys(this.getTargets(type)).length > 0;\n }\n /**\n * 删除指定id的target\n * @param id target id\n */\n removeTarget(id, type = DepTargetType.DEFAULT) {\n const targets = this.getTargets(type);\n if (targets[id]) {\n targets[id].destroy();\n delete targets[id];\n }\n }\n /**\n * 删除指定分类的所有target\n * @param type 分类\n * @returns void\n */\n removeTargets(type = DepTargetType.DEFAULT) {\n const targets = this.targetsList[type];\n if (!targets) return;\n for (const target of Object.values(targets)) target.destroy();\n delete this.targetsList[type];\n }\n /**\n * 删除所有target\n */\n clearTargets() {\n for (const key of Object.keys(this.targetsList)) delete this.targetsList[key];\n }\n /**\n * 收集依赖\n * @param nodes 需要收集的节点\n * @param deep 是否需要收集子节点\n * @param type 强制收集指定类型的依赖\n */\n collect(nodes, depExtendedData = {}, deep = false, type) {\n const targets = this.getCollectableTargets(type);\n if (!targets.length) return;\n for (const node of nodes) {\n this.removeTargetsDep(targets, node);\n this.collectItems(node, targets, depExtendedData, deep);\n }\n }\n /**\n * 获取本次需要参与收集的 target(过滤规则与 collectByCallback 一致)\n *\n * 注:供 editor 的 dep service / worker 跨包批量收集时复用,因此为 public。\n * @param type 强制收集指定类型的依赖\n */\n getCollectableTargets(type) {\n const targets = [];\n traverseTarget(this.targetsList, (target) => {\n if (!type && !target.isCollectByDefault) return;\n targets.push(target);\n }, type);\n return targets;\n }\n collectByCallback(nodes, type, cb) {\n traverseTarget(this.targetsList, (target) => {\n if (!type && !target.isCollectByDefault) return;\n for (const node of nodes) cb({\n node,\n target\n });\n }, type);\n }\n /**\n * 清除所有目标的依赖\n * @param nodes 需要清除依赖的节点\n */\n clear(nodes, type) {\n let { targetsList } = this;\n if (type) targetsList = { [type]: this.getTargets(type) };\n const clearedItemsNodeIds = /* @__PURE__ */ new Set();\n traverseTarget(targetsList, (target) => {\n if (nodes) for (const node of nodes) {\n target.removeDep(node[this.idProp]);\n if (Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length && !clearedItemsNodeIds.has(node[this.idProp])) {\n clearedItemsNodeIds.add(node[this.idProp]);\n this.clear(node[this.childrenProp]);\n }\n }\n else target.removeDep();\n });\n }\n /**\n * 清除指定类型的依赖\n * @param type 类型\n * @param nodes 需要清除依赖的节点\n */\n clearByType(type, nodes) {\n this.clear(nodes, type);\n }\n /**\n * 收集单个 target 的依赖,等价于 collectItems(node, [target], ...)\n */\n collectItem(node, target, depExtendedData = {}, deep = false) {\n this.collectItems(node, [target], depExtendedData, deep);\n }\n removeTargetDep(target, node, key) {\n target.removeDep(node[this.idProp], key);\n if (typeof key === \"undefined\" && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) for (const item of node[this.childrenProp]) this.removeTargetDep(target, item, key);\n }\n /**\n * 与 removeTargetDep 等价,但一次子树递归同时处理多个 target,\n * 把删除阶段的结构遍历从 ×targets 降到 ×1。\n *\n * 注:供 editor 的 dep service 跨包批量删除时复用,因此为 public。\n */\n removeTargetsDep(targets, node, key) {\n const id = node[this.idProp];\n for (const target of targets) target.removeDep(id, key);\n if (typeof key === \"undefined\" && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) for (const item of node[this.childrenProp]) this.removeTargetsDep(targets, item, key);\n }\n /**\n * 与 collectItem 等价,但一次遍历同时处理多个 target(不含删除阶段)。\n *\n * 关键优化:原实现对每个 target 都完整遍历一遍节点树(O(targets × 树规模)),大页面 + 大量数据源时,\n * 结构遍历(Object.entries / 递归 / fullKey 字符串拼接)会被重复 targets 次。这里改为「整棵树只遍历一次,\n * 在每个属性上检查所有 target」,把结构遍历开销从 ×targets 降到 ×1,isTarget 调用次数不变,收集结果完全一致。\n *\n * 注:供 editor 的 dep service / worker 跨包批量收集时复用,因此为 public。\n */\n collectItems(node, targets, depExtendedData = {}, deep = false) {\n const activeTargets = this.filterTargetsByNode(node, targets);\n if (!activeTargets.length) return;\n this.collectTargetForTargets(node, node, \"\", activeTargets, depExtendedData, deep);\n }\n filterTargetsByNode(node, targets) {\n const disableDataSource = Boolean(node[NODE_DISABLE_DATA_SOURCE_KEY]);\n const disableCodeBlock = Boolean(node[NODE_DISABLE_CODE_BLOCK_KEY]);\n if (!disableDataSource && !disableCodeBlock) return targets;\n return targets.filter((target) => {\n if (disableDataSource && DATA_SOURCE_TARGET_TYPES.has(target.type)) return false;\n if (disableCodeBlock && target.type === DepTargetType.CODE_BLOCK) return false;\n return true;\n });\n }\n collectTargetForTargets(node, config, prop, targets, depExtendedData, deep) {\n const id = node[this.idProp];\n const name = `${node[this.nameProp] || node[this.idProp]}`;\n for (const [key, value] of Object.entries(config)) {\n if (typeof value === \"undefined\" || value === \"\") continue;\n const keyIsItems = key === this.childrenProp;\n const fullKey = prop ? `${prop}.${key}` : key;\n let notMatched = null;\n for (let i = 0, l = targets.length; i < l; i++) {\n const target = targets[i];\n if (target.isTarget(fullKey, value, config)) target.updateDep({\n id,\n name,\n data: depExtendedData,\n key: fullKey\n });\n else (notMatched || (notMatched = [])).push(target);\n }\n if (notMatched) {\n if (!keyIsItems && Array.isArray(value)) for (let i = 0, l = value.length; i < l; i++) {\n const item = value[i];\n if (isObject(item)) this.collectTargetForTargets(node, item, `${fullKey}[${i}]`, notMatched, depExtendedData, deep);\n }\n else if (isObject(value)) this.collectTargetForTargets(node, value, fullKey, notMatched, depExtendedData, deep);\n }\n if (keyIsItems && deep && Array.isArray(value)) for (const child of value) this.collectItems(child, targets, depExtendedData, deep);\n }\n }\n };\n //#endregion\n //#region packages/editor/src/utils/logger.ts\n const error = (...args) => {\n if (process.env.NODE_ENV === \"development\") console.error(\"magic editor: \", ...args);\n };\n //#endregion\n //#region packages/editor/src/utils/dep/worker.ts\n /**\n * 收集本次会覆盖到的节点 id\n * 必须与 deep 一致:deep=false 时不能带上子孙 id,否则写回阶段 removeDep 会清掉未重收的子节点依赖\n */\n const getNodeIds = (nodes, deep, ids = []) => {\n for (const node of nodes) {\n ids.push(node.id);\n if (deep && Array.isArray(node.items) && node.items.length) getNodeIds(node.items, deep, ids);\n }\n return ids;\n };\n const collectDsl = ({ id, dsl }) => {\n try {\n const mApp = eval(`(${dsl})`);\n if (!mApp) {\n postMessage({\n id,\n deps: {}\n });\n return;\n }\n const watcher = new Watcher({ initialTargets: {} });\n if (mApp.codeBlocks) for (const [id, code] of Object.entries(mApp.codeBlocks)) watcher.addTarget(createCodeBlockTarget(id, code));\n if (mApp.dataSources) for (const ds of mApp.dataSources) {\n watcher.addTarget(createDataSourceTarget(ds, {}));\n watcher.addTarget(createDataSourceMethodTarget(ds, {}));\n watcher.addTarget(createDataSourceCondTarget(ds, {}));\n }\n const targets = watcher.getCollectableTargets();\n for (const page of mApp.items) watcher.collectItems(page, targets, { pageId: page.id }, true);\n const deps = {\n [DepTargetType.DATA_SOURCE]: {},\n [DepTargetType.DATA_SOURCE_METHOD]: {},\n [DepTargetType.DATA_SOURCE_COND]: {},\n [DepTargetType.CODE_BLOCK]: {}\n };\n traverseTarget(watcher.getTargetsList(), (target) => {\n deps[target.type][target.id] = target.deps;\n });\n const response = {\n id,\n deps\n };\n postMessage(response);\n } catch (e) {\n error(e);\n postMessage({\n id,\n deps: {},\n failed: true\n });\n }\n };\n const collectNodes = ({ id, payload }) => {\n try {\n const { nodes, targets: descriptors, depExtendedData, deep } = eval(`(${payload})`);\n const watcher = new Watcher({ initialTargets: {} });\n const targets = descriptors.map((descriptor) => createTargetByDescriptor(descriptor));\n for (const node of nodes) watcher.collectItems(node, targets, depExtendedData, deep);\n const deps = {};\n for (const target of targets) {\n if (!deps[target.type]) deps[target.type] = {};\n deps[target.type][target.id] = target.deps;\n }\n const response = {\n id,\n deps,\n nodeIds: getNodeIds(nodes, deep)\n };\n postMessage(response);\n } catch (e) {\n error(e);\n postMessage({\n id,\n deps: {},\n nodeIds: [],\n failed: true\n });\n }\n };\n /**\n * 依赖收集 worker\n *\n * 依赖收集需要深度遍历整棵节点树并对每个属性做 target 匹配,节点/数据源多时会长时间占用主线程导致页面卡死,\n * 因此把遍历匹配放到 worker 中执行。\n * 全量与增量收集共用一个常驻 worker:收集逻辑只打进一份 inline worker 产物,\n * 也不会因为频繁收集而反复创建线程,每个请求用 id 与调用方对应。\n */\n onmessage = (e) => {\n if (\"dsl\" in e.data) {\n collectDsl(e.data);\n return;\n }\n collectNodes(e.data);\n };\n //#endregion\n})();\n";
|
|
3
3
|
var blob = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent], { type: "text/javascript;charset=utf-8" });
|
|
4
4
|
function WorkerWrapper(options) {
|
|
5
5
|
let objURL;
|
|
@@ -3,35 +3,13 @@ import editor_default from "../services/editor.js";
|
|
|
3
3
|
import { getFieldType, resolveFieldByPath } from "./data-source/index.js";
|
|
4
4
|
import codeBlock_default from "../services/codeBlock.js";
|
|
5
5
|
import dataSource_default from "../services/dataSource.js";
|
|
6
|
-
import { validateTypeMatch } from "@tmagic/form";
|
|
6
|
+
import { MAX_SUGGESTION_OPTIONS, optionSuggestion, stringifyExampleValue, validateTypeMatch } from "@tmagic/form";
|
|
7
7
|
import { appendValidateSuggestion } from "@tmagic/design";
|
|
8
8
|
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_SET_DATA_METHOD_NAME, dataSourceTemplateRegExp, getKeysArray, removeDataSourceFieldPrefix } from "@tmagic/utils";
|
|
9
9
|
import { NodeType } from "@tmagic/core";
|
|
10
10
|
//#region packages/editor/src/utils/type-match-rules.ts
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
var stringifyExampleValue = (value) => {
|
|
15
|
-
if (typeof value === "string") return `"${value}"`;
|
|
16
|
-
if (value === null || value === void 0) return String(value);
|
|
17
|
-
if (typeof value === "object") try {
|
|
18
|
-
return JSON.stringify(value);
|
|
19
|
-
} catch {
|
|
20
|
-
return String(value);
|
|
21
|
-
}
|
|
22
|
-
return String(value);
|
|
23
|
-
};
|
|
24
|
-
var MAX_SUGGESTION_OPTIONS = 20;
|
|
25
|
-
/**
|
|
26
|
-
* 生成「请使用以下某一个值:xxx;xxx」形式的参考建议;无可选值时返回空字符串(不追加建议)。
|
|
27
|
-
*/
|
|
28
|
-
var listSuggestion = (values) => {
|
|
29
|
-
const list = values.filter((item) => typeof item !== "undefined" && item !== null && item !== "");
|
|
30
|
-
if (!list.length) return "";
|
|
31
|
-
const shown = list.slice(0, MAX_SUGGESTION_OPTIONS).map(stringifyExampleValue);
|
|
32
|
-
const suffix = list.length > MAX_SUGGESTION_OPTIONS ? " 等" : "";
|
|
33
|
-
return `请使用以下某一个值:${shown.join(";")}${suffix}`;
|
|
34
|
-
};
|
|
11
|
+
/** 复用 form 的可选项建议文案;额外过滤 null / 空串,避免业务侧无效枚举污染提示。 */
|
|
12
|
+
var listSuggestion = (values) => optionSuggestion(values.filter((item) => item !== null && item !== ""));
|
|
35
13
|
/**
|
|
36
14
|
* 列出当前可用数据源 id 的参考建议。
|
|
37
15
|
*/
|
|
@@ -166,8 +144,9 @@ var displayCondSuggestion = () => {
|
|
|
166
144
|
const ds = getDataSources()[0];
|
|
167
145
|
if (!ds) return SUGGESTION_DISPLAY_COND;
|
|
168
146
|
const field = firstDataSourceFieldName(ds);
|
|
147
|
+
const fieldPath = field ? [`${ds.id}`, field] : [`${ds.id}`];
|
|
169
148
|
return `请参考以下示例值:${stringifyExampleValue([{ cond: [{
|
|
170
|
-
field:
|
|
149
|
+
field: fieldPath,
|
|
171
150
|
op: "=="
|
|
172
151
|
}] }])}`;
|
|
173
152
|
};
|
|
@@ -236,7 +215,8 @@ var validateCondOpSelect = (value, { message, props }) => {
|
|
|
236
215
|
const parentFields = props.config?.parentFields || [];
|
|
237
216
|
const fieldPath = Array.isArray(props.model?.field) ? props.model.field : [];
|
|
238
217
|
const [id, ...fieldNames] = [...parentFields, ...fieldPath];
|
|
239
|
-
const
|
|
218
|
+
const ds = id ? findDataSource(`${id}`) : void 0;
|
|
219
|
+
const allowed = getCondOpsByFieldType(getFieldType(ds, fieldNames));
|
|
240
220
|
if (!allowed.has(value)) return defaultMessage(message, `${value} 不在可选项中`, listSuggestion([...allowed]));
|
|
241
221
|
};
|
|
242
222
|
var validateCodeSelectCol = (value, { message }) => {
|