@tmagic/editor 1.8.0-beta.1 → 1.8.0-beta.3
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/Editor.vue_vue_type_script_setup_true_lang.js +20 -2
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +18 -110
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +6 -0
- package/dist/es/components/CompareForm.js +5 -0
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +171 -0
- package/dist/es/editorProps.js +2 -0
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +19 -3
- package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +2 -5
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +18 -2
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +37 -6
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +3 -1
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +15 -7
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +58 -11
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +11 -6
- package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +8 -5
- package/dist/es/hooks/use-code-block-edit.js +2 -2
- package/dist/es/hooks/use-stage.js +17 -8
- package/dist/es/index.js +5 -1
- package/dist/es/initService.js +6 -6
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +6 -5
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +12 -7
- package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +8 -0
- package/dist/es/layouts/history-list/Bucket.js +5 -0
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +103 -0
- package/dist/es/layouts/history-list/BucketTab.js +5 -0
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +73 -0
- package/dist/es/layouts/history-list/GroupRow.js +5 -0
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +198 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +245 -0
- package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +340 -0
- package/dist/es/layouts/history-list/InitialRow.js +5 -0
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +54 -0
- package/dist/es/layouts/history-list/PageTab.js +5 -0
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +105 -0
- package/dist/es/layouts/history-list/composables.js +172 -0
- package/dist/es/layouts/sidebar/layer/use-node-status.js +1 -1
- package/dist/es/services/BaseService.js +31 -68
- package/dist/es/services/codeBlock.js +205 -8
- package/dist/es/services/componentList.js +2 -2
- package/dist/es/services/dataSource.js +217 -8
- package/dist/es/services/dep.js +21 -9
- package/dist/es/services/editor.js +350 -160
- package/dist/es/services/events.js +2 -2
- package/dist/es/services/history.js +410 -12
- package/dist/es/services/keybinding.js +2 -2
- package/dist/es/services/props.js +8 -8
- package/dist/es/services/stageOverlay.js +2 -2
- package/dist/es/services/storage.js +2 -2
- package/dist/es/services/ui.js +2 -2
- package/dist/es/style.css +379 -1
- package/dist/es/utils/code-block.js +122 -0
- package/dist/es/utils/content-menu.js +4 -5
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/editor.js +2 -2
- package/dist/es/utils/undo-redo.js +20 -0
- package/dist/style.css +379 -1
- package/dist/tmagic-editor.umd.cjs +3331 -787
- package/package.json +7 -7
- package/src/Editor.vue +15 -0
- package/src/components/CodeBlockEditor.vue +20 -109
- package/src/components/CodeParams.vue +6 -0
- package/src/components/CompareForm.vue +238 -0
- package/src/editorProps.ts +9 -0
- package/src/fields/Code.vue +19 -3
- package/src/fields/CodeLink.vue +2 -5
- package/src/fields/CodeSelect.vue +17 -1
- package/src/fields/CodeSelectCol.vue +30 -2
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +7 -1
- package/src/fields/DataSourceFieldSelect/Index.vue +2 -2
- package/src/fields/DataSourceFields.vue +12 -3
- package/src/fields/DataSourceMethodSelect.vue +4 -1
- package/src/fields/DataSourceMethods.vue +14 -4
- package/src/fields/DataSourceMocks.vue +13 -3
- package/src/fields/DataSourceSelect.vue +4 -1
- package/src/fields/EventSelect.vue +57 -8
- package/src/fields/KeyValue.vue +17 -4
- package/src/fields/UISelect.vue +5 -2
- package/src/hooks/use-code-block-edit.ts +5 -2
- package/src/hooks/use-stage.ts +24 -7
- package/src/index.ts +3 -0
- package/src/layouts/CodeEditor.vue +17 -5
- package/src/layouts/Framework.vue +10 -5
- package/src/layouts/NavMenu.vue +9 -0
- package/src/layouts/history-list/Bucket.vue +124 -0
- package/src/layouts/history-list/BucketTab.vue +77 -0
- package/src/layouts/history-list/GroupRow.vue +242 -0
- package/src/layouts/history-list/HistoryDiffDialog.vue +231 -0
- package/src/layouts/history-list/HistoryListPanel.vue +380 -0
- package/src/layouts/history-list/InitialRow.vue +52 -0
- package/src/layouts/history-list/PageTab.vue +108 -0
- package/src/layouts/history-list/composables.ts +226 -0
- package/src/layouts/sidebar/layer/use-node-status.ts +9 -6
- package/src/services/BaseService.ts +35 -86
- package/src/services/codeBlock.ts +256 -5
- package/src/services/dataSource.ts +257 -6
- package/src/services/dep.ts +32 -9
- package/src/services/editor.ts +454 -171
- package/src/services/history.ts +455 -8
- package/src/services/props.ts +2 -2
- package/src/theme/history-list-panel.scss +462 -0
- package/src/theme/theme.scss +1 -0
- package/src/type.ts +269 -6
- package/src/utils/code-block.ts +150 -0
- package/src/utils/content-menu.ts +8 -5
- package/src/utils/dep/worker.ts +5 -3
- package/src/utils/index.ts +1 -0
- package/src/utils/undo-redo.ts +23 -0
- package/types/index.d.ts +2274 -819
- package/dist/es/utils/compose.js +0 -44
- package/dist/es/utils/editor-history.js +0 -100
- package/src/utils/compose.ts +0 -52
- package/src/utils/editor-history.ts +0 -138
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { CODE_DRAFT_STORAGE_KEY } from "../type.js";
|
|
2
|
-
import
|
|
2
|
+
import BaseService from "./BaseService.js";
|
|
3
|
+
import history_default from "./history.js";
|
|
3
4
|
import { getEditorConfig } from "../utils/config.js";
|
|
4
5
|
import storage_default, { Protocol } from "./storage.js";
|
|
5
6
|
import { COPY_CODE_STORAGE_KEY } from "../utils/editor.js";
|
|
6
7
|
import editor_default from "./editor.js";
|
|
8
|
+
import { getValueByKeyPath, setValueByKeyPath } from "@tmagic/utils";
|
|
7
9
|
import { Target, Watcher } from "@tmagic/core";
|
|
8
10
|
import { reactive } from "vue";
|
|
9
|
-
import { cloneDeep, get, keys, pick } from "lodash-es";
|
|
11
|
+
import { cloneDeep as cloneDeep$1, get, keys, pick } from "lodash-es";
|
|
10
12
|
//#region packages/editor/src/services/codeBlock.ts
|
|
11
13
|
var canUsePluginMethods = {
|
|
12
14
|
async: [
|
|
@@ -18,7 +20,18 @@ var canUsePluginMethods = {
|
|
|
18
20
|
],
|
|
19
21
|
sync: ["setCodeDslByIdSync"]
|
|
20
22
|
};
|
|
21
|
-
|
|
23
|
+
/**
|
|
24
|
+
* 「回滚」生成的新 step 简短描述。仅 service 层使用。
|
|
25
|
+
*/
|
|
26
|
+
var describeRevertCodeBlockStep = (step) => {
|
|
27
|
+
const { oldContent, newContent, changeRecords, id } = step;
|
|
28
|
+
if (oldContent === null && newContent) return `撤回新增 ${newContent.name || newContent.id || id}`;
|
|
29
|
+
if (oldContent && newContent === null) return `还原已删除的 ${oldContent.name || oldContent.id || id}`;
|
|
30
|
+
const name = newContent?.name || oldContent?.name || `${id}`;
|
|
31
|
+
const propPath = changeRecords?.[0]?.propPath;
|
|
32
|
+
return propPath ? `还原 ${name} · ${propPath}` : `还原 ${name}`;
|
|
33
|
+
};
|
|
34
|
+
var CodeBlock = class extends BaseService {
|
|
22
35
|
state = reactive({
|
|
23
36
|
codeDsl: null,
|
|
24
37
|
editable: true,
|
|
@@ -68,10 +81,16 @@ var CodeBlock = class extends BaseService_default {
|
|
|
68
81
|
* 设置代码块ID和代码内容到源dsl
|
|
69
82
|
* @param {Id} id 代码块id
|
|
70
83
|
* @param {CodeBlockContent} codeConfig 代码块内容配置信息
|
|
84
|
+
* @param options 可选配置
|
|
85
|
+
* @param options.changeRecords form 端 propPath/value 列表,用于历史记录的精细化撤销/重做
|
|
86
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
71
87
|
* @returns {void}
|
|
72
88
|
*/
|
|
73
|
-
async setCodeDslById(id, codeConfig) {
|
|
74
|
-
this.setCodeDslByIdSync(id, codeConfig, true
|
|
89
|
+
async setCodeDslById(id, codeConfig, { changeRecords, doNotPushHistory = false } = {}) {
|
|
90
|
+
this.setCodeDslByIdSync(id, codeConfig, true, {
|
|
91
|
+
changeRecords,
|
|
92
|
+
doNotPushHistory
|
|
93
|
+
});
|
|
75
94
|
}
|
|
76
95
|
/**
|
|
77
96
|
* 为了兼容历史原因
|
|
@@ -79,21 +98,33 @@ var CodeBlock = class extends BaseService_default {
|
|
|
79
98
|
* @param {Id} id 代码块id
|
|
80
99
|
* @param {CodeBlockContent} codeConfig 代码块内容配置信息
|
|
81
100
|
* @param {boolean} force 是否强制写入,默认true
|
|
101
|
+
* @param options 可选配置
|
|
102
|
+
* @param options.changeRecords form 端 propPath/value 列表,用于历史记录的精细化撤销/重做
|
|
103
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
104
|
+
* @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
|
|
82
105
|
* @returns {void}
|
|
83
106
|
*/
|
|
84
|
-
setCodeDslByIdSync(id, codeConfig, force = true) {
|
|
107
|
+
setCodeDslByIdSync(id, codeConfig, force = true, { changeRecords, doNotPushHistory = false, historyDescription } = {}) {
|
|
85
108
|
const codeDsl = this.getCodeDsl();
|
|
86
109
|
if (!codeDsl) throw new Error("dsl中没有codeBlocks");
|
|
87
110
|
if (codeDsl[id] && !force) return;
|
|
88
|
-
const codeConfigProcessed = cloneDeep(codeConfig);
|
|
111
|
+
const codeConfigProcessed = cloneDeep$1(codeConfig);
|
|
89
112
|
if (codeConfigProcessed.content) {
|
|
90
113
|
const parseDSL = getEditorConfig("parseDSL");
|
|
91
114
|
if (typeof codeConfigProcessed.content === "string") codeConfigProcessed.content = parseDSL(codeConfigProcessed.content);
|
|
92
115
|
}
|
|
116
|
+
const oldContent = codeDsl[id] ? cloneDeep$1(codeDsl[id]) : null;
|
|
93
117
|
codeDsl[id] = {
|
|
94
118
|
...codeDsl[id] || {},
|
|
95
119
|
...codeConfigProcessed
|
|
96
120
|
};
|
|
121
|
+
const newContent = cloneDeep$1(codeDsl[id]);
|
|
122
|
+
if (!doNotPushHistory) history_default.pushCodeBlock(id, {
|
|
123
|
+
oldContent,
|
|
124
|
+
newContent,
|
|
125
|
+
changeRecords,
|
|
126
|
+
historyDescription
|
|
127
|
+
});
|
|
97
128
|
this.emit("addOrUpdate", id, codeDsl[id]);
|
|
98
129
|
}
|
|
99
130
|
/**
|
|
@@ -170,12 +201,20 @@ var CodeBlock = class extends BaseService_default {
|
|
|
170
201
|
/**
|
|
171
202
|
* 在dsl数据源中删除指定id的代码块
|
|
172
203
|
* @param {Id[]} codeIds 需要删除的代码块id数组
|
|
204
|
+
* @param options 可选配置
|
|
205
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
173
206
|
*/
|
|
174
|
-
async deleteCodeDslByIds(codeIds) {
|
|
207
|
+
async deleteCodeDslByIds(codeIds, { doNotPushHistory = false, historyDescription } = {}) {
|
|
175
208
|
const currentDsl = await this.getCodeDsl();
|
|
176
209
|
if (!currentDsl) return;
|
|
177
210
|
codeIds.forEach((id) => {
|
|
211
|
+
const oldContent = currentDsl[id] ? cloneDeep$1(currentDsl[id]) : null;
|
|
178
212
|
delete currentDsl[id];
|
|
213
|
+
if (oldContent && !doNotPushHistory) history_default.pushCodeBlock(id, {
|
|
214
|
+
oldContent,
|
|
215
|
+
newContent: null,
|
|
216
|
+
historyDescription
|
|
217
|
+
});
|
|
179
218
|
this.emit("remove", id);
|
|
180
219
|
});
|
|
181
220
|
}
|
|
@@ -186,6 +225,78 @@ var CodeBlock = class extends BaseService_default {
|
|
|
186
225
|
return this.state.paramsColConfig;
|
|
187
226
|
}
|
|
188
227
|
/**
|
|
228
|
+
* 撤销指定代码块的最近一次变更。
|
|
229
|
+
*
|
|
230
|
+
* 内部走 setCodeDslByIdSync / deleteCodeDslByIds,因此会自动触发 codeBlockService 的
|
|
231
|
+
* `addOrUpdate` / `remove` 事件,由 initService 中的 handler 重新维护 dep target
|
|
232
|
+
* (DepTargetType.CODE_BLOCK 的 add / remove)。所有写回都带 `doNotPushHistory: true`,
|
|
233
|
+
* 确保不会在历史栈里产生新的记录。
|
|
234
|
+
*
|
|
235
|
+
* @param id 代码块 id
|
|
236
|
+
* @returns 撤销的 step;栈不存在或已无可撤销时返回 null
|
|
237
|
+
*/
|
|
238
|
+
async undo(id) {
|
|
239
|
+
const step = history_default.undoCodeBlock(id);
|
|
240
|
+
if (!step) return null;
|
|
241
|
+
await this.applyHistoryStep(step, true);
|
|
242
|
+
return step;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 重做指定代码块的下一次变更。
|
|
246
|
+
* @param id 代码块 id
|
|
247
|
+
* @returns 重做的 step;栈不存在或已无可重做时返回 null
|
|
248
|
+
*/
|
|
249
|
+
async redo(id) {
|
|
250
|
+
const step = history_default.redoCodeBlock(id);
|
|
251
|
+
if (!step) return null;
|
|
252
|
+
await this.applyHistoryStep(step, false);
|
|
253
|
+
return step;
|
|
254
|
+
}
|
|
255
|
+
/** 是否可对指定代码块撤销。 */
|
|
256
|
+
canUndo(id) {
|
|
257
|
+
return history_default.canUndoCodeBlock(id);
|
|
258
|
+
}
|
|
259
|
+
/** 是否可对指定代码块重做。 */
|
|
260
|
+
canRedo(id) {
|
|
261
|
+
return history_default.canRedoCodeBlock(id);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 跳转指定代码块的历史栈到目标游标。语义同 editor.gotoPageStep。
|
|
265
|
+
*
|
|
266
|
+
* @param id 代码块 id
|
|
267
|
+
* @param targetCursor 目标游标位置(已应用步骤数量)
|
|
268
|
+
* @returns 实际移动到的最终游标位置
|
|
269
|
+
*/
|
|
270
|
+
async goto(id, targetCursor) {
|
|
271
|
+
let cursor = history_default.getCodeBlockCursor(id);
|
|
272
|
+
const target = Math.max(0, targetCursor);
|
|
273
|
+
while (cursor > target) {
|
|
274
|
+
if (!await this.undo(id)) break;
|
|
275
|
+
cursor -= 1;
|
|
276
|
+
}
|
|
277
|
+
while (cursor < target) {
|
|
278
|
+
if (!await this.redo(id)) break;
|
|
279
|
+
cursor += 1;
|
|
280
|
+
}
|
|
281
|
+
return cursor;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 「回滚」指定代码块历史步骤(类 git revert 语义):
|
|
285
|
+
* - 不动原始栈结构(不移动 cursor、不丢弃任何步骤);
|
|
286
|
+
* - 把目标 step 的修改**反向应用**一次,并作为**新步骤**追加到栈顶;
|
|
287
|
+
* - 仅对已应用的步骤生效。
|
|
288
|
+
*
|
|
289
|
+
* @param id 代码块 id
|
|
290
|
+
* @param index 目标 step 在该栈中的索引(0 为最早),通常由历史面板传入
|
|
291
|
+
* @returns 反向后产生的新 step;目标不存在 / 未应用时返回 null
|
|
292
|
+
*/
|
|
293
|
+
async revert(id, index) {
|
|
294
|
+
const entry = history_default.getCodeBlockStepList(id)[index];
|
|
295
|
+
if (!entry?.applied) return null;
|
|
296
|
+
const description = `回滚 #${index + 1}: ${describeRevertCodeBlockStep(entry.step)}`;
|
|
297
|
+
return await this.applyRevertStep(entry.step, description);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
189
300
|
* 生成代码块唯一id
|
|
190
301
|
* @returns {Id} 代码块唯一id
|
|
191
302
|
*/
|
|
@@ -245,6 +356,92 @@ var CodeBlock = class extends BaseService_default {
|
|
|
245
356
|
usePlugin(options) {
|
|
246
357
|
super.usePlugin(options);
|
|
247
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* 反向应用一个 step 并以新 step 入栈。逻辑与 applyHistoryStep(reverse=true) 同构,
|
|
361
|
+
* 差异仅在于通过公开的 setCodeDslByIdSync / deleteCodeDslByIds 触发 push。
|
|
362
|
+
*/
|
|
363
|
+
async applyRevertStep(step, historyDescription) {
|
|
364
|
+
const { id, oldContent, newContent, changeRecords } = step;
|
|
365
|
+
if (oldContent === null && newContent) {
|
|
366
|
+
await this.deleteCodeDslByIds([id], { historyDescription });
|
|
367
|
+
return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
368
|
+
}
|
|
369
|
+
if (oldContent && newContent === null) {
|
|
370
|
+
this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { historyDescription });
|
|
371
|
+
return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
372
|
+
}
|
|
373
|
+
if (!oldContent || !newContent) return null;
|
|
374
|
+
if (changeRecords?.length) {
|
|
375
|
+
const current = this.getCodeContentById(id);
|
|
376
|
+
if (!current) return null;
|
|
377
|
+
const patched = cloneDeep$1(current);
|
|
378
|
+
let fallbackToFullReplace = false;
|
|
379
|
+
for (const record of changeRecords) {
|
|
380
|
+
if (!record.propPath) {
|
|
381
|
+
fallbackToFullReplace = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
const value = cloneDeep$1(getValueByKeyPath(record.propPath, oldContent));
|
|
385
|
+
setValueByKeyPath(record.propPath, value, patched);
|
|
386
|
+
}
|
|
387
|
+
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(oldContent) : patched, true, {
|
|
388
|
+
changeRecords,
|
|
389
|
+
historyDescription
|
|
390
|
+
});
|
|
391
|
+
return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
392
|
+
}
|
|
393
|
+
this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { historyDescription });
|
|
394
|
+
return history_default.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* 把一条历史 step 应用到当前代码块服务上。
|
|
398
|
+
*
|
|
399
|
+
* 复用现有的 setCodeDslByIdSync / deleteCodeDslByIds,目的是借助它们发出的事件
|
|
400
|
+
* 触发 initService 中的 dep target 维护(CODE_BLOCK 的 add / remove)。
|
|
401
|
+
* 所有写回都带 `doNotPushHistory: true`,确保不会在历史栈里产生新的记录。
|
|
402
|
+
*
|
|
403
|
+
* - oldContent=null, newContent≠null:原始为新增 → undo 删除;redo 再次 setCodeDslByIdSync
|
|
404
|
+
* - oldContent≠null, newContent=null:原始为删除 → undo 还原写入;redo 再次删除
|
|
405
|
+
* - 两侧都有:原始为更新 → 按 changeRecords 局部 patch;缺省退化为整内容替换
|
|
406
|
+
*
|
|
407
|
+
* @param step 历史 step
|
|
408
|
+
* @param reverse true=撤销,false=重做
|
|
409
|
+
*/
|
|
410
|
+
async applyHistoryStep(step, reverse) {
|
|
411
|
+
const { id, oldContent, newContent, changeRecords } = step;
|
|
412
|
+
if (oldContent === null && newContent) {
|
|
413
|
+
if (reverse) await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
|
|
414
|
+
else this.setCodeDslByIdSync(id, cloneDeep$1(newContent), true, { doNotPushHistory: true });
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (oldContent && newContent === null) {
|
|
418
|
+
if (reverse) this.setCodeDslByIdSync(id, cloneDeep$1(oldContent), true, { doNotPushHistory: true });
|
|
419
|
+
else await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (!oldContent || !newContent) return;
|
|
423
|
+
const sourceForValues = reverse ? oldContent : newContent;
|
|
424
|
+
if (changeRecords?.length) {
|
|
425
|
+
const current = this.getCodeContentById(id);
|
|
426
|
+
if (!current) return;
|
|
427
|
+
const patched = cloneDeep$1(current);
|
|
428
|
+
let fallbackToFullReplace = false;
|
|
429
|
+
for (const record of changeRecords) {
|
|
430
|
+
if (!record.propPath) {
|
|
431
|
+
fallbackToFullReplace = true;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
const value = cloneDeep$1(getValueByKeyPath(record.propPath, sourceForValues));
|
|
435
|
+
setValueByKeyPath(record.propPath, value, patched);
|
|
436
|
+
}
|
|
437
|
+
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep$1(sourceForValues) : patched, true, {
|
|
438
|
+
changeRecords,
|
|
439
|
+
doNotPushHistory: true
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
this.setCodeDslByIdSync(id, cloneDeep$1(sourceForValues), true, { doNotPushHistory: true });
|
|
444
|
+
}
|
|
248
445
|
};
|
|
249
446
|
var codeBlock_default = new CodeBlock();
|
|
250
447
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import BaseService from "./BaseService.js";
|
|
2
2
|
import { shallowReactive } from "vue";
|
|
3
3
|
//#region packages/editor/src/services/componentList.ts
|
|
4
|
-
var ComponentList = class extends
|
|
4
|
+
var ComponentList = class extends BaseService {
|
|
5
5
|
state = shallowReactive({ list: [] });
|
|
6
6
|
constructor() {
|
|
7
7
|
super([]);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import BaseService from "./BaseService.js";
|
|
2
|
+
import history_default from "./history.js";
|
|
2
3
|
import storage_default, { Protocol } from "./storage.js";
|
|
3
4
|
import { COPY_DS_STORAGE_KEY } from "../utils/editor.js";
|
|
4
5
|
import editor_default from "./editor.js";
|
|
5
6
|
import { getFormConfig, getFormValue } from "../utils/data-source/index.js";
|
|
6
|
-
import { guid, toLine } from "@tmagic/utils";
|
|
7
|
+
import { getValueByKeyPath, guid, setValueByKeyPath, toLine } from "@tmagic/utils";
|
|
7
8
|
import { Target, Watcher } from "@tmagic/core";
|
|
8
9
|
import { reactive } from "vue";
|
|
9
|
-
import { cloneDeep, get } from "lodash-es";
|
|
10
|
+
import { cloneDeep as cloneDeep$1, get } from "lodash-es";
|
|
10
11
|
//#region packages/editor/src/services/dataSource.ts
|
|
11
12
|
var canUsePluginMethods = {
|
|
12
13
|
async: [],
|
|
@@ -25,7 +26,19 @@ var canUsePluginMethods = {
|
|
|
25
26
|
"createId"
|
|
26
27
|
]
|
|
27
28
|
};
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 「回滚」生成的新 step 简短描述。
|
|
31
|
+
* 仅在 service 层使用,避免依赖 UI 层 composables。
|
|
32
|
+
*/
|
|
33
|
+
var describeRevertDataSourceStep = (step) => {
|
|
34
|
+
const { oldSchema, newSchema, changeRecords, id } = step;
|
|
35
|
+
if (oldSchema === null && newSchema) return `撤回新增 ${newSchema.title || newSchema.id || id}`;
|
|
36
|
+
if (oldSchema && newSchema === null) return `还原已删除的 ${oldSchema.title || oldSchema.id || id}`;
|
|
37
|
+
const title = newSchema?.title || oldSchema?.title || `${id}`;
|
|
38
|
+
const propPath = changeRecords?.[0]?.propPath;
|
|
39
|
+
return propPath ? `还原 ${title} · ${propPath}` : `还原 ${title}`;
|
|
40
|
+
};
|
|
41
|
+
var DataSource = class extends BaseService {
|
|
29
42
|
state = reactive({
|
|
30
43
|
datasourceTypeList: [],
|
|
31
44
|
dataSources: [],
|
|
@@ -71,33 +84,143 @@ var DataSource = class extends BaseService_default {
|
|
|
71
84
|
setFormMethod(type, value = []) {
|
|
72
85
|
this.get("methods")[toLine(type)] = value;
|
|
73
86
|
}
|
|
74
|
-
|
|
87
|
+
/**
|
|
88
|
+
* 新增数据源
|
|
89
|
+
* @param config 数据源配置
|
|
90
|
+
* @param options 可选配置
|
|
91
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
92
|
+
* @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
|
|
93
|
+
*/
|
|
94
|
+
add(config, { doNotPushHistory = false, historyDescription } = {}) {
|
|
75
95
|
const newConfig = {
|
|
76
96
|
...config,
|
|
77
97
|
id: config.id && !this.getDataSourceById(config.id) ? config.id : this.createId()
|
|
78
98
|
};
|
|
79
99
|
this.get("dataSources").push(newConfig);
|
|
100
|
+
if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
|
|
101
|
+
oldSchema: null,
|
|
102
|
+
newSchema: newConfig,
|
|
103
|
+
historyDescription
|
|
104
|
+
});
|
|
80
105
|
this.emit("add", newConfig);
|
|
81
106
|
return newConfig;
|
|
82
107
|
}
|
|
83
|
-
|
|
108
|
+
/**
|
|
109
|
+
* 更新数据源
|
|
110
|
+
* @param config 数据源配置
|
|
111
|
+
* @param data 额外数据
|
|
112
|
+
* @param data.changeRecords form 端变更记录
|
|
113
|
+
* @param data.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
114
|
+
* @param data.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
|
|
115
|
+
*/
|
|
116
|
+
update(config, { changeRecords = [], doNotPushHistory = false, historyDescription } = {}) {
|
|
84
117
|
const dataSources = this.get("dataSources");
|
|
85
118
|
const index = dataSources.findIndex((ds) => ds.id === config.id);
|
|
86
119
|
const oldConfig = dataSources[index];
|
|
87
|
-
const newConfig = cloneDeep(config);
|
|
120
|
+
const newConfig = cloneDeep$1(config);
|
|
88
121
|
dataSources[index] = newConfig;
|
|
122
|
+
if (!doNotPushHistory) history_default.pushDataSource(newConfig.id, {
|
|
123
|
+
oldSchema: oldConfig ? cloneDeep$1(oldConfig) : null,
|
|
124
|
+
newSchema: newConfig,
|
|
125
|
+
changeRecords,
|
|
126
|
+
historyDescription
|
|
127
|
+
});
|
|
89
128
|
this.emit("update", newConfig, {
|
|
90
129
|
oldConfig,
|
|
91
130
|
changeRecords
|
|
92
131
|
});
|
|
93
132
|
return newConfig;
|
|
94
133
|
}
|
|
95
|
-
|
|
134
|
+
/**
|
|
135
|
+
* 删除数据源
|
|
136
|
+
* @param id 数据源 id
|
|
137
|
+
* @param options 可选配置
|
|
138
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
139
|
+
* @param options.historyDescription 入栈时附带的人类可读描述,用于历史面板展示
|
|
140
|
+
*/
|
|
141
|
+
remove(id, { doNotPushHistory = false, historyDescription } = {}) {
|
|
96
142
|
const dataSources = this.get("dataSources");
|
|
97
143
|
const index = dataSources.findIndex((ds) => ds.id === id);
|
|
144
|
+
const oldConfig = index !== -1 ? dataSources[index] : null;
|
|
98
145
|
dataSources.splice(index, 1);
|
|
146
|
+
if (oldConfig && !doNotPushHistory) history_default.pushDataSource(id, {
|
|
147
|
+
oldSchema: cloneDeep$1(oldConfig),
|
|
148
|
+
newSchema: null,
|
|
149
|
+
historyDescription
|
|
150
|
+
});
|
|
99
151
|
this.emit("remove", id);
|
|
100
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* 撤销指定数据源的最近一次变更。
|
|
155
|
+
*
|
|
156
|
+
* 内部走 add / update / remove,因此会自动触发 dataSourceService 的事件,
|
|
157
|
+
* 由 initService 中的对应 handler 重新收集 dep(DATA_SOURCE / DATA_SOURCE_COND / DATA_SOURCE_METHOD)。
|
|
158
|
+
* 所有写回都带 `doNotPushHistory: true`,避免在历史栈里产生新的记录。
|
|
159
|
+
*
|
|
160
|
+
* @param id 数据源 id
|
|
161
|
+
* @returns 撤销的 step;栈不存在或已无可撤销时返回 null
|
|
162
|
+
*/
|
|
163
|
+
undo(id) {
|
|
164
|
+
const step = history_default.undoDataSource(id);
|
|
165
|
+
if (!step) return null;
|
|
166
|
+
this.applyHistoryStep(step, true);
|
|
167
|
+
return step;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 重做指定数据源的下一次变更。
|
|
171
|
+
* @param id 数据源 id
|
|
172
|
+
* @returns 重做的 step;栈不存在或已无可重做时返回 null
|
|
173
|
+
*/
|
|
174
|
+
redo(id) {
|
|
175
|
+
const step = history_default.redoDataSource(id);
|
|
176
|
+
if (!step) return null;
|
|
177
|
+
this.applyHistoryStep(step, false);
|
|
178
|
+
return step;
|
|
179
|
+
}
|
|
180
|
+
/** 是否可对指定数据源撤销。 */
|
|
181
|
+
canUndo(id) {
|
|
182
|
+
return history_default.canUndoDataSource(id);
|
|
183
|
+
}
|
|
184
|
+
/** 是否可对指定数据源重做。 */
|
|
185
|
+
canRedo(id) {
|
|
186
|
+
return history_default.canRedoDataSource(id);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 跳转指定数据源的历史栈到目标游标。语义同 editor.gotoPageStep。
|
|
190
|
+
*
|
|
191
|
+
* @param id 数据源 id
|
|
192
|
+
* @param targetCursor 目标游标位置(已应用步骤数量)
|
|
193
|
+
* @returns 实际移动到的最终游标位置
|
|
194
|
+
*/
|
|
195
|
+
goto(id, targetCursor) {
|
|
196
|
+
let cursor = history_default.getDataSourceCursor(id);
|
|
197
|
+
const target = Math.max(0, targetCursor);
|
|
198
|
+
while (cursor > target) {
|
|
199
|
+
if (!this.undo(id)) break;
|
|
200
|
+
cursor -= 1;
|
|
201
|
+
}
|
|
202
|
+
while (cursor < target) {
|
|
203
|
+
if (!this.redo(id)) break;
|
|
204
|
+
cursor += 1;
|
|
205
|
+
}
|
|
206
|
+
return cursor;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 「回滚」指定数据源历史步骤(类 git revert 语义):
|
|
210
|
+
* - 不动原始栈结构(不移动 cursor、不丢弃任何步骤);
|
|
211
|
+
* - 把目标 step 的修改**反向应用**一次,并作为**新步骤**追加到栈顶;
|
|
212
|
+
* - 仅对已应用的步骤生效——未应用步骤本身就不存在于当前 schema 中,反向无意义。
|
|
213
|
+
*
|
|
214
|
+
* @param id 数据源 id
|
|
215
|
+
* @param index 目标 step 在该栈中的索引(0 为最早),通常由历史面板传入
|
|
216
|
+
* @returns 反向后产生的新 step;目标不存在 / 未应用时返回 null
|
|
217
|
+
*/
|
|
218
|
+
revert(id, index) {
|
|
219
|
+
const entry = history_default.getDataSourceStepList(id)[index];
|
|
220
|
+
if (!entry?.applied) return null;
|
|
221
|
+
const description = `回滚 #${index + 1}: ${describeRevertDataSourceStep(entry.step)}`;
|
|
222
|
+
return this.applyRevertStep(entry.step, description);
|
|
223
|
+
}
|
|
101
224
|
createId() {
|
|
102
225
|
return `ds_${guid()}`;
|
|
103
226
|
}
|
|
@@ -151,6 +274,92 @@ var DataSource = class extends BaseService_default {
|
|
|
151
274
|
if (!this.getDataSourceById(item.id)) this.add(item);
|
|
152
275
|
});
|
|
153
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* 反向应用一个 step 并以新 step 入栈(不带 doNotPushHistory)。逻辑与 applyHistoryStep(reverse=true)
|
|
279
|
+
* 同构,差异仅在于走对应的公共 add / update / remove 而不是带 doNotPushHistory 的版本。
|
|
280
|
+
*/
|
|
281
|
+
applyRevertStep(step, historyDescription) {
|
|
282
|
+
const { id, oldSchema, newSchema, changeRecords } = step;
|
|
283
|
+
if (oldSchema === null && newSchema) {
|
|
284
|
+
this.remove(`${id}`, { historyDescription });
|
|
285
|
+
return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
286
|
+
}
|
|
287
|
+
if (oldSchema && newSchema === null) {
|
|
288
|
+
this.add(cloneDeep$1(oldSchema), { historyDescription });
|
|
289
|
+
return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
290
|
+
}
|
|
291
|
+
if (!oldSchema || !newSchema) return null;
|
|
292
|
+
if (changeRecords?.length) {
|
|
293
|
+
const current = this.getDataSourceById(`${id}`);
|
|
294
|
+
if (!current) return null;
|
|
295
|
+
const patched = cloneDeep$1(current);
|
|
296
|
+
let fallbackToFullReplace = false;
|
|
297
|
+
for (const record of changeRecords) {
|
|
298
|
+
if (!record.propPath) {
|
|
299
|
+
fallbackToFullReplace = true;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
const value = cloneDeep$1(getValueByKeyPath(record.propPath, oldSchema));
|
|
303
|
+
setValueByKeyPath(record.propPath, value, patched);
|
|
304
|
+
}
|
|
305
|
+
this.update(fallbackToFullReplace ? cloneDeep$1(oldSchema) : patched, {
|
|
306
|
+
changeRecords,
|
|
307
|
+
historyDescription
|
|
308
|
+
});
|
|
309
|
+
return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
310
|
+
}
|
|
311
|
+
this.update(cloneDeep$1(oldSchema), { historyDescription });
|
|
312
|
+
return history_default.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 把一条历史 step 应用到当前数据源服务上。
|
|
316
|
+
*
|
|
317
|
+
* 复用现有的 add / update / remove,目的是借助它们发出的事件触发 initService 中
|
|
318
|
+
* 的依赖收集逻辑(DATA_SOURCE / DATA_SOURCE_COND / DATA_SOURCE_METHOD)。
|
|
319
|
+
* 所有写回都带 `doNotPushHistory: true`,确保不会在历史栈里产生新的记录。
|
|
320
|
+
*
|
|
321
|
+
* - oldSchema=null, newSchema≠null:原始为新增 → undo 删除;redo 再次 add
|
|
322
|
+
* - oldSchema≠null, newSchema=null:原始为删除 → undo 还原 add;redo 再次删除
|
|
323
|
+
* - 两侧都有:原始为更新 → 按 changeRecords 局部 patch;缺省退化为整 schema 替换
|
|
324
|
+
*
|
|
325
|
+
* @param step 历史 step
|
|
326
|
+
* @param reverse true=撤销,false=重做
|
|
327
|
+
*/
|
|
328
|
+
applyHistoryStep(step, reverse) {
|
|
329
|
+
const { id, oldSchema, newSchema, changeRecords } = step;
|
|
330
|
+
if (oldSchema === null && newSchema) {
|
|
331
|
+
if (reverse) this.remove(`${id}`, { doNotPushHistory: true });
|
|
332
|
+
else this.add(cloneDeep$1(newSchema), { doNotPushHistory: true });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (oldSchema && newSchema === null) {
|
|
336
|
+
if (reverse) this.add(cloneDeep$1(oldSchema), { doNotPushHistory: true });
|
|
337
|
+
else this.remove(`${id}`, { doNotPushHistory: true });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!oldSchema || !newSchema) return;
|
|
341
|
+
const sourceForValues = reverse ? oldSchema : newSchema;
|
|
342
|
+
if (changeRecords?.length) {
|
|
343
|
+
const current = this.getDataSourceById(`${id}`);
|
|
344
|
+
if (!current) return;
|
|
345
|
+
const patched = cloneDeep$1(current);
|
|
346
|
+
let fallbackToFullReplace = false;
|
|
347
|
+
for (const record of changeRecords) {
|
|
348
|
+
if (!record.propPath) {
|
|
349
|
+
fallbackToFullReplace = true;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
const value = cloneDeep$1(getValueByKeyPath(record.propPath, sourceForValues));
|
|
353
|
+
setValueByKeyPath(record.propPath, value, patched);
|
|
354
|
+
}
|
|
355
|
+
this.update(fallbackToFullReplace ? cloneDeep$1(sourceForValues) : patched, {
|
|
356
|
+
changeRecords,
|
|
357
|
+
doNotPushHistory: true
|
|
358
|
+
});
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
this.update(cloneDeep$1(sourceForValues), { doNotPushHistory: true });
|
|
362
|
+
}
|
|
154
363
|
};
|
|
155
364
|
var dataSource_default = new DataSource();
|
|
156
365
|
//#endregion
|
package/dist/es/services/dep.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import BaseService from "./BaseService.js";
|
|
2
2
|
import { IdleTask } from "../utils/dep/idle-task.js";
|
|
3
3
|
import WorkerWrapper from "../utils/dep/worker.js";
|
|
4
4
|
import { isPage } from "@tmagic/utils";
|
|
@@ -7,7 +7,7 @@ import { reactive, shallowReactive } from "vue";
|
|
|
7
7
|
import { throttle } from "lodash-es";
|
|
8
8
|
import serialize from "serialize-javascript";
|
|
9
9
|
//#region packages/editor/src/services/dep.ts
|
|
10
|
-
var Dep = class extends
|
|
10
|
+
var Dep = class extends BaseService {
|
|
11
11
|
state = shallowReactive({
|
|
12
12
|
collecting: false,
|
|
13
13
|
taskLength: 0
|
|
@@ -52,9 +52,12 @@ var Dep = class extends BaseService_default {
|
|
|
52
52
|
}
|
|
53
53
|
collect(nodes, depExtendedData = {}, deep = false, type) {
|
|
54
54
|
this.set("collecting", true);
|
|
55
|
-
this.watcher.
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const targets = this.watcher.getCollectableTargets(type);
|
|
56
|
+
if (targets.length) for (const node of nodes) {
|
|
57
|
+
if (isPage(node)) for (const target of targets) this.removePageDep(target, depExtendedData);
|
|
58
|
+
else this.watcher.removeTargetsDep(targets, node);
|
|
59
|
+
this.watcher.collectItems(node, targets, depExtendedData, deep);
|
|
60
|
+
}
|
|
58
61
|
this.set("collecting", false);
|
|
59
62
|
this.emit("collected", nodes, deep);
|
|
60
63
|
this.emit("ds-collected", nodes, deep);
|
|
@@ -103,7 +106,7 @@ var Dep = class extends BaseService_default {
|
|
|
103
106
|
target.deps = reactive(depsData[target.type][target.id]);
|
|
104
107
|
if (target.type === DepTargetType.DATA_SOURCE && dsl.dataSourceDeps) dsl.dataSourceDeps[target.id] = target.deps;
|
|
105
108
|
else if (target.type === DepTargetType.DATA_SOURCE_COND && dsl.dataSourceCondDeps) dsl.dataSourceCondDeps[target.id] = target.deps;
|
|
106
|
-
else if (target.type === DepTargetType.DATA_SOURCE_METHOD) dsl.dataSourceMethodDeps[target.id] = target.deps;
|
|
109
|
+
else if (target.type === DepTargetType.DATA_SOURCE_METHOD && dsl.dataSourceMethodDeps) dsl.dataSourceMethodDeps[target.id] = target.deps;
|
|
107
110
|
}
|
|
108
111
|
});
|
|
109
112
|
this.set("collecting", false);
|
|
@@ -114,9 +117,8 @@ var Dep = class extends BaseService_default {
|
|
|
114
117
|
});
|
|
115
118
|
}
|
|
116
119
|
collectNode(node, target, depExtendedData = {}, deep = false) {
|
|
117
|
-
if (isPage(node))
|
|
118
|
-
|
|
119
|
-
} else this.watcher.removeTargetDep(target, node);
|
|
120
|
+
if (isPage(node)) this.removePageDep(target, depExtendedData);
|
|
121
|
+
else this.watcher.removeTargetDep(target, node);
|
|
120
122
|
this.watcher.collectItem(node, target, depExtendedData, deep);
|
|
121
123
|
}
|
|
122
124
|
clear(nodes) {
|
|
@@ -155,6 +157,16 @@ var Dep = class extends BaseService_default {
|
|
|
155
157
|
emit(eventName, ...args) {
|
|
156
158
|
return super.emit(eventName, ...args);
|
|
157
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* 删除指定 page 在该 target 下的旧依赖:
|
|
162
|
+
* 按 pageId 匹配,可清掉页面内已被删除节点的残留依赖
|
|
163
|
+
*/
|
|
164
|
+
removePageDep(target, depExtendedData = {}) {
|
|
165
|
+
for (const depKey of Object.keys(target.deps)) {
|
|
166
|
+
const dep = target.deps[depKey];
|
|
167
|
+
if (dep.data?.pageId && dep.data.pageId === depExtendedData.pageId) delete target.deps[depKey];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
158
170
|
enqueueTask(node, target, depExtendedData, deep) {
|
|
159
171
|
this.idleTask.enqueueTask(({ node, deep, target }) => {
|
|
160
172
|
this.collectNode(node, target, depExtendedData, deep);
|