@tmagic/editor 1.8.0-beta.1 → 1.8.0-beta.11
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 +31 -5
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +21 -113
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +7 -1
- package/dist/es/components/CompareForm.js +5 -0
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +196 -0
- package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/components/Icon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/Resizer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ScrollBar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ScrollViewer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/SearchInput.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/SplitView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +7 -7
- package/dist/es/components/Tree.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/TreeNode.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/editorProps.js +2 -0
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +20 -4
- package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +3 -6
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +19 -3
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +42 -8
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +41 -16
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +9 -5
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +40 -19
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +17 -9
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +59 -12
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +12 -7
- package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +28 -6
- package/dist/es/fields/StyleSetter/components/Box.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +1 -1
- package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/Block.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/Flex.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/Inline.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/InlineBlock.js +1 -1
- package/dist/es/fields/StyleSetter/icons/display/None.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +1 -1
- package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +1 -1
- package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +1 -1
- package/dist/es/fields/StyleSetter/icons/text-align/Center.js +1 -1
- package/dist/es/fields/StyleSetter/icons/text-align/Left.js +1 -1
- package/dist/es/fields/StyleSetter/icons/text-align/Right.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/es/hooks/use-code-block-edit.js +7 -4
- package/dist/es/hooks/use-data-source-edit.js +5 -2
- package/dist/es/hooks/use-stage.js +22 -10
- package/dist/es/icons/CenterIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/icons/CodeIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/icons/FolderMinusIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/index.js +14 -3
- package/dist/es/initService.js +7 -7
- package/dist/es/layouts/AddPageBox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +7 -6
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +14 -9
- package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +12 -4
- package/dist/es/layouts/NavMenuColumn.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/Bucket.js +5 -0
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +71 -0
- package/dist/es/layouts/history-list/BucketTab.js +5 -0
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +62 -0
- package/dist/es/layouts/history-list/GroupRow.js +5 -0
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +269 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +294 -0
- package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +327 -0
- package/dist/es/layouts/history-list/InitialRow.js +5 -0
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +83 -0
- package/dist/es/layouts/history-list/PageTab.js +5 -0
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +95 -0
- package/dist/es/layouts/history-list/composables.js +184 -0
- package/dist/es/layouts/history-list/useHistoryList.js +56 -0
- package/dist/es/layouts/history-list/useHistoryRevert.js +313 -0
- package/dist/es/layouts/page-bar/AddButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +5 -3
- package/dist/es/layouts/page-bar/PageBarScrollContainer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/PageList.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/page-bar/Search.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/es/layouts/sidebar/ComponentListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +8 -4
- package/dist/es/layouts/sidebar/code-block/CodeBlockList.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceAddButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +19 -6
- package/dist/es/layouts/sidebar/data-source/DataSourceList.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +25 -3
- package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
- package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/es/layouts/sidebar/layer/LayerNodeTool.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/layer/use-node-status.js +1 -1
- package/dist/es/layouts/workspace/Breadcrumb.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/Workspace.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/es/services/BaseService.js +31 -68
- package/dist/es/services/codeBlock.js +286 -8
- package/dist/es/services/componentList.js +2 -2
- package/dist/es/services/dataSource.js +287 -8
- package/dist/es/services/dep.js +21 -9
- package/dist/es/services/editor.js +655 -194
- package/dist/es/services/events.js +2 -2
- package/dist/es/services/history.js +390 -47
- package/dist/es/services/keybinding.js +5 -5
- package/dist/es/services/props.js +8 -8
- package/dist/es/services/stageOverlay.js +2 -2
- package/dist/es/services/storage.js +2 -2
- package/dist/es/services/ui.js +5 -2
- package/dist/es/style.css +456 -8
- package/dist/es/utils/code-block.js +122 -0
- package/dist/es/utils/content-menu.js +19 -12
- package/dist/es/utils/data-source/index.js +2 -0
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/editor.js +105 -51
- package/dist/es/utils/history.js +220 -0
- package/dist/es/utils/indexed-db.js +86 -0
- package/dist/es/utils/undo-redo.js +80 -1
- package/dist/style.css +456 -8
- package/dist/tmagic-editor.umd.cjs +11149 -6646
- package/package.json +8 -8
- package/src/Editor.vue +25 -1
- package/src/components/CodeBlockEditor.vue +20 -109
- package/src/components/CodeParams.vue +6 -0
- package/src/components/CompareForm.vue +277 -0
- package/src/components/ToolButton.vue +2 -2
- package/src/editorProps.ts +9 -0
- package/src/fields/Code.vue +19 -3
- package/src/fields/CodeLink.vue +2 -5
- package/src/fields/CodeSelect.vue +17 -1
- package/src/fields/CodeSelectCol.vue +37 -3
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +23 -3
- package/src/fields/DataSourceFieldSelect/Index.vue +2 -2
- package/src/fields/DataSourceFields.vue +48 -10
- package/src/fields/DataSourceInput.vue +3 -3
- package/src/fields/DataSourceMethodSelect.vue +12 -4
- package/src/fields/DataSourceMethods.vue +55 -24
- package/src/fields/DataSourceMocks.vue +13 -3
- package/src/fields/DataSourceSelect.vue +4 -1
- package/src/fields/EventSelect.vue +57 -8
- package/src/fields/KeyValue.vue +17 -4
- package/src/fields/StyleSetter/components/Border.vue +15 -6
- package/src/fields/UISelect.vue +5 -2
- package/src/hooks/use-code-block-edit.ts +9 -5
- package/src/hooks/use-data-source-edit.ts +2 -2
- package/src/hooks/use-stage.ts +26 -9
- package/src/index.ts +8 -0
- package/src/initService.ts +1 -1
- package/src/layouts/CodeEditor.vue +17 -5
- package/src/layouts/Framework.vue +11 -6
- package/src/layouts/NavMenu.vue +12 -3
- package/src/layouts/history-list/Bucket.vue +91 -0
- package/src/layouts/history-list/BucketTab.vue +72 -0
- package/src/layouts/history-list/GroupRow.vue +304 -0
- package/src/layouts/history-list/HistoryDiffDialog.vue +281 -0
- package/src/layouts/history-list/HistoryListPanel.vue +367 -0
- package/src/layouts/history-list/InitialRow.vue +71 -0
- package/src/layouts/history-list/PageTab.vue +118 -0
- package/src/layouts/history-list/composables.ts +309 -0
- package/src/layouts/history-list/useHistoryList.ts +60 -0
- package/src/layouts/history-list/useHistoryRevert.ts +421 -0
- package/src/layouts/page-bar/PageBar.vue +4 -2
- package/src/layouts/props-panel/PropsPanel.vue +5 -1
- package/src/layouts/sidebar/ComponentListPanel.vue +9 -5
- package/src/layouts/sidebar/Sidebar.vue +8 -3
- package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -5
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
- package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +31 -2
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +27 -2
- package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
- package/src/layouts/sidebar/layer/LayerMenu.vue +19 -11
- package/src/layouts/sidebar/layer/LayerNodeTool.vue +7 -4
- package/src/layouts/sidebar/layer/use-node-status.ts +9 -6
- package/src/layouts/workspace/viewer/Stage.vue +1 -1
- package/src/layouts/workspace/viewer/ViewerMenu.vue +8 -8
- package/src/services/BaseService.ts +35 -86
- package/src/services/codeBlock.ts +361 -5
- package/src/services/dataSource.ts +336 -6
- package/src/services/dep.ts +32 -9
- package/src/services/editor.ts +834 -194
- package/src/services/history.ts +509 -49
- package/src/services/keybinding.ts +3 -3
- package/src/services/props.ts +2 -2
- package/src/services/ui.ts +7 -0
- package/src/theme/history-list-panel.scss +557 -0
- package/src/theme/page-bar.scss +0 -4
- package/src/theme/props-panel.scss +3 -3
- package/src/theme/style-setter/border.scss +4 -1
- package/src/theme/theme.scss +1 -0
- package/src/type.ts +613 -31
- package/src/utils/code-block.ts +150 -0
- package/src/utils/content-menu.ts +25 -13
- package/src/utils/data-source/index.ts +2 -0
- package/src/utils/dep/worker.ts +5 -3
- package/src/utils/editor.ts +166 -60
- package/src/utils/history.ts +286 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/indexed-db.ts +122 -0
- package/src/utils/undo-redo.ts +111 -0
- package/types/index.d.ts +3197 -896
- package/dist/es/utils/compose.js +0 -44
- package/dist/es/utils/editor-history.js +0 -100
- package/src/utils/compose.ts +0 -52
- package/src/utils/editor-history.ts +0 -138
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { tMagicMessageBox } from '@tmagic/design';
|
|
2
|
+
import { datetimeFormatter } from '@tmagic/form';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
BaseStepValue,
|
|
6
|
+
HistoryGroup,
|
|
7
|
+
HistoryOpSource,
|
|
8
|
+
HistoryOpType,
|
|
9
|
+
HistoryRowDescriptor,
|
|
10
|
+
StepValue,
|
|
11
|
+
} from '@editor/type';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 通用 bucket 分组(数据源 / 代码块及业务自定义历史)在面板中的展示结构。
|
|
15
|
+
* 由 Bucket / BucketTab 复用,step 类型通过泛型 T 收窄(约束为 {@link BaseStepValue})。
|
|
16
|
+
*/
|
|
17
|
+
export interface HistoryBucketGroup<T extends BaseStepValue = BaseStepValue> {
|
|
18
|
+
/** 组内最后一步是否已应用 */
|
|
19
|
+
applied: boolean;
|
|
20
|
+
/** 是否为当前所在的分组 */
|
|
21
|
+
isCurrent?: boolean;
|
|
22
|
+
/** 该分组的操作类型 */
|
|
23
|
+
opType: HistoryOpType;
|
|
24
|
+
/** 组内所有步骤 */
|
|
25
|
+
steps: { index: number; applied: boolean; isCurrent?: boolean; step: T }[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GroupRow 的组头部与子步共用的展示字段(均由 {@link toRowGroup} 预先派生)。
|
|
30
|
+
* 抽出公共部分避免 {@link HistoryRowStep} / {@link HistoryRowGroup} 重复声明,
|
|
31
|
+
* 也便于消费方用本类型收窄「组头 / 子步」通用渲染逻辑的入参。
|
|
32
|
+
*/
|
|
33
|
+
export interface HistoryRowDisplay {
|
|
34
|
+
/** 是否已应用(false 表示已被 undo,UI 灰态)。 */
|
|
35
|
+
applied: boolean;
|
|
36
|
+
/** 是否为当前所在步骤 / 分组。 */
|
|
37
|
+
isCurrent: boolean;
|
|
38
|
+
/** 描述文案。 */
|
|
39
|
+
desc: string;
|
|
40
|
+
/** 操作途径。 */
|
|
41
|
+
source?: HistoryOpSource;
|
|
42
|
+
/** 操作人。 */
|
|
43
|
+
operator?: string;
|
|
44
|
+
/** 时间文案。 */
|
|
45
|
+
time?: string;
|
|
46
|
+
/** 时间的完整 title 提示。 */
|
|
47
|
+
timeTitle?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** GroupRow 渲染所需的单个子步视图模型(已由 {@link toRowGroup} 预先派生,组件内部不再触碰原始 step)。 */
|
|
51
|
+
export interface HistoryRowStep extends HistoryRowDisplay {
|
|
52
|
+
/** 该子步在所属栈中的稳定索引。 */
|
|
53
|
+
index: number;
|
|
54
|
+
/** 是否为最近一次保存的记录。 */
|
|
55
|
+
saved?: boolean;
|
|
56
|
+
/** 是否可查看差异。 */
|
|
57
|
+
diffable?: boolean;
|
|
58
|
+
/** 是否可回滚。 */
|
|
59
|
+
revertable?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* GroupRow 渲染所需的整组视图模型(由 {@link toRowGroup} 统一派生)。
|
|
64
|
+
* 把原先 GroupRow 上十多个扁平 props 收敛为单一对象,header 信息与子步列表一并携带。
|
|
65
|
+
*/
|
|
66
|
+
export interface HistoryRowGroup extends HistoryRowDisplay {
|
|
67
|
+
/** 分组的稳定 key,作为 toggle 事件 payload 与折叠状态的索引。 */
|
|
68
|
+
key: string;
|
|
69
|
+
/** 操作类型,用于徽标颜色与文案。 */
|
|
70
|
+
opType: HistoryOpType;
|
|
71
|
+
/** 子步列表(时间正序);其长度即合并步数,length > 1 即为合并组。 */
|
|
72
|
+
subSteps: HistoryRowStep[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 合并组默认展开;仅当 expanded[key] === false 时为收起。 */
|
|
76
|
+
export const isHistoryGroupExpanded = (expanded: Record<string, boolean>, key: string) => expanded[key] !== false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 历史面板的时间展示:
|
|
80
|
+
* - 当天的记录只显示 `HH:mm:ss`;
|
|
81
|
+
* - 跨天的记录显示 `MM-DD HH:mm:ss`。
|
|
82
|
+
* 无时间戳(旧数据 / 未写入)时返回空串,UI 据此不渲染时间。
|
|
83
|
+
*/
|
|
84
|
+
export const formatHistoryTime = (timestamp?: number): string => {
|
|
85
|
+
if (!timestamp) return '';
|
|
86
|
+
const isToday =
|
|
87
|
+
datetimeFormatter(new Date(timestamp), '', 'YYYY-MM-DD') ===
|
|
88
|
+
(datetimeFormatter(new Date(), '', 'YYYY-MM-DD') as string);
|
|
89
|
+
return `${
|
|
90
|
+
isToday
|
|
91
|
+
? datetimeFormatter(new Date(timestamp), '', 'HH:mm:ss')
|
|
92
|
+
: datetimeFormatter(new Date(timestamp), '', 'MM-DD HH:mm:ss')
|
|
93
|
+
}`;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** 完整时间(含年份与秒),用于 title 悬浮提示。无时间戳时返回空串。 */
|
|
97
|
+
export const formatHistoryFullTime = (timestamp?: number): string =>
|
|
98
|
+
timestamp ? `${datetimeFormatter(new Date(timestamp), '', 'YYYY-MM-DD HH:mm:ss')}` : '';
|
|
99
|
+
|
|
100
|
+
/** 取一组历史步骤里最后一步(最近一次)的时间戳,用于组头部展示。 */
|
|
101
|
+
export const groupTimestamp = (group: { steps: { step: { timestamp?: number } }[] }): number | undefined =>
|
|
102
|
+
group.steps[group.steps.length - 1]?.step.timestamp;
|
|
103
|
+
|
|
104
|
+
export const opLabel = (op: HistoryOpType) => {
|
|
105
|
+
switch (op) {
|
|
106
|
+
case 'add':
|
|
107
|
+
return '新增';
|
|
108
|
+
case 'remove':
|
|
109
|
+
return '删除';
|
|
110
|
+
case 'update':
|
|
111
|
+
default:
|
|
112
|
+
return '修改';
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** 内置操作途径的中文文案;自定义来源直接回显原值,未知 / 缺省返回空串(UI 据此不渲染)。 */
|
|
117
|
+
const HISTORY_SOURCE_LABELS: Record<string, string> = {
|
|
118
|
+
stage: '画布',
|
|
119
|
+
tree: '树面板',
|
|
120
|
+
'component-panel': '组件面板',
|
|
121
|
+
props: '配置面板',
|
|
122
|
+
code: '源码',
|
|
123
|
+
'root-code': 'DSL源码',
|
|
124
|
+
'stage-contextmenu': '画布菜单',
|
|
125
|
+
'tree-contextmenu': '树菜单',
|
|
126
|
+
toolbar: '工具栏',
|
|
127
|
+
shortcut: '快捷键',
|
|
128
|
+
rollback: '回滚',
|
|
129
|
+
api: '接口',
|
|
130
|
+
ai: 'AI',
|
|
131
|
+
initial: '初始值',
|
|
132
|
+
sync: '同步',
|
|
133
|
+
unknown: '未知',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** 操作途径文案:用于历史面板展示「画布 / 树面板 / 配置面板…」标签。 */
|
|
137
|
+
export const sourceLabel = (source: HistoryOpSource = 'unknown'): string => {
|
|
138
|
+
return HISTORY_SOURCE_LABELS[source] ?? `${source}`;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** 取一组历史步骤里最后一步(最近一次)的操作途径,用于组头部展示。 */
|
|
142
|
+
export const groupSource = (group: { steps: { step: { source?: HistoryOpSource } }[] }): HistoryOpSource | undefined =>
|
|
143
|
+
group.steps[group.steps.length - 1]?.step.source;
|
|
144
|
+
|
|
145
|
+
/** 取一组历史步骤里最后一步(最近一次)的操作人,用于组头部展示。 */
|
|
146
|
+
export const groupOperator = (group: { steps: { step: { operator?: string } }[] }): string | undefined =>
|
|
147
|
+
group.steps[group.steps.length - 1]?.step.operator;
|
|
148
|
+
|
|
149
|
+
/** {@link toRowGroup} 接受的最小分组结构 */
|
|
150
|
+
interface RowGroupInput<T extends BaseStepValue = BaseStepValue> {
|
|
151
|
+
applied: boolean;
|
|
152
|
+
isCurrent?: boolean;
|
|
153
|
+
opType: HistoryOpType;
|
|
154
|
+
steps: { index: number; applied: boolean; isCurrent?: boolean; step: T }[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 把一个历史分组(页面 / bucket)派生为 GroupRow 直接消费的视图模型 {@link HistoryRowGroup}。
|
|
159
|
+
* 统一了原先 PageTab / Bucket 各自内联的 sub-steps 映射逻辑:描述、可差异、可回滚、时间、途径
|
|
160
|
+
* 全部在此一次性算好,组件层只负责渲染。
|
|
161
|
+
*/
|
|
162
|
+
export const toRowGroup = <T extends BaseStepValue = BaseStepValue>(
|
|
163
|
+
group: RowGroupInput<T>,
|
|
164
|
+
key: string,
|
|
165
|
+
descriptor: HistoryRowDescriptor<T>,
|
|
166
|
+
): HistoryRowGroup => {
|
|
167
|
+
const { describeGroup, describeStep, isStepDiffable, isStepRevertable } = descriptor;
|
|
168
|
+
const timestamp = groupTimestamp(group);
|
|
169
|
+
// 无 describeGroup 时回退到组内最后一步的 describeStep:数据源/代码块不做相邻合并,每组恒为单步,二者等价。
|
|
170
|
+
const lastStep = group.steps[group.steps.length - 1]?.step;
|
|
171
|
+
return {
|
|
172
|
+
key,
|
|
173
|
+
applied: group.applied,
|
|
174
|
+
isCurrent: Boolean(group.isCurrent),
|
|
175
|
+
opType: group.opType,
|
|
176
|
+
desc: describeGroup ? describeGroup(group) : describeStep(lastStep),
|
|
177
|
+
source: groupSource(group),
|
|
178
|
+
operator: groupOperator(group),
|
|
179
|
+
time: formatHistoryTime(timestamp),
|
|
180
|
+
timeTitle: formatHistoryFullTime(timestamp),
|
|
181
|
+
subSteps: group.steps.map((s) => ({
|
|
182
|
+
index: s.index,
|
|
183
|
+
applied: s.applied,
|
|
184
|
+
isCurrent: Boolean(s.isCurrent),
|
|
185
|
+
saved: s.step.saved,
|
|
186
|
+
desc: describeStep(s.step),
|
|
187
|
+
diffable: isStepDiffable ? isStepDiffable(s.step) : false,
|
|
188
|
+
revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
|
|
189
|
+
source: s.step.source,
|
|
190
|
+
operator: s.step.operator,
|
|
191
|
+
time: formatHistoryTime(s.step.timestamp),
|
|
192
|
+
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const nameOf = (node?: { name?: string; id?: string | number; type?: string }) =>
|
|
198
|
+
node?.name || node?.type || `${node?.id ?? ''}`;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 默认描述里展示「名称 (id: xxx)」,便于区分同名实体。
|
|
202
|
+
* - 当未传入 id,或 label 本身就是 id 字符串(即没有 name/type/title 可用)时,仅展示 label,避免出现「123 (id: 123)」。
|
|
203
|
+
*/
|
|
204
|
+
const labelWithId = (label: string | number | undefined, id: string | number | undefined): string => {
|
|
205
|
+
const labelStr = label === undefined || label === null ? '' : `${label}`;
|
|
206
|
+
if (id === undefined || id === null || id === '') return labelStr;
|
|
207
|
+
if (labelStr === '' || labelStr === `${id}`) return `${id}`;
|
|
208
|
+
return `${labelStr} (id: ${id})`;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** 从一组可选 historyDescription 中取最后一条非空值;都为空时返回 undefined。 */
|
|
212
|
+
const pickLastDescription = (descs: (string | undefined)[]): string | undefined => {
|
|
213
|
+
for (let i = descs.length - 1; i >= 0; i--) {
|
|
214
|
+
if (descs[i]) return descs[i];
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 页面 / 数据源 / 代码块三类历史共用的单步描述核心。
|
|
221
|
+
* 各类型只在「取展示名」与「实体单位名」上有差异,通过参数注入,文案模板完全一致:
|
|
222
|
+
* - 新增 / 删除:单实体展示「label」,多实体(仅页面可能出现)退化为「N 个X」;
|
|
223
|
+
* - 修改:展示「label · propPath」,无 diff 时兜底「X」,多实体退化为「N 个X」。
|
|
224
|
+
* 操作类型(新增 / 删除 / 修改)已由列表行的 op 徽标单独展示,故描述文案不再重复动词。
|
|
225
|
+
* 展示 id 统一取 schema.id;调用方显式传入的 historyDescription 永远优先。
|
|
226
|
+
*/
|
|
227
|
+
export const describeStep = <T>(
|
|
228
|
+
step: BaseStepValue<T>,
|
|
229
|
+
getLabel: (_schema?: T) => string | number | undefined,
|
|
230
|
+
unit: string,
|
|
231
|
+
): string => {
|
|
232
|
+
if (step.historyDescription) return step.historyDescription;
|
|
233
|
+
const items = step.diff ?? [];
|
|
234
|
+
const label = (schema?: T) => labelWithId(getLabel(schema), (schema as { id?: string | number } | undefined)?.id);
|
|
235
|
+
|
|
236
|
+
if (step.opType === 'add') {
|
|
237
|
+
const node = items[0]?.newSchema;
|
|
238
|
+
return items.length === 1 && node ? label(node) : `${items.length} 个${unit}`;
|
|
239
|
+
}
|
|
240
|
+
if (step.opType === 'remove') {
|
|
241
|
+
const node = items[0]?.oldSchema;
|
|
242
|
+
return items.length === 1 && node ? label(node) : `${items.length} 个${unit}`;
|
|
243
|
+
}
|
|
244
|
+
if (!items.length) return unit;
|
|
245
|
+
if (items.length === 1) {
|
|
246
|
+
const { newSchema, oldSchema, changeRecords } = items[0];
|
|
247
|
+
const propPath = changeRecords?.map((changeRecord) => changeRecord.propPath).join(',');
|
|
248
|
+
const target = label(newSchema ?? oldSchema);
|
|
249
|
+
return propPath ? `${target} · ${propPath}` : target;
|
|
250
|
+
}
|
|
251
|
+
return `${items.length} 个${unit}`;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const describePageStep = (step: StepValue): string => describeStep(step, (node) => nameOf(node), '节点');
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 合并组的展示文案:
|
|
258
|
+
* - 若组内任一步显式提供了 historyDescription:取最后一条非空 historyDescription(最近一次的描述更准确);
|
|
259
|
+
* - 单步组:复用 describePageStep;
|
|
260
|
+
* - 多步组(连续修改同一节点):展示节点名 + 涉及的前几个 propPath。
|
|
261
|
+
*/
|
|
262
|
+
export const describePageGroup = (group: HistoryGroup<StepValue>) => {
|
|
263
|
+
const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
|
|
264
|
+
if (lastDesc) return lastDesc;
|
|
265
|
+
if (group.steps.length === 1) return describePageStep(group.steps[0].step);
|
|
266
|
+
|
|
267
|
+
return labelWithId(group.targetName ?? (group.targetId !== undefined ? `${group.targetId}` : '节点'), group.targetId);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 页面 step 是否支持「回滚」(类 git revert):
|
|
272
|
+
* - 新增 / 删除:不依赖 changeRecords,反向应用即删除 / 加回,始终可回滚;
|
|
273
|
+
* - 更新:必须每个被更新节点都带有 changeRecords,才支持按 propPath 局部反向 patch。
|
|
274
|
+
* 缺失 changeRecords 的更新只能整节点替换,会冲掉该节点后续的无关变更,因此不支持回滚。
|
|
275
|
+
*/
|
|
276
|
+
export const isPageStepRevertable = (step: StepValue): boolean => {
|
|
277
|
+
if (step.opType !== 'update') return true;
|
|
278
|
+
const items = step.diff ?? [];
|
|
279
|
+
if (!items.length) return false;
|
|
280
|
+
return items.every((item) => Boolean(item.changeRecords?.length));
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 单 diff 项历史(数据源 / 代码块)是否支持「回滚」:
|
|
285
|
+
* - 新增(无 oldSchema)/ 删除(无 newSchema):不依赖 changeRecords,始终可回滚;
|
|
286
|
+
* - 更新(前后内容都存在):必须有 changeRecords 才支持局部反向 patch,否则不支持回滚。
|
|
287
|
+
*/
|
|
288
|
+
export const isSingleDiffStepRevertable = (step: BaseStepValue): boolean => {
|
|
289
|
+
const item = step.diff?.[0];
|
|
290
|
+
if (!item?.oldSchema || !item?.newSchema) return true;
|
|
291
|
+
return Boolean(item.changeRecords?.length);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 通用二次确认弹窗:清空历史 / 无法差异对比的回滚等会改变状态的操作,先弹出确认框,
|
|
296
|
+
* 用户点击「确定」返回 true,取消(confirm reject)时返回 false 并静默忽略。
|
|
297
|
+
*/
|
|
298
|
+
export const confirmHistoryAction = async (message: string): Promise<boolean> => {
|
|
299
|
+
try {
|
|
300
|
+
await tMagicMessageBox.confirm(message, '提示', {
|
|
301
|
+
confirmButtonText: '确定',
|
|
302
|
+
cancelButtonText: '取消',
|
|
303
|
+
type: 'warning',
|
|
304
|
+
});
|
|
305
|
+
return true;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { computed, reactive } from 'vue';
|
|
2
|
+
|
|
3
|
+
import { useServices } from '@editor/hooks/use-services';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 历史记录面板共享逻辑:
|
|
7
|
+
* - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
|
|
8
|
+
* - 提供折叠状态管理;
|
|
9
|
+
* - 提供操作描述文案生成器。
|
|
10
|
+
*
|
|
11
|
+
* 所有数据基于 historyService 的 reactive state 派生,自动跟随历史变化刷新。
|
|
12
|
+
*/
|
|
13
|
+
export const useHistoryList = () => {
|
|
14
|
+
const { editorService, historyService } = useServices();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
|
|
18
|
+
* 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
|
|
19
|
+
* 合并组默认展开;仅当值为 `false` 时表示收起。
|
|
20
|
+
*/
|
|
21
|
+
const expanded = reactive<Record<string, boolean>>({});
|
|
22
|
+
const toggleGroup = (key: string) => {
|
|
23
|
+
expanded[key] = expanded[key] === false;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const pageGroups = computed(() => historyService.getHistoryGroups('page', editorService.get('page')?.id));
|
|
27
|
+
const dataSourceGroups = computed(() => historyService.getHistoryGroups('dataSource'));
|
|
28
|
+
const codeBlockGroups = computed(() => historyService.getHistoryGroups('codeBlock'));
|
|
29
|
+
|
|
30
|
+
/** 页面 tab 倒序展示(最新一组在最上面)。 */
|
|
31
|
+
const pageGroupsDisplay = computed(() => pageGroups.value.slice().reverse());
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 把按时间正序的 group 列表,再按 id 聚拢成 bucket(同 id 的所有分组放一起)。
|
|
35
|
+
* 每个 bucket 内部仍然按时间倒序展示(最近的操作最先看到)。
|
|
36
|
+
*/
|
|
37
|
+
const groupByTarget = <G extends { id: string | number }>(groups: G[]) => {
|
|
38
|
+
const map = new Map<string | number, G[]>();
|
|
39
|
+
groups.forEach((g) => {
|
|
40
|
+
const list = map.get(g.id) ?? [];
|
|
41
|
+
list.push(g);
|
|
42
|
+
map.set(g.id, list);
|
|
43
|
+
});
|
|
44
|
+
return Array.from(map.entries()).map(([id, gs]) => ({ id, groups: gs.slice().reverse() }));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const dataSourceGroupsByTarget = computed(() => groupByTarget(dataSourceGroups.value));
|
|
48
|
+
const codeBlockGroupsByTarget = computed(() => groupByTarget(codeBlockGroups.value));
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
expanded,
|
|
52
|
+
toggleGroup,
|
|
53
|
+
pageGroups,
|
|
54
|
+
dataSourceGroups,
|
|
55
|
+
codeBlockGroups,
|
|
56
|
+
pageGroupsDisplay,
|
|
57
|
+
dataSourceGroupsByTarget,
|
|
58
|
+
codeBlockGroupsByTarget,
|
|
59
|
+
};
|
|
60
|
+
};
|