@tmagic/editor 1.8.0-beta.8 → 1.8.0-manmanyu.8
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 +13 -3
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +1 -4
- package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +11 -4
- package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +9 -4
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +68 -43
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +10 -7
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +4 -1
- 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 +4 -1
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +101 -64
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/icons/align-items/Center.js +31 -0
- package/dist/es/fields/StyleSetter/icons/align-items/FlexEnd.js +31 -0
- package/dist/es/fields/StyleSetter/icons/align-items/FlexStart.js +31 -0
- package/dist/es/fields/StyleSetter/icons/align-items/SpaceAround.js +50 -0
- package/dist/es/fields/StyleSetter/icons/align-items/SpaceBetween.js +50 -0
- package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +6 -6
- package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +5 -2
- package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +28 -5
- package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +31 -5
- package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +10 -2
- package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +10 -2
- package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +10 -2
- package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +10 -2
- package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +16 -2
- package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +18 -2
- package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +18 -2
- package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +36 -2
- package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +32 -2
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +11 -6
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +82 -46
- package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/icons/DatasourceIcon.js +17 -0
- package/dist/es/index.js +2 -2
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +14 -10
- package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +18 -28
- package/dist/es/layouts/history-list/useHistoryList.js +4 -4
- package/dist/es/layouts/history-list/useHistoryRevert.js +6 -6
- package/dist/es/services/codeBlock.js +28 -32
- package/dist/es/services/dataSource.js +34 -43
- package/dist/es/services/editor.js +26 -31
- package/dist/es/services/history.js +392 -259
- package/dist/es/style.css +294 -37
- package/dist/es/utils/const.js +1 -1
- package/dist/es/utils/history.js +47 -35
- package/dist/es/utils/props.js +137 -31
- package/dist/style.css +294 -37
- package/dist/themes/magic-admin.css +2316 -0
- package/dist/tmagic-editor.umd.cjs +1665 -880
- package/package.json +7 -7
- package/src/Editor.vue +13 -1
- package/src/components/ContentMenu.vue +10 -2
- package/src/components/FloatingBox.vue +13 -2
- package/src/editorProps.ts +9 -0
- package/src/fields/CodeSelect.vue +51 -40
- package/src/fields/CodeSelectCol.vue +1 -0
- package/src/fields/DataSourceFieldSelect/Index.vue +14 -5
- package/src/fields/DisplayConds.vue +4 -1
- package/src/fields/EventSelect.vue +58 -32
- package/src/fields/StyleSetter/Index.vue +1 -4
- package/src/fields/StyleSetter/components/Border.vue +1 -1
- package/src/fields/StyleSetter/icons/align-items/Center.vue +19 -0
- package/src/fields/StyleSetter/icons/align-items/FlexEnd.vue +19 -0
- package/src/fields/StyleSetter/icons/align-items/FlexStart.vue +19 -0
- package/src/fields/StyleSetter/icons/align-items/SpaceAround.vue +34 -0
- package/src/fields/StyleSetter/icons/align-items/SpaceBetween.vue +34 -0
- package/src/fields/StyleSetter/icons/align-items/index copy.ts +5 -0
- package/src/fields/StyleSetter/icons/align-items/index.ts +5 -0
- package/src/fields/StyleSetter/icons/background-repeat/NoRepeat.vue +20 -4
- package/src/fields/StyleSetter/icons/background-repeat/Repeat.vue +10 -28
- package/src/fields/StyleSetter/icons/background-repeat/RepeatX.vue +4 -5
- package/src/fields/StyleSetter/icons/background-repeat/RepeatY.vue +4 -5
- package/src/fields/StyleSetter/icons/flex-direction/Column.vue +4 -3
- package/src/fields/StyleSetter/icons/flex-direction/ColumnReverse.vue +4 -3
- package/src/fields/StyleSetter/icons/flex-direction/Row.vue +4 -3
- package/src/fields/StyleSetter/icons/flex-direction/RowReverse.vue +4 -3
- package/src/fields/StyleSetter/icons/justify-content/Center.vue +5 -2
- package/src/fields/StyleSetter/icons/justify-content/FlexEnd.vue +15 -2
- package/src/fields/StyleSetter/icons/justify-content/FlexStart.vue +15 -2
- package/src/fields/StyleSetter/icons/justify-content/SpaceAround.vue +28 -3
- package/src/fields/StyleSetter/icons/justify-content/SpaceBetween.vue +28 -2
- package/src/fields/StyleSetter/pro/Background.vue +11 -5
- package/src/fields/StyleSetter/pro/Font.vue +3 -3
- package/src/fields/StyleSetter/pro/Layout.vue +105 -28
- package/src/fields/UISelect.vue +1 -1
- package/src/icons/DatasourceIcon.vue +7 -0
- package/src/layouts/Framework.vue +7 -1
- package/src/theme/code-block.scss +37 -0
- package/src/theme/common/var.scss +1 -1
- package/src/theme/component-list-panel.scss +2 -2
- package/src/theme/data-source-field.scss +4 -0
- package/src/theme/display-conds.scss +11 -0
- package/src/theme/event.scss +55 -1
- package/src/theme/history-list-panel.scss +9 -9
- package/src/theme/props-panel.scss +2 -1
- package/src/theme/style-setter/border.scss +14 -5
- package/src/theme/theme.scss +1 -0
- package/src/theme/themes/magic-admin/index.scss +125 -0
- package/src/utils/const.ts +2 -1
- package/src/utils/props.ts +138 -9
- package/types/index.d.ts +310 -315
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseService from "./BaseService.js";
|
|
2
2
|
import { getEditorConfig } from "../utils/config.js";
|
|
3
3
|
import { UndoRedo } from "../utils/undo-redo.js";
|
|
4
|
-
import { deserializeStacks, getOrCreateStack, markStackSaved,
|
|
4
|
+
import { createStackStep, deserializeStacks, getOrCreateStack, markStackSaved, mergePageSteps, mergeStackSteps, serializeStacks, undoFloor } from "../utils/history.js";
|
|
5
5
|
import { idbGet, idbSet } from "../utils/indexed-db.js";
|
|
6
6
|
import editor_default from "./editor.js";
|
|
7
7
|
import { guid } from "@tmagic/utils";
|
|
@@ -9,92 +9,78 @@ import { reactive } from "vue";
|
|
|
9
9
|
//#region packages/editor/src/services/history.ts
|
|
10
10
|
var canUsePluginMethods = { sync: [
|
|
11
11
|
"push",
|
|
12
|
+
"pushCodeBlock",
|
|
13
|
+
"pushDataSource",
|
|
14
|
+
"undoCodeBlock",
|
|
15
|
+
"redoCodeBlock",
|
|
16
|
+
"undoDataSource",
|
|
17
|
+
"redoDataSource",
|
|
12
18
|
"undo",
|
|
13
19
|
"redo"
|
|
14
20
|
] };
|
|
15
|
-
/** 各内置历史类型的默认展示名称(用于历史面板 tab / 分组标题等)。扩展类型可通过 registerStepType / setStepName 登记。 */
|
|
16
|
-
var DEFAULT_STEP_NAMES = {
|
|
17
|
-
page: "页面",
|
|
18
|
-
codeBlock: "代码块",
|
|
19
|
-
dataSource: "数据源"
|
|
20
|
-
};
|
|
21
|
-
/** 各历史类型对应的分组 `kind`(展示用):page→page,codeBlock→code-block,dataSource→data-source。扩展类型缺省回退到 stepType 本身。 */
|
|
22
|
-
var STEP_TYPE_KIND = {
|
|
23
|
-
page: "page",
|
|
24
|
-
codeBlock: "code-block",
|
|
25
|
-
dataSource: "data-source"
|
|
26
|
-
};
|
|
27
21
|
/** 历史记录持久化快照的默认存储位置与结构版本。 */
|
|
28
22
|
var DEFAULT_DB_NAME = "tmagic-editor";
|
|
29
23
|
var DEFAULT_STORE_NAME = "history";
|
|
30
24
|
var DEFAULT_KEY = "default";
|
|
31
|
-
var PERSIST_VERSION =
|
|
25
|
+
var PERSIST_VERSION = 2;
|
|
32
26
|
var History = class extends BaseService {
|
|
33
27
|
state = reactive({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
|
|
28
|
+
pageSteps: {},
|
|
29
|
+
pageId: void 0,
|
|
30
|
+
canRedo: false,
|
|
31
|
+
canUndo: false,
|
|
32
|
+
codeBlockState: {},
|
|
33
|
+
dataSourceState: {}
|
|
40
34
|
});
|
|
41
35
|
constructor() {
|
|
42
36
|
super([...canUsePluginMethods.sync.map((methodName) => ({
|
|
43
37
|
name: methodName,
|
|
44
38
|
isAsync: false
|
|
45
39
|
}))]);
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 注册一个扩展历史类型,使其可与内置 `page` / `codeBlock` / `dataSource` 一样走统一的
|
|
49
|
-
* {@link push} / {@link undo} / {@link redo}(按 id 分栈、独立 undo/redo)。
|
|
50
|
-
*
|
|
51
|
-
* @param stepType 自定义历史类型标识(勿与内置类型重名)
|
|
52
|
-
* @param options.event push/undo/redo 后派发的事件名,缺省为 `${stepType}-history-change`
|
|
53
|
-
* @param options.name 历史面板中的展示名称(tab / 分组标题等),缺省回退到 stepType 本身
|
|
54
|
-
*/
|
|
55
|
-
registerStepType(stepType, options = {}) {
|
|
56
|
-
this.getStepBucket(stepType);
|
|
57
|
-
if (options.name !== void 0) this.state.stepNames[stepType] = options.name;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* 读取指定历史类型的展示名称(用于历史面板 tab / 分组标题等)。
|
|
61
|
-
* 未登记名称时回退到 stepType 本身。
|
|
62
|
-
*/
|
|
63
|
-
getStepName(stepType) {
|
|
64
|
-
return this.state.stepNames[stepType] ?? `${stepType}`;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* 设置指定历史类型的展示名称(用于历史面板 tab / 分组标题等)。
|
|
68
|
-
* 内置 `page` / `codeBlock` / `dataSource` 也可在此覆盖默认中文名。
|
|
69
|
-
*/
|
|
70
|
-
setStepName(stepType, name) {
|
|
71
|
-
this.state.stepNames[stepType] = name;
|
|
40
|
+
this.on("change", this.setCanUndoRedo);
|
|
72
41
|
}
|
|
73
42
|
reset() {
|
|
74
|
-
this.
|
|
43
|
+
this.state.pageSteps = {};
|
|
44
|
+
this.state.codeBlockState = {};
|
|
45
|
+
this.state.dataSourceState = {};
|
|
46
|
+
this.resetPage();
|
|
47
|
+
}
|
|
48
|
+
resetPage() {
|
|
49
|
+
this.state.pageId = void 0;
|
|
50
|
+
this.state.canRedo = false;
|
|
51
|
+
this.state.canUndo = false;
|
|
52
|
+
}
|
|
53
|
+
changePage(page) {
|
|
54
|
+
if (!page) return;
|
|
55
|
+
this.state.pageId = page.id;
|
|
56
|
+
if (!this.state.pageSteps[this.state.pageId]) this.state.pageSteps[this.state.pageId] = new UndoRedo();
|
|
57
|
+
this.setCanUndoRedo();
|
|
58
|
+
this.emit("page-change", this.state.pageSteps[this.state.pageId]);
|
|
75
59
|
}
|
|
76
60
|
resetState() {
|
|
77
|
-
this.
|
|
61
|
+
this.state.pageId = void 0;
|
|
62
|
+
this.state.pageSteps = {};
|
|
63
|
+
this.state.canRedo = false;
|
|
64
|
+
this.state.canUndo = false;
|
|
65
|
+
this.state.codeBlockState = {};
|
|
66
|
+
this.state.dataSourceState = {};
|
|
78
67
|
}
|
|
79
68
|
/**
|
|
80
|
-
*
|
|
69
|
+
* 为指定页面 / 页面片种入一条「初始基线」记录(如加载 DSL 时的「初始 / 加载」基线)。
|
|
81
70
|
*
|
|
82
|
-
* 该记录是一条 `opType: 'initial'` 的
|
|
71
|
+
* 该记录是一条 `opType: 'initial'` 的 {@link StepValue},作为页面历史栈 **index 0 的固定底线**:
|
|
83
72
|
* - 它是一条真实入栈的 step(随栈一起持久化),但被钉为撤销/回滚的下限——cursor 永不低于它,
|
|
84
|
-
* 因此不会被 undo / goto / revert 触达(详见 {@link undo} / {@link
|
|
85
|
-
* - 历史面板把它过滤出分组列表(见 {@link
|
|
86
|
-
*
|
|
87
|
-
* 仅当目标栈为空时种入(保证 initial 一定位于 index 0);已存在 initial 底线时不重复种入。
|
|
73
|
+
* 因此不会被 undo / goto / revert 触达(详见 {@link undo} / {@link setCanUndoRedo});
|
|
74
|
+
* - 历史面板把它过滤出分组列表(见 {@link getPageHistoryGroups}),改由底部「初始」行展示。
|
|
88
75
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* @param options 基线展示信息(名称 / 描述 / 来源)
|
|
76
|
+
* 仅当目标页面栈为空时种入(保证 initial 一定位于 index 0);已存在 initial 底线时默认不重复种入,
|
|
77
|
+
* 传 `force=true` 且栈为空时按新基线种入。
|
|
92
78
|
*/
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
const existing = this.
|
|
79
|
+
setPageMarker(pageId, options = {}) {
|
|
80
|
+
if (pageId === void 0 || pageId === null || `${pageId}` === "") return null;
|
|
81
|
+
const existing = this.getPageMarker(pageId);
|
|
96
82
|
if (existing) return existing;
|
|
97
|
-
const stack = getOrCreateStack(this.
|
|
83
|
+
const stack = getOrCreateStack(this.state.pageSteps, pageId);
|
|
98
84
|
if (stack.getLength() > 0) return null;
|
|
99
85
|
const marker = {
|
|
100
86
|
uuid: guid(),
|
|
@@ -102,95 +88,164 @@ var History = class extends BaseService {
|
|
|
102
88
|
diff: [],
|
|
103
89
|
data: {
|
|
104
90
|
name: options.name || "",
|
|
105
|
-
id
|
|
106
|
-
},
|
|
107
|
-
extra: {
|
|
108
|
-
...options.extra || {},
|
|
109
|
-
...stepType === "page" ? {
|
|
110
|
-
selectedBefore: [],
|
|
111
|
-
selectedAfter: [],
|
|
112
|
-
modifiedNodeIds: /* @__PURE__ */ new Map()
|
|
113
|
-
} : {}
|
|
91
|
+
id: pageId
|
|
114
92
|
},
|
|
93
|
+
selectedBefore: [],
|
|
94
|
+
selectedAfter: [],
|
|
95
|
+
modifiedNodeIds: /* @__PURE__ */ new Map(),
|
|
115
96
|
historyDescription: options.description || "未修改的初始状态",
|
|
116
97
|
timestamp: Date.now(),
|
|
117
98
|
...options.source ? { source: options.source } : {}
|
|
118
99
|
};
|
|
119
100
|
stack.pushElement(marker);
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
marker,
|
|
123
|
-
stepType
|
|
124
|
-
});
|
|
101
|
+
if (`${pageId}` === `${this.state.pageId}`) this.setCanUndoRedo();
|
|
102
|
+
this.emit("page-marker-change", marker);
|
|
125
103
|
return marker;
|
|
126
104
|
}
|
|
127
105
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
106
|
+
* 读取指定页面(缺省当前活动页)的初始基线 step(页面栈 index 0 且 `opType: 'initial'`);
|
|
107
|
+
* 不存在时返回 undefined。
|
|
130
108
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
109
|
+
getPageMarker(pageId) {
|
|
110
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
111
|
+
if (!targetPageId) return void 0;
|
|
112
|
+
const first = this.state.pageSteps[targetPageId]?.getElementList()[0];
|
|
134
113
|
return first?.opType === "initial" ? first : void 0;
|
|
135
114
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
115
|
+
/**
|
|
116
|
+
* 把一条步骤推入指定页面的栈;不指定 pageId 时落到当前活动页。
|
|
117
|
+
*
|
|
118
|
+
* 跨页操作(例如 `moveToContainer` 把节点搬到其它页)必须显式传入 `pageId`,
|
|
119
|
+
* 否则会把记录错误地落到操作发起页 / 当前激活页,破坏目标页 / 源页的撤销栈语义。
|
|
120
|
+
*/
|
|
121
|
+
push(state, pageId) {
|
|
122
|
+
const undoRedo = this.getUndoRedo(pageId);
|
|
123
|
+
if (!undoRedo) return null;
|
|
124
|
+
if (state.uuid === void 0) state.uuid = guid();
|
|
125
|
+
if (state.timestamp === void 0) state.timestamp = Date.now();
|
|
126
|
+
undoRedo.pushElement(state);
|
|
127
|
+
if (pageId === void 0 || `${pageId}` === `${this.state.pageId}`) this.emit("change", state);
|
|
128
|
+
return state;
|
|
129
|
+
}
|
|
130
|
+
/** 读取指定页面(缺省当前活动页)历史栈当前游标所在的 step(cursor - 1);无则返回 null。 */
|
|
131
|
+
getCurrentPageStep(pageId) {
|
|
132
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
133
|
+
if (!targetPageId) return null;
|
|
134
|
+
return this.state.pageSteps[targetPageId]?.getCurrentElement() ?? null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 用 `state` 替换指定页面栈当前游标所在的 step(并丢弃其后的重做尾部),游标不变。
|
|
138
|
+
* 用于「连续 set root 记录合并」等就地替换最新一条的场景;替换成功后按需刷新 / 通知。
|
|
139
|
+
*/
|
|
140
|
+
replaceCurrentPageStep(state, pageId) {
|
|
141
|
+
const undoRedo = this.getUndoRedo(pageId);
|
|
142
|
+
if (!undoRedo) return null;
|
|
143
|
+
if (state.uuid === void 0) state.uuid = guid();
|
|
144
|
+
if (state.timestamp === void 0) state.timestamp = Date.now();
|
|
145
|
+
if (!undoRedo.replaceCurrentElement(state)) return null;
|
|
146
|
+
this.emit("change", state);
|
|
147
|
+
return state;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 推入一条代码块变更记录(与页面/节点完全无关),按 `codeBlockId` 维度独立一份 UndoRedo 栈。
|
|
151
|
+
*
|
|
152
|
+
* - 新增:oldContent = null,newContent = 新内容
|
|
153
|
+
* - 更新:oldContent / newContent 都为对应内容
|
|
154
|
+
* - 删除:newContent = null,oldContent = 删除前内容
|
|
155
|
+
* - `changeRecords` 来自 form 端,撤销/重做时若有则按 propPath 局部覆盖;缺省才退化为整内容替换。
|
|
156
|
+
* - 不直接驱动 codeBlockService,调用方负责实际写回。
|
|
157
|
+
*/
|
|
158
|
+
pushCodeBlock(codeBlockId, payload) {
|
|
159
|
+
const step = createStackStep(codeBlockId, {
|
|
160
|
+
oldValue: payload.oldContent,
|
|
161
|
+
newValue: payload.newContent,
|
|
162
|
+
changeRecords: payload.changeRecords,
|
|
163
|
+
historyDescription: payload.historyDescription,
|
|
164
|
+
source: payload.source
|
|
165
|
+
});
|
|
166
|
+
if (!step) return null;
|
|
167
|
+
getOrCreateStack(this.state.codeBlockState, codeBlockId).pushElement(step);
|
|
168
|
+
this.emit("code-block-history-change", codeBlockId, step);
|
|
169
|
+
return step;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 推入一条数据源变更记录(与页面/节点完全无关),按 `dataSourceId` 维度独立一份 UndoRedo 栈。
|
|
173
|
+
* 行为同 pushCodeBlock(新增 oldSchema=null;删除 newSchema=null)。
|
|
174
|
+
*/
|
|
175
|
+
pushDataSource(dataSourceId, payload) {
|
|
176
|
+
const step = createStackStep(dataSourceId, {
|
|
177
|
+
oldValue: payload.oldSchema,
|
|
178
|
+
newValue: payload.newSchema,
|
|
179
|
+
changeRecords: payload.changeRecords,
|
|
180
|
+
historyDescription: payload.historyDescription,
|
|
181
|
+
source: payload.source
|
|
182
|
+
});
|
|
183
|
+
if (!step) return null;
|
|
184
|
+
getOrCreateStack(this.state.dataSourceState, dataSourceId).pushElement(step);
|
|
185
|
+
this.emit("data-source-history-change", dataSourceId, step);
|
|
141
186
|
return step;
|
|
142
187
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const undoRedo = this.state.
|
|
188
|
+
/** 撤销指定代码块的最近一次变更。 */
|
|
189
|
+
undoCodeBlock(codeBlockId) {
|
|
190
|
+
const undoRedo = this.state.codeBlockState[codeBlockId];
|
|
146
191
|
if (!undoRedo) return null;
|
|
147
|
-
if (undoRedo.getCursor() <= undoFloor(undoRedo)) return null;
|
|
148
192
|
const step = undoRedo.undo();
|
|
149
|
-
if (step) this.emit("change",
|
|
193
|
+
if (step) this.emit("code-block-history-change", codeBlockId, step);
|
|
150
194
|
return step;
|
|
151
195
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const undoRedo = this.state.
|
|
196
|
+
/** 重做指定代码块的下一次变更。 */
|
|
197
|
+
redoCodeBlock(codeBlockId) {
|
|
198
|
+
const undoRedo = this.state.codeBlockState[codeBlockId];
|
|
155
199
|
if (!undoRedo) return null;
|
|
156
200
|
const step = undoRedo.redo();
|
|
157
|
-
if (step) this.emit("change",
|
|
201
|
+
if (step) this.emit("code-block-history-change", codeBlockId, step);
|
|
158
202
|
return step;
|
|
159
203
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
* @param id 目标栈 id;缺省 / 无效时返回 false
|
|
164
|
-
*/
|
|
165
|
-
canUndo(stepType, id) {
|
|
166
|
-
if (!this.isValidStackId(id)) return false;
|
|
167
|
-
const undoRedo = this.state.steps[stepType]?.[id];
|
|
168
|
-
return undoRedo ? undoRedo.getCursor() > undoFloor(undoRedo) : false;
|
|
204
|
+
/** 是否可对指定代码块撤销。 */
|
|
205
|
+
canUndoCodeBlock(codeBlockId) {
|
|
206
|
+
return this.state.codeBlockState[codeBlockId]?.canUndo() ?? false;
|
|
169
207
|
}
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
* @param id 目标栈 id;缺省 / 无效时返回 false
|
|
174
|
-
*/
|
|
175
|
-
canRedo(stepType, id) {
|
|
176
|
-
if (!this.isValidStackId(id)) return false;
|
|
177
|
-
return this.state.steps[stepType]?.[id]?.canRedo() ?? false;
|
|
208
|
+
/** 是否可对指定代码块重做。 */
|
|
209
|
+
canRedoCodeBlock(codeBlockId) {
|
|
210
|
+
return this.state.codeBlockState[codeBlockId]?.canRedo() ?? false;
|
|
178
211
|
}
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
212
|
+
/** 撤销指定数据源的最近一次变更。 */
|
|
213
|
+
undoDataSource(dataSourceId) {
|
|
214
|
+
const undoRedo = this.state.dataSourceState[dataSourceId];
|
|
215
|
+
if (!undoRedo) return null;
|
|
216
|
+
const step = undoRedo.undo();
|
|
217
|
+
if (step) this.emit("data-source-history-change", dataSourceId, step);
|
|
218
|
+
return step;
|
|
183
219
|
}
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
220
|
+
/** 重做指定数据源的下一次变更。 */
|
|
221
|
+
redoDataSource(dataSourceId) {
|
|
222
|
+
const undoRedo = this.state.dataSourceState[dataSourceId];
|
|
223
|
+
if (!undoRedo) return null;
|
|
224
|
+
const step = undoRedo.redo();
|
|
225
|
+
if (step) this.emit("data-source-history-change", dataSourceId, step);
|
|
226
|
+
return step;
|
|
227
|
+
}
|
|
228
|
+
/** 是否可对指定数据源撤销。 */
|
|
229
|
+
canUndoDataSource(dataSourceId) {
|
|
230
|
+
return this.state.dataSourceState[dataSourceId]?.canUndo() ?? false;
|
|
231
|
+
}
|
|
232
|
+
/** 是否可对指定数据源重做。 */
|
|
233
|
+
canRedoDataSource(dataSourceId) {
|
|
234
|
+
return this.state.dataSourceState[dataSourceId]?.canRedo() ?? false;
|
|
235
|
+
}
|
|
236
|
+
undo() {
|
|
237
|
+
const undoRedo = this.getUndoRedo();
|
|
238
|
+
if (!undoRedo) return null;
|
|
239
|
+
if (undoRedo.getCursor() <= undoFloor(undoRedo)) return null;
|
|
240
|
+
const state = undoRedo.undo();
|
|
241
|
+
this.emit("change", state);
|
|
242
|
+
return state;
|
|
243
|
+
}
|
|
244
|
+
redo() {
|
|
245
|
+
const undoRedo = this.getUndoRedo();
|
|
246
|
+
if (!undoRedo) return null;
|
|
247
|
+
const state = undoRedo.redo();
|
|
248
|
+
this.emit("change", state);
|
|
194
249
|
return state;
|
|
195
250
|
}
|
|
196
251
|
destroy() {
|
|
@@ -199,52 +254,84 @@ var History = class extends BaseService {
|
|
|
199
254
|
this.removeAllPlugins();
|
|
200
255
|
}
|
|
201
256
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* - 缺省 `id`:清空 `stepType` 下的全部栈。
|
|
205
|
-
*
|
|
206
|
-
* 仅删除撤销/重做记录,不会改动 DSL / 代码块 / 数据源本身。清空时会**保留各栈原有的
|
|
207
|
-
* initial 基线**(文案 / 来源),无基线时清空成空栈。清空后派发 `clear`(签名 `(id, stepType)`)。
|
|
208
|
-
*
|
|
209
|
-
* @param stepType 历史类型
|
|
210
|
-
* @param id 目标栈 id;缺省表示清空该类型下全部栈
|
|
257
|
+
* 清空指定页面(缺省当前活动页)的历史记录栈。
|
|
258
|
+
* 仅删除撤销/重做记录,不会改动当前 DSL;清空后该页将无法再撤销/重做之前的操作。
|
|
211
259
|
*/
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
if (!
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
clearPage(pageId) {
|
|
261
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
262
|
+
if (!targetPageId) return;
|
|
263
|
+
const marker = this.getPageMarker(targetPageId);
|
|
264
|
+
this.state.pageSteps[targetPageId] = new UndoRedo();
|
|
265
|
+
if (marker) this.setPageMarker(targetPageId, {
|
|
266
|
+
name: marker.data?.name,
|
|
267
|
+
description: marker.historyDescription,
|
|
268
|
+
source: marker.source
|
|
221
269
|
});
|
|
270
|
+
if (`${targetPageId}` === `${this.state.pageId}`) {
|
|
271
|
+
this.setCanUndoRedo();
|
|
272
|
+
this.emit("clear-page", null);
|
|
273
|
+
}
|
|
222
274
|
}
|
|
223
275
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* - 传入 `id`:仅标记 `stepType` 下该 id 对应的栈,派发 `mark-saved` 且 `kind` 为 `stepType`。
|
|
227
|
-
*
|
|
228
|
-
* 同一栈内任意时刻最多只有一条记录为 `saved`;从 IndexedDB 恢复时游标会被定位到最近一条已保存记录之后。
|
|
229
|
-
*
|
|
230
|
-
* @param stepType 历史类型
|
|
231
|
-
* @param id 目标栈 id;缺省表示标记全部类型 / 全部栈
|
|
276
|
+
* 清空数据源历史记录栈:传入 `dataSourceId` 仅清空该数据源,缺省清空全部数据源。
|
|
277
|
+
* 仅删除撤销/重做记录,不会改动数据源本身。
|
|
232
278
|
*/
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
279
|
+
clearDataSource(dataSourceId) {
|
|
280
|
+
if (dataSourceId !== void 0) delete this.state.dataSourceState[dataSourceId];
|
|
281
|
+
else this.state.dataSourceState = {};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 清空代码块历史记录栈:传入 `codeBlockId` 仅清空该代码块,缺省清空全部代码块。
|
|
285
|
+
* 仅删除撤销/重做记录,不会改动代码块本身。
|
|
286
|
+
*/
|
|
287
|
+
clearCodeBlock(codeBlockId) {
|
|
288
|
+
if (codeBlockId !== void 0) delete this.state.codeBlockState[codeBlockId];
|
|
289
|
+
else this.state.codeBlockState = {};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 标记「整份 DSL 已保存」:把页面 / 代码块 / 数据源所有栈当前游标所在的记录都标为 `saved`。
|
|
293
|
+
* 适用于「整体落库」场景;若只保存了其中一类,请改用更细粒度的
|
|
294
|
+
* {@link markPageSaved} / {@link markCodeBlockSaved} / {@link markDataSourceSaved}。
|
|
295
|
+
*/
|
|
296
|
+
markSaved() {
|
|
297
|
+
Object.values(this.state.pageSteps).forEach(markStackSaved);
|
|
298
|
+
Object.values(this.state.codeBlockState).forEach(markStackSaved);
|
|
299
|
+
Object.values(this.state.dataSourceState).forEach(markStackSaved);
|
|
300
|
+
this.emit("mark-saved", { kind: "all" });
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* 标记指定页面(缺省为当前活动页)的历史栈当前记录为已保存。
|
|
304
|
+
* 仅影响该页面自己的栈,不波及代码块 / 数据源 / 其它页面。
|
|
305
|
+
*/
|
|
306
|
+
markPageSaved(pageId) {
|
|
307
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
308
|
+
if (!targetPageId) return;
|
|
309
|
+
markStackSaved(this.state.pageSteps[targetPageId]);
|
|
310
|
+
this.emit("mark-saved", {
|
|
311
|
+
kind: "page",
|
|
312
|
+
id: targetPageId
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** 标记指定代码块的历史栈当前记录为已保存,仅影响该代码块自己的栈。 */
|
|
316
|
+
markCodeBlockSaved(codeBlockId) {
|
|
317
|
+
if (!codeBlockId) return;
|
|
318
|
+
markStackSaved(this.state.codeBlockState[codeBlockId]);
|
|
319
|
+
this.emit("mark-saved", {
|
|
320
|
+
kind: "code-block",
|
|
321
|
+
id: codeBlockId
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/** 标记指定数据源的历史栈当前记录为已保存,仅影响该数据源自己的栈。 */
|
|
325
|
+
markDataSourceSaved(dataSourceId) {
|
|
326
|
+
if (!dataSourceId) return;
|
|
327
|
+
markStackSaved(this.state.dataSourceState[dataSourceId]);
|
|
241
328
|
this.emit("mark-saved", {
|
|
242
|
-
kind:
|
|
243
|
-
id
|
|
329
|
+
kind: "data-source",
|
|
330
|
+
id: dataSourceId
|
|
244
331
|
});
|
|
245
332
|
}
|
|
246
333
|
/**
|
|
247
|
-
* 把当前内存中的全部历史栈(页面 / 代码块 /
|
|
334
|
+
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源)序列化后写入本地 IndexedDB。
|
|
248
335
|
*
|
|
249
336
|
* - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
|
|
250
337
|
* - `key` 用于区分不同活动页 / 项目(同一 store 下可保存多份快照),缺省为 `default`;
|
|
@@ -253,17 +340,12 @@ var History = class extends BaseService {
|
|
|
253
340
|
*/
|
|
254
341
|
async saveToIndexedDB(options = {}) {
|
|
255
342
|
const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
|
|
256
|
-
const steps = {
|
|
257
|
-
page: {},
|
|
258
|
-
codeBlock: {},
|
|
259
|
-
dataSource: {}
|
|
260
|
-
};
|
|
261
|
-
Object.entries(this.state.steps).forEach(([stepType, bucket]) => {
|
|
262
|
-
steps[stepType] = serializeStacks(bucket);
|
|
263
|
-
});
|
|
264
343
|
const snapshot = {
|
|
265
344
|
version: PERSIST_VERSION,
|
|
266
|
-
|
|
345
|
+
pageId: this.state.pageId,
|
|
346
|
+
pageSteps: serializeStacks(this.state.pageSteps),
|
|
347
|
+
codeBlockState: serializeStacks(this.state.codeBlockState),
|
|
348
|
+
dataSourceState: serializeStacks(this.state.dataSourceState),
|
|
267
349
|
savedAt: Date.now()
|
|
268
350
|
};
|
|
269
351
|
await idbSet(this.resolveDbName(dbName, appId), storeName, key, snapshot);
|
|
@@ -274,7 +356,7 @@ var History = class extends BaseService {
|
|
|
274
356
|
* 从本地 IndexedDB 读取此前保存的历史快照并重建全部撤销/重做栈。
|
|
275
357
|
*
|
|
276
358
|
* - 读取到的每个栈都会经 {@link UndoRedo.fromSerialized} 还原(含游标),随后可直接 undo/redo;
|
|
277
|
-
* -
|
|
359
|
+
* - 会整体覆盖当前内存中的历史状态,并把活动页恢复为快照中的 pageId;
|
|
278
360
|
* - 找不到对应记录时返回 null,且不改动当前状态;
|
|
279
361
|
* - 不支持 IndexedDB 的环境(如 SSR)会 reject。
|
|
280
362
|
*/
|
|
@@ -283,21 +365,23 @@ var History = class extends BaseService {
|
|
|
283
365
|
const snapshot = await idbGet(this.resolveDbName(dbName, appId), storeName, key);
|
|
284
366
|
if (!snapshot) return null;
|
|
285
367
|
const parseDSL = getEditorConfig("parseDSL");
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Object.entries(snapshot.steps).forEach(([stepType, bucket]) => {
|
|
292
|
-
steps[stepType] = deserializeStacks(bucket, parseDSL);
|
|
293
|
-
});
|
|
294
|
-
this.state.steps = steps;
|
|
368
|
+
this.state.pageSteps = deserializeStacks(snapshot.pageSteps, parseDSL);
|
|
369
|
+
this.state.codeBlockState = deserializeStacks(snapshot.codeBlockState, parseDSL);
|
|
370
|
+
this.state.dataSourceState = deserializeStacks(snapshot.dataSourceState, parseDSL);
|
|
371
|
+
this.state.pageId = snapshot.pageId;
|
|
372
|
+
this.setCanUndoRedo();
|
|
295
373
|
this.emit("restore-from-indexed-db", snapshot);
|
|
296
374
|
return snapshot;
|
|
297
375
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
376
|
+
/**
|
|
377
|
+
* 取出当前活动页的历史步骤平铺列表(包含已应用 + 已撤销)。
|
|
378
|
+
* 列表按时间正序,最早一步在最前面。
|
|
379
|
+
* 通常 UI 应使用 `getPageHistoryGroups` 取已合并分组的版本;本方法仅为兼容/调试保留。
|
|
380
|
+
*/
|
|
381
|
+
getPageStepList(pageId) {
|
|
382
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
383
|
+
if (!targetPageId) return [];
|
|
384
|
+
const undoRedo = this.state.pageSteps[targetPageId];
|
|
301
385
|
if (!undoRedo) return [];
|
|
302
386
|
const list = undoRedo.getElementList();
|
|
303
387
|
const cursor = undoRedo.getCursor();
|
|
@@ -307,103 +391,147 @@ var History = class extends BaseService {
|
|
|
307
391
|
applied: index < cursor
|
|
308
392
|
}));
|
|
309
393
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
394
|
+
/**
|
|
395
|
+
* 取出当前活动页的历史栈,按"目标节点"做相邻合并:
|
|
396
|
+
* - 连续修改同一节点(单节点 update)的多步合并为一个 group,组内可展开查看每步;
|
|
397
|
+
* - add / remove / 多节点 update 始终独立成组。
|
|
398
|
+
* 用于历史面板的"页面"tab 展示。
|
|
399
|
+
*/
|
|
400
|
+
getPageHistoryGroups(pageId) {
|
|
401
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
402
|
+
if (!targetPageId) return [];
|
|
403
|
+
const undoRedo = this.state.pageSteps[targetPageId];
|
|
404
|
+
if (!undoRedo) return [];
|
|
405
|
+
const list = undoRedo.getElementList();
|
|
406
|
+
if (!list.length) return [];
|
|
407
|
+
return mergePageSteps(targetPageId, list, undoRedo.getCursor()).filter((group) => group.opType !== "initial");
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 取出全部代码块的历史栈,按 codeBlockId 分桶展示。
|
|
411
|
+
* 同一栈内每条操作记录独立成组,不做相邻 update 合并。
|
|
412
|
+
*/
|
|
413
|
+
getCodeBlockHistoryGroups() {
|
|
414
|
+
const groups = [];
|
|
415
|
+
Object.entries(this.state.codeBlockState).forEach(([id, undoRedo]) => {
|
|
416
|
+
if (!undoRedo) return;
|
|
317
417
|
const list = undoRedo.getElementList();
|
|
318
|
-
if (!list.length) return
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return
|
|
418
|
+
if (!list.length) return;
|
|
419
|
+
const cursor = undoRedo.getCursor();
|
|
420
|
+
groups.push(...mergeStackSteps("code-block", id, list, cursor));
|
|
421
|
+
});
|
|
422
|
+
return groups;
|
|
323
423
|
}
|
|
324
424
|
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
* - `id` 缺省或非法、或对应栈不存在时返回 0;
|
|
328
|
-
* - `stepType` 支持 `page` / `codeBlock` / `dataSource` / 扩展类型。
|
|
425
|
+
* 读取指定页面历史栈的当前游标(已应用步骤数量)。不传则取当前活动页。
|
|
426
|
+
* 没有对应栈时返回 0。
|
|
329
427
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
428
|
+
getPageCursor(pageId) {
|
|
429
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
430
|
+
if (!targetPageId) return 0;
|
|
431
|
+
return this.state.pageSteps[targetPageId]?.getCursor() ?? 0;
|
|
432
|
+
}
|
|
433
|
+
/** 读取指定代码块历史栈的当前游标。 */
|
|
434
|
+
getCodeBlockCursor(codeBlockId) {
|
|
435
|
+
return this.state.codeBlockState[codeBlockId]?.getCursor() ?? 0;
|
|
436
|
+
}
|
|
437
|
+
/** 读取指定数据源历史栈的当前游标。 */
|
|
438
|
+
getDataSourceCursor(dataSourceId) {
|
|
439
|
+
return this.state.dataSourceState[dataSourceId]?.getCursor() ?? 0;
|
|
333
440
|
}
|
|
334
441
|
/**
|
|
335
|
-
*
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
442
|
+
* 取出指定代码块历史栈的平铺步骤列表(含 applied 标记)。供 revert 等按 index 索引步骤使用。
|
|
443
|
+
*/
|
|
444
|
+
getCodeBlockStepList(codeBlockId) {
|
|
445
|
+
const undoRedo = this.state.codeBlockState[codeBlockId];
|
|
446
|
+
if (!undoRedo) return [];
|
|
447
|
+
const list = undoRedo.getElementList();
|
|
448
|
+
const cursor = undoRedo.getCursor();
|
|
449
|
+
return list.map((step, index) => ({
|
|
450
|
+
step,
|
|
451
|
+
index,
|
|
452
|
+
applied: index < cursor
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* 取出指定数据源历史栈的平铺步骤列表(含 applied 标记)。供 revert 等按 index 索引步骤使用。
|
|
346
457
|
*/
|
|
347
|
-
|
|
458
|
+
getDataSourceStepList(dataSourceId) {
|
|
459
|
+
const undoRedo = this.state.dataSourceState[dataSourceId];
|
|
460
|
+
if (!undoRedo) return [];
|
|
461
|
+
const list = undoRedo.getElementList();
|
|
462
|
+
const cursor = undoRedo.getCursor();
|
|
463
|
+
return list.map((step, index) => ({
|
|
464
|
+
step,
|
|
465
|
+
index,
|
|
466
|
+
applied: index < cursor
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* 按历史记录 uuid 在指定页面(默认当前活动页)的栈中查找其索引。
|
|
471
|
+
* 找不到时返回 -1。供「按 uuid 回滚」等需要把 uuid 映射回 index 的场景使用。
|
|
472
|
+
*/
|
|
473
|
+
getPageStepIndexByUuid(uuid, pageId) {
|
|
474
|
+
if (!uuid) return -1;
|
|
475
|
+
return this.getPageStepList(pageId).findIndex((entry) => entry.step.uuid === uuid);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 按历史记录 uuid 在全部代码块栈中查找其所属 codeBlockId 与索引。
|
|
479
|
+
* 找不到时返回 null。
|
|
480
|
+
*/
|
|
481
|
+
findCodeBlockStepLocationByUuid(uuid) {
|
|
348
482
|
if (!uuid) return null;
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
if (this.isValidStackId(id)) {
|
|
352
|
-
const index = this.getStepList(stepType, id).findIndex((entry) => entry.step.uuid === uuid);
|
|
353
|
-
return index >= 0 ? {
|
|
354
|
-
id,
|
|
355
|
-
index
|
|
356
|
-
} : null;
|
|
357
|
-
}
|
|
358
|
-
for (const stackId of Object.keys(bucket)) {
|
|
359
|
-
const index = this.getStepList(stepType, stackId).findIndex((entry) => entry.step.uuid === uuid);
|
|
483
|
+
for (const id of Object.keys(this.state.codeBlockState)) {
|
|
484
|
+
const index = this.getCodeBlockStepList(id).findIndex((entry) => entry.step.uuid === uuid);
|
|
360
485
|
if (index >= 0) return {
|
|
361
|
-
id
|
|
486
|
+
id,
|
|
362
487
|
index
|
|
363
488
|
};
|
|
364
489
|
}
|
|
365
490
|
return null;
|
|
366
491
|
}
|
|
367
|
-
usePlugin(options) {
|
|
368
|
-
super.usePlugin(options);
|
|
369
|
-
}
|
|
370
|
-
emit(eventName, ...args) {
|
|
371
|
-
return super.emit(eventName, ...args);
|
|
372
|
-
}
|
|
373
492
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
493
|
+
* 按历史记录 uuid 在全部数据源栈中查找其所属 dataSourceId 与索引。
|
|
494
|
+
* 找不到时返回 null。
|
|
376
495
|
*/
|
|
377
|
-
|
|
378
|
-
if (!
|
|
379
|
-
|
|
496
|
+
findDataSourceStepLocationByUuid(uuid) {
|
|
497
|
+
if (!uuid) return null;
|
|
498
|
+
for (const id of Object.keys(this.state.dataSourceState)) {
|
|
499
|
+
const index = this.getDataSourceStepList(id).findIndex((entry) => entry.step.uuid === uuid);
|
|
500
|
+
if (index >= 0) return {
|
|
501
|
+
id,
|
|
502
|
+
index
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return null;
|
|
380
506
|
}
|
|
381
507
|
/**
|
|
382
|
-
*
|
|
508
|
+
* 取出全部数据源的历史栈,按 dataSourceId 分桶展示。同上,每条操作独立成组。
|
|
383
509
|
*/
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
this.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
510
|
+
getDataSourceHistoryGroups() {
|
|
511
|
+
const groups = [];
|
|
512
|
+
Object.entries(this.state.dataSourceState).forEach(([id, undoRedo]) => {
|
|
513
|
+
if (!undoRedo) return;
|
|
514
|
+
const list = undoRedo.getElementList();
|
|
515
|
+
if (!list.length) return;
|
|
516
|
+
const cursor = undoRedo.getCursor();
|
|
517
|
+
groups.push(...mergeStackSteps("data-source", id, list, cursor));
|
|
391
518
|
});
|
|
519
|
+
return groups;
|
|
392
520
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (step.uuid === void 0) step.uuid = guid();
|
|
396
|
-
if (step.timestamp === void 0) step.timestamp = Date.now();
|
|
397
|
-
}
|
|
398
|
-
/** 校验「按 id 分栈」类型(codeBlock / dataSource / 扩展)的 id 是否有效。 */
|
|
399
|
-
isValidStackId(id) {
|
|
400
|
-
return id !== void 0 && id !== null && `${id}` !== "";
|
|
521
|
+
usePlugin(options) {
|
|
522
|
+
super.usePlugin(options);
|
|
401
523
|
}
|
|
402
|
-
/**
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
524
|
+
/**
|
|
525
|
+
* 取出指定页面的栈;不传 pageId 时按当前活动页取。
|
|
526
|
+
*
|
|
527
|
+
* 跨页 push 时如果目标页的栈尚不存在(用户从未进入过该页),会按需创建一条空栈,
|
|
528
|
+
* 这样切到目标页时 Ctrl+Z 也能撤回该步骤。
|
|
529
|
+
*/
|
|
530
|
+
getUndoRedo(pageId) {
|
|
531
|
+
const targetPageId = pageId ?? this.state.pageId;
|
|
532
|
+
if (!targetPageId) return null;
|
|
533
|
+
if (!this.state.pageSteps[targetPageId]) this.state.pageSteps[targetPageId] = new UndoRedo();
|
|
534
|
+
return this.state.pageSteps[targetPageId];
|
|
407
535
|
}
|
|
408
536
|
/**
|
|
409
537
|
* 把基础 dbName 与当前 DSL(root app)的 id 拼成最终库名,实现不同应用历史隔离。
|
|
@@ -413,6 +541,11 @@ var History = class extends BaseService {
|
|
|
413
541
|
const resolvedAppId = appId ?? editor_default.get("root")?.id;
|
|
414
542
|
return resolvedAppId ? `${dbName}-${resolvedAppId}` : dbName;
|
|
415
543
|
}
|
|
544
|
+
setCanUndoRedo() {
|
|
545
|
+
const undoRedo = this.getUndoRedo();
|
|
546
|
+
this.state.canRedo = undoRedo?.canRedo() || false;
|
|
547
|
+
this.state.canUndo = undoRedo ? undoRedo.getCursor() > undoFloor(undoRedo) : false;
|
|
548
|
+
}
|
|
416
549
|
};
|
|
417
550
|
var history_default = new History();
|
|
418
551
|
//#endregion
|