@tmagic/editor 1.8.0-beta.5 → 1.8.0-beta.7
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/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +25 -8
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +6 -4
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +25 -12
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +27 -5
- package/dist/es/index.js +2 -2
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +10 -14
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +12 -4
- package/dist/es/layouts/history-list/composables.js +28 -77
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +16 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +23 -1
- package/dist/es/services/codeBlock.js +30 -19
- package/dist/es/services/dataSource.js +29 -21
- package/dist/es/services/editor.js +52 -33
- package/dist/es/services/history.js +10 -15
- package/dist/es/style.css +4 -1
- package/dist/es/utils/data-source/index.js +2 -0
- package/dist/es/utils/editor.js +68 -48
- package/dist/es/utils/history.js +42 -33
- package/dist/style.css +4 -1
- package/dist/tmagic-editor.umd.cjs +412 -295
- package/package.json +7 -7
- package/src/fields/CodeSelectCol.vue +7 -1
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +16 -2
- package/src/fields/DataSourceFields.vue +37 -8
- package/src/fields/DataSourceMethodSelect.vue +9 -4
- package/src/fields/DataSourceMethods.vue +42 -21
- package/src/fields/StyleSetter/components/Border.vue +15 -6
- package/src/layouts/history-list/HistoryListPanel.vue +12 -25
- package/src/layouts/history-list/InitialRow.vue +3 -0
- package/src/layouts/history-list/composables.ts +34 -94
- package/src/layouts/sidebar/Sidebar.vue +2 -2
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +31 -2
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +26 -1
- package/src/services/codeBlock.ts +28 -22
- package/src/services/dataSource.ts +29 -24
- package/src/services/editor.ts +41 -37
- package/src/services/history.ts +19 -25
- package/src/theme/style-setter/border.scss +4 -1
- package/src/type.ts +38 -26
- package/src/utils/data-source/index.ts +2 -0
- package/src/utils/editor.ts +125 -59
- package/src/utils/history.ts +48 -45
- package/types/index.d.ts +109 -112
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script setup lang="ts">
|
|
25
|
-
import { inject, nextTick, Ref, ref, watch, watchEffect } from 'vue';
|
|
25
|
+
import { computed, inject, nextTick, provide, Ref, ref, watch, watchEffect } from 'vue';
|
|
26
26
|
|
|
27
27
|
import type { DataSourceSchema } from '@tmagic/core';
|
|
28
28
|
import { tMagicMessage } from '@tmagic/design';
|
|
@@ -41,6 +41,10 @@ const props = defineProps<{
|
|
|
41
41
|
title?: string;
|
|
42
42
|
values: any;
|
|
43
43
|
disabled: boolean;
|
|
44
|
+
/** 打开后需要直接定位并打开的方法名,传入时默认激活「方法定义」tab */
|
|
45
|
+
editMethodName?: string;
|
|
46
|
+
/** 打开后需要直接定位并打开的字段路径,传入时默认激活「数据定义」tab */
|
|
47
|
+
editFieldPath?: string[];
|
|
44
48
|
}>();
|
|
45
49
|
|
|
46
50
|
const boxVisible = defineModel<boolean>('visible', { default: false });
|
|
@@ -62,9 +66,34 @@ const { height: editorHeight } = useEditorContentHeight();
|
|
|
62
66
|
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
|
63
67
|
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
|
64
68
|
|
|
69
|
+
/** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
|
|
70
|
+
provide(
|
|
71
|
+
'editingDataSourceMethodName',
|
|
72
|
+
computed(() => props.editMethodName),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
/** 供「数据定义」tab 内的字段消费,用于打开数据源详情后自动打开指定字段 */
|
|
76
|
+
provide(
|
|
77
|
+
'editingDataSourceFieldPath',
|
|
78
|
+
computed(() => props.editFieldPath || []),
|
|
79
|
+
);
|
|
80
|
+
|
|
65
81
|
watchEffect(() => {
|
|
66
82
|
initValues.value = props.values;
|
|
67
|
-
|
|
83
|
+
const config = dataSourceService.getFormConfig(initValues.value.type);
|
|
84
|
+
|
|
85
|
+
// 传入方法名/字段路径时,将外层 tab 容器默认激活到对应 tab(status: methods / fields)
|
|
86
|
+
// 未传入时默认激活「数据定义」tab(fields)
|
|
87
|
+
let activeTab = 'fields';
|
|
88
|
+
if (props.editMethodName) {
|
|
89
|
+
activeTab = 'methods';
|
|
90
|
+
} else if (props.editFieldPath?.length) {
|
|
91
|
+
activeTab = 'fields';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
dataSourceConfig.value = config.map((item) =>
|
|
95
|
+
(item as { type?: string }).type === 'tab' ? { ...item, active: activeTab } : item,
|
|
96
|
+
);
|
|
68
97
|
});
|
|
69
98
|
|
|
70
99
|
const submitHandler = (values: any, data: ContainerChangeEventData) => {
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
:disabled="!editable"
|
|
30
30
|
:values="dataSourceValues"
|
|
31
31
|
:title="dialogTitle"
|
|
32
|
+
:edit-method-name="editMethodName"
|
|
33
|
+
:edit-field-path="editFieldPath"
|
|
32
34
|
@submit="submitDataSourceHandler"
|
|
33
35
|
@close="editDialogCloseHandler"
|
|
34
36
|
></DataSourceConfigPanel>
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
</template>
|
|
46
48
|
|
|
47
49
|
<script setup lang="ts">
|
|
48
|
-
import { computed, inject, useTemplateRef, watch } from 'vue';
|
|
50
|
+
import { computed, inject, ref, useTemplateRef, watch } from 'vue';
|
|
49
51
|
import { mergeWith } from 'lodash-es';
|
|
50
52
|
|
|
51
53
|
import { tMagicMessageBox, TMagicScrollbar } from '@tmagic/design';
|
|
@@ -79,7 +81,16 @@ const { dataSourceService } = useServices();
|
|
|
79
81
|
const { editDialog, dataSourceValues, dialogTitle, editable, editHandler, submitDataSourceHandler } =
|
|
80
82
|
useDataSourceEdit(dataSourceService);
|
|
81
83
|
|
|
84
|
+
/** 打开数据源详情时需要直接定位并打开的方法名,为空则正常展示「数据定义」tab */
|
|
85
|
+
const editMethodName = ref('');
|
|
86
|
+
|
|
87
|
+
/** 打开数据源详情时需要直接定位并打开的字段路径,为空则不自动打开字段配置 */
|
|
88
|
+
const editFieldPath = ref<string[]>([]);
|
|
89
|
+
|
|
82
90
|
const editDialogCloseHandler = () => {
|
|
91
|
+
editMethodName.value = '';
|
|
92
|
+
editFieldPath.value = [];
|
|
93
|
+
|
|
83
94
|
if (dataSourceListRef.value) {
|
|
84
95
|
for (const [, status] of dataSourceListRef.value.nodeStatusMap.entries()) {
|
|
85
96
|
status.selected = false;
|
|
@@ -139,6 +150,20 @@ const filterTextChangeHandler = (val: string) => {
|
|
|
139
150
|
};
|
|
140
151
|
|
|
141
152
|
eventBus?.on('edit-data-source', (id: string) => {
|
|
153
|
+
editMethodName.value = '';
|
|
154
|
+
editFieldPath.value = [];
|
|
155
|
+
editHandler(id);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
eventBus?.on('edit-data-source-method', (id: string, methodName: string) => {
|
|
159
|
+
editMethodName.value = methodName;
|
|
160
|
+
editFieldPath.value = [];
|
|
161
|
+
editHandler(id);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
eventBus?.on('edit-data-source-field', (id: string, fieldPath: string[]) => {
|
|
165
|
+
editMethodName.value = '';
|
|
166
|
+
editFieldPath.value = fieldPath;
|
|
142
167
|
editHandler(id);
|
|
143
168
|
});
|
|
144
169
|
|
|
@@ -32,13 +32,14 @@ import type {
|
|
|
32
32
|
AsyncHookPlugin,
|
|
33
33
|
CodeBlockStepValue,
|
|
34
34
|
CodeState,
|
|
35
|
+
DslOpWithHistoryIdsResult,
|
|
35
36
|
HistoryOpOptions,
|
|
36
37
|
HistoryOpOptionsWithChangeRecords,
|
|
37
38
|
} from '@editor/type';
|
|
38
39
|
import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
|
|
39
40
|
import { getEditorConfig } from '@editor/utils/config';
|
|
40
41
|
import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
|
|
41
|
-
import { describeRevertStep } from '@editor/utils/history';
|
|
42
|
+
import { describeRevertStep, getLastPushedHistoryIds } from '@editor/utils/history';
|
|
42
43
|
|
|
43
44
|
import BaseService from './BaseService';
|
|
44
45
|
|
|
@@ -321,43 +322,46 @@ class CodeBlock extends BaseService {
|
|
|
321
322
|
// #region AndGetHistoryId
|
|
322
323
|
/**
|
|
323
324
|
* 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
|
|
324
|
-
*
|
|
325
|
+
* 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入的历史 uuid 列表,
|
|
325
326
|
* 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
326
327
|
*
|
|
327
|
-
* 当本次操作未写入历史(doNotPushHistory 为 true
|
|
328
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
|
|
328
329
|
*/
|
|
329
330
|
|
|
330
|
-
/** 等价于 {@link setCodeDslById}
|
|
331
|
+
/** 等价于 {@link setCodeDslById},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
331
332
|
public async setCodeDslByIdAndGetHistoryId(
|
|
332
333
|
id: Id,
|
|
333
334
|
codeConfig: Partial<CodeBlockContent>,
|
|
334
335
|
options: HistoryOpOptionsWithChangeRecords = {},
|
|
335
|
-
): Promise<
|
|
336
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
336
337
|
this.lastPushedHistoryId = null;
|
|
337
338
|
await this.setCodeDslById(id, codeConfig, options);
|
|
338
|
-
return this.lastPushedHistoryId;
|
|
339
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
/** 等价于 {@link setCodeDslByIdSync}
|
|
342
|
+
/** 等价于 {@link setCodeDslByIdSync},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
342
343
|
public setCodeDslByIdSyncAndGetHistoryId(
|
|
343
344
|
id: Id,
|
|
344
345
|
codeConfig: Partial<CodeBlockContent>,
|
|
345
346
|
force = true,
|
|
346
347
|
options: HistoryOpOptionsWithChangeRecords = {},
|
|
347
|
-
):
|
|
348
|
+
): DslOpWithHistoryIdsResult<void> {
|
|
348
349
|
this.lastPushedHistoryId = null;
|
|
349
350
|
this.setCodeDslByIdSync(id, codeConfig, force, options);
|
|
350
|
-
return this.lastPushedHistoryId;
|
|
351
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
/**
|
|
354
|
-
* 等价于 {@link deleteCodeDslByIds}
|
|
355
|
-
*
|
|
355
|
+
* 等价于 {@link deleteCodeDslByIds},并额外返回本次写入的全部历史记录 uuid(按删除顺序)。
|
|
356
|
+
* 一次删除多个代码块会产生多条历史记录;未写入任何历史时 historyIds 为 `[]`。
|
|
356
357
|
*/
|
|
357
|
-
public async deleteCodeDslByIdsAndGetHistoryId(
|
|
358
|
+
public async deleteCodeDslByIdsAndGetHistoryId(
|
|
359
|
+
codeIds: Id[],
|
|
360
|
+
options: HistoryOpOptions = {},
|
|
361
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
358
362
|
this.lastDeletedHistoryIds = [];
|
|
359
363
|
await this.deleteCodeDslByIds(codeIds, options);
|
|
360
|
-
return [...this.lastDeletedHistoryIds];
|
|
364
|
+
return { result: undefined, historyIds: [...this.lastDeletedHistoryIds] };
|
|
361
365
|
}
|
|
362
366
|
// #endregion AndGetHistoryId
|
|
363
367
|
|
|
@@ -455,17 +459,19 @@ class CodeBlock extends BaseService {
|
|
|
455
459
|
}
|
|
456
460
|
|
|
457
461
|
/**
|
|
458
|
-
* 通过历史记录 uuid
|
|
459
|
-
* 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid
|
|
460
|
-
*
|
|
462
|
+
* 通过历史记录 uuid 回滚代码块历史步骤,语义同 {@link revert},
|
|
463
|
+
* 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚。
|
|
464
|
+
* 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
|
|
461
465
|
*
|
|
462
|
-
* @param
|
|
463
|
-
* @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
|
|
466
|
+
* @param uuids 目标历史记录的 uuid 列表,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回的 `historyIds`
|
|
464
467
|
*/
|
|
465
|
-
public async revertById(
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
468
|
+
public async revertById(uuids: string[]): Promise<(CodeBlockStepValue | null)[]> {
|
|
469
|
+
const results: (CodeBlockStepValue | null)[] = [];
|
|
470
|
+
for (const uuid of uuids) {
|
|
471
|
+
const location = historyService.findCodeBlockStepLocationByUuid(uuid);
|
|
472
|
+
results.push(location ? await this.revert(location.id, location.index) : null);
|
|
473
|
+
}
|
|
474
|
+
return results;
|
|
469
475
|
}
|
|
470
476
|
|
|
471
477
|
/**
|
|
@@ -13,13 +13,14 @@ import storageService, { Protocol } from '@editor/services/storage';
|
|
|
13
13
|
import type {
|
|
14
14
|
DataSourceStepValue,
|
|
15
15
|
DatasourceTypeOption,
|
|
16
|
+
DslOpWithHistoryIdsResult,
|
|
16
17
|
HistoryOpOptions,
|
|
17
18
|
HistoryOpOptionsWithChangeRecords,
|
|
18
19
|
SyncHookPlugin,
|
|
19
20
|
} from '@editor/type';
|
|
20
21
|
import { getFormConfig, getFormValue } from '@editor/utils/data-source';
|
|
21
22
|
import { COPY_DS_STORAGE_KEY } from '@editor/utils/editor';
|
|
22
|
-
import { describeRevertStep } from '@editor/utils/history';
|
|
23
|
+
import { describeRevertStep, getLastPushedHistoryIds } from '@editor/utils/history';
|
|
23
24
|
|
|
24
25
|
import BaseService from './BaseService';
|
|
25
26
|
|
|
@@ -224,34 +225,37 @@ class DataSource extends BaseService {
|
|
|
224
225
|
// #region AndGetHistoryId
|
|
225
226
|
/**
|
|
226
227
|
* 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
|
|
227
|
-
*
|
|
228
|
-
*
|
|
228
|
+
* 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link DataSourceStepValue.uuid}),
|
|
229
|
+
* 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
229
230
|
*
|
|
230
|
-
* 当本次操作未写入历史(doNotPushHistory 为 true
|
|
231
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
|
|
231
232
|
*/
|
|
232
233
|
|
|
233
|
-
/** 等价于 {@link add}
|
|
234
|
-
public addAndGetHistoryId(
|
|
234
|
+
/** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
235
|
+
public addAndGetHistoryId(
|
|
236
|
+
config: DataSourceSchema,
|
|
237
|
+
options: HistoryOpOptions = {},
|
|
238
|
+
): DslOpWithHistoryIdsResult<DataSourceSchema> {
|
|
235
239
|
this.lastPushedHistoryId = null;
|
|
236
|
-
this.add(config, options);
|
|
237
|
-
return this.lastPushedHistoryId;
|
|
240
|
+
const result = this.add(config, options);
|
|
241
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
238
242
|
}
|
|
239
243
|
|
|
240
|
-
/** 等价于 {@link update}
|
|
244
|
+
/** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
241
245
|
public updateAndGetHistoryId(
|
|
242
246
|
config: DataSourceSchema,
|
|
243
247
|
options: HistoryOpOptionsWithChangeRecords = {},
|
|
244
|
-
):
|
|
248
|
+
): DslOpWithHistoryIdsResult<DataSourceSchema> {
|
|
245
249
|
this.lastPushedHistoryId = null;
|
|
246
|
-
this.update(config, options);
|
|
247
|
-
return this.lastPushedHistoryId;
|
|
250
|
+
const result = this.update(config, options);
|
|
251
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
248
252
|
}
|
|
249
253
|
|
|
250
|
-
/** 等价于 {@link remove}
|
|
251
|
-
public removeAndGetHistoryId(id: string, options: HistoryOpOptions = {}):
|
|
254
|
+
/** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
255
|
+
public removeAndGetHistoryId(id: string, options: HistoryOpOptions = {}): DslOpWithHistoryIdsResult<void> {
|
|
252
256
|
this.lastPushedHistoryId = null;
|
|
253
257
|
this.remove(id, options);
|
|
254
|
-
return this.lastPushedHistoryId;
|
|
258
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
255
259
|
}
|
|
256
260
|
// #endregion AndGetHistoryId
|
|
257
261
|
|
|
@@ -337,17 +341,18 @@ class DataSource extends BaseService {
|
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
/**
|
|
340
|
-
* 通过历史记录 uuid
|
|
341
|
-
* 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid
|
|
342
|
-
*
|
|
344
|
+
* 通过历史记录 uuid 回滚数据源历史步骤,语义同 {@link revert},
|
|
345
|
+
* 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚。
|
|
346
|
+
* 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
|
|
343
347
|
*
|
|
344
|
-
* @param
|
|
345
|
-
* @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
|
|
348
|
+
* @param uuids 目标历史记录的 uuid 列表,通常由 {@link addAndGetHistoryId} 等方法返回的 `historyIds`
|
|
346
349
|
*/
|
|
347
|
-
public revertById(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
350
|
+
public revertById(uuids: string[]): (DataSourceStepValue | null)[] {
|
|
351
|
+
return uuids.map((uuid) => {
|
|
352
|
+
const location = historyService.findDataSourceStepLocationByUuid(uuid);
|
|
353
|
+
if (!location) return null;
|
|
354
|
+
return this.revert(location.id, location.index);
|
|
355
|
+
});
|
|
351
356
|
}
|
|
352
357
|
|
|
353
358
|
public createId(): string {
|
package/src/services/editor.ts
CHANGED
|
@@ -42,6 +42,7 @@ import type {
|
|
|
42
42
|
AsyncHookPlugin,
|
|
43
43
|
AsyncMethodName,
|
|
44
44
|
DslOpOptions,
|
|
45
|
+
DslOpWithHistoryIdsResult,
|
|
45
46
|
EditorEvents,
|
|
46
47
|
EditorNodeInfo,
|
|
47
48
|
HistoryOpSource,
|
|
@@ -73,6 +74,7 @@ import {
|
|
|
73
74
|
setLayout,
|
|
74
75
|
toggleFixedPosition,
|
|
75
76
|
} from '@editor/utils/editor';
|
|
77
|
+
import { getLastPushedHistoryIds } from '@editor/utils/history';
|
|
76
78
|
import { beforePaste, getAddParent } from '@editor/utils/operator';
|
|
77
79
|
|
|
78
80
|
type MoveItem = { node: MNode; parent: MContainer; pageForOp: { name: string; id: Id } | null };
|
|
@@ -177,7 +179,7 @@ class Editor extends BaseService {
|
|
|
177
179
|
this.state.stageLoading = false;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
this.emit('root-change', value as StoreState['root'], preValue as StoreState['root']);
|
|
182
|
+
this.emit('root-change', value as StoreState['root'], preValue as StoreState['root'], options);
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
|
|
@@ -203,11 +205,11 @@ class Editor extends BaseService {
|
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
if (!root) {
|
|
206
|
-
return { node: null, parent: null, page: null };
|
|
208
|
+
return { node: null, parent: null, page: null, path: [] };
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
if (id === root.id) {
|
|
210
|
-
return { node: root, parent: null, page: null };
|
|
212
|
+
return { node: root, parent: null, page: null, path: [] };
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
// 大多数查找的目标都在当前页面内,优先在当前页面子树中查找以避免对整棵树做全量遍历。
|
|
@@ -693,7 +695,7 @@ class Editor extends BaseService {
|
|
|
693
695
|
|
|
694
696
|
const node = toRaw(info.node);
|
|
695
697
|
|
|
696
|
-
let newConfig = await toggleFixedPosition(toRaw(config), node,
|
|
698
|
+
let newConfig = await toggleFixedPosition(toRaw(config), node, info.path, this.getLayout);
|
|
697
699
|
|
|
698
700
|
newConfig = mergeWith(cloneDeep(node), newConfig, editorNodeMergeCustomizer);
|
|
699
701
|
|
|
@@ -1236,34 +1238,34 @@ class Editor extends BaseService {
|
|
|
1236
1238
|
// #region AndGetHistoryId
|
|
1237
1239
|
/**
|
|
1238
1240
|
* 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1241
|
+
* 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link StepValue.uuid}),
|
|
1242
|
+
* 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
1241
1243
|
*
|
|
1242
|
-
* 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 /
|
|
1244
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时 historyIds 为 `[]`。
|
|
1243
1245
|
*/
|
|
1244
1246
|
|
|
1245
|
-
/** 等价于 {@link add}
|
|
1247
|
+
/** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1246
1248
|
public async addAndGetHistoryId(
|
|
1247
1249
|
addNode: AddMNode | MNode[],
|
|
1248
1250
|
parent?: MContainer | null,
|
|
1249
1251
|
options: DslOpOptions = {},
|
|
1250
|
-
): Promise<
|
|
1252
|
+
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
|
|
1251
1253
|
this.lastPushedHistoryId = null;
|
|
1252
|
-
await this.add(addNode, parent, options);
|
|
1253
|
-
return this.lastPushedHistoryId;
|
|
1254
|
+
const result = await this.add(addNode, parent, options);
|
|
1255
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
1254
1256
|
}
|
|
1255
1257
|
|
|
1256
|
-
/** 等价于 {@link remove}
|
|
1258
|
+
/** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1257
1259
|
public async removeAndGetHistoryId(
|
|
1258
1260
|
nodeOrNodeList: MNode | MNode[],
|
|
1259
1261
|
options: DslOpOptions = {},
|
|
1260
|
-
): Promise<
|
|
1262
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
1261
1263
|
this.lastPushedHistoryId = null;
|
|
1262
1264
|
await this.remove(nodeOrNodeList, options);
|
|
1263
|
-
return this.lastPushedHistoryId;
|
|
1265
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
1264
1266
|
}
|
|
1265
1267
|
|
|
1266
|
-
/** 等价于 {@link update}
|
|
1268
|
+
/** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1267
1269
|
public async updateAndGetHistoryId(
|
|
1268
1270
|
config: MNode | MNode[],
|
|
1269
1271
|
data: {
|
|
@@ -1273,43 +1275,43 @@ class Editor extends BaseService {
|
|
|
1273
1275
|
historyDescription?: string;
|
|
1274
1276
|
historySource?: HistoryOpSource;
|
|
1275
1277
|
} = {},
|
|
1276
|
-
): Promise<
|
|
1278
|
+
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
|
|
1277
1279
|
this.lastPushedHistoryId = null;
|
|
1278
|
-
await this.update(config, data);
|
|
1279
|
-
return this.lastPushedHistoryId;
|
|
1280
|
+
const result = await this.update(config, data);
|
|
1281
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
1280
1282
|
}
|
|
1281
1283
|
|
|
1282
|
-
/** 等价于 {@link moveLayer}
|
|
1284
|
+
/** 等价于 {@link moveLayer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1283
1285
|
public async moveLayerAndGetHistoryId(
|
|
1284
1286
|
offset: number | LayerOffset,
|
|
1285
1287
|
options: DslOpOptions = {},
|
|
1286
|
-
): Promise<
|
|
1288
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
1287
1289
|
this.lastPushedHistoryId = null;
|
|
1288
1290
|
await this.moveLayer(offset, options);
|
|
1289
|
-
return this.lastPushedHistoryId;
|
|
1291
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
1290
1292
|
}
|
|
1291
1293
|
|
|
1292
|
-
/** 等价于 {@link moveToContainer}
|
|
1294
|
+
/** 等价于 {@link moveToContainer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1293
1295
|
public async moveToContainerAndGetHistoryId(
|
|
1294
1296
|
config: MNode | MNode[],
|
|
1295
1297
|
targetId: Id,
|
|
1296
1298
|
options: DslOpOptions = {},
|
|
1297
|
-
): Promise<
|
|
1299
|
+
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
|
|
1298
1300
|
this.lastPushedHistoryId = null;
|
|
1299
|
-
await this.moveToContainer(config, targetId, options);
|
|
1300
|
-
return this.lastPushedHistoryId;
|
|
1301
|
+
const result = await this.moveToContainer(config, targetId, options);
|
|
1302
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
1301
1303
|
}
|
|
1302
1304
|
|
|
1303
|
-
/** 等价于 {@link dragTo}
|
|
1305
|
+
/** 等价于 {@link dragTo},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1304
1306
|
public async dragToAndGetHistoryId(
|
|
1305
1307
|
config: MNode | MNode[],
|
|
1306
1308
|
targetParent: MContainer,
|
|
1307
1309
|
targetIndex: number,
|
|
1308
1310
|
options: DslOpOptions = {},
|
|
1309
|
-
): Promise<
|
|
1311
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
1310
1312
|
this.lastPushedHistoryId = null;
|
|
1311
1313
|
await this.dragTo(config, targetParent, targetIndex, options);
|
|
1312
|
-
return this.lastPushedHistoryId;
|
|
1314
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
1313
1315
|
}
|
|
1314
1316
|
// #endregion AndGetHistoryId
|
|
1315
1317
|
|
|
@@ -1451,17 +1453,19 @@ class Editor extends BaseService {
|
|
|
1451
1453
|
}
|
|
1452
1454
|
|
|
1453
1455
|
/**
|
|
1454
|
-
* 通过历史记录 uuid
|
|
1455
|
-
* 仅入参从 index 改为 uuid
|
|
1456
|
-
*
|
|
1456
|
+
* 通过历史记录 uuid 回滚当前页面的历史步骤,语义与 {@link revertPageStep} 完全一致,
|
|
1457
|
+
* 仅入参从 index 改为 uuid 列表({@link StepValue.uuid})。按数组顺序依次回滚,
|
|
1458
|
+
* 返回与入参同序的结果列表(某项失败时为 `null`)。
|
|
1457
1459
|
*
|
|
1458
|
-
* @param
|
|
1459
|
-
* @returns 反向后产生的新 step;找不到对应 uuid / 未应用 / 反向失败时返回 null
|
|
1460
|
+
* @param uuids 目标历史记录的 uuid 列表,通常由 *AndGetHistoryId 方法返回的 `historyIds`
|
|
1460
1461
|
*/
|
|
1461
|
-
public async revertPageStepById(
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1462
|
+
public async revertPageStepById(uuids: string[]): Promise<(StepValue | null)[]> {
|
|
1463
|
+
const results: (StepValue | null)[] = [];
|
|
1464
|
+
for (const uuid of uuids) {
|
|
1465
|
+
const index = historyService.getPageStepIndexByUuid(uuid);
|
|
1466
|
+
results.push(index < 0 ? null : await this.revertPageStep(index));
|
|
1467
|
+
}
|
|
1468
|
+
return results;
|
|
1465
1469
|
}
|
|
1466
1470
|
|
|
1467
1471
|
/**
|
package/src/services/history.ts
CHANGED
|
@@ -17,16 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { reactive } from 'vue';
|
|
20
|
-
import serialize from 'serialize-javascript';
|
|
21
20
|
|
|
22
21
|
import type { CodeBlockContent, DataSourceSchema, Id, MPage, MPageFragment } from '@tmagic/core';
|
|
23
22
|
import type { ChangeRecord } from '@tmagic/form';
|
|
24
23
|
import { guid } from '@tmagic/utils';
|
|
25
24
|
|
|
26
25
|
import type {
|
|
27
|
-
CodeBlockHistoryGroup,
|
|
28
26
|
CodeBlockStepValue,
|
|
29
|
-
DataSourceHistoryGroup,
|
|
30
27
|
DataSourceStepValue,
|
|
31
28
|
HistoryOpSource,
|
|
32
29
|
HistoryPersistOptions,
|
|
@@ -34,6 +31,7 @@ import type {
|
|
|
34
31
|
PageHistoryGroup,
|
|
35
32
|
PageHistoryStepEntry,
|
|
36
33
|
PersistedHistoryState,
|
|
34
|
+
StackHistoryGroup,
|
|
37
35
|
StepValue,
|
|
38
36
|
} from '@editor/type';
|
|
39
37
|
import { getEditorConfig } from '@editor/utils/config';
|
|
@@ -57,7 +55,8 @@ import editorService from './editor';
|
|
|
57
55
|
const DEFAULT_DB_NAME = 'tmagic-editor';
|
|
58
56
|
const DEFAULT_STORE_NAME = 'history';
|
|
59
57
|
const DEFAULT_KEY: IDBValidKey = 'default';
|
|
60
|
-
|
|
58
|
+
// v2:仅把每条 step 的 diff 序列化成字符串,其余字段交给 IndexedDB 结构化克隆原生存储(见 saveToIndexedDB)。
|
|
59
|
+
const PERSIST_VERSION = 2;
|
|
61
60
|
|
|
62
61
|
class History extends BaseService {
|
|
63
62
|
public state = reactive<HistoryState>({
|
|
@@ -442,6 +441,8 @@ class History extends BaseService {
|
|
|
442
441
|
public async saveToIndexedDB(options: HistoryPersistOptions = {}): Promise<PersistedHistoryState> {
|
|
443
442
|
const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
|
|
444
443
|
|
|
444
|
+
// serializeStacks 会在序列化各栈时只把每条 step 的 diff(可能含函数)序列化成字符串,其余字段原样保留,
|
|
445
|
+
// 因此整份快照可直接按对象写入 IndexedDB(结构化克隆),避免序列化整份快照的开销;读取时再用 parseDSL 还原 diff。
|
|
445
446
|
const snapshot: PersistedHistoryState = {
|
|
446
447
|
version: PERSIST_VERSION,
|
|
447
448
|
pageId: this.state.pageId,
|
|
@@ -451,9 +452,7 @@ class History extends BaseService {
|
|
|
451
452
|
savedAt: Date.now(),
|
|
452
453
|
};
|
|
453
454
|
|
|
454
|
-
|
|
455
|
-
// 因此用 serialize-javascript 序列化成字符串后再写入(支持函数 / Map 等),读取时用 parseDSL 还原。
|
|
456
|
-
await idbSet(this.resolveDbName(dbName, appId), storeName, key, serialize(snapshot));
|
|
455
|
+
await idbSet(this.resolveDbName(dbName, appId), storeName, key, snapshot);
|
|
457
456
|
this.emit('save-to-indexed-db', snapshot);
|
|
458
457
|
return snapshot;
|
|
459
458
|
}
|
|
@@ -469,16 +468,14 @@ class History extends BaseService {
|
|
|
469
468
|
public async restoreFromIndexedDB(options: HistoryPersistOptions = {}): Promise<PersistedHistoryState | null> {
|
|
470
469
|
const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
|
|
471
470
|
|
|
472
|
-
const
|
|
473
|
-
if (!raw) return null;
|
|
474
|
-
|
|
475
|
-
// 新版以序列化字符串存储(含函数),用 parseDSL 还原;兼容历史上以对象形式存入的旧数据。
|
|
476
|
-
const snapshot = (typeof raw === 'string' ? getEditorConfig('parseDSL')(`(${raw})`) : raw) as PersistedHistoryState;
|
|
471
|
+
const snapshot = await idbGet<PersistedHistoryState>(this.resolveDbName(dbName, appId), storeName, key);
|
|
477
472
|
if (!snapshot) return null;
|
|
478
473
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
this.state.
|
|
474
|
+
// 各 step 的 diff 以序列化字符串存储(含函数),由 deserializeStacks 逐条用 parseDSL 还原。
|
|
475
|
+
const parseDSL = getEditorConfig('parseDSL');
|
|
476
|
+
this.state.pageSteps = deserializeStacks(snapshot.pageSteps, parseDSL);
|
|
477
|
+
this.state.codeBlockState = deserializeStacks(snapshot.codeBlockState, parseDSL);
|
|
478
|
+
this.state.dataSourceState = deserializeStacks(snapshot.dataSourceState, parseDSL);
|
|
482
479
|
// initial 基线作为页面栈 index 0 的 step 随 pageSteps 一并还原,无需单独恢复。
|
|
483
480
|
this.state.pageId = snapshot.pageId;
|
|
484
481
|
|
|
@@ -526,14 +523,11 @@ class History extends BaseService {
|
|
|
526
523
|
}
|
|
527
524
|
|
|
528
525
|
/**
|
|
529
|
-
* 取出全部代码块的历史栈,按 codeBlockId
|
|
530
|
-
*
|
|
531
|
-
* - 这正是"代码块/数据源各自按 id 分栈"的天然表现,再叠加"连续修改同目标的相邻步骤合并展示"。
|
|
532
|
-
* - 合并后 group 暴露子步骤数组,UI 可展开查看每一步的 changeRecords。
|
|
533
|
-
* - applied 字段:组内最后一步是否处于已应用段。
|
|
526
|
+
* 取出全部代码块的历史栈,按 codeBlockId 分桶展示。
|
|
527
|
+
* 同一栈内每条操作记录独立成组,不做相邻 update 合并。
|
|
534
528
|
*/
|
|
535
|
-
public getCodeBlockHistoryGroups():
|
|
536
|
-
const groups:
|
|
529
|
+
public getCodeBlockHistoryGroups(): StackHistoryGroup<CodeBlockStepValue, 'code-block'>[] {
|
|
530
|
+
const groups: StackHistoryGroup<CodeBlockStepValue, 'code-block'>[] = [];
|
|
537
531
|
Object.entries(this.state.codeBlockState).forEach(([id, undoRedo]) => {
|
|
538
532
|
if (!undoRedo) return;
|
|
539
533
|
const list = undoRedo.getElementList();
|
|
@@ -622,10 +616,10 @@ class History extends BaseService {
|
|
|
622
616
|
}
|
|
623
617
|
|
|
624
618
|
/**
|
|
625
|
-
* 取出全部数据源的历史栈,按 dataSourceId
|
|
619
|
+
* 取出全部数据源的历史栈,按 dataSourceId 分桶展示。同上,每条操作独立成组。
|
|
626
620
|
*/
|
|
627
|
-
public getDataSourceHistoryGroups():
|
|
628
|
-
const groups:
|
|
621
|
+
public getDataSourceHistoryGroups(): StackHistoryGroup<DataSourceStepValue, 'data-source'>[] {
|
|
622
|
+
const groups: StackHistoryGroup<DataSourceStepValue, 'data-source'>[] = [];
|
|
629
623
|
Object.entries(this.state.dataSourceState).forEach(([id, undoRedo]) => {
|
|
630
624
|
if (!undoRedo) return;
|
|
631
625
|
const list = undoRedo.getElementList();
|
|
@@ -24,9 +24,12 @@
|
|
|
24
24
|
& + .border-icon {
|
|
25
25
|
margin-left: 8px;
|
|
26
26
|
}
|
|
27
|
+
&.configured {
|
|
28
|
+
border-color: var(--el-color-success, var(--td-success-color, #2ba471));
|
|
29
|
+
}
|
|
27
30
|
&.active {
|
|
28
31
|
border-width: 1px;
|
|
29
|
-
border-color: var(--el-color-primary);
|
|
32
|
+
border-color: var(--el-color-primary, var(--td-brand-color, #0052d9));
|
|
30
33
|
}
|
|
31
34
|
&.border-icon-top {
|
|
32
35
|
border-top-width: 2px;
|