@tmagic/editor 1.8.0-beta.4 → 1.8.0-beta.5
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/CompareForm.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/es/index.js +6 -3
- package/dist/es/initService.js +1 -1
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -55
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -39
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +149 -103
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +32 -8
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +333 -211
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +28 -7
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -60
- package/dist/es/layouts/history-list/composables.js +73 -32
- package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/services/codeBlock.js +85 -37
- package/dist/es/services/dataSource.js +62 -26
- package/dist/es/services/editor.js +243 -100
- package/dist/es/services/history.js +270 -206
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +49 -6
- package/dist/es/utils/editor.js +35 -1
- package/dist/es/utils/history.js +223 -0
- package/dist/es/utils/indexed-db.js +86 -0
- package/dist/es/utils/undo-redo.js +60 -1
- package/dist/style.css +49 -6
- package/dist/tmagic-editor.umd.cjs +1799 -905
- package/package.json +7 -7
- package/src/components/CompareForm.vue +3 -1
- package/src/components/ToolButton.vue +2 -2
- package/src/index.ts +1 -0
- package/src/initService.ts +1 -1
- package/src/layouts/Framework.vue +1 -1
- package/src/layouts/history-list/Bucket.vue +34 -71
- package/src/layouts/history-list/BucketTab.vue +46 -54
- package/src/layouts/history-list/GroupRow.vue +146 -111
- package/src/layouts/history-list/HistoryDiffDialog.vue +94 -68
- package/src/layouts/history-list/HistoryListPanel.vue +296 -113
- package/src/layouts/history-list/InitialRow.vue +25 -9
- package/src/layouts/history-list/PageTab.vue +57 -51
- package/src/layouts/history-list/composables.ts +157 -36
- package/src/layouts/page-bar/PageBar.vue +4 -2
- package/src/layouts/sidebar/Sidebar.vue +6 -1
- package/src/services/codeBlock.ts +107 -37
- package/src/services/dataSource.ts +89 -40
- package/src/services/editor.ts +370 -134
- package/src/services/history.ts +305 -203
- package/src/services/ui.ts +7 -0
- package/src/theme/history-list-panel.scss +72 -5
- package/src/theme/page-bar.scss +0 -4
- package/src/type.ts +167 -63
- package/src/utils/editor.ts +41 -1
- package/src/utils/history.ts +298 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/indexed-db.ts +122 -0
- package/src/utils/undo-redo.ts +88 -0
- package/types/index.d.ts +783 -291
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
|
|
39
39
|
import { getEditorConfig } from '@editor/utils/config';
|
|
40
40
|
import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
|
|
41
|
+
import { describeRevertStep } from '@editor/utils/history';
|
|
41
42
|
|
|
42
43
|
import BaseService from './BaseService';
|
|
43
44
|
|
|
@@ -48,18 +49,6 @@ const canUsePluginMethods = {
|
|
|
48
49
|
|
|
49
50
|
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
-
* 「回滚」生成的新 step 简短描述。仅 service 层使用。
|
|
53
|
-
*/
|
|
54
|
-
const describeRevertCodeBlockStep = (step: CodeBlockStepValue): string => {
|
|
55
|
-
const { oldContent, newContent, changeRecords, id } = step;
|
|
56
|
-
if (oldContent === null && newContent) return `撤回新增 ${newContent.name || newContent.id || id}`;
|
|
57
|
-
if (oldContent && newContent === null) return `还原已删除的 ${oldContent.name || oldContent.id || id}`;
|
|
58
|
-
const name = newContent?.name || oldContent?.name || `${id}`;
|
|
59
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
60
|
-
return propPath ? `还原 ${name} · ${propPath}` : `还原 ${name}`;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
52
|
class CodeBlock extends BaseService {
|
|
64
53
|
private state = reactive<CodeState>({
|
|
65
54
|
codeDsl: null,
|
|
@@ -69,6 +58,17 @@ class CodeBlock extends BaseService {
|
|
|
69
58
|
paramsColConfig: undefined,
|
|
70
59
|
});
|
|
71
60
|
|
|
61
|
+
/**
|
|
62
|
+
* 最近一次写入历史栈的代码块历史记录 uuid(单条写入场景:新增 / 更新)。
|
|
63
|
+
* 供 setCodeDslById(Sync)AndGetHistoryId 取回,普通方法不读取它。
|
|
64
|
+
*/
|
|
65
|
+
private lastPushedHistoryId: string | null = null;
|
|
66
|
+
/**
|
|
67
|
+
* deleteCodeDslByIds 一次删除多个代码块时,按写入顺序收集的历史记录 uuid 列表。
|
|
68
|
+
* 在 deleteCodeDslByIds 入口处重置,供 deleteCodeDslByIdsAndGetHistoryId 取回。
|
|
69
|
+
*/
|
|
70
|
+
private lastDeletedHistoryIds: string[] = [];
|
|
71
|
+
|
|
72
72
|
constructor() {
|
|
73
73
|
super([
|
|
74
74
|
...canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
|
|
@@ -187,13 +187,14 @@ class CodeBlock extends BaseService {
|
|
|
187
187
|
const newContent = cloneDeep(codeDsl[id]);
|
|
188
188
|
|
|
189
189
|
if (!doNotPushHistory) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
190
|
+
this.lastPushedHistoryId =
|
|
191
|
+
historyService.pushCodeBlock(id, {
|
|
192
|
+
oldContent,
|
|
193
|
+
newContent,
|
|
194
|
+
changeRecords,
|
|
195
|
+
historyDescription,
|
|
196
|
+
source: historySource,
|
|
197
|
+
})?.uuid ?? null;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
this.emit('addOrUpdate', id, codeDsl[id]);
|
|
@@ -295,6 +296,8 @@ class CodeBlock extends BaseService {
|
|
|
295
296
|
|
|
296
297
|
if (!currentDsl) return;
|
|
297
298
|
|
|
299
|
+
this.lastDeletedHistoryIds = [];
|
|
300
|
+
|
|
298
301
|
codeIds.forEach((id) => {
|
|
299
302
|
// 历史记录:删除前快照内容;不存在的 id 直接跳过历史推入
|
|
300
303
|
const oldContent: CodeBlockContent | null = currentDsl[id] ? cloneDeep(currentDsl[id]) : null;
|
|
@@ -302,13 +305,62 @@ class CodeBlock extends BaseService {
|
|
|
302
305
|
delete currentDsl[id];
|
|
303
306
|
|
|
304
307
|
if (oldContent && !doNotPushHistory) {
|
|
305
|
-
historyService.pushCodeBlock(id, {
|
|
308
|
+
const uuid = historyService.pushCodeBlock(id, {
|
|
309
|
+
oldContent,
|
|
310
|
+
newContent: null,
|
|
311
|
+
historyDescription,
|
|
312
|
+
source: historySource,
|
|
313
|
+
})?.uuid;
|
|
314
|
+
if (uuid) this.lastDeletedHistoryIds.push(uuid);
|
|
306
315
|
}
|
|
307
316
|
|
|
308
317
|
this.emit('remove', id);
|
|
309
318
|
});
|
|
310
319
|
}
|
|
311
320
|
|
|
321
|
+
// #region AndGetHistoryId
|
|
322
|
+
/**
|
|
323
|
+
* 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
|
|
324
|
+
* 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link CodeBlockStepValue.uuid}),
|
|
325
|
+
* 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
326
|
+
*
|
|
327
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时:单条写入返回 null,批量删除返回空数组。
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
/** 等价于 {@link setCodeDslById},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
331
|
+
public async setCodeDslByIdAndGetHistoryId(
|
|
332
|
+
id: Id,
|
|
333
|
+
codeConfig: Partial<CodeBlockContent>,
|
|
334
|
+
options: HistoryOpOptionsWithChangeRecords = {},
|
|
335
|
+
): Promise<string | null> {
|
|
336
|
+
this.lastPushedHistoryId = null;
|
|
337
|
+
await this.setCodeDslById(id, codeConfig, options);
|
|
338
|
+
return this.lastPushedHistoryId;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** 等价于 {@link setCodeDslByIdSync},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
342
|
+
public setCodeDslByIdSyncAndGetHistoryId(
|
|
343
|
+
id: Id,
|
|
344
|
+
codeConfig: Partial<CodeBlockContent>,
|
|
345
|
+
force = true,
|
|
346
|
+
options: HistoryOpOptionsWithChangeRecords = {},
|
|
347
|
+
): string | null {
|
|
348
|
+
this.lastPushedHistoryId = null;
|
|
349
|
+
this.setCodeDslByIdSync(id, codeConfig, force, options);
|
|
350
|
+
return this.lastPushedHistoryId;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 等价于 {@link deleteCodeDslByIds},但返回本次写入的全部历史记录 uuid(按删除顺序)。
|
|
355
|
+
* 一次删除多个代码块会产生多条历史记录,因此返回数组;未写入任何历史时返回空数组。
|
|
356
|
+
*/
|
|
357
|
+
public async deleteCodeDslByIdsAndGetHistoryId(codeIds: Id[], options: HistoryOpOptions = {}): Promise<string[]> {
|
|
358
|
+
this.lastDeletedHistoryIds = [];
|
|
359
|
+
await this.deleteCodeDslByIds(codeIds, options);
|
|
360
|
+
return [...this.lastDeletedHistoryIds];
|
|
361
|
+
}
|
|
362
|
+
// #endregion AndGetHistoryId
|
|
363
|
+
|
|
312
364
|
public setParamsColConfig(config: TableColumnConfig): void {
|
|
313
365
|
this.state.paramsColConfig = config;
|
|
314
366
|
}
|
|
@@ -395,11 +447,27 @@ class CodeBlock extends BaseService {
|
|
|
395
447
|
const entry = list[index];
|
|
396
448
|
if (!entry?.applied) return null;
|
|
397
449
|
// 更新类步骤(前后 content 都存在)必须带 changeRecords 才支持回滚,否则只能整内容替换,会冲掉后续无关变更。
|
|
398
|
-
|
|
399
|
-
|
|
450
|
+
const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
|
|
451
|
+
|
|
452
|
+
if (oldSchema && newSchema && !changeRecords?.length) return null;
|
|
453
|
+
const description = `回滚 #${index + 1}: ${describeRevertStep<CodeBlockContent>(entry.step.id, entry.step.diff?.[0], (s) => s.name)}`;
|
|
400
454
|
return await this.applyRevertStep(entry.step, description);
|
|
401
455
|
}
|
|
402
456
|
|
|
457
|
+
/**
|
|
458
|
+
* 通过历史记录 uuid 回滚某条代码块历史步骤,语义同 {@link revert},
|
|
459
|
+
* 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid({@link CodeBlockStepValue.uuid})
|
|
460
|
+
* 在全部代码块栈中定位对应步骤后再回滚。
|
|
461
|
+
*
|
|
462
|
+
* @param uuid 目标历史记录的 uuid,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回
|
|
463
|
+
* @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
|
|
464
|
+
*/
|
|
465
|
+
public async revertById(uuid: string): Promise<CodeBlockStepValue | null> {
|
|
466
|
+
const location = historyService.findCodeBlockStepLocationByUuid(uuid);
|
|
467
|
+
if (!location) return null;
|
|
468
|
+
return await this.revert(location.id, location.index);
|
|
469
|
+
}
|
|
470
|
+
|
|
403
471
|
/**
|
|
404
472
|
* 生成代码块唯一id
|
|
405
473
|
* @returns {Id} 代码块唯一id
|
|
@@ -490,21 +558,22 @@ class CodeBlock extends BaseService {
|
|
|
490
558
|
step: CodeBlockStepValue,
|
|
491
559
|
historyDescription: string,
|
|
492
560
|
): Promise<CodeBlockStepValue | null> {
|
|
493
|
-
const { id
|
|
561
|
+
const { id } = step;
|
|
562
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
494
563
|
|
|
495
564
|
// 原本是新增 → revert 即删除
|
|
496
|
-
if (
|
|
565
|
+
if (!oldSchema && newSchema) {
|
|
497
566
|
await this.deleteCodeDslByIds([id], { historyDescription, historySource: 'rollback' });
|
|
498
567
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
499
568
|
}
|
|
500
569
|
|
|
501
570
|
// 原本是删除 → revert 即写回
|
|
502
|
-
if (
|
|
503
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
571
|
+
if (oldSchema && !newSchema) {
|
|
572
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldSchema), true, { historyDescription, historySource: 'rollback' });
|
|
504
573
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
505
574
|
}
|
|
506
575
|
|
|
507
|
-
if (!
|
|
576
|
+
if (!oldSchema || !newSchema) return null;
|
|
508
577
|
|
|
509
578
|
// 原本是更新 → 把 oldContent 写回;优先按 changeRecords 局部 patch
|
|
510
579
|
if (changeRecords?.length) {
|
|
@@ -517,10 +586,10 @@ class CodeBlock extends BaseService {
|
|
|
517
586
|
fallbackToFullReplace = true;
|
|
518
587
|
break;
|
|
519
588
|
}
|
|
520
|
-
const value = cloneDeep(getValueByKeyPath(record.propPath,
|
|
589
|
+
const value = cloneDeep(getValueByKeyPath(record.propPath, oldSchema));
|
|
521
590
|
setValueByKeyPath(record.propPath, value, patched);
|
|
522
591
|
}
|
|
523
|
-
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep(
|
|
592
|
+
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep(oldSchema) : patched, true, {
|
|
524
593
|
changeRecords,
|
|
525
594
|
historyDescription,
|
|
526
595
|
historySource: 'rollback',
|
|
@@ -528,7 +597,7 @@ class CodeBlock extends BaseService {
|
|
|
528
597
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
529
598
|
}
|
|
530
599
|
|
|
531
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
600
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldSchema), true, { historyDescription, historySource: 'rollback' });
|
|
532
601
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
533
602
|
}
|
|
534
603
|
|
|
@@ -547,31 +616,32 @@ class CodeBlock extends BaseService {
|
|
|
547
616
|
* @param reverse true=撤销,false=重做
|
|
548
617
|
*/
|
|
549
618
|
private async applyHistoryStep(step: CodeBlockStepValue, reverse: boolean): Promise<void> {
|
|
550
|
-
const { id
|
|
619
|
+
const { id } = step;
|
|
620
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
551
621
|
|
|
552
622
|
// 新增 / 删除:直接 set 或 delete,不走 patch 逻辑
|
|
553
|
-
if (
|
|
623
|
+
if (!oldSchema && newSchema) {
|
|
554
624
|
if (reverse) {
|
|
555
625
|
await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
|
|
556
626
|
} else {
|
|
557
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
627
|
+
this.setCodeDslByIdSync(id, cloneDeep(newSchema), true, { doNotPushHistory: true });
|
|
558
628
|
}
|
|
559
629
|
return;
|
|
560
630
|
}
|
|
561
631
|
|
|
562
|
-
if (
|
|
632
|
+
if (oldSchema && !newSchema) {
|
|
563
633
|
if (reverse) {
|
|
564
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
634
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldSchema), true, { doNotPushHistory: true });
|
|
565
635
|
} else {
|
|
566
636
|
await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
|
|
567
637
|
}
|
|
568
638
|
return;
|
|
569
639
|
}
|
|
570
640
|
|
|
571
|
-
if (!
|
|
641
|
+
if (!oldSchema || !newSchema) return;
|
|
572
642
|
|
|
573
643
|
// 更新场景:优先按 changeRecords 局部 patch;缺省退化为整内容替换
|
|
574
|
-
const sourceForValues = reverse ?
|
|
644
|
+
const sourceForValues = reverse ? oldSchema : newSchema;
|
|
575
645
|
|
|
576
646
|
if (changeRecords?.length) {
|
|
577
647
|
const current = this.getCodeContentById(id);
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
} from '@editor/type';
|
|
20
20
|
import { getFormConfig, getFormValue } from '@editor/utils/data-source';
|
|
21
21
|
import { COPY_DS_STORAGE_KEY } from '@editor/utils/editor';
|
|
22
|
+
import { describeRevertStep } from '@editor/utils/history';
|
|
22
23
|
|
|
23
24
|
import BaseService from './BaseService';
|
|
24
25
|
|
|
@@ -54,19 +55,6 @@ const canUsePluginMethods = {
|
|
|
54
55
|
|
|
55
56
|
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
|
|
56
57
|
|
|
57
|
-
/**
|
|
58
|
-
* 「回滚」生成的新 step 简短描述。
|
|
59
|
-
* 仅在 service 层使用,避免依赖 UI 层 composables。
|
|
60
|
-
*/
|
|
61
|
-
const describeRevertDataSourceStep = (step: DataSourceStepValue): string => {
|
|
62
|
-
const { oldSchema, newSchema, changeRecords, id } = step;
|
|
63
|
-
if (oldSchema === null && newSchema) return `撤回新增 ${newSchema.title || newSchema.id || id}`;
|
|
64
|
-
if (oldSchema && newSchema === null) return `还原已删除的 ${oldSchema.title || oldSchema.id || id}`;
|
|
65
|
-
const title = newSchema?.title || oldSchema?.title || `${id}`;
|
|
66
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
67
|
-
return propPath ? `还原 ${title} · ${propPath}` : `还原 ${title}`;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
58
|
class DataSource extends BaseService {
|
|
71
59
|
private state = reactive<State>({
|
|
72
60
|
datasourceTypeList: [],
|
|
@@ -78,6 +66,13 @@ class DataSource extends BaseService {
|
|
|
78
66
|
methods: {},
|
|
79
67
|
});
|
|
80
68
|
|
|
69
|
+
/**
|
|
70
|
+
* 最近一次写入历史栈的数据源历史记录 uuid。
|
|
71
|
+
* 供 *AndGetHistoryId 系列方法在调用 add / update / remove 后取回本次产生的历史记录 id;
|
|
72
|
+
* 普通方法不读取它,调用前由 *AndGetHistoryId 重置为 null。
|
|
73
|
+
*/
|
|
74
|
+
private lastPushedHistoryId: string | null = null;
|
|
75
|
+
|
|
81
76
|
constructor() {
|
|
82
77
|
super(canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })));
|
|
83
78
|
}
|
|
@@ -141,12 +136,13 @@ class DataSource extends BaseService {
|
|
|
141
136
|
this.get('dataSources').push(newConfig);
|
|
142
137
|
|
|
143
138
|
if (!doNotPushHistory) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
this.lastPushedHistoryId =
|
|
140
|
+
historyService.pushDataSource(newConfig.id, {
|
|
141
|
+
oldSchema: null,
|
|
142
|
+
newSchema: newConfig,
|
|
143
|
+
historyDescription,
|
|
144
|
+
source: historySource,
|
|
145
|
+
})?.uuid ?? null;
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
this.emit('add', newConfig);
|
|
@@ -181,13 +177,14 @@ class DataSource extends BaseService {
|
|
|
181
177
|
dataSources[index] = newConfig;
|
|
182
178
|
|
|
183
179
|
if (!doNotPushHistory) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
180
|
+
this.lastPushedHistoryId =
|
|
181
|
+
historyService.pushDataSource(newConfig.id, {
|
|
182
|
+
oldSchema: oldConfig ? cloneDeep(oldConfig) : null,
|
|
183
|
+
newSchema: newConfig,
|
|
184
|
+
changeRecords,
|
|
185
|
+
historyDescription,
|
|
186
|
+
source: historySource,
|
|
187
|
+
})?.uuid ?? null;
|
|
191
188
|
}
|
|
192
189
|
|
|
193
190
|
this.emit('update', newConfig, {
|
|
@@ -212,17 +209,52 @@ class DataSource extends BaseService {
|
|
|
212
209
|
dataSources.splice(index, 1);
|
|
213
210
|
|
|
214
211
|
if (oldConfig && !doNotPushHistory) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
212
|
+
this.lastPushedHistoryId =
|
|
213
|
+
historyService.pushDataSource(id, {
|
|
214
|
+
oldSchema: cloneDeep(oldConfig),
|
|
215
|
+
newSchema: null,
|
|
216
|
+
historyDescription,
|
|
217
|
+
source: historySource,
|
|
218
|
+
})?.uuid ?? null;
|
|
221
219
|
}
|
|
222
220
|
|
|
223
221
|
this.emit('remove', id);
|
|
224
222
|
}
|
|
225
223
|
|
|
224
|
+
// #region AndGetHistoryId
|
|
225
|
+
/**
|
|
226
|
+
* 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
|
|
227
|
+
* 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link DataSourceStepValue.uuid}),
|
|
228
|
+
* 而非数据源配置。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
229
|
+
*
|
|
230
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时返回 null。
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
/** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
234
|
+
public addAndGetHistoryId(config: DataSourceSchema, options: HistoryOpOptions = {}): string | null {
|
|
235
|
+
this.lastPushedHistoryId = null;
|
|
236
|
+
this.add(config, options);
|
|
237
|
+
return this.lastPushedHistoryId;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
241
|
+
public updateAndGetHistoryId(
|
|
242
|
+
config: DataSourceSchema,
|
|
243
|
+
options: HistoryOpOptionsWithChangeRecords = {},
|
|
244
|
+
): string | null {
|
|
245
|
+
this.lastPushedHistoryId = null;
|
|
246
|
+
this.update(config, options);
|
|
247
|
+
return this.lastPushedHistoryId;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
|
|
251
|
+
public removeAndGetHistoryId(id: string, options: HistoryOpOptions = {}): string | null {
|
|
252
|
+
this.lastPushedHistoryId = null;
|
|
253
|
+
this.remove(id, options);
|
|
254
|
+
return this.lastPushedHistoryId;
|
|
255
|
+
}
|
|
256
|
+
// #endregion AndGetHistoryId
|
|
257
|
+
|
|
226
258
|
/**
|
|
227
259
|
* 撤销指定数据源的最近一次变更。
|
|
228
260
|
*
|
|
@@ -298,11 +330,26 @@ class DataSource extends BaseService {
|
|
|
298
330
|
const entry = list[index];
|
|
299
331
|
if (!entry?.applied) return null;
|
|
300
332
|
// 更新类步骤(前后 schema 都存在)必须带 changeRecords 才支持回滚,否则只能整 schema 替换,会冲掉后续无关变更。
|
|
301
|
-
|
|
302
|
-
|
|
333
|
+
const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
|
|
334
|
+
if (oldSchema && newSchema && !changeRecords?.length) return null;
|
|
335
|
+
const description = `回滚 #${index + 1}: ${describeRevertStep<DataSourceSchema>(entry.step.id, entry.step.diff?.[0], (s) => s.title)}`;
|
|
303
336
|
return this.applyRevertStep(entry.step, description);
|
|
304
337
|
}
|
|
305
338
|
|
|
339
|
+
/**
|
|
340
|
+
* 通过历史记录 uuid 回滚某条数据源历史步骤,语义同 {@link revert},
|
|
341
|
+
* 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid({@link DataSourceStepValue.uuid})
|
|
342
|
+
* 在全部数据源栈中定位对应步骤后再回滚。
|
|
343
|
+
*
|
|
344
|
+
* @param uuid 目标历史记录的 uuid,通常由 {@link addAndGetHistoryId} 等方法返回
|
|
345
|
+
* @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
|
|
346
|
+
*/
|
|
347
|
+
public revertById(uuid: string): DataSourceStepValue | null {
|
|
348
|
+
const location = historyService.findDataSourceStepLocationByUuid(uuid);
|
|
349
|
+
if (!location) return null;
|
|
350
|
+
return this.revert(location.id, location.index);
|
|
351
|
+
}
|
|
352
|
+
|
|
306
353
|
public createId(): string {
|
|
307
354
|
return `ds_${guid()}`;
|
|
308
355
|
}
|
|
@@ -383,16 +430,17 @@ class DataSource extends BaseService {
|
|
|
383
430
|
* 同构,差异仅在于走对应的公共 add / update / remove 而不是带 doNotPushHistory 的版本。
|
|
384
431
|
*/
|
|
385
432
|
private applyRevertStep(step: DataSourceStepValue, historyDescription: string): DataSourceStepValue | null {
|
|
386
|
-
const { id
|
|
433
|
+
const { id } = step;
|
|
434
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
387
435
|
|
|
388
436
|
// 原本是新增 → revert 即删除
|
|
389
|
-
if (oldSchema
|
|
437
|
+
if (!oldSchema && newSchema) {
|
|
390
438
|
this.remove(`${id}`, { historyDescription, historySource: 'rollback' });
|
|
391
439
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
392
440
|
}
|
|
393
441
|
|
|
394
442
|
// 原本是删除 → revert 即重新加回
|
|
395
|
-
if (oldSchema && newSchema
|
|
443
|
+
if (oldSchema && !newSchema) {
|
|
396
444
|
this.add(cloneDeep(oldSchema), { historyDescription, historySource: 'rollback' });
|
|
397
445
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
398
446
|
}
|
|
@@ -440,10 +488,11 @@ class DataSource extends BaseService {
|
|
|
440
488
|
* @param reverse true=撤销,false=重做
|
|
441
489
|
*/
|
|
442
490
|
private applyHistoryStep(step: DataSourceStepValue, reverse: boolean): void {
|
|
443
|
-
const { id
|
|
491
|
+
const { id } = step;
|
|
492
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
444
493
|
|
|
445
494
|
// 新增 / 删除:直接 add 或 remove,不走 patch 逻辑
|
|
446
|
-
if (oldSchema
|
|
495
|
+
if (!oldSchema && newSchema) {
|
|
447
496
|
if (reverse) {
|
|
448
497
|
this.remove(`${id}`, { doNotPushHistory: true });
|
|
449
498
|
} else {
|
|
@@ -452,7 +501,7 @@ class DataSource extends BaseService {
|
|
|
452
501
|
return;
|
|
453
502
|
}
|
|
454
503
|
|
|
455
|
-
if (oldSchema && newSchema
|
|
504
|
+
if (oldSchema && !newSchema) {
|
|
456
505
|
if (reverse) {
|
|
457
506
|
this.add(cloneDeep(oldSchema), { doNotPushHistory: true });
|
|
458
507
|
} else {
|