@tmagic/editor 1.8.0-beta.22 → 1.8.0-beta.24
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/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +12 -7
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +6 -4
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +8 -5
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/hooks/use-compare-form.js +1 -2
- package/dist/es/index.js +2 -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/plugin.js +9 -1
- package/dist/es/services/codeBlock.js +4 -2
- package/dist/es/services/editor.js +43 -37
- package/dist/es/services/props.js +1 -1
- package/dist/es/utils/dep/collect-worker-client.js +67 -27
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/event.js +1 -1
- package/dist/es/utils/props.js +1 -29
- package/dist/es/utils/type-match-rules.js +7 -27
- package/dist/tmagic-editor.umd.cjs +663 -629
- package/package.json +13 -13
- package/src/fields/CodeSelectCol.vue +4 -2
- package/src/fields/DataSourceFieldSelect/Index.vue +5 -3
- package/src/fields/DataSourceFields.vue +6 -0
- package/src/fields/DataSourceMethodSelect.vue +4 -1
- package/src/fields/DataSourceMethods.vue +11 -2
- package/src/fields/DataSourceMocks.vue +10 -1
- package/src/fields/EventSelect.vue +3 -3
- package/src/fields/KeyValue.vue +4 -3
- package/src/hooks/use-compare-form.ts +1 -4
- package/src/plugin.ts +9 -1
- package/src/services/editor.ts +53 -85
- package/src/type.ts +23 -0
- package/src/utils/dep/collect-worker-client.ts +97 -31
- package/src/utils/event.ts +2 -2
- package/src/utils/props.ts +0 -32
- package/src/utils/type-match-rules.ts +4 -30
- package/types/index.d.ts +121 -210
|
@@ -12,6 +12,8 @@ import { NodeType } from "@tmagic/core";
|
|
|
12
12
|
import { nextTick, reactive, toRaw } from "vue";
|
|
13
13
|
import { cloneDeep as cloneDeep$1, isEmpty, isEqual, isObject, mergeWith, uniq } from "lodash-es";
|
|
14
14
|
//#region packages/editor/src/services/editor.ts
|
|
15
|
+
/** 历史插回时把记录的下标收敛到 [0, length],越界(含未记录)一律追加到末尾 */
|
|
16
|
+
var clampIndex = (index, length) => typeof index === "number" && index >= 0 && index <= length ? index : length;
|
|
15
17
|
/**
|
|
16
18
|
* 把「变更前后节点快照」列表归一成 update 类型的 {@link StepDiffItem} 列表,供 {@link StepValue.diff} 使用。
|
|
17
19
|
* `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
|
|
@@ -51,6 +53,11 @@ var Editor = class extends BaseService {
|
|
|
51
53
|
* 普通操作不会读取它,调用前由 *AndGetHistoryId 重置为 null。
|
|
52
54
|
*/
|
|
53
55
|
lastPushedHistoryId = null;
|
|
56
|
+
/**
|
|
57
|
+
* 上一次 doAdd 的插入记忆:nodeId 为插入的节点,selectedId 为当时的选中节点。
|
|
58
|
+
* 仅在选中节点未变化时复用(连续 add / doNotSelect / 批量粘贴),选中变化后自动失效,无需手动清理。
|
|
59
|
+
*/
|
|
60
|
+
lastAdded = null;
|
|
54
61
|
constructor() {
|
|
55
62
|
super(canUsePluginMethods.async.map((methodName) => ({
|
|
56
63
|
name: methodName,
|
|
@@ -246,24 +253,29 @@ var Editor = class extends BaseService {
|
|
|
246
253
|
this.set("stage", null);
|
|
247
254
|
this.set("highlightNode", null);
|
|
248
255
|
}
|
|
249
|
-
async doAdd(node, parent) {
|
|
256
|
+
async doAdd(node, parent, _options = {}) {
|
|
250
257
|
const root = this.get("root");
|
|
251
258
|
if (!root) throw new Error("root为空");
|
|
252
259
|
const curNode = this.get("node");
|
|
253
260
|
const stage = this.get("stage");
|
|
254
261
|
if (!curNode) throw new Error("当前选中节点为空");
|
|
255
262
|
if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !isPageOrFragment(node)) throw new Error("app下不能添加组件");
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
const anchorId = this.lastAdded?.selectedId === curNode.id ? this.lastAdded.nodeId : curNode.id;
|
|
264
|
+
const anchorIndex = isPageOrFragment(node) ? -1 : Math.max(getNodeIndex(anchorId, parent), getNodeIndex(curNode.id, parent));
|
|
265
|
+
const insertIndex = anchorIndex < 0 ? parent.items.length : anchorIndex + 1;
|
|
266
|
+
parent.items.splice(insertIndex, 0, node);
|
|
267
|
+
this.lastAdded = {
|
|
268
|
+
selectedId: curNode.id,
|
|
269
|
+
nodeId: node.id
|
|
270
|
+
};
|
|
260
271
|
const layout = await this.getLayout(toRaw(parent), node);
|
|
261
272
|
node.style = getInitPositionStyle(node.style, layout);
|
|
262
273
|
await stage?.add({
|
|
263
274
|
config: cloneDeep$1(node),
|
|
264
275
|
parent: cloneDeep$1(parent),
|
|
265
276
|
parentId: parent.id,
|
|
266
|
-
root: cloneDeep$1(root)
|
|
277
|
+
root: cloneDeep$1(root),
|
|
278
|
+
index: insertIndex
|
|
267
279
|
});
|
|
268
280
|
const newStyle = fixNodePosition(node, parent, stage);
|
|
269
281
|
if (newStyle && (newStyle.top !== node.style.top || newStyle.left !== node.style.left)) {
|
|
@@ -281,14 +293,12 @@ var Editor = class extends BaseService {
|
|
|
281
293
|
* 向指点容器添加组件节点
|
|
282
294
|
* @param addConfig 将要添加的组件节点配置
|
|
283
295
|
* @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
|
|
284
|
-
* @param options
|
|
285
|
-
* @param options.doNotSelect 添加后是否不更新当前选中节点(默认 false,添加后会选中新增的节点)
|
|
286
|
-
* @param options.doNotSwitchPage 添加后是否不切换当前页面(默认 false;新增页面 / 跨页新增时为 true 会跳过会引发页面切换的选中操作)
|
|
287
|
-
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
296
|
+
* @param options 可选配置,见 {@link DslOpOptions}
|
|
288
297
|
* @returns 添加后的节点
|
|
289
298
|
*/
|
|
290
|
-
async add(addNode, parent,
|
|
299
|
+
async add(addNode, parent, options = {}) {
|
|
291
300
|
this.captureSelectionBeforeOp();
|
|
301
|
+
const { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = options;
|
|
292
302
|
const stage = this.get("stage");
|
|
293
303
|
const addNodes = [];
|
|
294
304
|
if (!Array.isArray(addNode)) {
|
|
@@ -296,13 +306,13 @@ var Editor = class extends BaseService {
|
|
|
296
306
|
if (!type) throw new Error("组件类型不能为空");
|
|
297
307
|
addNodes.push({ ...toRaw(await props_default.getPropsValue(type, config)) });
|
|
298
308
|
} else addNodes.push(...addNode);
|
|
299
|
-
const newNodes =
|
|
309
|
+
const newNodes = [];
|
|
310
|
+
for (const node of addNodes) {
|
|
300
311
|
const root = this.get("root");
|
|
301
|
-
|
|
302
|
-
const parentNode = parent ?? getAddParent(node);
|
|
312
|
+
const parentNode = isPageOrFragment(node) && root ? root : parent ?? getAddParent(node);
|
|
303
313
|
if (!parentNode) throw new Error("未找到父元素");
|
|
304
|
-
|
|
305
|
-
}
|
|
314
|
+
newNodes.push(await this.doAdd(node, parentNode, options));
|
|
315
|
+
}
|
|
306
316
|
if (newNodes.length > 1) {
|
|
307
317
|
const wouldSwitchPage = newNodes.some((n) => this.isOnDifferentPage(n));
|
|
308
318
|
if (!doNotSelect && !(doNotSwitchPage && wouldSwitchPage)) {
|
|
@@ -418,7 +428,8 @@ var Editor = class extends BaseService {
|
|
|
418
428
|
* @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
|
419
429
|
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
420
430
|
*/
|
|
421
|
-
async remove(nodeOrNodeList,
|
|
431
|
+
async remove(nodeOrNodeList, options = {}) {
|
|
432
|
+
const { doNotPushHistory = false, historyDescription, historySource } = options;
|
|
422
433
|
this.captureSelectionBeforeOp();
|
|
423
434
|
const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
|
|
424
435
|
const changeItems = nodes.map((node) => ({
|
|
@@ -442,10 +453,7 @@ var Editor = class extends BaseService {
|
|
|
442
453
|
});
|
|
443
454
|
}
|
|
444
455
|
}
|
|
445
|
-
await Promise.all(nodes.map((node) => this.doRemove(node,
|
|
446
|
-
doNotSelect,
|
|
447
|
-
doNotSwitchPage
|
|
448
|
-
})));
|
|
456
|
+
await Promise.all(nodes.map((node) => this.doRemove(node, options)));
|
|
449
457
|
this.removeInvalidNodesBySubtree(nodes);
|
|
450
458
|
if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", {
|
|
451
459
|
diff: removedItems,
|
|
@@ -467,7 +475,8 @@ var Editor = class extends BaseService {
|
|
|
467
475
|
remove: removedPages
|
|
468
476
|
});
|
|
469
477
|
}
|
|
470
|
-
async doUpdate(config,
|
|
478
|
+
async doUpdate(config, data = {}) {
|
|
479
|
+
const { changeRecords = [], historySource, replace = false } = data;
|
|
471
480
|
if (!this.get("root")) throw new Error("root为空");
|
|
472
481
|
if (!config?.id) throw new Error("没有配置或者配置缺少id值");
|
|
473
482
|
const info = this.getNodeInfo(config.id, false);
|
|
@@ -516,24 +525,18 @@ var Editor = class extends BaseService {
|
|
|
516
525
|
* 更新节点
|
|
517
526
|
* update后会触发依赖收集,收集完后会掉stage.update方法
|
|
518
527
|
* @param config 新的节点配置,配置中需要有id信息
|
|
519
|
-
* @param data
|
|
520
|
-
* @param data.changeRecords 单节点 form 端变更记录(多节点场景下被忽略,使用 changeRecordList)
|
|
521
|
-
* @param data.changeRecordList 多节点 form 端变更记录列表,按 config 数组同序对应每个节点;优先级高于 changeRecords
|
|
522
|
-
* @param data.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
523
|
-
* @param data.historyDescription 入栈时附带的人类可读描述,用于历史面板展示(不影响 undo/redo 行为)
|
|
524
|
-
* @param data.replace 是否整节点替换:为 true 时跳过 mergeWith / toggleFixedPosition / setChildrenLayout,直接用传入配置覆盖(默认 false)
|
|
528
|
+
* @param data 额外数据,见 {@link UpdateOptions}
|
|
525
529
|
* @returns 更新后的节点配置
|
|
526
530
|
*/
|
|
527
531
|
async update(config, data = {}) {
|
|
528
532
|
this.captureSelectionBeforeOp();
|
|
529
|
-
const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription, historySource,
|
|
533
|
+
const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription, historySource, invalidInfo } = data;
|
|
530
534
|
const nodes = Array.isArray(config) ? config : [config];
|
|
531
535
|
const updateData = await Promise.all(nodes.map((node, index) => {
|
|
532
536
|
const recordsForNode = changeRecordList ? changeRecordList[index] ?? [] : changeRecords ?? [];
|
|
533
537
|
return this.doUpdate(node, {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
replace
|
|
538
|
+
...data,
|
|
539
|
+
changeRecords: recordsForNode
|
|
537
540
|
});
|
|
538
541
|
}));
|
|
539
542
|
this.applyInvalidInfo(config, invalidInfo);
|
|
@@ -1376,14 +1379,15 @@ var Editor = class extends BaseService {
|
|
|
1376
1379
|
const parent = this.getNodeById(parentId, false);
|
|
1377
1380
|
if (parent?.items) {
|
|
1378
1381
|
const addedNode = cloneDeep$1(newSchema);
|
|
1379
|
-
|
|
1380
|
-
|
|
1382
|
+
const insertIndex = clampIndex(index, parent.items.length);
|
|
1383
|
+
parent.items.splice(insertIndex, 0, addedNode);
|
|
1381
1384
|
addedNodes.push(addedNode);
|
|
1382
1385
|
await stage?.add({
|
|
1383
1386
|
config: cloneDeep$1(newSchema),
|
|
1384
1387
|
parent: cloneDeep$1(parent),
|
|
1385
1388
|
parentId: parent.id,
|
|
1386
|
-
root: cloneDeep$1(root)
|
|
1389
|
+
root: cloneDeep$1(root),
|
|
1390
|
+
index: insertIndex
|
|
1387
1391
|
});
|
|
1388
1392
|
}
|
|
1389
1393
|
}
|
|
@@ -1401,13 +1405,15 @@ var Editor = class extends BaseService {
|
|
|
1401
1405
|
const parent = this.getNodeById(parentId, false);
|
|
1402
1406
|
if (parent?.items) {
|
|
1403
1407
|
const addedNode = cloneDeep$1(oldSchema);
|
|
1404
|
-
|
|
1408
|
+
const insertIndex = clampIndex(index, parent.items.length);
|
|
1409
|
+
parent.items.splice(insertIndex, 0, addedNode);
|
|
1405
1410
|
addedNodes.push(addedNode);
|
|
1406
1411
|
await stage?.add({
|
|
1407
1412
|
config: cloneDeep$1(oldSchema),
|
|
1408
1413
|
parent: cloneDeep$1(parent),
|
|
1409
1414
|
parentId,
|
|
1410
|
-
root: cloneDeep$1(root)
|
|
1415
|
+
root: cloneDeep$1(root),
|
|
1416
|
+
index: insertIndex
|
|
1411
1417
|
});
|
|
1412
1418
|
}
|
|
1413
1419
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fillConfig } from "../utils/props.js";
|
|
2
1
|
import BaseService from "./BaseService.js";
|
|
2
|
+
import { fillConfig } from "../utils/props.js";
|
|
3
3
|
import editor_default from "./editor.js";
|
|
4
4
|
import { getNodePath, getValueByKeyPath, guid, setValueByKeyPath, toLine } from "@tmagic/utils";
|
|
5
5
|
import { Target, Watcher } from "@tmagic/core";
|
|
@@ -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;
|
package/dist/es/utils/event.js
CHANGED
|
@@ -19,7 +19,7 @@ var normalizeCompActionValue = (value) => {
|
|
|
19
19
|
var getEventNameOptions = (src, formValue = {}) => {
|
|
20
20
|
if (!formValue.type) return [];
|
|
21
21
|
if (src === "component") {
|
|
22
|
-
const sourceNode = editor_default.getNodeById(formValue.id);
|
|
22
|
+
const sourceNode = editor_default.getNodeById(formValue.id) || formValue;
|
|
23
23
|
let events = events_default.getEvent(formValue.type, { node: sourceNode }) || [];
|
|
24
24
|
if (formValue.type === "page-fragment-container" && formValue.pageFragmentId) {
|
|
25
25
|
const pageFragment = editor_default.get("root")?.items?.find((page) => page.id === formValue.pageFragmentId);
|
package/dist/es/utils/props.js
CHANGED
|
@@ -62,7 +62,6 @@ var getCondOpOptionsByFieldType = (type) => {
|
|
|
62
62
|
};
|
|
63
63
|
var styleTabConfig = {
|
|
64
64
|
title: "样式",
|
|
65
|
-
lazy: true,
|
|
66
65
|
display: ({ services }) => !(services?.uiService?.get("showStylePanel") ?? true),
|
|
67
66
|
items: [{
|
|
68
67
|
name: "style",
|
|
@@ -127,7 +126,6 @@ var styleTabConfig = {
|
|
|
127
126
|
};
|
|
128
127
|
var eventTabConfig = {
|
|
129
128
|
title: "事件",
|
|
130
|
-
lazy: true,
|
|
131
129
|
items: [{
|
|
132
130
|
name: "events",
|
|
133
131
|
src: "component",
|
|
@@ -138,7 +136,6 @@ var eventTabConfig = {
|
|
|
138
136
|
};
|
|
139
137
|
var advancedTabConfig = {
|
|
140
138
|
title: "高级",
|
|
141
|
-
lazy: true,
|
|
142
139
|
items: [
|
|
143
140
|
{
|
|
144
141
|
name: NODE_DISABLE_CODE_BLOCK_KEY,
|
|
@@ -288,30 +285,5 @@ var fillConfig = (config = [], { labelWidth = "80px", disabledDataSource = false
|
|
|
288
285
|
if (!disabledDataSource) tabConfig.items.push({ ...displayTabConfig });
|
|
289
286
|
return [tabConfig];
|
|
290
287
|
};
|
|
291
|
-
/**
|
|
292
|
-
* 将属性表单配置中「样式」tab-pane 的 `display` 强制置为 `true`。
|
|
293
|
-
*
|
|
294
|
-
* `propsService.getPropsConfig` 返回的样式 tab 默认带有
|
|
295
|
-
* `display: ({ services }) => !(services?.uiService?.get('showStylePanel') ?? true)`,
|
|
296
|
-
* 在对比 / 只读展示场景(CompareForm / ViewForm)下并不需要跟随 uiService 状态隐藏,
|
|
297
|
-
* 这里统一放开,保证样式 tab 始终可见。
|
|
298
|
-
*
|
|
299
|
-
* @param formConfig 组件属性表单配置
|
|
300
|
-
* @returns 处理后的表单配置(不修改入参,返回浅拷贝)
|
|
301
|
-
*/
|
|
302
|
-
var removeStyleDisplayConfig = (formConfig) => formConfig.map((item) => {
|
|
303
|
-
if (!("type" in item)) return item;
|
|
304
|
-
if (item?.type !== "tab" || !Array.isArray(item.items)) return item;
|
|
305
|
-
return {
|
|
306
|
-
...item,
|
|
307
|
-
items: item.items.map((tabPane) => {
|
|
308
|
-
if (tabPane?.title !== "样式" || !Array.isArray(tabPane.items)) return tabPane;
|
|
309
|
-
return {
|
|
310
|
-
...tabPane,
|
|
311
|
-
display: true
|
|
312
|
-
};
|
|
313
|
-
})
|
|
314
|
-
};
|
|
315
|
-
});
|
|
316
288
|
//#endregion
|
|
317
|
-
export { advancedTabConfig, arrayOptions, booleanOptions, displayTabConfig, eqOptions, eventTabConfig, fillConfig, getCondOpOptionsByFieldType, numberOptions,
|
|
289
|
+
export { advancedTabConfig, arrayOptions, booleanOptions, displayTabConfig, eqOptions, eventTabConfig, fillConfig, getCondOpOptionsByFieldType, numberOptions, styleTabConfig };
|
|
@@ -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 }) => {
|