@tmagic/editor 1.8.0-beta.10 → 1.8.0-beta.12
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 +7 -4
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +60 -14
- package/dist/es/components/ScrollBar.js +0 -1
- package/dist/es/editorProps.js +6 -1
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +15 -3
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +2 -17
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +7 -0
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +5 -17
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +29 -3
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +50 -88
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +2 -5
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +48 -23
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/UISelect.js +0 -1
- package/dist/es/index.js +9 -6
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +21 -6
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +14 -4
- package/dist/es/layouts/history-list/composables.js +1 -1
- package/dist/es/layouts/history-list/useHistoryRevert.js +17 -6
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +44 -5
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +15 -8
- package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +15 -31
- 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 +18 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/data-source/useContentMenu.js +18 -3
- package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
- package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/plugin.js +3 -1
- package/dist/es/services/editor.js +104 -5
- package/dist/es/services/props.js +2 -1
- package/dist/es/services/storage.js +1 -1
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +104 -55
- package/dist/es/utils/data-source/index.js +30 -11
- package/dist/es/utils/event.js +123 -0
- package/dist/es/utils/monaco-editor.js +22 -6
- package/dist/es/utils/props.js +94 -8
- package/dist/es/utils/type-match-rules.js +360 -0
- package/dist/style.css +104 -55
- package/dist/tmagic-editor.umd.cjs +16923 -15771
- package/package.json +8 -8
- package/src/Editor.vue +3 -1
- package/src/components/CodeBlockEditor.vue +3 -0
- package/src/components/CompareForm.vue +7 -0
- package/src/components/FloatingBox.vue +77 -8
- package/src/components/ScrollBar.vue +0 -67
- package/src/editorProps.ts +13 -0
- package/src/fields/CodeSelect.vue +4 -1
- package/src/fields/CondOpSelect.vue +2 -24
- package/src/fields/DataSourceFields.vue +5 -0
- package/src/fields/DataSourceInput.vue +7 -25
- package/src/fields/DataSourceMocks.vue +3 -0
- package/src/fields/DisplayConds.vue +14 -0
- package/src/fields/EventSelect.vue +84 -129
- package/src/fields/StyleSetter/Index.vue +2 -8
- package/src/fields/StyleSetter/pro/Background.vue +94 -55
- package/src/fields/StyleSetter/pro/Border.vue +9 -11
- package/src/fields/StyleSetter/pro/Font.vue +66 -64
- package/src/fields/StyleSetter/pro/Layout.vue +140 -138
- package/src/fields/StyleSetter/pro/Position.vue +69 -67
- package/src/fields/StyleSetter/pro/Transform.vue +23 -25
- package/src/fields/UISelect.vue +0 -12
- package/src/index.ts +1 -0
- package/src/layouts/CodeEditor.vue +32 -6
- package/src/layouts/Framework.vue +7 -6
- package/src/layouts/history-list/HistoryDiffDialog.vue +23 -5
- package/src/layouts/history-list/useHistoryRevert.ts +25 -5
- package/src/layouts/props-panel/FormPanel.vue +66 -5
- package/src/layouts/props-panel/PropsPanel.vue +20 -6
- package/src/layouts/props-panel/use-style-panel.ts +4 -3
- package/src/layouts/sidebar/Sidebar.vue +2 -0
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +4 -1
- package/src/layouts/sidebar/code-block/useContentMenu.ts +13 -1
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +3 -0
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +2 -2
- package/src/layouts/sidebar/data-source/useContentMenu.ts +13 -1
- package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
- package/src/layouts/sidebar/layer/LayerPanel.vue +4 -1
- package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
- package/src/plugin.ts +3 -1
- package/src/services/editor.ts +168 -3
- package/src/services/ui.ts +6 -0
- package/src/theme/event.scss +8 -0
- package/src/theme/floating-box.scss +9 -1
- package/src/theme/layer-node-content.scss +14 -0
- package/src/theme/scroll-bar.scss +64 -0
- package/src/theme/theme.scss +3 -0
- package/src/theme/ui-select.scss +9 -0
- package/src/type.ts +72 -2
- package/src/utils/data-source/index.ts +30 -12
- package/src/utils/event.ts +224 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/monaco-editor.ts +27 -0
- package/src/utils/props.ts +160 -2
- package/src/utils/type-match-rules.ts +678 -0
- package/types/index.d.ts +391 -85
package/src/plugin.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type { App } from 'vue';
|
|
|
21
21
|
import type { DesignPluginOptions } from '@tmagic/design';
|
|
22
22
|
import designPlugin from '@tmagic/design';
|
|
23
23
|
import type { FormInstallOptions } from '@tmagic/form';
|
|
24
|
-
import formPlugin from '@tmagic/form';
|
|
24
|
+
import formPlugin, { registerTypeMatchRules } from '@tmagic/form';
|
|
25
25
|
import tablePlugin from '@tmagic/table';
|
|
26
26
|
|
|
27
27
|
import Code from './fields/Code.vue';
|
|
@@ -44,6 +44,7 @@ import StyleSetter from './fields/StyleSetter/Index.vue';
|
|
|
44
44
|
import uiSelect from './fields/UISelect.vue';
|
|
45
45
|
import CodeEditor from './layouts/CodeEditor.vue';
|
|
46
46
|
import { setEditorConfig } from './utils/config';
|
|
47
|
+
import { editorTypeMatchRules } from './utils/type-match-rules';
|
|
47
48
|
import Editor from './Editor.vue';
|
|
48
49
|
import type { EditorInstallOptions } from './type';
|
|
49
50
|
|
|
@@ -64,6 +65,7 @@ export default {
|
|
|
64
65
|
app.use(designPlugin, opt || {});
|
|
65
66
|
app.use(formPlugin, opt || {});
|
|
66
67
|
app.use(tablePlugin);
|
|
68
|
+
registerTypeMatchRules(editorTypeMatchRules);
|
|
67
69
|
|
|
68
70
|
app.config.globalProperties.$TMAGIC_EDITOR = option;
|
|
69
71
|
setEditorConfig(option);
|
package/src/services/editor.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
isPage,
|
|
32
32
|
isPageFragment,
|
|
33
33
|
setValueByKeyPath,
|
|
34
|
+
traverseNode,
|
|
34
35
|
} from '@tmagic/utils';
|
|
35
36
|
|
|
36
37
|
import BaseService from '@editor/services//BaseService';
|
|
@@ -47,6 +48,8 @@ import type {
|
|
|
47
48
|
EditorNodeInfo,
|
|
48
49
|
HistoryOpSource,
|
|
49
50
|
HistoryOpType,
|
|
51
|
+
NodeInvalidInfo,
|
|
52
|
+
NodeInvalidSource,
|
|
50
53
|
PastePosition,
|
|
51
54
|
StepDiffItem,
|
|
52
55
|
StepValue,
|
|
@@ -104,12 +107,18 @@ class Editor extends BaseService {
|
|
|
104
107
|
stageLoading: true,
|
|
105
108
|
highlightNode: null,
|
|
106
109
|
modifiedNodeIds: new Map(),
|
|
110
|
+
invalidNodeIds: new Map(),
|
|
107
111
|
pageLength: 0,
|
|
108
112
|
pageFragmentLength: 0,
|
|
109
113
|
disabledMultiSelect: false,
|
|
110
114
|
alwaysMultiSelect: false,
|
|
111
115
|
});
|
|
112
116
|
private selectionBeforeOp: Id[] | null = null;
|
|
117
|
+
/**
|
|
118
|
+
* 操作前的节点校验错误快照,与 selectionBeforeOp 同时在 captureSelectionBeforeOp 中捕获,
|
|
119
|
+
* 供 pushOpHistory 写入 step.extra.invalidNodeIdsBefore,用于撤销时还原到「操作前」的错误状态。
|
|
120
|
+
*/
|
|
121
|
+
private invalidNodeIdsBeforeOp: Map<Id, NodeInvalidInfo> | null = null;
|
|
113
122
|
/**
|
|
114
123
|
* 最近一次 pushOpHistory 写入的历史记录 uuid。
|
|
115
124
|
* 供 *AndGetHistoryId 系列方法在调用普通操作后取回本次产生的历史记录 id;
|
|
@@ -127,7 +136,7 @@ class Editor extends BaseService {
|
|
|
127
136
|
|
|
128
137
|
/**
|
|
129
138
|
* 设置当前指点节点配置
|
|
130
|
-
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength
|
|
139
|
+
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'invalidNodeIds' | 'pageLength' | 'pageFragmentLength
|
|
131
140
|
* @param value MNode
|
|
132
141
|
* @param options.historySource 设置 root 时,本次变更写入历史记录的「操作来源」(仅 name === 'root' 时生效)
|
|
133
142
|
*/
|
|
@@ -180,12 +189,15 @@ class Editor extends BaseService {
|
|
|
180
189
|
}
|
|
181
190
|
|
|
182
191
|
this.emit('root-change', value as StoreState['root'], preValue as StoreState['root'], options);
|
|
192
|
+
|
|
193
|
+
// 整体替换 DSL 后,清理不再存在于新 DSL 中的失效节点错误记录,避免残留误报。
|
|
194
|
+
this.pruneInvalidNodeIds();
|
|
183
195
|
}
|
|
184
196
|
}
|
|
185
197
|
|
|
186
198
|
/**
|
|
187
199
|
* 获取当前指点节点配置
|
|
188
|
-
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength'
|
|
200
|
+
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'invalidNodeIds' | 'pageLength' | 'pageFragmentLength'
|
|
189
201
|
* @returns MNode
|
|
190
202
|
*/
|
|
191
203
|
public get<K extends StoreStateKey>(name: K): StoreState[K] {
|
|
@@ -663,6 +675,9 @@ class Editor extends BaseService {
|
|
|
663
675
|
|
|
664
676
|
await Promise.all(nodes.map((node) => this.doRemove(node, { doNotSelect, doNotSwitchPage })));
|
|
665
677
|
|
|
678
|
+
// 删除节点时同步清理其(含子树)的校验错误记录;置于 pushOpHistory 之前,使历史快照与本次删除对齐。
|
|
679
|
+
this.removeInvalidNodesBySubtree(nodes);
|
|
680
|
+
|
|
666
681
|
if (removedItems.length > 0 && pageForOp) {
|
|
667
682
|
if (!doNotPushHistory) {
|
|
668
683
|
this.pushOpHistory('remove', {
|
|
@@ -770,11 +785,23 @@ class Editor extends BaseService {
|
|
|
770
785
|
doNotPushHistory?: boolean;
|
|
771
786
|
historyDescription?: string;
|
|
772
787
|
historySource?: HistoryOpSource;
|
|
788
|
+
/**
|
|
789
|
+
* 属性面板提交时携带的校验错误信息,在写入历史记录之前落库,
|
|
790
|
+
* 使历史快照与本次变更对齐,从而 undo/redo 能正确还原错误标记。
|
|
791
|
+
*/
|
|
792
|
+
invalidInfo?: { id: Id; source: NodeInvalidSource; error?: string };
|
|
773
793
|
} = {},
|
|
774
794
|
): Promise<MNode | MNode[]> {
|
|
775
795
|
this.captureSelectionBeforeOp();
|
|
776
796
|
|
|
777
|
-
const {
|
|
797
|
+
const {
|
|
798
|
+
doNotPushHistory = false,
|
|
799
|
+
changeRecordList,
|
|
800
|
+
changeRecords,
|
|
801
|
+
historyDescription,
|
|
802
|
+
historySource,
|
|
803
|
+
invalidInfo,
|
|
804
|
+
} = data;
|
|
778
805
|
|
|
779
806
|
const nodes = Array.isArray(config) ? config : [config];
|
|
780
807
|
|
|
@@ -787,6 +814,9 @@ class Editor extends BaseService {
|
|
|
787
814
|
}),
|
|
788
815
|
);
|
|
789
816
|
|
|
817
|
+
// 校验错误信息在 pushOpHistory 之前落库,保证历史快照包含本次变更对应的错误状态。
|
|
818
|
+
this.applyInvalidInfo(config, invalidInfo);
|
|
819
|
+
|
|
790
820
|
if (updateData[0].oldNode?.type !== NodeType.ROOT) {
|
|
791
821
|
const curNodes = this.get('nodes');
|
|
792
822
|
if (curNodes.length) {
|
|
@@ -1542,6 +1572,7 @@ class Editor extends BaseService {
|
|
|
1542
1572
|
this.set('stage', null);
|
|
1543
1573
|
this.set('highlightNode', null);
|
|
1544
1574
|
this.set('modifiedNodeIds', new Map());
|
|
1575
|
+
this.set('invalidNodeIds', new Map());
|
|
1545
1576
|
this.set('pageLength', 0);
|
|
1546
1577
|
}
|
|
1547
1578
|
|
|
@@ -1555,6 +1586,62 @@ class Editor extends BaseService {
|
|
|
1555
1586
|
this.get('modifiedNodeIds').clear();
|
|
1556
1587
|
}
|
|
1557
1588
|
|
|
1589
|
+
/**
|
|
1590
|
+
* 记录(或覆盖)某个节点在指定来源(属性表单 / 样式表单)上的校验错误信息。
|
|
1591
|
+
* @param id 节点 id
|
|
1592
|
+
* @param source 错误来源:'props'(属性表单)| 'style'(样式表单)
|
|
1593
|
+
* @param message 错误文案(可能是包含 <br> 的 HTML)
|
|
1594
|
+
*/
|
|
1595
|
+
public setInvalidNode(id: Id, source: NodeInvalidSource, message: string) {
|
|
1596
|
+
const map = this.get('invalidNodeIds');
|
|
1597
|
+
const info: NodeInvalidInfo = { ...(map.get(id) || {}) };
|
|
1598
|
+
info[source] = message;
|
|
1599
|
+
map.set(id, info);
|
|
1600
|
+
this.emit('invalid-node-change', map);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* 删除节点的校验错误记录。
|
|
1605
|
+
* @param id 节点 id
|
|
1606
|
+
* @param source 指定来源则仅删除该来源;不传则删除该节点全部来源的错误
|
|
1607
|
+
*/
|
|
1608
|
+
public deleteInvalidNode(id: Id, source?: NodeInvalidSource) {
|
|
1609
|
+
const map = this.get('invalidNodeIds');
|
|
1610
|
+
if (!map.has(id)) return;
|
|
1611
|
+
|
|
1612
|
+
if (!source) {
|
|
1613
|
+
map.delete(id);
|
|
1614
|
+
} else {
|
|
1615
|
+
const info: NodeInvalidInfo = { ...(map.get(id) || {}) };
|
|
1616
|
+
delete info[source];
|
|
1617
|
+
if (info.props || info.style) {
|
|
1618
|
+
map.set(id, info);
|
|
1619
|
+
} else {
|
|
1620
|
+
map.delete(id);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
this.emit('invalid-node-change', map);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/** 获取当前存在校验错误的节点错误 Map(key 为节点 id) */
|
|
1628
|
+
public getInvalidNodeIds(): Map<Id, NodeInvalidInfo> {
|
|
1629
|
+
return this.get('invalidNodeIds');
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/** 获取指定节点的校验错误信息 */
|
|
1633
|
+
public getInvalidNodeInfo(id: Id): NodeInvalidInfo | undefined {
|
|
1634
|
+
return this.get('invalidNodeIds').get(id);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/** 清空全部校验错误记录 */
|
|
1638
|
+
public resetInvalidNodeId() {
|
|
1639
|
+
const map = this.get('invalidNodeIds');
|
|
1640
|
+
if (map.size === 0) return;
|
|
1641
|
+
map.clear();
|
|
1642
|
+
this.emit('invalid-node-change', map);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1558
1645
|
public usePlugin(options: AsyncHookPlugin<AsyncMethodName, Editor>): void {
|
|
1559
1646
|
super.usePlugin(options);
|
|
1560
1647
|
}
|
|
@@ -1577,6 +1664,68 @@ class Editor extends BaseService {
|
|
|
1577
1664
|
return super.emit(eventName, ...args);
|
|
1578
1665
|
}
|
|
1579
1666
|
|
|
1667
|
+
/**
|
|
1668
|
+
* 应用一次属性面板提交携带的校验错误信息(在写入历史记录之前调用,使历史快照与本次变更对齐)。
|
|
1669
|
+
* - invalidInfo 为空(调用方未携带):认为本次更新对应的节点已无校验错误,清除其全部来源的错误;
|
|
1670
|
+
* - error 非空则记录错误,为空则清除对应来源的错误。
|
|
1671
|
+
*/
|
|
1672
|
+
private applyInvalidInfo(
|
|
1673
|
+
config: MNode | MNode[],
|
|
1674
|
+
invalidInfo?: { id: Id; source: NodeInvalidSource; error?: string },
|
|
1675
|
+
) {
|
|
1676
|
+
if (!invalidInfo) {
|
|
1677
|
+
// 调用方未携带 invalidInfo:本次更新对应节点不应再保留校验错误,清除其全部来源。
|
|
1678
|
+
const ids = (Array.isArray(config) ? config : [config])
|
|
1679
|
+
.map((node) => node.id)
|
|
1680
|
+
.filter((id): id is Id => id !== undefined);
|
|
1681
|
+
ids.forEach((id) => this.deleteInvalidNode(id));
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const { id, source, error } = invalidInfo;
|
|
1685
|
+
if (error) {
|
|
1686
|
+
this.setInvalidNode(id, source, error);
|
|
1687
|
+
} else {
|
|
1688
|
+
this.deleteInvalidNode(id, source);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/** 删除被移除节点(含其子树)的校验错误记录,避免残留误报 */
|
|
1693
|
+
private removeInvalidNodesBySubtree(nodes: MNode[]) {
|
|
1694
|
+
const map = this.get('invalidNodeIds');
|
|
1695
|
+
if (map.size === 0) return;
|
|
1696
|
+
|
|
1697
|
+
let changed = false;
|
|
1698
|
+
nodes.forEach((node) => {
|
|
1699
|
+
traverseNode(node, (n) => {
|
|
1700
|
+
if (n.id !== undefined && map.delete(n.id)) {
|
|
1701
|
+
changed = true;
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
if (changed) {
|
|
1707
|
+
this.emit('invalid-node-change', map);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/** 清理不在当前 DSL 中的失效节点错误记录(用于整体替换 root 后) */
|
|
1712
|
+
private pruneInvalidNodeIds() {
|
|
1713
|
+
const map = this.get('invalidNodeIds');
|
|
1714
|
+
if (map.size === 0) return;
|
|
1715
|
+
|
|
1716
|
+
let changed = false;
|
|
1717
|
+
for (const id of [...map.keys()]) {
|
|
1718
|
+
if (!this.getNodeById(id, false)) {
|
|
1719
|
+
map.delete(id);
|
|
1720
|
+
changed = true;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if (changed) {
|
|
1725
|
+
this.emit('invalid-node-change', map);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1580
1729
|
private addModifiedNodeId(id: Id) {
|
|
1581
1730
|
this.get('modifiedNodeIds').set(id, id);
|
|
1582
1731
|
}
|
|
@@ -1598,6 +1747,9 @@ class Editor extends BaseService {
|
|
|
1598
1747
|
private captureSelectionBeforeOp() {
|
|
1599
1748
|
if (this.selectionBeforeOp) return;
|
|
1600
1749
|
this.selectionBeforeOp = this.get('nodes').map((n) => n.id);
|
|
1750
|
+
// 与选区快照同步捕获「操作前」的校验错误状态;因 applyInvalidInfo 会在 pushOpHistory 前修改 invalidNodeIds,
|
|
1751
|
+
// 必须在此(操作最开始)留存操作前快照,供 undo 还原。
|
|
1752
|
+
this.invalidNodeIdsBeforeOp = new Map(this.get('invalidNodeIds'));
|
|
1601
1753
|
}
|
|
1602
1754
|
|
|
1603
1755
|
/**
|
|
@@ -1704,6 +1856,9 @@ class Editor extends BaseService {
|
|
|
1704
1856
|
selectedBefore: this.selectionBeforeOp ?? [],
|
|
1705
1857
|
selectedAfter: this.get('nodes').map((n) => n.id),
|
|
1706
1858
|
modifiedNodeIds: new Map(this.get('modifiedNodeIds')),
|
|
1859
|
+
// 方向性双快照:undo 还原「操作前」错误状态,redo 还原「操作后」错误状态。
|
|
1860
|
+
invalidNodeIdsBefore: new Map(this.invalidNodeIdsBeforeOp ?? this.get('invalidNodeIds')),
|
|
1861
|
+
invalidNodeIdsAfter: new Map(this.get('invalidNodeIds')),
|
|
1707
1862
|
},
|
|
1708
1863
|
diff,
|
|
1709
1864
|
};
|
|
@@ -1716,6 +1871,7 @@ class Editor extends BaseService {
|
|
|
1716
1871
|
const historyId = pushed ? step.uuid : null;
|
|
1717
1872
|
this.lastPushedHistoryId = historyId;
|
|
1718
1873
|
this.selectionBeforeOp = null;
|
|
1874
|
+
this.invalidNodeIdsBeforeOp = null;
|
|
1719
1875
|
return historyId;
|
|
1720
1876
|
}
|
|
1721
1877
|
|
|
@@ -1841,6 +1997,15 @@ class Editor extends BaseService {
|
|
|
1841
1997
|
|
|
1842
1998
|
this.set('modifiedNodeIds', step.extra?.modifiedNodeIds ?? new Map());
|
|
1843
1999
|
|
|
2000
|
+
// 还原校验错误标记:因 undo/redo 复用同一 step,需按方向取「操作前 / 操作后」快照——
|
|
2001
|
+
// undo(reverse=true) 还原到操作前的错误状态(撤销一个「校验失败」的改动后错误消失),
|
|
2002
|
+
// redo(reverse=false) 还原到操作后的错误状态(重做后错误恢复)。
|
|
2003
|
+
// 浅拷贝一份以隔离历史快照,避免后续 set/deleteInvalidNode 反向污染 step.extra。
|
|
2004
|
+
const invalidToRestore =
|
|
2005
|
+
(reverse ? step.extra?.invalidNodeIdsBefore : step.extra?.invalidNodeIdsAfter) ?? new Map();
|
|
2006
|
+
this.set('invalidNodeIds', new Map(invalidToRestore));
|
|
2007
|
+
this.emit('invalid-node-change', this.get('invalidNodeIds'));
|
|
2008
|
+
|
|
1844
2009
|
const page = toRaw(this.get('page'));
|
|
1845
2010
|
if (page) {
|
|
1846
2011
|
const selectIds = (reverse ? step.extra?.selectedBefore : step.extra?.selectedAfter) ?? [];
|
package/src/services/ui.ts
CHANGED
|
@@ -27,6 +27,9 @@ import {
|
|
|
27
27
|
DEFAULT_LEFT_COLUMN_WIDTH,
|
|
28
28
|
DEFAULT_RIGHT_COLUMN_WIDTH,
|
|
29
29
|
LEFT_COLUMN_WIDTH_STORAGE_KEY,
|
|
30
|
+
MIN_CENTER_COLUMN_WIDTH,
|
|
31
|
+
MIN_LEFT_COLUMN_WIDTH,
|
|
32
|
+
MIN_RIGHT_COLUMN_WIDTH,
|
|
30
33
|
RIGHT_COLUMN_WIDTH_STORAGE_KEY,
|
|
31
34
|
} from '@editor/utils/const';
|
|
32
35
|
|
|
@@ -54,6 +57,9 @@ const state = shallowReactive<UiState>({
|
|
|
54
57
|
storageService.getItem(RIGHT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) ||
|
|
55
58
|
DEFAULT_RIGHT_COLUMN_WIDTH,
|
|
56
59
|
},
|
|
60
|
+
minLeftColumnWidth: MIN_LEFT_COLUMN_WIDTH,
|
|
61
|
+
minCenterColumnWidth: MIN_CENTER_COLUMN_WIDTH,
|
|
62
|
+
minRightColumnWidth: MIN_RIGHT_COLUMN_WIDTH,
|
|
57
63
|
showGuides: true,
|
|
58
64
|
hasGuides: false,
|
|
59
65
|
showRule: true,
|
package/src/theme/event.scss
CHANGED
|
@@ -25,10 +25,18 @@
|
|
|
25
25
|
.m-editor-float-box-body {
|
|
26
26
|
overflow: auto;
|
|
27
27
|
flex: 1;
|
|
28
|
-
padding: 0 16px;
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
.m-editor-floating-box-moveable {
|
|
33
32
|
opacity: 0;
|
|
34
33
|
}
|
|
34
|
+
|
|
35
|
+
.m-editor-float-box-drag-mask {
|
|
36
|
+
position: fixed;
|
|
37
|
+
top: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
width: 100%;
|
|
40
|
+
height: 100%;
|
|
41
|
+
background-color: transparent;
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.m-editor-layer-node-content {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
|
|
5
|
+
&.is-invalid .m-editor-layer-node-label {
|
|
6
|
+
color: var(--el-color-danger, #f56c6c);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.m-editor-layer-node-error-icon {
|
|
10
|
+
margin-left: 4px;
|
|
11
|
+
color: var(--el-color-danger, #f56c6c);
|
|
12
|
+
cursor: help;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.m-editor-scroll-bar {
|
|
2
|
+
position: absolute;
|
|
3
|
+
background-color: transparent;
|
|
4
|
+
opacity: 0.3;
|
|
5
|
+
transition:
|
|
6
|
+
background-color 0.2s linear,
|
|
7
|
+
opacity 0.2s linear;
|
|
8
|
+
|
|
9
|
+
.m-editor-scroll-bar-thumb {
|
|
10
|
+
background-color: #aaa;
|
|
11
|
+
border-radius: 6px;
|
|
12
|
+
position: absolute;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&.horizontal {
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 15px;
|
|
18
|
+
bottom: 0;
|
|
19
|
+
|
|
20
|
+
.m-editor-scroll-bar-thumb {
|
|
21
|
+
height: 6px;
|
|
22
|
+
transition:
|
|
23
|
+
background-color 0.2s linear,
|
|
24
|
+
height 0.2s ease-in-out;
|
|
25
|
+
bottom: 2px;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.vertical {
|
|
30
|
+
height: 100%;
|
|
31
|
+
width: 15px;
|
|
32
|
+
right: 5px;
|
|
33
|
+
|
|
34
|
+
.m-editor-scroll-bar-thumb {
|
|
35
|
+
width: 6px;
|
|
36
|
+
transition:
|
|
37
|
+
background-color 0.2s linear,
|
|
38
|
+
width 0.2s ease-in-out;
|
|
39
|
+
right: 2px;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:hover,
|
|
44
|
+
&:focus {
|
|
45
|
+
background-color: #eee;
|
|
46
|
+
opacity: 0.9;
|
|
47
|
+
|
|
48
|
+
.m-editor-scroll-bar-thumb {
|
|
49
|
+
background-color: #999;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&.horizontal {
|
|
53
|
+
.m-editor-scroll-bar-thumb {
|
|
54
|
+
height: 11px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&.vertical {
|
|
59
|
+
.m-editor-scroll-bar-thumb {
|
|
60
|
+
width: 11px;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/theme/theme.scss
CHANGED
package/src/type.ts
CHANGED
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
MPage,
|
|
34
34
|
MPageFragment,
|
|
35
35
|
} from '@tmagic/core';
|
|
36
|
+
import type { FieldSize } from '@tmagic/design';
|
|
36
37
|
import type { ChangeRecord, FormConfig, FormState, TableColumnConfig, TypeFunction } from '@tmagic/form';
|
|
37
38
|
import type StageCore from '@tmagic/stage';
|
|
38
39
|
import type {
|
|
@@ -76,7 +77,7 @@ export interface FrameworkSlots {
|
|
|
76
77
|
empty(props: {}): any;
|
|
77
78
|
workspace(props: {}): any;
|
|
78
79
|
'props-panel'(props: {}): any;
|
|
79
|
-
|
|
80
|
+
footer(props: {}): any;
|
|
80
81
|
'page-bar'(props: {}): any;
|
|
81
82
|
'page-bar-add-button'(props: {}): any;
|
|
82
83
|
'page-bar-title'(props: { page: MPage | MPageFragment }): any;
|
|
@@ -209,6 +210,22 @@ export interface StageOptions {
|
|
|
209
210
|
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
/**
|
|
214
|
+
* 节点校验错误信息,按来源(属性表单 / 样式表单)分别保存错误文案。
|
|
215
|
+
* 属性表单与样式表单是两个独立的 FormPanel,均指向同一节点,故以来源为键,
|
|
216
|
+
* 避免某个面板校验通过时误清另一个面板记录的错误。
|
|
217
|
+
* 节点视为存在错误当且仅当任一来源存在非空文本。
|
|
218
|
+
*/
|
|
219
|
+
export interface NodeInvalidInfo {
|
|
220
|
+
/** 属性表单校验错误文案(可能为包含 <br> 的 HTML) */
|
|
221
|
+
props?: string;
|
|
222
|
+
/** 样式表单校验错误文案(可能为包含 <br> 的 HTML) */
|
|
223
|
+
style?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** 节点校验错误来源 */
|
|
227
|
+
export type NodeInvalidSource = keyof NodeInvalidInfo;
|
|
228
|
+
|
|
212
229
|
export interface StoreState {
|
|
213
230
|
root: MApp | null;
|
|
214
231
|
page: MPage | MPageFragment | null;
|
|
@@ -219,6 +236,8 @@ export interface StoreState {
|
|
|
219
236
|
stage: StageCore | null;
|
|
220
237
|
stageLoading: boolean;
|
|
221
238
|
modifiedNodeIds: Map<Id, Id>;
|
|
239
|
+
/** 校验失败的节点错误信息,按节点 id 存储,供组件树标记与保存拦截读取 */
|
|
240
|
+
invalidNodeIds: Map<Id, NodeInvalidInfo>;
|
|
222
241
|
pageLength: number;
|
|
223
242
|
pageFragmentLength: number;
|
|
224
243
|
disabledMultiSelect: boolean;
|
|
@@ -296,6 +315,12 @@ export interface UiState {
|
|
|
296
315
|
stageRect: StageRect;
|
|
297
316
|
/** 编辑器列布局每一列的宽度,分为左中右三列 */
|
|
298
317
|
columnWidth: GetColumnWidth;
|
|
318
|
+
/** 编辑器列布局左侧列最小宽度 */
|
|
319
|
+
minLeftColumnWidth: number;
|
|
320
|
+
/** 编辑器列布局中间列最小宽度 */
|
|
321
|
+
minCenterColumnWidth: number;
|
|
322
|
+
/** 编辑器列布局右侧列最小宽度 */
|
|
323
|
+
minRightColumnWidth: number;
|
|
299
324
|
/** 是否显示画布参考线,true: 显示,false: 不显示,默认为true */
|
|
300
325
|
showGuides: boolean;
|
|
301
326
|
/** 画布上是否存在参考线 */
|
|
@@ -844,6 +869,10 @@ export interface StepExtra {
|
|
|
844
869
|
selectedAfter?: Id[];
|
|
845
870
|
/** 本次操作涉及的节点 id 集合(page 类型) */
|
|
846
871
|
modifiedNodeIds?: Map<Id, Id>;
|
|
872
|
+
/** 操作前的节点校验错误快照,撤销后还原(使撤销一个「校验失败」的改动后错误消失) */
|
|
873
|
+
invalidNodeIdsBefore?: Map<Id, NodeInvalidInfo>;
|
|
874
|
+
/** 操作后的节点校验错误快照,重做后还原(使重做后错误恢复) */
|
|
875
|
+
invalidNodeIdsAfter?: Map<Id, NodeInvalidInfo>;
|
|
847
876
|
[key: string]: any;
|
|
848
877
|
}
|
|
849
878
|
// #endregion StepExtra
|
|
@@ -1200,9 +1229,24 @@ export interface PageBarSortOptions extends PartSortableOptions {
|
|
|
1200
1229
|
}
|
|
1201
1230
|
// #endregion PageBarSortOptions
|
|
1202
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* 右键菜单当前目标(侧栏树节点等)。
|
|
1234
|
+
* 数据源 / 代码块面板通过 `customContentMenu` 的 `getTarget` 暴露,业务在 handler 内自行读取。
|
|
1235
|
+
*/
|
|
1236
|
+
export interface ContentMenuTarget {
|
|
1237
|
+
/** 目标 ID */
|
|
1238
|
+
id: string;
|
|
1239
|
+
/** 原始节点数据(树节点等) */
|
|
1240
|
+
data?: TreeNodeData;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
export type ContentMenuType = 'layer' | 'data-source' | 'viewer' | 'code-block';
|
|
1244
|
+
|
|
1203
1245
|
export type CustomContentMenuFunction = (
|
|
1204
1246
|
menus: (MenuButton | MenuComponent)[],
|
|
1205
|
-
type:
|
|
1247
|
+
type: ContentMenuType,
|
|
1248
|
+
/** 读取当前右键目标;数据源 / 代码块面板会传入,图层 / 画布一般不需要 */
|
|
1249
|
+
getTarget?: () => ContentMenuTarget | null,
|
|
1206
1250
|
) => (MenuButton | MenuComponent)[];
|
|
1207
1251
|
|
|
1208
1252
|
export interface EditorEvents {
|
|
@@ -1225,6 +1269,8 @@ export interface EditorEvents {
|
|
|
1225
1269
|
* add / remove / update 触发本事件;如需区分「用户操作」与「撤销重做」请配合 `history-change`。
|
|
1226
1270
|
*/
|
|
1227
1271
|
change: [event: EditorChangeEvent];
|
|
1272
|
+
/** 节点校验错误状态发生变化时触发,携带当前完整的错误 Map(供非响应式消费方订阅) */
|
|
1273
|
+
'invalid-node-change': [invalidNodeIds: Map<Id, NodeInvalidInfo>];
|
|
1228
1274
|
}
|
|
1229
1275
|
|
|
1230
1276
|
// #region EditorChangeEvent
|
|
@@ -1352,6 +1398,14 @@ export interface DiffDialogPayload {
|
|
|
1352
1398
|
targetLabel?: string;
|
|
1353
1399
|
/** 用于标题展示的目标 id */
|
|
1354
1400
|
id?: string | number;
|
|
1401
|
+
/**
|
|
1402
|
+
* 指定打开时的初始对比模式:
|
|
1403
|
+
* - before:该步骤修改前 vs 修改后
|
|
1404
|
+
* - current:该步骤修改后 vs 当前最新值
|
|
1405
|
+
* 不传时按是否存在「修改后的值」/「当前值」自动推断。
|
|
1406
|
+
* 若指定的模式当前不可用(对应数据缺失),将回退到自动推断结果。
|
|
1407
|
+
*/
|
|
1408
|
+
mode?: 'before' | 'current';
|
|
1355
1409
|
}
|
|
1356
1410
|
|
|
1357
1411
|
/**
|
|
@@ -1408,6 +1462,12 @@ export interface UseHistoryRevertOptions {
|
|
|
1408
1462
|
* 以保证两处 filterFunction 读取到一致的运行态上下文。
|
|
1409
1463
|
*/
|
|
1410
1464
|
getPropsPanelFormState?: () => FormState | undefined;
|
|
1465
|
+
/**
|
|
1466
|
+
* 内置页面 / 数据源 / 代码块的差异 / 回滚确认弹窗默认宽度(透传给 TMagicDialog 的 `width`),
|
|
1467
|
+
* 如 `'1200px'` / `'80%'`。缺省时使用弹窗内置默认宽度(900px)。
|
|
1468
|
+
* 业务自有历史(`viewDiff` / `confirmAndRevert`)可在调用时通过各自入参的 `width` 单独覆盖。
|
|
1469
|
+
*/
|
|
1470
|
+
dialogWidth?: string;
|
|
1411
1471
|
}
|
|
1412
1472
|
|
|
1413
1473
|
/**
|
|
@@ -1428,6 +1488,16 @@ export interface CustomDiffFormOptions {
|
|
|
1428
1488
|
* 对比弹窗会用它覆盖 CompareForm 中同名扩展字段,避免上下文不一致。
|
|
1429
1489
|
*/
|
|
1430
1490
|
compareFormState?: FormState;
|
|
1491
|
+
/**
|
|
1492
|
+
* 差异 / 确认回滚弹窗宽度(透传给 HistoryDiffDialog 的 TMagicDialog `width`),
|
|
1493
|
+
* 如 `'1200px'` / `'80%'`。缺省时使用弹窗内置默认宽度(900px)。
|
|
1494
|
+
*/
|
|
1495
|
+
width?: string;
|
|
1496
|
+
/**
|
|
1497
|
+
* 差异 / 确认回滚弹窗内 form 表单的尺寸(透传给 CompareForm 的 `size`),
|
|
1498
|
+
* 可选 `'large' | 'default' | 'small'`,缺省时使用表单内置默认尺寸。
|
|
1499
|
+
*/
|
|
1500
|
+
size?: FieldSize;
|
|
1431
1501
|
}
|
|
1432
1502
|
|
|
1433
1503
|
/**
|
|
@@ -235,19 +235,37 @@ export const getCascaderOptionsFromFields = (
|
|
|
235
235
|
return result;
|
|
236
236
|
};
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
238
|
+
/**
|
|
239
|
+
* 按字段名路径下钻 DataSchema。
|
|
240
|
+
* @param skipNumberIndices 为 true 时跳过数字段(模板路径中的数组下标,如 arr[0].x)
|
|
241
|
+
*/
|
|
242
|
+
export const resolveFieldByPath = (
|
|
243
|
+
fields: DataSchema[] | undefined,
|
|
244
|
+
fieldNames: string[],
|
|
245
|
+
options: { skipNumberIndices?: boolean } = {},
|
|
246
|
+
): { ok: boolean; field?: DataSchema; fields: DataSchema[] } => {
|
|
247
|
+
let currentFields = fields || [];
|
|
248
|
+
let field: DataSchema | undefined;
|
|
247
249
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
+
for (const name of fieldNames) {
|
|
251
|
+
if (options.skipNumberIndices && isNumber(name)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!currentFields.length) {
|
|
255
|
+
return { ok: false, fields: currentFields };
|
|
256
|
+
}
|
|
257
|
+
field = currentFields.find((item) => item.name === name);
|
|
258
|
+
if (!field) {
|
|
259
|
+
return { ok: false, fields: currentFields };
|
|
260
|
+
}
|
|
261
|
+
currentFields = field.fields || [];
|
|
250
262
|
}
|
|
251
263
|
|
|
252
|
-
return
|
|
264
|
+
return { field, ok: true, fields: currentFields };
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const getFieldType = (ds: DataSourceSchema | undefined, fieldNames: string[]) => {
|
|
268
|
+
const { ok, field } = resolveFieldByPath(ds?.fields, fieldNames);
|
|
269
|
+
if (!ok) return '';
|
|
270
|
+
return field?.type || '';
|
|
253
271
|
};
|