@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
|
@@ -45,6 +45,28 @@
|
|
|
45
45
|
list-style: none;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// 历史列表工具条:放置「清空」等列表级操作,右对齐。
|
|
49
|
+
.m-editor-history-list-toolbar {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: flex-end;
|
|
53
|
+
padding: 0 4px 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 「清空」按钮:红色文字按钮,强调破坏性操作(点击后会二次确认)。
|
|
57
|
+
.m-editor-history-list-clear {
|
|
58
|
+
padding: 2px 8px;
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
color: #f56c6c;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
user-select: none;
|
|
64
|
+
|
|
65
|
+
&:hover {
|
|
66
|
+
background-color: rgba(245, 108, 108, 0.12);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
.m-editor-history-list-item {
|
|
49
71
|
display: flex;
|
|
50
72
|
align-items: center;
|
|
@@ -215,6 +237,10 @@
|
|
|
215
237
|
|
|
216
238
|
.m-editor-history-list-item-index {
|
|
217
239
|
flex: 0 0 auto;
|
|
240
|
+
// 固定最小宽度并右对齐:序号位数不一(#6 / #16)时右边界仍统一,
|
|
241
|
+
// 使紧随其后的「类型」徽标在各行间对齐成整齐的一列。
|
|
242
|
+
min-width: 30px;
|
|
243
|
+
text-align: right;
|
|
218
244
|
color: #909399;
|
|
219
245
|
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
220
246
|
font-size: 11px;
|
|
@@ -222,6 +248,15 @@
|
|
|
222
248
|
white-space: nowrap;
|
|
223
249
|
}
|
|
224
250
|
|
|
251
|
+
// 合并组头部展示的是步骤区间(如 #10-#20),宽度本就不定、也无需与单步行对齐,
|
|
252
|
+
// 恢复自然宽度与左对齐,避免被强制成固定列后显得突兀。
|
|
253
|
+
.m-editor-history-list-group.is-merged
|
|
254
|
+
> .m-editor-history-list-group-head
|
|
255
|
+
> .m-editor-history-list-item-index {
|
|
256
|
+
min-width: 0;
|
|
257
|
+
text-align: left;
|
|
258
|
+
}
|
|
259
|
+
|
|
225
260
|
// 操作时间:弱化展示,紧贴在描述之后、各操作按钮之前。
|
|
226
261
|
.m-editor-history-list-item-time {
|
|
227
262
|
flex: 0 0 auto;
|
|
@@ -295,6 +330,20 @@
|
|
|
295
330
|
font-weight: 400; // 防止被合并组头部的粗体继承
|
|
296
331
|
}
|
|
297
332
|
|
|
333
|
+
// 「已保存」徽标:绿色实心胶囊,标记最近一次保存对应的历史记录(与 historyService.markSaved 对应)。
|
|
334
|
+
.m-editor-history-list-item-saved {
|
|
335
|
+
flex: 0 0 auto;
|
|
336
|
+
padding: 0 6px;
|
|
337
|
+
border-radius: 8px;
|
|
338
|
+
font-size: 10px;
|
|
339
|
+
line-height: 16px;
|
|
340
|
+
color: #fff;
|
|
341
|
+
background-color: #67c23a;
|
|
342
|
+
white-space: nowrap;
|
|
343
|
+
font-weight: 500;
|
|
344
|
+
letter-spacing: 0.2px;
|
|
345
|
+
}
|
|
346
|
+
|
|
298
347
|
// 「合并 N 步」徽标:紫色实心胶囊,与合并组卡片色系一致,醒目区分单步条目。
|
|
299
348
|
.m-editor-history-list-item-merge {
|
|
300
349
|
flex: 0 0 auto;
|
|
@@ -308,6 +357,24 @@
|
|
|
308
357
|
letter-spacing: 0.2px;
|
|
309
358
|
}
|
|
310
359
|
|
|
360
|
+
// 操作区:把「回滚 / 回到 / 查看差异」收敛为一个统一容器,默认隐藏,
|
|
361
|
+
// 仅在指针悬停于所在行时显示。静止状态下每行最右侧固定为「时间」,
|
|
362
|
+
// 各行因此能对齐成整齐的右侧列,避免按钮数量不一导致的参差错乱。
|
|
363
|
+
.m-editor-history-list-item-actions {
|
|
364
|
+
display: none;
|
|
365
|
+
flex: 0 0 auto;
|
|
366
|
+
align-items: center;
|
|
367
|
+
gap: 6px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.m-editor-history-list-group-head:hover > .m-editor-history-list-item-actions,
|
|
371
|
+
.m-editor-history-list-substeps
|
|
372
|
+
> li:hover
|
|
373
|
+
> .m-editor-history-list-item-actions,
|
|
374
|
+
.m-editor-history-list-initial:hover > .m-editor-history-list-item-actions {
|
|
375
|
+
display: flex;
|
|
376
|
+
}
|
|
377
|
+
|
|
311
378
|
.m-editor-history-list-item-diff {
|
|
312
379
|
flex: 0 0 auto;
|
|
313
380
|
padding: 0 6px;
|
|
@@ -324,21 +391,21 @@
|
|
|
324
391
|
}
|
|
325
392
|
}
|
|
326
393
|
|
|
327
|
-
//
|
|
328
|
-
//
|
|
394
|
+
// 「回到」按钮:将历史游标移动到该 step。使用绿色色系,
|
|
395
|
+
// 与红色「回滚」、蓝色「查看差异」区分,也避免与紧邻的灰色「来源」徽标混淆。
|
|
329
396
|
.m-editor-history-list-item-goto {
|
|
330
397
|
flex: 0 0 auto;
|
|
331
398
|
padding: 0 6px;
|
|
332
399
|
border-radius: 2px;
|
|
333
400
|
font-size: 10px;
|
|
334
401
|
line-height: 16px;
|
|
335
|
-
color: #
|
|
336
|
-
background-color: rgba(
|
|
402
|
+
color: #529b2e;
|
|
403
|
+
background-color: rgba(103, 194, 58, 0.12);
|
|
337
404
|
cursor: pointer;
|
|
338
405
|
user-select: none;
|
|
339
406
|
|
|
340
407
|
&:hover {
|
|
341
|
-
background-color: rgba(
|
|
408
|
+
background-color: rgba(103, 194, 58, 0.24);
|
|
342
409
|
}
|
|
343
410
|
}
|
|
344
411
|
|
package/src/theme/page-bar.scss
CHANGED
package/src/type.ts
CHANGED
|
@@ -56,7 +56,7 @@ import type { PropsService } from './services/props';
|
|
|
56
56
|
import type { StageOverlayService } from './services/stageOverlay';
|
|
57
57
|
import type { StorageService } from './services/storage';
|
|
58
58
|
import type { UiService } from './services/ui';
|
|
59
|
-
import type { UndoRedo } from './utils/undo-redo';
|
|
59
|
+
import type { SerializedUndoRedo, UndoRedo } from './utils/undo-redo';
|
|
60
60
|
|
|
61
61
|
export type EditorSlots = FrameworkSlots &
|
|
62
62
|
WorkspaceSlots &
|
|
@@ -312,6 +312,8 @@ export interface UiState {
|
|
|
312
312
|
hideSlideBar: boolean;
|
|
313
313
|
/** 侧边栏面板配置 */
|
|
314
314
|
sideBarItems: SideComponent[];
|
|
315
|
+
/** 当前激活的侧边栏面板 */
|
|
316
|
+
sideBarActiveTabName: string;
|
|
315
317
|
|
|
316
318
|
// navMenu 的宽高
|
|
317
319
|
navMenuRect: {
|
|
@@ -390,6 +392,9 @@ export interface MenuButton {
|
|
|
390
392
|
items?: MenuButton[];
|
|
391
393
|
/** 唯一标识,用于高亮 */
|
|
392
394
|
id?: string | number;
|
|
395
|
+
buttonProps?: {
|
|
396
|
+
type?: string;
|
|
397
|
+
};
|
|
393
398
|
}
|
|
394
399
|
// #endregion MenuButton
|
|
395
400
|
|
|
@@ -680,7 +685,13 @@ export interface CodeParamStatement {
|
|
|
680
685
|
}
|
|
681
686
|
|
|
682
687
|
// #region HistoryOpType
|
|
683
|
-
|
|
688
|
+
/**
|
|
689
|
+
* 历史记录操作类型:
|
|
690
|
+
* - `add` / `remove` / `update`:普通可撤销/重做的节点变更;
|
|
691
|
+
* - `initial`:页面「未修改的初始状态」基线(设置 root 时生成),作为页面栈 index 0 的固定底线 step。
|
|
692
|
+
* 该 step 不可被撤销/回滚(cursor 不会低于它),仅用于历史面板底部的初始行展示。
|
|
693
|
+
*/
|
|
694
|
+
export type HistoryOpType = 'add' | 'remove' | 'update' | 'initial';
|
|
684
695
|
// #endregion HistoryOpType
|
|
685
696
|
|
|
686
697
|
// #region HistoryOpSource
|
|
@@ -705,11 +716,13 @@ export type HistoryOpType = 'add' | 'remove' | 'update';
|
|
|
705
716
|
* 通过 `(string & {})` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。
|
|
706
717
|
*/
|
|
707
718
|
export type HistoryOpSource =
|
|
719
|
+
| 'initial'
|
|
708
720
|
| 'stage'
|
|
709
721
|
| 'tree'
|
|
710
722
|
| 'component-panel'
|
|
711
723
|
| 'props'
|
|
712
724
|
| 'code'
|
|
725
|
+
| 'root-code'
|
|
713
726
|
| 'stage-contextmenu'
|
|
714
727
|
| 'tree-contextmenu'
|
|
715
728
|
| 'toolbar'
|
|
@@ -717,35 +730,61 @@ export type HistoryOpSource =
|
|
|
717
730
|
| 'rollback'
|
|
718
731
|
| 'api'
|
|
719
732
|
| 'ai'
|
|
733
|
+
// 同步
|
|
734
|
+
| 'sync'
|
|
720
735
|
| 'unknown'
|
|
721
736
|
| (string & {});
|
|
722
737
|
// #endregion HistoryOpSource
|
|
723
738
|
|
|
724
|
-
// #region
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
739
|
+
// #region StepDiffItem
|
|
740
|
+
/**
|
|
741
|
+
* 单条变更的 diff 描述,统一表达「页面节点 / 代码块 / 数据源」的变化内容,
|
|
742
|
+
* 被 {@link StepValue} / {@link CodeBlockStepValue} / {@link DataSourceStepValue} 的 `diff` 复用。
|
|
743
|
+
*
|
|
744
|
+
* 按 `opType` 区分携带的字段:
|
|
745
|
+
* - `add`:仅 `newSchema`(页面节点还带 `parentId` / `index`);
|
|
746
|
+
* - `remove`:仅 `oldSchema`(页面节点还带 `parentId` / `index`);
|
|
747
|
+
* - `update`:`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新。
|
|
748
|
+
*
|
|
749
|
+
* 泛型 `T` 为变化内容的快照类型:页面节点为 `MNode`,代码块为 `CodeBlockContent`,数据源为 `DataSourceSchema`。
|
|
750
|
+
*/
|
|
751
|
+
export interface StepDiffItem<T = unknown> {
|
|
752
|
+
/** 变更后的内容快照。`opType` 为 `add` / `update` 时有,`remove` 时无。 */
|
|
753
|
+
newSchema?: T;
|
|
754
|
+
/** 变更前的内容快照。`opType` 为 `remove` / `update` 时有,`add` 时无。 */
|
|
755
|
+
oldSchema?: T;
|
|
756
|
+
/** 父节点 id。仅页面节点有(数据源 / 代码块没有父节点)。 */
|
|
737
757
|
parentId?: Id;
|
|
738
|
-
/**
|
|
739
|
-
|
|
740
|
-
/** opType 'remove': 被删除的节点及其位置信息 */
|
|
741
|
-
removedItems?: { node: MNode; parentId: Id; index: number }[];
|
|
758
|
+
/** 在父节点 items 数组中的索引。仅页面节点有(数据源 / 代码块无需排序)。 */
|
|
759
|
+
index?: number;
|
|
742
760
|
/**
|
|
743
|
-
* opType
|
|
744
|
-
*
|
|
745
|
-
* `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
|
|
746
|
-
* 缺省(未传 / 空数组)才退化为整节点替换。
|
|
761
|
+
* form 端 propPath/value 变更列表,仅 `opType` 为 `update` 时有;
|
|
762
|
+
* 撤销/重做时若有则按 propPath 局部更新,缺省才退化为整内容替换。
|
|
747
763
|
*/
|
|
748
|
-
|
|
764
|
+
changeRecords?: ChangeRecord[];
|
|
765
|
+
}
|
|
766
|
+
// #endregion StepDiffItem
|
|
767
|
+
|
|
768
|
+
// #region BaseStepValue
|
|
769
|
+
/**
|
|
770
|
+
* 历史记录条目公共字段,被 {@link StepValue} / {@link CodeBlockStepValue} / {@link DataSourceStepValue} 复用。
|
|
771
|
+
*
|
|
772
|
+
* 泛型 `T` 为 `diff` 中变化内容的快照类型(页面节点 `MNode` / 代码块 `CodeBlockContent` / 数据源 `DataSourceSchema`)。
|
|
773
|
+
*/
|
|
774
|
+
export interface BaseStepValue<T = unknown> {
|
|
775
|
+
/**
|
|
776
|
+
* 历史记录唯一标识(uuid)。入栈时自动写入(若调用方未指定),
|
|
777
|
+
* 用于精确定位 / 引用某一条历史记录(如 revert、埋点、跨端同步等)。
|
|
778
|
+
* 注意与各自的 `id`(关联的页面 / 代码块 / 数据源 id)区分。
|
|
779
|
+
*/
|
|
780
|
+
uuid: string;
|
|
781
|
+
/** 操作类型:新增 / 删除 / 更新(三类历史记录统一携带)。 */
|
|
782
|
+
opType: HistoryOpType;
|
|
783
|
+
/**
|
|
784
|
+
* 本次变更的内容(统一 diff 表达),每项见 {@link StepDiffItem}。
|
|
785
|
+
* 页面节点(add/remove 多节点、update 多节点)会有多项,代码块 / 数据源通常只有一项。
|
|
786
|
+
*/
|
|
787
|
+
diff: StepDiffItem<T>[];
|
|
749
788
|
/**
|
|
750
789
|
* 调用方可选传入的人类可读描述(如「调整按钮颜色」),用于历史面板展示。
|
|
751
790
|
* 不影响 undo/redo 行为;缺省时面板会根据节点 / propPath 自动生成描述。
|
|
@@ -758,65 +797,60 @@ export interface StepValue {
|
|
|
758
797
|
*/
|
|
759
798
|
source?: HistoryOpSource;
|
|
760
799
|
/**
|
|
761
|
-
*
|
|
800
|
+
* 入栈时间戳(毫秒)。入栈时自动写入(若调用方未指定),仅用于历史面板展示。
|
|
762
801
|
*/
|
|
763
802
|
timestamp?: number;
|
|
803
|
+
/**
|
|
804
|
+
* 是否为「已保存」记录:DSL 落库(如保存到后端 / 本地)时由 historyService.markSaved 标记。
|
|
805
|
+
* 同一栈内任意时刻最多只有一条记录为 true;从 IndexedDB 恢复时游标会被定位到最近一条已保存记录之后。
|
|
806
|
+
*/
|
|
807
|
+
saved?: boolean;
|
|
808
|
+
/**
|
|
809
|
+
* 是否为「整体设置 root」(set root)产生的记录(由 {@link Editor.pushRootDiffHistory} 写入)。
|
|
810
|
+
* 用于「连续 set root 合并」:当某页栈最新一条已是 root 记录时,下一条 set root 会替换它而非新增,
|
|
811
|
+
* 避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录。
|
|
812
|
+
*/
|
|
813
|
+
rootStep?: boolean;
|
|
814
|
+
}
|
|
815
|
+
// #endregion BaseStepValue
|
|
816
|
+
|
|
817
|
+
// #region StepValue
|
|
818
|
+
export interface StepValue extends BaseStepValue<MNode> {
|
|
819
|
+
/** 页面信息 */
|
|
820
|
+
data: { name: string; id: Id };
|
|
821
|
+
/** 操作前选中的节点 ID,用于撤销后恢复选择状态 */
|
|
822
|
+
selectedBefore: Id[];
|
|
823
|
+
/** 操作后选中的节点 ID,用于重做后恢复选择状态 */
|
|
824
|
+
selectedAfter: Id[];
|
|
825
|
+
modifiedNodeIds: Map<Id, Id>;
|
|
764
826
|
}
|
|
765
827
|
// #endregion StepValue
|
|
766
828
|
|
|
767
829
|
// #region CodeBlockStepValue
|
|
768
830
|
/**
|
|
769
831
|
* 代码块历史记录条目。按 codeBlock.id 分组保存到 historyState.codeBlockState。
|
|
770
|
-
*
|
|
771
|
-
* -
|
|
772
|
-
* -
|
|
832
|
+
* 变更内容统一由 `diff`(单项)表达,每项见 {@link StepDiffItem}:
|
|
833
|
+
* - 新增(opType 'add'):仅 `newSchema`(新内容);
|
|
834
|
+
* - 更新(opType 'update'):`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新;
|
|
835
|
+
* - 删除(opType 'remove'):仅 `oldSchema`(删除前内容)。
|
|
773
836
|
*/
|
|
774
|
-
export interface CodeBlockStepValue {
|
|
837
|
+
export interface CodeBlockStepValue extends BaseStepValue<CodeBlockContent> {
|
|
775
838
|
/** 关联的代码块 id */
|
|
776
839
|
id: Id;
|
|
777
|
-
/** 变更前的代码块内容,新增时为 null */
|
|
778
|
-
oldContent: CodeBlockContent | null;
|
|
779
|
-
/** 变更后的代码块内容,删除时为 null */
|
|
780
|
-
newContent: CodeBlockContent | null;
|
|
781
|
-
/**
|
|
782
|
-
* form 端 propPath/value 列表。撤销/重做时若有则按 propPath 局部更新;
|
|
783
|
-
* 缺省才退化为整内容替换。新增/删除场景通常无 changeRecords。
|
|
784
|
-
*/
|
|
785
|
-
changeRecords?: ChangeRecord[];
|
|
786
|
-
/** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
|
|
787
|
-
historyDescription?: string;
|
|
788
|
-
/** 操作途径:标记本次变更由哪条交互入口触发,取值见 {@link HistoryOpSource};仅用于历史面板展示与埋点,不影响 undo/redo 行为。 */
|
|
789
|
-
source?: HistoryOpSource;
|
|
790
|
-
/** 入栈时间戳(毫秒),入栈时自动写入,仅用于历史面板展示。 */
|
|
791
|
-
timestamp?: number;
|
|
792
840
|
}
|
|
793
841
|
// #endregion CodeBlockStepValue
|
|
794
842
|
|
|
795
843
|
// #region DataSourceStepValue
|
|
796
844
|
/**
|
|
797
845
|
* 数据源历史记录条目。按 dataSource.id 分组保存到 historyState.dataSourceState。
|
|
798
|
-
*
|
|
799
|
-
* -
|
|
800
|
-
* -
|
|
846
|
+
* 变更内容统一由 `diff`(单项)表达,每项见 {@link StepDiffItem}:
|
|
847
|
+
* - 新增(opType 'add'):仅 `newSchema`(新 schema);
|
|
848
|
+
* - 更新(opType 'update'):`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新;
|
|
849
|
+
* - 删除(opType 'remove'):仅 `oldSchema`(删除前 schema)。
|
|
801
850
|
*/
|
|
802
|
-
export interface DataSourceStepValue {
|
|
851
|
+
export interface DataSourceStepValue extends BaseStepValue<DataSourceSchema> {
|
|
803
852
|
/** 关联的数据源 id */
|
|
804
853
|
id: Id;
|
|
805
|
-
/** 变更前的数据源 schema,新增时为 null */
|
|
806
|
-
oldSchema: DataSourceSchema | null;
|
|
807
|
-
/** 变更后的数据源 schema,删除时为 null */
|
|
808
|
-
newSchema: DataSourceSchema | null;
|
|
809
|
-
/**
|
|
810
|
-
* form 端 propPath/value 列表。撤销/重做时若有则按 propPath 局部更新;
|
|
811
|
-
* 缺省才退化为整 schema 替换。新增/删除场景通常无 changeRecords。
|
|
812
|
-
*/
|
|
813
|
-
changeRecords?: ChangeRecord[];
|
|
814
|
-
/** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
|
|
815
|
-
historyDescription?: string;
|
|
816
|
-
/** 操作途径:标记本次变更由哪条交互入口触发,取值见 {@link HistoryOpSource};仅用于历史面板展示与埋点,不影响 undo/redo 行为。 */
|
|
817
|
-
source?: HistoryOpSource;
|
|
818
|
-
/** 入栈时间戳(毫秒),入栈时自动写入,仅用于历史面板展示。 */
|
|
819
|
-
timestamp?: number;
|
|
820
854
|
}
|
|
821
855
|
// #endregion DataSourceStepValue
|
|
822
856
|
|
|
@@ -837,6 +871,45 @@ export interface HistoryState {
|
|
|
837
871
|
dataSourceState: Record<Id, UndoRedo<DataSourceStepValue>>;
|
|
838
872
|
}
|
|
839
873
|
|
|
874
|
+
// #region PersistedHistoryState
|
|
875
|
+
/**
|
|
876
|
+
* 历史记录的可持久化快照。由 historyService.saveToIndexedDB 写入 IndexedDB,
|
|
877
|
+
* 再由 historyService.restoreFromIndexedDB 读出并重建各 UndoRedo 栈。
|
|
878
|
+
*/
|
|
879
|
+
export interface PersistedHistoryState {
|
|
880
|
+
/** 快照结构版本号,便于后续兼容升级。 */
|
|
881
|
+
version: number;
|
|
882
|
+
/** 保存时的活动页 id。 */
|
|
883
|
+
pageId?: Id;
|
|
884
|
+
/** 各页面历史栈的序列化快照,按 pageId 分组。 */
|
|
885
|
+
pageSteps: Record<Id, SerializedUndoRedo<StepValue>>;
|
|
886
|
+
/** 各代码块历史栈的序列化快照,按 codeBlockId 分组。 */
|
|
887
|
+
codeBlockState: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>;
|
|
888
|
+
/** 各数据源历史栈的序列化快照,按 dataSourceId 分组。 */
|
|
889
|
+
dataSourceState: Record<Id, SerializedUndoRedo<DataSourceStepValue>>;
|
|
890
|
+
/** 保存时间戳(毫秒)。 */
|
|
891
|
+
savedAt: number;
|
|
892
|
+
}
|
|
893
|
+
// #endregion PersistedHistoryState
|
|
894
|
+
|
|
895
|
+
// #region HistoryPersistOptions
|
|
896
|
+
/** historyService 持久化相关 API 的可选配置。 */
|
|
897
|
+
export interface HistoryPersistOptions {
|
|
898
|
+
/** IndexedDB 数据库名,默认 `tmagic-editor`(最终库名会拼上当前 DSL app id)。 */
|
|
899
|
+
dbName?: string;
|
|
900
|
+
/** objectStore 名,默认 `history`。 */
|
|
901
|
+
storeName?: string;
|
|
902
|
+
/** 记录 key,用于区分不同活动页 / 项目,默认 `default`。 */
|
|
903
|
+
key?: IDBValidKey;
|
|
904
|
+
/**
|
|
905
|
+
* 显式指定用于库名隔离的 DSL app id。
|
|
906
|
+
* 缺省时回退到当前 editorService 的 `root.id`;在「先恢复历史再 set root」场景下 root 尚未设置,
|
|
907
|
+
* 需由调用方(如从待加载 DSL 取 id)显式传入,否则会读 / 写到未按 app 隔离的默认库。
|
|
908
|
+
*/
|
|
909
|
+
appId?: Id;
|
|
910
|
+
}
|
|
911
|
+
// #endregion HistoryPersistOptions
|
|
912
|
+
|
|
840
913
|
// #region HistoryListEntry
|
|
841
914
|
/**
|
|
842
915
|
* 历史面板用:当前页面的一条历史步骤(包含位置和是否已应用)。
|
|
@@ -1202,3 +1275,34 @@ export interface DiffDialogPayload {
|
|
|
1202
1275
|
/** 用于标题展示的目标 id */
|
|
1203
1276
|
id?: string | number;
|
|
1204
1277
|
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* 一组「描述 + 可操作性」的判定函数集合。页面 / 数据源 / 代码块及业务自定义历史
|
|
1281
|
+
* 各自实现一份,作为整体注入,避免把 describe* / isStep* 拆成多个独立 props 反复透传。
|
|
1282
|
+
*/
|
|
1283
|
+
export interface HistoryRowDescriptor<T extends BaseStepValue = BaseStepValue> {
|
|
1284
|
+
/** 组级描述文案生成器,接收一个 group,返回展示文本。 */
|
|
1285
|
+
describeGroup: (_group: any) => string;
|
|
1286
|
+
/** 单步描述文案生成器,接收一个 step,返回展示文本(合并组展开后的子步列表用)。 */
|
|
1287
|
+
describeStep: (_step: T) => string;
|
|
1288
|
+
/** 判断某个 step 是否可查看差异(前后值都存在)。不传则一律不展示差异入口。 */
|
|
1289
|
+
isStepDiffable?: (_step: T) => boolean;
|
|
1290
|
+
/** 判断某个 step 是否支持回滚(如更新需带 changeRecords)。不传则已应用即可回滚。 */
|
|
1291
|
+
isStepRevertable?: (_step: T) => boolean;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* 通用 bucket(数据源 / 代码块 / 业务自定义历史)的整体渲染配置。
|
|
1296
|
+
* 把原先散落在 Bucket / BucketTab 上的 title / prefix / describe* / isStep* / showInitial / gotoEnabled
|
|
1297
|
+
* 收敛成一个对象作为单一 prop 传递,调用方一次配齐、组件内部按需读取。
|
|
1298
|
+
*/
|
|
1299
|
+
export interface HistoryBucketConfig<T extends BaseStepValue = BaseStepValue> extends HistoryRowDescriptor<T> {
|
|
1300
|
+
/** bucket 头部标题,例如 "数据源" / "代码块"。 */
|
|
1301
|
+
title: string;
|
|
1302
|
+
/** 子项 key 的命名空间前缀(`ds` 数据源 / `cb` 代码块 / 业务自定义如 `mod`)。 */
|
|
1303
|
+
prefix: string;
|
|
1304
|
+
/** 是否展示底部「回到初始状态」入口,默认 true。无 undo cursor 语义的自定义历史可传 false。 */
|
|
1305
|
+
showInitial?: boolean;
|
|
1306
|
+
/** 是否支持「跳转到该记录」(goto),默认 true。 */
|
|
1307
|
+
gotoEnabled?: boolean;
|
|
1308
|
+
}
|
package/src/utils/editor.ts
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
isValueIncludeDataSource,
|
|
36
36
|
} from '@tmagic/utils';
|
|
37
37
|
|
|
38
|
-
import type { EditorNodeInfo } from '@editor/type';
|
|
38
|
+
import type { EditorNodeInfo, StepValue } from '@editor/type';
|
|
39
39
|
import { LayerOffset, Layout } from '@editor/type';
|
|
40
40
|
|
|
41
41
|
export const COPY_STORAGE_KEY = '$MagicEditorCopyData';
|
|
@@ -684,3 +684,43 @@ export const classifyDragSources = (
|
|
|
684
684
|
|
|
685
685
|
return { sameParentIndices, crossParentConfigs, aborted: false };
|
|
686
686
|
};
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* 给「回滚」生成的新 step 用的简短描述生成器。
|
|
690
|
+
* 与 UI 层 `describePageStep` 同义,但避免 service 反向依赖 layouts/,故放在此工具函数中。
|
|
691
|
+
*/
|
|
692
|
+
export const describeStepForRevert = (step: StepValue): string => {
|
|
693
|
+
const items = step.diff ?? [];
|
|
694
|
+
// 在可读名后拼接组件 id,便于在历史面板中精确定位被回滚的组件;id 缺失时退化为仅展示名称。
|
|
695
|
+
const withId = (node: MNode | undefined, label: string): string => {
|
|
696
|
+
const id = node?.id;
|
|
697
|
+
if (id === undefined || id === null || `${id}` === '') return label;
|
|
698
|
+
return label ? `${label}(id: ${id})` : `id: ${id}`;
|
|
699
|
+
};
|
|
700
|
+
switch (step.opType) {
|
|
701
|
+
case 'add': {
|
|
702
|
+
const count = items.length;
|
|
703
|
+
const node = items[0]?.newSchema;
|
|
704
|
+
const label = node?.name || node?.type || '';
|
|
705
|
+
return `撤回新增 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ''}`;
|
|
706
|
+
}
|
|
707
|
+
case 'remove': {
|
|
708
|
+
const count = items.length;
|
|
709
|
+
const node = items[0]?.oldSchema;
|
|
710
|
+
const label = node?.name || node?.type || '';
|
|
711
|
+
return `还原已删除的 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ''}`;
|
|
712
|
+
}
|
|
713
|
+
case 'update':
|
|
714
|
+
default: {
|
|
715
|
+
if (items.length === 1) {
|
|
716
|
+
const { newSchema, oldSchema, changeRecords } = items[0];
|
|
717
|
+
const node = newSchema || oldSchema;
|
|
718
|
+
const label = newSchema?.name || newSchema?.type || oldSchema?.name || oldSchema?.type || '';
|
|
719
|
+
const target = withId(node, label);
|
|
720
|
+
const propPath = changeRecords?.[0]?.propPath;
|
|
721
|
+
return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
|
|
722
|
+
}
|
|
723
|
+
return `还原 ${items.length} 个节点的修改`;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
};
|