@tmagic/editor 1.8.0-beta.23 → 1.8.0-beta.24
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 +5 -4
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +10 -6
- 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 +4 -1
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +6 -4
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/hooks/use-compare-form.js +1 -2
- package/dist/es/index.js +2 -2
- package/dist/es/plugin.js +9 -1
- package/dist/es/services/editor.js +43 -37
- package/dist/es/services/props.js +1 -1
- package/dist/es/utils/event.js +1 -1
- package/dist/es/utils/props.js +1 -29
- package/dist/tmagic-editor.umd.cjs +373 -368
- package/package.json +8 -8
- package/src/fields/CodeSelectCol.vue +4 -2
- package/src/fields/DataSourceFieldSelect/Index.vue +5 -3
- package/src/fields/DataSourceFields.vue +6 -0
- package/src/fields/DataSourceMethodSelect.vue +4 -1
- package/src/fields/DataSourceMethods.vue +11 -2
- package/src/fields/DataSourceMocks.vue +10 -1
- package/src/fields/EventSelect.vue +3 -3
- package/src/fields/KeyValue.vue +4 -3
- package/src/hooks/use-compare-form.ts +1 -4
- package/src/plugin.ts +9 -1
- package/src/services/editor.ts +53 -85
- package/src/type.ts +23 -0
- package/src/utils/event.ts +2 -2
- package/src/utils/props.ts +0 -32
- package/types/index.d.ts +38 -60
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.24",
|
|
3
3
|
"name": "@tmagic/editor",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"moveable": "^0.53.0",
|
|
59
59
|
"serialize-javascript": "^7.0.7",
|
|
60
60
|
"sortablejs": "^1.15.7",
|
|
61
|
-
"@tmagic/design": "1.8.0-beta.
|
|
62
|
-
"@tmagic/
|
|
63
|
-
"@tmagic/utils": "1.8.0-beta.
|
|
64
|
-
"@tmagic/
|
|
65
|
-
"@tmagic/
|
|
61
|
+
"@tmagic/design": "1.8.0-beta.24",
|
|
62
|
+
"@tmagic/form": "1.8.0-beta.24",
|
|
63
|
+
"@tmagic/utils": "1.8.0-beta.24",
|
|
64
|
+
"@tmagic/stage": "1.8.0-beta.24",
|
|
65
|
+
"@tmagic/table": "1.8.0-beta.24"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/events": "^3.0.3",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"monaco-editor": "^0.55.1 ",
|
|
76
76
|
"type-fest": "^5.2.0",
|
|
77
77
|
"typescript": "^6.0.3",
|
|
78
|
-
"vue": "^3.5.
|
|
79
|
-
"@tmagic/core": "1.8.0-beta.
|
|
78
|
+
"vue": "^3.5.41",
|
|
79
|
+
"@tmagic/core": "1.8.0-beta.24"
|
|
80
80
|
},
|
|
81
81
|
"peerDependenciesMeta": {
|
|
82
82
|
"typescript": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
:prop="prop"
|
|
16
16
|
></MFormContainer>
|
|
17
17
|
<MSelect
|
|
18
|
-
v-else
|
|
18
|
+
v-else-if="!silentMode"
|
|
19
19
|
class="select"
|
|
20
20
|
:config="selectConfig"
|
|
21
21
|
:name="name"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<!-- 查看/编辑按钮:对比模式为只读,不展示 -->
|
|
29
29
|
<TMagicButton
|
|
30
|
-
v-if="!isCompareMode && model[name] && hasCodeBlockSidePanel"
|
|
30
|
+
v-if="!isCompareMode && !silentMode && model[name] && hasCodeBlockSidePanel"
|
|
31
31
|
class="m-fields-select-action-button"
|
|
32
32
|
:size="size"
|
|
33
33
|
@click="editCode(model[name])"
|
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
createValues,
|
|
66
66
|
type FieldProps,
|
|
67
67
|
filterFunction,
|
|
68
|
+
FORM_SILENT_MODE_KEY,
|
|
68
69
|
type FormItemConfig,
|
|
69
70
|
type FormState,
|
|
70
71
|
MContainer as MFormContainer,
|
|
@@ -83,6 +84,7 @@ defineOptions({
|
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
const mForm = inject<FormState | undefined>('mForm');
|
|
87
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
86
88
|
const { codeBlockService, uiService } = useServices();
|
|
87
89
|
const eventBus = inject<EventBus>('eventBus');
|
|
88
90
|
const emit = defineEmits<{
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
}"
|
|
7
7
|
>
|
|
8
8
|
<FieldSelect
|
|
9
|
-
v-if="isSelectValid"
|
|
9
|
+
v-if="isSelectValid && !silentMode"
|
|
10
10
|
:model-value="model[name]"
|
|
11
11
|
:disabled="disabled"
|
|
12
12
|
:size="size"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
></FieldSelect>
|
|
19
19
|
|
|
20
20
|
<component
|
|
21
|
-
v-else
|
|
21
|
+
v-else-if="!silentMode"
|
|
22
22
|
:is="tagName"
|
|
23
23
|
:config="config.fieldConfig"
|
|
24
24
|
:model="model"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
></component>
|
|
34
34
|
|
|
35
35
|
<TMagicTooltip
|
|
36
|
-
v-if="config.fieldConfig && !disabledDataSource && !mForm?.isCompare"
|
|
36
|
+
v-if="!silentMode && config.fieldConfig && !disabledDataSource && !mForm?.isCompare"
|
|
37
37
|
:disabled="showDataSourceFieldSelect"
|
|
38
38
|
content="选择数据源"
|
|
39
39
|
>
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
type ContainerChangeEventData,
|
|
58
58
|
type DataSourceFieldSelectConfig,
|
|
59
59
|
type FieldProps,
|
|
60
|
+
FORM_SILENT_MODE_KEY,
|
|
60
61
|
type FormState,
|
|
61
62
|
getFormField,
|
|
62
63
|
} from '@tmagic/form';
|
|
@@ -104,6 +105,7 @@ watch(
|
|
|
104
105
|
|
|
105
106
|
const { dataSourceService, propsService } = useServices();
|
|
106
107
|
const mForm = inject<FormState | undefined>('mForm');
|
|
108
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
107
109
|
|
|
108
110
|
const dataSources = computed(() => dataSourceService.get('dataSources') || []);
|
|
109
111
|
const disabledDataSource = computed(() => propsService.getDisabledDataSource());
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<FloatingBox
|
|
11
|
+
v-if="!silentMode"
|
|
11
12
|
:body-style="{ padding: '0 16px' }"
|
|
12
13
|
v-model:visible="addDialogVisible"
|
|
13
14
|
v-model:width="width"
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
</FloatingBox>
|
|
31
32
|
|
|
32
33
|
<FloatingBox
|
|
34
|
+
v-if="!silentMode"
|
|
33
35
|
:body-style="{ padding: '0 16px' }"
|
|
34
36
|
v-model:visible="addFromJsonDialogVisible"
|
|
35
37
|
v-model:width="width"
|
|
@@ -60,6 +62,7 @@ import {
|
|
|
60
62
|
type ContainerChangeEventData,
|
|
61
63
|
type DataSourceFieldsConfig,
|
|
62
64
|
type FieldProps,
|
|
65
|
+
FORM_SILENT_MODE_KEY,
|
|
63
66
|
type FormConfig,
|
|
64
67
|
type FormState,
|
|
65
68
|
MFormBox,
|
|
@@ -89,6 +92,7 @@ const emit = defineEmits<{
|
|
|
89
92
|
|
|
90
93
|
const { uiService } = useServices();
|
|
91
94
|
const mForm = inject<FormState | undefined>('mForm');
|
|
95
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
92
96
|
|
|
93
97
|
/** 对比模式下隐藏新增/编辑/删除等操作按钮,仅保留只读展示。 */
|
|
94
98
|
const isCompare = computed(() => Boolean(mForm?.isCompare));
|
|
@@ -379,6 +383,8 @@ provide(
|
|
|
379
383
|
);
|
|
380
384
|
|
|
381
385
|
onMounted(() => {
|
|
386
|
+
if (silentMode) return;
|
|
387
|
+
|
|
382
388
|
const path = editingFieldPath.value;
|
|
383
389
|
if (!path.length) return;
|
|
384
390
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<div class="m-fields-data-source-method-select">
|
|
3
3
|
<div class="data-source-method-select-container">
|
|
4
4
|
<MCascader
|
|
5
|
+
v-if="!silentMode"
|
|
5
6
|
class="select"
|
|
6
7
|
:config="cascaderConfig"
|
|
7
8
|
:model="model"
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
></MCascader>
|
|
14
15
|
|
|
15
16
|
<TMagicTooltip
|
|
16
|
-
v-if="model[name] && isCustomMethod && dataSourceSidePanel && !isCompare"
|
|
17
|
+
v-if="!silentMode && model[name] && isCustomMethod && dataSourceSidePanel && !isCompare"
|
|
17
18
|
:content="notEditable ? '查看' : '编辑'"
|
|
18
19
|
>
|
|
19
20
|
<TMagicButton class="m-fields-select-action-button" :size="size" @click="editCodeHandler">
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
type DataSourceMethodSelectConfig,
|
|
49
50
|
type FieldProps,
|
|
50
51
|
filterFunction,
|
|
52
|
+
FORM_SILENT_MODE_KEY,
|
|
51
53
|
type FormItemConfig,
|
|
52
54
|
type FormState,
|
|
53
55
|
MCascader,
|
|
@@ -67,6 +69,7 @@ defineOptions({
|
|
|
67
69
|
|
|
68
70
|
const { dataSourceService, uiService } = useServices();
|
|
69
71
|
const mForm = inject<FormState | undefined>('mForm');
|
|
72
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
70
73
|
const eventBus = inject<EventBus>('eventBus');
|
|
71
74
|
|
|
72
75
|
const emit = defineEmits(['change']);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<CodeBlockEditor
|
|
12
|
-
v-if="codeConfig"
|
|
12
|
+
v-if="codeConfig && !silentMode"
|
|
13
13
|
ref="codeBlockEditor"
|
|
14
14
|
:disabled="disabled"
|
|
15
15
|
:content="codeConfig"
|
|
@@ -26,7 +26,13 @@ import { cloneDeep } from 'lodash-es';
|
|
|
26
26
|
|
|
27
27
|
import type { CodeBlockContent } from '@tmagic/core';
|
|
28
28
|
import { TMagicButton, tMagicMessageBox } from '@tmagic/design';
|
|
29
|
-
import
|
|
29
|
+
import {
|
|
30
|
+
type ContainerChangeEventData,
|
|
31
|
+
type DataSourceMethodsConfig,
|
|
32
|
+
type FieldProps,
|
|
33
|
+
FORM_SILENT_MODE_KEY,
|
|
34
|
+
type FormState,
|
|
35
|
+
} from '@tmagic/form';
|
|
30
36
|
import { type ColumnConfig, MagicTable } from '@tmagic/table';
|
|
31
37
|
|
|
32
38
|
import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
|
|
@@ -43,6 +49,7 @@ const props = withDefaults(defineProps<FieldProps<DataSourceMethodsConfig>>(), {
|
|
|
43
49
|
const emit = defineEmits(['change']);
|
|
44
50
|
|
|
45
51
|
const mForm = inject<FormState | undefined>('mForm');
|
|
52
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
46
53
|
|
|
47
54
|
/** 对比模式下隐藏新增/编辑/删除等操作按钮,仅保留只读展示。 */
|
|
48
55
|
const isCompare = computed(() => Boolean(mForm?.isCompare));
|
|
@@ -170,6 +177,8 @@ const editingMethodName = inject<ComputedRef<string | undefined>>(
|
|
|
170
177
|
);
|
|
171
178
|
|
|
172
179
|
onMounted(() => {
|
|
180
|
+
if (silentMode) return;
|
|
181
|
+
|
|
173
182
|
const methodName = editingMethodName.value;
|
|
174
183
|
if (!methodName) return;
|
|
175
184
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<FloatingBox
|
|
10
|
+
v-if="!silentMode"
|
|
10
11
|
:body-style="{ padding: '0 16px' }"
|
|
11
12
|
v-model:visible="addDialogVisible"
|
|
12
13
|
v-model:width="width"
|
|
@@ -34,7 +35,14 @@ import { computed, inject, Ref, ref } from 'vue';
|
|
|
34
35
|
|
|
35
36
|
import type { MockSchema } from '@tmagic/core';
|
|
36
37
|
import { TMagicButton, tMagicMessageBox, TMagicSwitch } from '@tmagic/design';
|
|
37
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
type DataSourceMocksConfig,
|
|
40
|
+
type FieldProps,
|
|
41
|
+
FORM_SILENT_MODE_KEY,
|
|
42
|
+
type FormConfig,
|
|
43
|
+
type FormState,
|
|
44
|
+
MFormBox,
|
|
45
|
+
} from '@tmagic/form';
|
|
38
46
|
import { type ColumnConfig, MagicTable } from '@tmagic/table';
|
|
39
47
|
import { getDefaultValueFromFields } from '@tmagic/utils';
|
|
40
48
|
|
|
@@ -57,6 +65,7 @@ const emit = defineEmits(['change']);
|
|
|
57
65
|
|
|
58
66
|
const { uiService } = useServices();
|
|
59
67
|
const mForm = inject<FormState | undefined>('mForm');
|
|
68
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
60
69
|
|
|
61
70
|
/** 对比模式下隐藏新增/编辑/删除等操作按钮,仅保留只读展示。 */
|
|
62
71
|
const isCompare = computed(() => Boolean(mForm?.isCompare));
|
|
@@ -85,7 +85,7 @@ import { Delete } from '@element-plus/icons-vue';
|
|
|
85
85
|
import { Plus } from '@element-plus/icons-vue';
|
|
86
86
|
import { has } from 'lodash-es';
|
|
87
87
|
|
|
88
|
-
import { ActionType } from '@tmagic/core';
|
|
88
|
+
import { ActionType, MNode } from '@tmagic/core';
|
|
89
89
|
import { TMagicButton } from '@tmagic/design';
|
|
90
90
|
import type {
|
|
91
91
|
CodeSelectColConfig,
|
|
@@ -327,8 +327,8 @@ const tableConfig = computed(
|
|
|
327
327
|
name: 'method',
|
|
328
328
|
label: '动作',
|
|
329
329
|
type: compActionConfig.value.type,
|
|
330
|
-
options: (mForm: FormState, { model }: any) => {
|
|
331
|
-
const node = editorService.getNodeById(model.to);
|
|
330
|
+
options: (mForm: FormState, { model, formValue }: any) => {
|
|
331
|
+
const node = editorService.getNodeById(model.to) || (formValue as MNode);
|
|
332
332
|
if (!node?.type) return [];
|
|
333
333
|
|
|
334
334
|
return eventsService.getMethod(node.type, { targetId: model.to, node }).map((option: any) => ({
|
package/src/fields/KeyValue.vue
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<MagicCodeEditor
|
|
47
|
-
v-if="config.advanced && showCode"
|
|
47
|
+
v-if="config.advanced && showCode && !silentMode"
|
|
48
48
|
editor-custom-type="m-fields-key-value"
|
|
49
49
|
language="javascript"
|
|
50
50
|
:init-values="model[name]"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
></MagicCodeEditor>
|
|
61
61
|
|
|
62
62
|
<TMagicButton
|
|
63
|
-
v-if="config.advanced && !isCompare"
|
|
63
|
+
v-if="config.advanced && !isCompare && !silentMode"
|
|
64
64
|
size="default"
|
|
65
65
|
:disabled="disabled"
|
|
66
66
|
link
|
|
@@ -75,7 +75,7 @@ import { computed, inject, ref, watchEffect } from 'vue';
|
|
|
75
75
|
import { Delete, Plus } from '@element-plus/icons-vue';
|
|
76
76
|
|
|
77
77
|
import { TMagicButton, TMagicInput } from '@tmagic/design';
|
|
78
|
-
import type
|
|
78
|
+
import { type FieldProps, FORM_SILENT_MODE_KEY, type FormState, type KeyValueConfig } from '@tmagic/form';
|
|
79
79
|
|
|
80
80
|
import CodeIcon from '@editor/icons/CodeIcon.vue';
|
|
81
81
|
import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
|
|
@@ -93,6 +93,7 @@ const emit = defineEmits<{
|
|
|
93
93
|
}>();
|
|
94
94
|
|
|
95
95
|
const mForm = inject<FormState | undefined>('mForm');
|
|
96
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
96
97
|
|
|
97
98
|
/** 对比模式下隐藏增删/代码切换等操作按钮,仅保留只读展示。 */
|
|
98
99
|
const isCompare = computed(() => Boolean(mForm?.isCompare));
|
|
@@ -23,7 +23,6 @@ import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/
|
|
|
23
23
|
|
|
24
24
|
import type { CompareFormBaseProps } from '@editor/type';
|
|
25
25
|
import { getCodeBlockFormConfig } from '@editor/utils/code-block';
|
|
26
|
-
import { removeStyleDisplayConfig } from '@editor/utils/props';
|
|
27
26
|
|
|
28
27
|
export interface UseCompareFormReturn {
|
|
29
28
|
config: Ref<FormConfig>;
|
|
@@ -107,9 +106,7 @@ export const useCompareForm = (props: CompareFormBaseProps): UseCompareFormRetur
|
|
|
107
106
|
if (!props.type) {
|
|
108
107
|
return [];
|
|
109
108
|
}
|
|
110
|
-
return
|
|
111
|
-
await props.services.propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode }),
|
|
112
|
-
);
|
|
109
|
+
return props.services.propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode });
|
|
113
110
|
}
|
|
114
111
|
case 'data-source': {
|
|
115
112
|
const config = props.services.dataSourceService.getFormConfig(props.type || 'base');
|
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, { registerTypeMatchRules } from '@tmagic/form';
|
|
24
|
+
import formPlugin, { registerSilentLeafFieldTypes, registerTypeMatchRules } from '@tmagic/form';
|
|
25
25
|
import tablePlugin from '@tmagic/table';
|
|
26
26
|
|
|
27
27
|
import Code from './fields/Code.vue';
|
|
@@ -68,6 +68,14 @@ export default {
|
|
|
68
68
|
app.use(formPlugin, opt || {});
|
|
69
69
|
app.use(tablePlugin);
|
|
70
70
|
registerTypeMatchRules(editorTypeMatchRules);
|
|
71
|
+
registerSilentLeafFieldTypes([
|
|
72
|
+
'vs-code',
|
|
73
|
+
'ui-select',
|
|
74
|
+
'cond-op-select',
|
|
75
|
+
'page-fragment-select',
|
|
76
|
+
'data-source-select',
|
|
77
|
+
'data-source-input',
|
|
78
|
+
]);
|
|
71
79
|
|
|
72
80
|
app.config.globalProperties.$TMAGIC_EDITOR = option;
|
|
73
81
|
setEditorConfig(option);
|
package/src/services/editor.ts
CHANGED
|
@@ -56,6 +56,7 @@ import type {
|
|
|
56
56
|
StepValue,
|
|
57
57
|
StoreState,
|
|
58
58
|
StoreStateKey,
|
|
59
|
+
UpdateOptions,
|
|
59
60
|
} from '@editor/type';
|
|
60
61
|
import { canUsePluginMethods, LayerOffset, Layout } from '@editor/type';
|
|
61
62
|
import {
|
|
@@ -83,6 +84,10 @@ import { beforePaste, getAddParent } from '@editor/utils/operator';
|
|
|
83
84
|
|
|
84
85
|
type MoveItem = { node: MNode; parent: MContainer; pageForOp: { name: string; id: Id } | null };
|
|
85
86
|
|
|
87
|
+
/** 历史插回时把记录的下标收敛到 [0, length],越界(含未记录)一律追加到末尾 */
|
|
88
|
+
const clampIndex = (index: number | undefined, length: number): number =>
|
|
89
|
+
typeof index === 'number' && index >= 0 && index <= length ? index : length;
|
|
90
|
+
|
|
86
91
|
/**
|
|
87
92
|
* 把「变更前后节点快照」列表归一成 update 类型的 {@link StepDiffItem} 列表,供 {@link StepValue.diff} 使用。
|
|
88
93
|
* `changeRecords` 来自 form 端的 propPath/value 列表,撤销/重做时只对这些 propPath 做局部更新;
|
|
@@ -126,6 +131,11 @@ class Editor extends BaseService {
|
|
|
126
131
|
* 普通操作不会读取它,调用前由 *AndGetHistoryId 重置为 null。
|
|
127
132
|
*/
|
|
128
133
|
private lastPushedHistoryId: string | null = null;
|
|
134
|
+
/**
|
|
135
|
+
* 上一次 doAdd 的插入记忆:nodeId 为插入的节点,selectedId 为当时的选中节点。
|
|
136
|
+
* 仅在选中节点未变化时复用(连续 add / doNotSelect / 批量粘贴),选中变化后自动失效,无需手动清理。
|
|
137
|
+
*/
|
|
138
|
+
private lastAdded: { selectedId: Id; nodeId: Id } | null = null;
|
|
129
139
|
|
|
130
140
|
constructor() {
|
|
131
141
|
super(
|
|
@@ -404,7 +414,7 @@ class Editor extends BaseService {
|
|
|
404
414
|
this.set('highlightNode', null);
|
|
405
415
|
}
|
|
406
416
|
|
|
407
|
-
public async doAdd(node: MNode, parent: MContainer): Promise<MNode> {
|
|
417
|
+
public async doAdd(node: MNode, parent: MContainer, _options: DslOpOptions = {}): Promise<MNode> {
|
|
408
418
|
const root = this.get('root');
|
|
409
419
|
|
|
410
420
|
if (!root) throw new Error('root为空');
|
|
@@ -418,13 +428,16 @@ class Editor extends BaseService {
|
|
|
418
428
|
throw new Error('app下不能添加组件');
|
|
419
429
|
}
|
|
420
430
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
431
|
+
// 连续 add 时接在上一个新增节点之后,否则接在当前选中节点之后;
|
|
432
|
+
// 锚点节点可能已不在 items 中(被删 / DSL 整体替换),回退到当前选中节点,都不在则追加到末尾
|
|
433
|
+
const anchorId = this.lastAdded?.selectedId === curNode.id ? this.lastAdded.nodeId : curNode.id;
|
|
434
|
+
const anchorIndex = isPageOrFragment(node)
|
|
435
|
+
? -1
|
|
436
|
+
: Math.max(getNodeIndex(anchorId, parent), getNodeIndex(curNode.id, parent));
|
|
437
|
+
const insertIndex = anchorIndex < 0 ? parent.items.length : anchorIndex + 1;
|
|
438
|
+
|
|
439
|
+
parent.items.splice(insertIndex, 0, node);
|
|
440
|
+
this.lastAdded = { selectedId: curNode.id, nodeId: node.id };
|
|
428
441
|
|
|
429
442
|
const layout = await this.getLayout(toRaw(parent), node as MNode);
|
|
430
443
|
node.style = getInitPositionStyle(node.style, layout);
|
|
@@ -434,6 +447,7 @@ class Editor extends BaseService {
|
|
|
434
447
|
parent: cloneDeep(parent),
|
|
435
448
|
parentId: parent.id,
|
|
436
449
|
root: cloneDeep(root),
|
|
450
|
+
index: insertIndex,
|
|
437
451
|
});
|
|
438
452
|
|
|
439
453
|
const newStyle = fixNodePosition(node, parent, stage);
|
|
@@ -452,24 +466,23 @@ class Editor extends BaseService {
|
|
|
452
466
|
* 向指点容器添加组件节点
|
|
453
467
|
* @param addConfig 将要添加的组件节点配置
|
|
454
468
|
* @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
|
|
455
|
-
* @param options
|
|
456
|
-
* @param options.doNotSelect 添加后是否不更新当前选中节点(默认 false,添加后会选中新增的节点)
|
|
457
|
-
* @param options.doNotSwitchPage 添加后是否不切换当前页面(默认 false;新增页面 / 跨页新增时为 true 会跳过会引发页面切换的选中操作)
|
|
458
|
-
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
469
|
+
* @param options 可选配置,见 {@link DslOpOptions}
|
|
459
470
|
* @returns 添加后的节点
|
|
460
471
|
*/
|
|
461
472
|
public async add(
|
|
462
473
|
addNode: AddMNode | MNode[],
|
|
463
474
|
parent?: MContainer | null,
|
|
464
|
-
{
|
|
475
|
+
options: DslOpOptions = {},
|
|
476
|
+
): Promise<MNode | MNode[]> {
|
|
477
|
+
this.captureSelectionBeforeOp();
|
|
478
|
+
|
|
479
|
+
const {
|
|
465
480
|
doNotSelect = false,
|
|
466
481
|
doNotSwitchPage = false,
|
|
467
482
|
doNotPushHistory = false,
|
|
468
483
|
historyDescription,
|
|
469
484
|
historySource,
|
|
470
|
-
}
|
|
471
|
-
): Promise<MNode | MNode[]> {
|
|
472
|
-
this.captureSelectionBeforeOp();
|
|
485
|
+
} = options;
|
|
473
486
|
|
|
474
487
|
const stage = this.get('stage');
|
|
475
488
|
|
|
@@ -485,17 +498,15 @@ class Editor extends BaseService {
|
|
|
485
498
|
addNodes.push(...addNode);
|
|
486
499
|
}
|
|
487
500
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}),
|
|
498
|
-
);
|
|
501
|
+
// 必须串行:每个节点的插入位置依赖上一个已插入的节点,并行时插入顺序会受
|
|
502
|
+
// doAdd 前置耗时(如异步 beforeDoAdd 插件钩子)影响而错乱
|
|
503
|
+
const newNodes: MNode[] = [];
|
|
504
|
+
for (const node of addNodes) {
|
|
505
|
+
const root = this.get('root');
|
|
506
|
+
const parentNode = isPageOrFragment(node) && root ? root : (parent ?? getAddParent(node));
|
|
507
|
+
if (!parentNode) throw new Error('未找到父元素');
|
|
508
|
+
newNodes.push(await this.doAdd(node, parentNode, options));
|
|
509
|
+
}
|
|
499
510
|
|
|
500
511
|
if (newNodes.length > 1) {
|
|
501
512
|
// 多选时只要任一新增节点位于非当前页面,触发的 multiSelect 就会引起页面切换
|
|
@@ -654,16 +665,8 @@ class Editor extends BaseService {
|
|
|
654
665
|
* @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
|
655
666
|
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
656
667
|
*/
|
|
657
|
-
public async remove(
|
|
658
|
-
|
|
659
|
-
{
|
|
660
|
-
doNotSelect = false,
|
|
661
|
-
doNotSwitchPage = false,
|
|
662
|
-
doNotPushHistory = false,
|
|
663
|
-
historyDescription,
|
|
664
|
-
historySource,
|
|
665
|
-
}: DslOpOptions = {},
|
|
666
|
-
): Promise<void> {
|
|
668
|
+
public async remove(nodeOrNodeList: MNode | MNode[], options: DslOpOptions = {}): Promise<void> {
|
|
669
|
+
const { doNotPushHistory = false, historyDescription, historySource } = options;
|
|
667
670
|
this.captureSelectionBeforeOp();
|
|
668
671
|
|
|
669
672
|
const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
|
|
@@ -690,7 +693,7 @@ class Editor extends BaseService {
|
|
|
690
693
|
}
|
|
691
694
|
}
|
|
692
695
|
|
|
693
|
-
await Promise.all(nodes.map((node) => this.doRemove(node,
|
|
696
|
+
await Promise.all(nodes.map((node) => this.doRemove(node, options)));
|
|
694
697
|
|
|
695
698
|
// 删除节点时同步清理其(含子树)的校验错误记录;置于 pushOpHistory 之前,使历史快照与本次删除对齐。
|
|
696
699
|
this.removeInvalidNodesBySubtree(nodes);
|
|
@@ -720,12 +723,9 @@ class Editor extends BaseService {
|
|
|
720
723
|
|
|
721
724
|
public async doUpdate(
|
|
722
725
|
config: MNode,
|
|
723
|
-
{
|
|
724
|
-
changeRecords = [],
|
|
725
|
-
historySource,
|
|
726
|
-
replace = false,
|
|
727
|
-
}: { changeRecords?: ChangeRecord[]; historySource?: HistoryOpSource; replace?: boolean } = {},
|
|
726
|
+
data: UpdateOptions = {},
|
|
728
727
|
): Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }> {
|
|
728
|
+
const { changeRecords = [], historySource, replace = false } = data;
|
|
729
729
|
const root = this.get('root');
|
|
730
730
|
if (!root) throw new Error('root为空');
|
|
731
731
|
|
|
@@ -804,34 +804,10 @@ class Editor extends BaseService {
|
|
|
804
804
|
* 更新节点
|
|
805
805
|
* update后会触发依赖收集,收集完后会掉stage.update方法
|
|
806
806
|
* @param config 新的节点配置,配置中需要有id信息
|
|
807
|
-
* @param data
|
|
808
|
-
* @param data.changeRecords 单节点 form 端变更记录(多节点场景下被忽略,使用 changeRecordList)
|
|
809
|
-
* @param data.changeRecordList 多节点 form 端变更记录列表,按 config 数组同序对应每个节点;优先级高于 changeRecords
|
|
810
|
-
* @param data.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
811
|
-
* @param data.historyDescription 入栈时附带的人类可读描述,用于历史面板展示(不影响 undo/redo 行为)
|
|
812
|
-
* @param data.replace 是否整节点替换:为 true 时跳过 mergeWith / toggleFixedPosition / setChildrenLayout,直接用传入配置覆盖(默认 false)
|
|
807
|
+
* @param data 额外数据,见 {@link UpdateOptions}
|
|
813
808
|
* @returns 更新后的节点配置
|
|
814
809
|
*/
|
|
815
|
-
public async update(
|
|
816
|
-
config: MNode | MNode[],
|
|
817
|
-
data: {
|
|
818
|
-
changeRecords?: ChangeRecord[];
|
|
819
|
-
changeRecordList?: ChangeRecord[][];
|
|
820
|
-
doNotPushHistory?: boolean;
|
|
821
|
-
historyDescription?: string;
|
|
822
|
-
historySource?: HistoryOpSource;
|
|
823
|
-
/**
|
|
824
|
-
* 为 true 时不做深合并等变换,直接用传入配置整节点替换现有节点。
|
|
825
|
-
* 适用于源码编辑、历史整节点快照回放等「完整 DSL」场景;默认 false(局部属性更新走 merge)。
|
|
826
|
-
*/
|
|
827
|
-
replace?: boolean;
|
|
828
|
-
/**
|
|
829
|
-
* 属性面板提交时携带的校验错误信息,在写入历史记录之前落库,
|
|
830
|
-
* 使历史快照与本次变更对齐,从而 undo/redo 能正确还原错误标记。
|
|
831
|
-
*/
|
|
832
|
-
invalidInfo?: { id: Id; source: NodeInvalidSource; error?: string };
|
|
833
|
-
} = {},
|
|
834
|
-
): Promise<MNode | MNode[]> {
|
|
810
|
+
public async update(config: MNode | MNode[], data: UpdateOptions = {}): Promise<MNode | MNode[]> {
|
|
835
811
|
this.captureSelectionBeforeOp();
|
|
836
812
|
|
|
837
813
|
const {
|
|
@@ -840,7 +816,6 @@ class Editor extends BaseService {
|
|
|
840
816
|
changeRecords,
|
|
841
817
|
historyDescription,
|
|
842
818
|
historySource,
|
|
843
|
-
replace = false,
|
|
844
819
|
invalidInfo,
|
|
845
820
|
} = data;
|
|
846
821
|
|
|
@@ -851,7 +826,7 @@ class Editor extends BaseService {
|
|
|
851
826
|
const updateData = await Promise.all(
|
|
852
827
|
nodes.map((node, index) => {
|
|
853
828
|
const recordsForNode = changeRecordList ? (changeRecordList[index] ?? []) : (changeRecords ?? []);
|
|
854
|
-
return this.doUpdate(node, { changeRecords: recordsForNode
|
|
829
|
+
return this.doUpdate(node, { ...data, changeRecords: recordsForNode });
|
|
855
830
|
}),
|
|
856
831
|
);
|
|
857
832
|
|
|
@@ -1360,14 +1335,7 @@ class Editor extends BaseService {
|
|
|
1360
1335
|
/** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
1361
1336
|
public async updateAndGetHistoryId(
|
|
1362
1337
|
config: MNode | MNode[],
|
|
1363
|
-
data: {
|
|
1364
|
-
changeRecords?: ChangeRecord[];
|
|
1365
|
-
changeRecordList?: ChangeRecord[][];
|
|
1366
|
-
doNotPushHistory?: boolean;
|
|
1367
|
-
historyDescription?: string;
|
|
1368
|
-
historySource?: HistoryOpSource;
|
|
1369
|
-
replace?: boolean;
|
|
1370
|
-
} = {},
|
|
1338
|
+
data: UpdateOptions = {},
|
|
1371
1339
|
): Promise<DslOpWithHistoryIdsResult<MNode | MNode[]>> {
|
|
1372
1340
|
this.lastPushedHistoryId = null;
|
|
1373
1341
|
const result = await this.update(config, data);
|
|
@@ -1987,17 +1955,15 @@ class Editor extends BaseService {
|
|
|
1987
1955
|
const parent = this.getNodeById(parentId, false) as MContainer | null;
|
|
1988
1956
|
if (parent?.items) {
|
|
1989
1957
|
const addedNode = cloneDeep(newSchema);
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
} else {
|
|
1993
|
-
parent.items.push(addedNode);
|
|
1994
|
-
}
|
|
1958
|
+
const insertIndex = clampIndex(index, parent.items.length);
|
|
1959
|
+
parent.items.splice(insertIndex, 0, addedNode);
|
|
1995
1960
|
addedNodes.push(addedNode);
|
|
1996
1961
|
await stage?.add({
|
|
1997
1962
|
config: cloneDeep(newSchema),
|
|
1998
1963
|
parent: cloneDeep(parent),
|
|
1999
1964
|
parentId: parent.id,
|
|
2000
1965
|
root: cloneDeep(root),
|
|
1966
|
+
index: insertIndex,
|
|
2001
1967
|
});
|
|
2002
1968
|
}
|
|
2003
1969
|
}
|
|
@@ -2016,13 +1982,15 @@ class Editor extends BaseService {
|
|
|
2016
1982
|
const parent = this.getNodeById(parentId, false) as MContainer | null;
|
|
2017
1983
|
if (parent?.items) {
|
|
2018
1984
|
const addedNode = cloneDeep(oldSchema);
|
|
2019
|
-
|
|
1985
|
+
const insertIndex = clampIndex(index, parent.items.length);
|
|
1986
|
+
parent.items.splice(insertIndex, 0, addedNode);
|
|
2020
1987
|
addedNodes.push(addedNode);
|
|
2021
1988
|
await stage?.add({
|
|
2022
1989
|
config: cloneDeep(oldSchema),
|
|
2023
1990
|
parent: cloneDeep(parent),
|
|
2024
1991
|
parentId,
|
|
2025
1992
|
root: cloneDeep(root),
|
|
1993
|
+
index: insertIndex,
|
|
2026
1994
|
});
|
|
2027
1995
|
}
|
|
2028
1996
|
}
|
package/src/type.ts
CHANGED
|
@@ -1464,6 +1464,29 @@ export interface DslOpOptions extends HistoryOpOptions {
|
|
|
1464
1464
|
}
|
|
1465
1465
|
// #endregion DslOpOptions
|
|
1466
1466
|
|
|
1467
|
+
// #region UpdateOptions
|
|
1468
|
+
/**
|
|
1469
|
+
* {@link EditorService.update} / {@link EditorService.doUpdate} 的额外数据
|
|
1470
|
+
* - changeRecords: 单节点 form 端变更记录(多节点场景下被忽略,使用 changeRecordList)
|
|
1471
|
+
* - changeRecordList: 多节点 form 端变更记录列表,按 config 数组同序对应每个节点;优先级高于 changeRecords
|
|
1472
|
+
* - replace: 为 true 时跳过 mergeWith / toggleFixedPosition / setChildrenLayout,直接用传入配置整节点替换
|
|
1473
|
+
* - invalidInfo: 属性面板提交时携带的校验错误信息,在写入历史记录之前落库
|
|
1474
|
+
*/
|
|
1475
|
+
export interface UpdateOptions extends HistoryOpOptionsWithChangeRecords {
|
|
1476
|
+
changeRecordList?: ChangeRecord[][];
|
|
1477
|
+
/**
|
|
1478
|
+
* 为 true 时不做深合并等变换,直接用传入配置整节点替换现有节点。
|
|
1479
|
+
* 适用于源码编辑、历史整节点快照回放等「完整 DSL」场景;默认 false(局部属性更新走 merge)。
|
|
1480
|
+
*/
|
|
1481
|
+
replace?: boolean;
|
|
1482
|
+
/**
|
|
1483
|
+
* 属性面板提交时携带的校验错误信息,在写入历史记录之前落库,
|
|
1484
|
+
* 使历史快照与本次变更对齐,从而 undo/redo 能正确还原错误标记。
|
|
1485
|
+
*/
|
|
1486
|
+
invalidInfo?: { id: Id; source: NodeInvalidSource; error?: string };
|
|
1487
|
+
}
|
|
1488
|
+
// #endregion UpdateOptions
|
|
1489
|
+
|
|
1467
1490
|
/** 差异对话框的入参 */
|
|
1468
1491
|
export interface DiffDialogPayload {
|
|
1469
1492
|
/** 表单类别 */
|