@tmagic/editor 1.7.9 → 1.7.10
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 +3 -1
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +17 -7
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +38 -9
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +38 -5
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -0
- package/dist/es/fields/StyleSetter/pro/Transform.js +5 -0
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +54 -0
- package/dist/es/hooks/use-code-block-edit.js +3 -2
- package/dist/es/index.js +3 -3
- package/dist/es/initService.js +4 -4
- package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +64 -1
- package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -5
- package/dist/es/services/editor.js +191 -174
- package/dist/es/services/history.js +1 -9
- package/dist/es/type.js +28 -1
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/editor-history.js +100 -0
- package/dist/es/utils/editor.js +178 -4
- package/dist/es/utils/props.js +4 -1
- package/dist/es/utils/undo-redo.js +7 -4
- package/dist/tmagic-editor.umd.cjs +744 -234
- package/package.json +8 -8
- package/src/Editor.vue +1 -0
- package/src/components/CodeBlockEditor.vue +1 -1
- package/src/components/CodeParams.vue +23 -7
- package/src/editorProps.ts +2 -0
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +39 -6
- package/src/fields/DataSourceFieldSelect/Index.vue +1 -0
- package/src/fields/DataSourceMethodSelect.vue +54 -13
- package/src/fields/DataSourceMethods.vue +9 -5
- package/src/fields/StyleSetter/Index.vue +5 -1
- package/src/fields/StyleSetter/pro/Transform.vue +54 -0
- package/src/fields/StyleSetter/pro/index.ts +1 -0
- package/src/hooks/use-code-block-edit.ts +8 -4
- package/src/initService.ts +6 -6
- package/src/layouts/workspace/viewer/Stage.vue +89 -1
- package/src/layouts/workspace/viewer/StageOverlay.vue +1 -6
- package/src/services/editor.ts +231 -270
- package/src/services/history.ts +1 -9
- package/src/type.ts +65 -5
- package/src/utils/editor-history.ts +138 -0
- package/src/utils/editor.ts +249 -4
- package/src/utils/props.ts +4 -0
- package/src/utils/undo-redo.ts +7 -6
- package/types/index.d.ts +179 -53
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.7.
|
|
2
|
+
"version": "1.7.10",
|
|
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.0",
|
|
60
60
|
"sortablejs": "^1.15.6",
|
|
61
|
-
"@tmagic/design": "1.7.
|
|
62
|
-
"@tmagic/form": "1.7.
|
|
63
|
-
"@tmagic/
|
|
64
|
-
"@tmagic/
|
|
65
|
-
"@tmagic/
|
|
61
|
+
"@tmagic/design": "1.7.10",
|
|
62
|
+
"@tmagic/form": "1.7.10",
|
|
63
|
+
"@tmagic/table": "1.7.10",
|
|
64
|
+
"@tmagic/stage": "1.7.10",
|
|
65
|
+
"@tmagic/utils": "1.7.10"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/events": "^3.0.3",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"monaco-editor": "^0.55.1 ",
|
|
76
76
|
"type-fest": "^5.2.0",
|
|
77
|
-
"typescript": "^
|
|
77
|
+
"typescript": "^6.0.2",
|
|
78
78
|
"vue": "^3.5.24",
|
|
79
|
-
"@tmagic/core": "1.7.
|
|
79
|
+
"@tmagic/core": "1.7.10"
|
|
80
80
|
},
|
|
81
81
|
"peerDependenciesMeta": {
|
|
82
82
|
"typescript": {
|
package/src/Editor.vue
CHANGED
|
@@ -207,6 +207,7 @@ const stageOptions: StageOptions = {
|
|
|
207
207
|
renderType: props.renderType,
|
|
208
208
|
guidesOptions: props.guidesOptions,
|
|
209
209
|
disabledMultiSelect: props.disabledMultiSelect,
|
|
210
|
+
beforeDblclick: props.beforeDblclick,
|
|
210
211
|
};
|
|
211
212
|
|
|
212
213
|
stageOverlayService.set('stageOptions', stageOptions);
|
|
@@ -88,7 +88,7 @@ const width = defineModel<number>('width', { default: 670 });
|
|
|
88
88
|
const boxVisible = defineModel<boolean>('visible', { default: false });
|
|
89
89
|
|
|
90
90
|
const props = defineProps<{
|
|
91
|
-
content: CodeBlockContent;
|
|
91
|
+
content: Omit<CodeBlockContent, 'content'> & { content: string };
|
|
92
92
|
disabled?: boolean;
|
|
93
93
|
isDataSource?: boolean;
|
|
94
94
|
dataSourceType?: string;
|
|
@@ -46,13 +46,29 @@ const getFormConfig = (items: FormItemConfig[] = []) => [
|
|
|
46
46
|
|
|
47
47
|
const codeParamsConfig = computed(() =>
|
|
48
48
|
getFormConfig(
|
|
49
|
-
props.paramsConfig.map(({ name, text, extra, ...config }) =>
|
|
50
|
-
type
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
props.paramsConfig.map(({ name, text, extra, ...config }) => {
|
|
50
|
+
let { type } = config;
|
|
51
|
+
if (typeof type === 'function') {
|
|
52
|
+
type = type(undefined, {
|
|
53
|
+
model: props.model[props.name],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (type && ['data-source-field-select', 'vs-code'].includes(type)) {
|
|
57
|
+
return {
|
|
58
|
+
...config,
|
|
59
|
+
name,
|
|
60
|
+
text,
|
|
61
|
+
extra,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
type: 'data-source-field-select' as const,
|
|
66
|
+
name,
|
|
67
|
+
text,
|
|
68
|
+
extra,
|
|
69
|
+
fieldConfig: config,
|
|
70
|
+
};
|
|
71
|
+
}) as FormItemConfig[],
|
|
56
72
|
),
|
|
57
73
|
);
|
|
58
74
|
|
package/src/editorProps.ts
CHANGED
|
@@ -98,6 +98,8 @@ export interface EditorProps {
|
|
|
98
98
|
isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
|
|
99
99
|
/** 用于自定义组件树与画布的右键菜单 */
|
|
100
100
|
customContentMenu?: CustomContentMenuFunction;
|
|
101
|
+
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
|
102
|
+
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
|
101
103
|
extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
102
104
|
/** 页面顺序拖拽配置参数 */
|
|
103
105
|
pageBarSortOptions?: PageBarSortOptions;
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="m-editor-data-source-field-select">
|
|
3
|
-
<template v-if="
|
|
3
|
+
<template v-if="dataSourceId">
|
|
4
|
+
<TMagicCascader
|
|
5
|
+
:model-value="selectFieldsId"
|
|
6
|
+
clearable
|
|
7
|
+
filterable
|
|
8
|
+
:size="size"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:options="fieldsOptions"
|
|
11
|
+
:props="{
|
|
12
|
+
checkStrictly,
|
|
13
|
+
}"
|
|
14
|
+
@change="fieldChangeHandler"
|
|
15
|
+
></TMagicCascader>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<template v-else-if="checkStrictly">
|
|
4
19
|
<TMagicSelect
|
|
5
20
|
:model-value="selectDataSourceId"
|
|
6
21
|
clearable
|
|
@@ -92,6 +107,8 @@ const props = defineProps<{
|
|
|
92
107
|
dataSourceFieldType?: DataSourceFieldType[];
|
|
93
108
|
/** 是否可以编辑数据源,disable表示的是是否可以选择数据源 */
|
|
94
109
|
notEditable?: boolean | FilterFunction;
|
|
110
|
+
/** 指定数据源ID,限定只能选择该数据源的字段 */
|
|
111
|
+
dataSourceId?: string;
|
|
95
112
|
}>();
|
|
96
113
|
|
|
97
114
|
const emit = defineEmits<{
|
|
@@ -106,7 +123,12 @@ const { dataSourceService, uiService } = useServices();
|
|
|
106
123
|
const mForm = inject<FormState | undefined>('mForm');
|
|
107
124
|
const eventBus = inject<EventBus>('eventBus');
|
|
108
125
|
|
|
109
|
-
const
|
|
126
|
+
const allDataSources = computed(() => dataSourceService.get('dataSources') || []);
|
|
127
|
+
|
|
128
|
+
const dataSources = computed(() => {
|
|
129
|
+
if (!props.dataSourceId) return allDataSources.value;
|
|
130
|
+
return allDataSources.value.filter((ds) => ds.id === props.dataSourceId);
|
|
131
|
+
});
|
|
110
132
|
|
|
111
133
|
const valueIsKey = computed(() => props.value === 'key');
|
|
112
134
|
const notEditable = computed(() => filterFunction(mForm, props.notEditable, props));
|
|
@@ -125,7 +147,13 @@ const selectFieldsId = ref<string[]>([]);
|
|
|
125
147
|
watch(
|
|
126
148
|
modelValue,
|
|
127
149
|
(value) => {
|
|
128
|
-
if (
|
|
150
|
+
if (props.dataSourceId) {
|
|
151
|
+
const dsIdValue = valueIsKey.value
|
|
152
|
+
? props.dataSourceId
|
|
153
|
+
: `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${props.dataSourceId}`;
|
|
154
|
+
selectDataSourceId.value = dsIdValue;
|
|
155
|
+
selectFieldsId.value = Array.isArray(value) ? value : [];
|
|
156
|
+
} else if (Array.isArray(value) && value.length) {
|
|
129
157
|
const [dsId, ...fields] = value;
|
|
130
158
|
selectDataSourceId.value = dsId;
|
|
131
159
|
selectFieldsId.value = fields;
|
|
@@ -140,7 +168,7 @@ watch(
|
|
|
140
168
|
);
|
|
141
169
|
|
|
142
170
|
const fieldsOptions = computed(() => {
|
|
143
|
-
const ds =
|
|
171
|
+
const ds = allDataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(selectDataSourceId.value));
|
|
144
172
|
|
|
145
173
|
if (!ds) return [];
|
|
146
174
|
|
|
@@ -163,8 +191,13 @@ const dsChangeHandler = (v: string) => {
|
|
|
163
191
|
};
|
|
164
192
|
|
|
165
193
|
const fieldChangeHandler = (v: string[] = []) => {
|
|
166
|
-
|
|
167
|
-
|
|
194
|
+
if (props.dataSourceId) {
|
|
195
|
+
modelValue.value = v;
|
|
196
|
+
emit('change', v);
|
|
197
|
+
} else {
|
|
198
|
+
modelValue.value = [selectDataSourceId.value, ...v];
|
|
199
|
+
emit('change', modelValue.value);
|
|
200
|
+
}
|
|
168
201
|
};
|
|
169
202
|
|
|
170
203
|
const onChangeHandler = (v: string[] = []) => {
|
|
@@ -52,12 +52,14 @@ import {
|
|
|
52
52
|
type FormState,
|
|
53
53
|
MCascader,
|
|
54
54
|
} from '@tmagic/form';
|
|
55
|
+
import { DATA_SOURCE_SET_DATA_METHOD_NAME } from '@tmagic/utils';
|
|
55
56
|
|
|
56
57
|
import CodeParams from '@editor/components/CodeParams.vue';
|
|
57
58
|
import MIcon from '@editor/components/Icon.vue';
|
|
58
59
|
import { useServices } from '@editor/hooks/use-services';
|
|
59
60
|
import type { CodeParamStatement, EventBus } from '@editor/type';
|
|
60
61
|
import { SideItemKey } from '@editor/type';
|
|
62
|
+
import { getFieldType } from '@editor/utils';
|
|
61
63
|
|
|
62
64
|
defineOptions({
|
|
63
65
|
name: 'MFieldsDataSourceMethodSelect',
|
|
@@ -92,6 +94,43 @@ const isCustomMethod = computed(() => {
|
|
|
92
94
|
const getParamItemsConfig = ([dataSourceId, methodName]: [Id, string] = ['', '']): CodeParamStatement[] => {
|
|
93
95
|
if (!dataSourceId) return [];
|
|
94
96
|
|
|
97
|
+
if (methodName === DATA_SOURCE_SET_DATA_METHOD_NAME) {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
name: 'field',
|
|
101
|
+
text: '字段',
|
|
102
|
+
type: 'data-source-field-select',
|
|
103
|
+
dataSourceId,
|
|
104
|
+
checkStrictly: true,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'data',
|
|
108
|
+
text: '数据',
|
|
109
|
+
type: (_formState, { model }) => {
|
|
110
|
+
const fieldType = getFieldType(dataSourceService.getDataSourceById(`${dataSourceId}`), model.field);
|
|
111
|
+
|
|
112
|
+
let type = 'vs-code';
|
|
113
|
+
|
|
114
|
+
if (fieldType === 'number') {
|
|
115
|
+
type = 'number';
|
|
116
|
+
} else if (fieldType === 'string') {
|
|
117
|
+
type = 'text';
|
|
118
|
+
} else if (fieldType === 'boolean') {
|
|
119
|
+
type = 'switch';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return type;
|
|
123
|
+
},
|
|
124
|
+
language: 'javascript',
|
|
125
|
+
options: inject('codeOptions', {}),
|
|
126
|
+
autosize: {
|
|
127
|
+
minRows: 1,
|
|
128
|
+
maxRows: 10,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
95
134
|
const paramStatements = dataSources.value
|
|
96
135
|
?.find((item) => item.id === dataSourceId)
|
|
97
136
|
?.methods?.find((item) => item.name === methodName)?.params;
|
|
@@ -108,19 +147,21 @@ const paramsConfig = ref<CodeParamStatement[]>(getParamItemsConfig(props.model[p
|
|
|
108
147
|
|
|
109
148
|
const methodsOptions = computed(
|
|
110
149
|
() =>
|
|
111
|
-
dataSources.value
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
dataSources.value?.map((ds) => ({
|
|
151
|
+
label: ds.title || ds.id,
|
|
152
|
+
value: ds.id,
|
|
153
|
+
children: [
|
|
154
|
+
{
|
|
155
|
+
label: '设置数据',
|
|
156
|
+
value: DATA_SOURCE_SET_DATA_METHOD_NAME,
|
|
157
|
+
},
|
|
158
|
+
...(dataSourceService?.getFormMethod(ds.type) || []),
|
|
159
|
+
...(ds.methods || []).map((method) => ({
|
|
160
|
+
label: method.name,
|
|
161
|
+
value: method.name,
|
|
162
|
+
})),
|
|
163
|
+
],
|
|
164
|
+
})) || [],
|
|
124
165
|
);
|
|
125
166
|
|
|
126
167
|
const cascaderConfig = computed<CascaderConfig>(() => ({
|
|
@@ -42,7 +42,7 @@ const props = withDefaults(defineProps<FieldProps<DataSourceMethodsConfig>>(), {
|
|
|
42
42
|
|
|
43
43
|
const emit = defineEmits(['change']);
|
|
44
44
|
|
|
45
|
-
const codeConfig = ref<CodeBlockContent>();
|
|
45
|
+
const codeConfig = ref<Omit<CodeBlockContent, 'content'> & { content: string }>();
|
|
46
46
|
const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>('codeBlockEditor');
|
|
47
47
|
|
|
48
48
|
let editIndex = -1;
|
|
@@ -72,10 +72,14 @@ const methodColumns: ColumnConfig[] = [
|
|
|
72
72
|
{
|
|
73
73
|
text: '编辑',
|
|
74
74
|
handler: (method: CodeBlockContent, index: number) => {
|
|
75
|
-
let codeContent =
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
75
|
+
let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
|
|
76
|
+
|
|
77
|
+
if (method.content) {
|
|
78
|
+
if (typeof method.content !== 'string') {
|
|
79
|
+
codeContent = method.content.toString();
|
|
80
|
+
} else {
|
|
81
|
+
codeContent = method.content;
|
|
82
|
+
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
codeConfig.value = {
|
|
@@ -26,7 +26,7 @@ import type { StyleSchema } from '@tmagic/schema';
|
|
|
26
26
|
|
|
27
27
|
import MIcon from '@editor/components/Icon.vue';
|
|
28
28
|
|
|
29
|
-
import { Background, Border, Font, Layout, Position } from './pro/';
|
|
29
|
+
import { Background, Border, Font, Layout, Position, Transform } from './pro/';
|
|
30
30
|
|
|
31
31
|
defineOptions({
|
|
32
32
|
name: 'MFieldsStyleSetter',
|
|
@@ -60,6 +60,10 @@ const list = [
|
|
|
60
60
|
title: '边框与圆角',
|
|
61
61
|
component: Border,
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
title: '变形',
|
|
65
|
+
component: Transform,
|
|
66
|
+
},
|
|
63
67
|
];
|
|
64
68
|
|
|
65
69
|
const collapseValue = shallowRef(
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import { type ContainerChangeEventData, defineFormItem, MContainer } from '@tmagic/form';
|
|
7
|
+
import type { StyleSchema } from '@tmagic/schema';
|
|
8
|
+
|
|
9
|
+
defineProps<{
|
|
10
|
+
values: Partial<StyleSchema>;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
size?: 'large' | 'default' | 'small';
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
change: [v: StyleSchema, eventData: ContainerChangeEventData];
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
const config = defineFormItem({
|
|
20
|
+
items: [
|
|
21
|
+
{
|
|
22
|
+
name: 'transform',
|
|
23
|
+
items: [
|
|
24
|
+
{
|
|
25
|
+
name: 'rotate',
|
|
26
|
+
text: '旋转角度',
|
|
27
|
+
labelWidth: '68px',
|
|
28
|
+
type: 'data-source-field-select',
|
|
29
|
+
checkStrictly: false,
|
|
30
|
+
dataSourceFieldType: ['string', 'number'],
|
|
31
|
+
fieldConfig: {
|
|
32
|
+
type: 'text',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'scale',
|
|
37
|
+
text: '缩放',
|
|
38
|
+
labelWidth: '68px',
|
|
39
|
+
type: 'data-source-field-select',
|
|
40
|
+
checkStrictly: false,
|
|
41
|
+
dataSourceFieldType: ['string', 'number'],
|
|
42
|
+
fieldConfig: {
|
|
43
|
+
type: 'text',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
|
|
52
|
+
emit('change', value, eventData);
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
@@ -8,7 +8,7 @@ import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
|
|
|
8
8
|
import type { Services } from '@editor/type';
|
|
9
9
|
|
|
10
10
|
export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService']) => {
|
|
11
|
-
const codeConfig = ref<CodeBlockContent>();
|
|
11
|
+
const codeConfig = ref<Omit<CodeBlockContent, 'content'> & { content: string }>();
|
|
12
12
|
const codeId = ref<string>();
|
|
13
13
|
const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>('codeBlockEditor');
|
|
14
14
|
|
|
@@ -36,10 +36,14 @@ export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService'])
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
let codeContent =
|
|
39
|
+
let codeContent = '';
|
|
40
40
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
41
|
+
if (codeBlock.content) {
|
|
42
|
+
if (typeof codeBlock.content !== 'string') {
|
|
43
|
+
codeContent = codeBlock.content.toString();
|
|
44
|
+
} else {
|
|
45
|
+
codeContent = codeBlock.content;
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
codeConfig.value = {
|
package/src/initService.ts
CHANGED
|
@@ -233,7 +233,7 @@ export const initServiceEvents = (
|
|
|
233
233
|
((event: 'update:modelValue', value: MApp | null) => void),
|
|
234
234
|
{ editorService, codeBlockService, dataSourceService, depService }: Services,
|
|
235
235
|
) => {
|
|
236
|
-
let
|
|
236
|
+
let getTMagicAppPromise: Promise<TMagicCore | undefined> | null = null;
|
|
237
237
|
|
|
238
238
|
const getTMagicApp = async (): Promise<TMagicCore | undefined> => {
|
|
239
239
|
const stage = await getStage();
|
|
@@ -246,11 +246,11 @@ export const initServiceEvents = (
|
|
|
246
246
|
return renderer.runtime.getApp?.();
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
if (
|
|
250
|
-
return
|
|
249
|
+
if (getTMagicAppPromise) {
|
|
250
|
+
return getTMagicAppPromise;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
getTMagicAppPromise = new Promise<TMagicCore | undefined>((resolve) => {
|
|
254
254
|
// 设置 10s 超时
|
|
255
255
|
const timeout = globalThis.setTimeout(() => {
|
|
256
256
|
resolve(void 0);
|
|
@@ -264,7 +264,7 @@ export const initServiceEvents = (
|
|
|
264
264
|
});
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
-
return
|
|
267
|
+
return getTMagicAppPromise;
|
|
268
268
|
};
|
|
269
269
|
|
|
270
270
|
const updateStageNodes = (nodes: MComponent[]) => {
|
|
@@ -560,7 +560,7 @@ export const initServiceEvents = (
|
|
|
560
560
|
depService.clear(nodes);
|
|
561
561
|
};
|
|
562
562
|
|
|
563
|
-
//
|
|
563
|
+
// 历史记录变化时,需要重新收集依赖
|
|
564
564
|
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
|
565
565
|
collectIdle([page], true).then(() => {
|
|
566
566
|
updateStageNode(page);
|
|
@@ -80,7 +80,7 @@ const props = withDefaults(
|
|
|
80
80
|
let stage: StageCore | null = null;
|
|
81
81
|
let runtime: Runtime | null = null;
|
|
82
82
|
|
|
83
|
-
const { editorService, uiService, keybindingService } = useServices();
|
|
83
|
+
const { editorService, uiService, keybindingService, stageOverlayService } = useServices();
|
|
84
84
|
|
|
85
85
|
const stageLoading = computed(() => editorService.get('stageLoading'));
|
|
86
86
|
|
|
@@ -97,6 +97,60 @@ const page = computed(() => editorService.get('page'));
|
|
|
97
97
|
const zoom = computed(() => uiService.get('zoom'));
|
|
98
98
|
const node = computed(() => editorService.get('node'));
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* 判断元素是否被非页面级的滚动容器裁剪(未完整显示)
|
|
102
|
+
*
|
|
103
|
+
* 从元素向上遍历祖先节点,跳过页面/页面片容器,
|
|
104
|
+
* 检查是否存在设置了 overflow 的滚动容器将该元素裁剪,
|
|
105
|
+
* 只有元素未被完整显示时才需要打开 overlay 以展示完整内容
|
|
106
|
+
*/
|
|
107
|
+
const isClippedByScrollContainer = (el: HTMLElement): boolean => {
|
|
108
|
+
const win = el.ownerDocument.defaultView;
|
|
109
|
+
if (!win) return false;
|
|
110
|
+
|
|
111
|
+
// 收集所有页面和页面片的 id
|
|
112
|
+
const root = editorService.get('root');
|
|
113
|
+
const pageIds = new Set(root?.items?.map((item) => `${item.id}`) ?? []);
|
|
114
|
+
|
|
115
|
+
// el 本身就是页面或页面片,无需判断
|
|
116
|
+
const elId = getIdFromEl()(el);
|
|
117
|
+
if (elId && pageIds.has(elId)) return false;
|
|
118
|
+
|
|
119
|
+
let parent = el.parentElement;
|
|
120
|
+
|
|
121
|
+
while (parent && parent !== el.ownerDocument.documentElement) {
|
|
122
|
+
const parentId = getIdFromEl()(parent);
|
|
123
|
+
|
|
124
|
+
// 到达页面或页面片层级,不再继续向上查找
|
|
125
|
+
if (parentId && pageIds.has(parentId)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { overflowX, overflowY } = win.getComputedStyle(parent);
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
['auto', 'scroll', 'hidden'].includes(overflowX) ||
|
|
133
|
+
['auto', 'scroll', 'hidden'].includes(overflowY) ||
|
|
134
|
+
parent.scrollWidth > parent.clientWidth ||
|
|
135
|
+
parent.scrollHeight > parent.clientHeight
|
|
136
|
+
) {
|
|
137
|
+
// 比较元素与容器的可视区域,判断元素是否被裁剪
|
|
138
|
+
const elRect = el.getBoundingClientRect();
|
|
139
|
+
const containerRect = parent.getBoundingClientRect();
|
|
140
|
+
if (
|
|
141
|
+
elRect.top < containerRect.top ||
|
|
142
|
+
elRect.left < containerRect.left ||
|
|
143
|
+
elRect.bottom > containerRect.bottom ||
|
|
144
|
+
elRect.right > containerRect.right
|
|
145
|
+
) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
parent = parent.parentElement;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
};
|
|
153
|
+
|
|
100
154
|
watchEffect(() => {
|
|
101
155
|
if (stage || !page.value) return;
|
|
102
156
|
|
|
@@ -109,6 +163,40 @@ watchEffect(() => {
|
|
|
109
163
|
stageWrapRef.value?.container?.focus();
|
|
110
164
|
});
|
|
111
165
|
|
|
166
|
+
stage.on('dblclick', async (event: MouseEvent) => {
|
|
167
|
+
if (props.stageOptions.beforeDblclick) {
|
|
168
|
+
const result = await props.stageOptions.beforeDblclick(event);
|
|
169
|
+
if (result === false) return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const el = (await stage?.actionManager?.getElementFromPoint(event)) || null;
|
|
173
|
+
if (!el) return;
|
|
174
|
+
|
|
175
|
+
const id = getIdFromEl()(el);
|
|
176
|
+
|
|
177
|
+
if (id) {
|
|
178
|
+
const node = editorService.getNodeById(id);
|
|
179
|
+
if (node?.type === 'page-fragment-container' && node.pageFragmentId) {
|
|
180
|
+
await editorService.select(node.pageFragmentId);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!props.disabledStageOverlay && isClippedByScrollContainer(el)) {
|
|
186
|
+
stageOverlayService.openOverlay(el);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const nextEl = (await stage?.actionManager?.getNextElementFromPoint(event)) || null;
|
|
191
|
+
if (nextEl) {
|
|
192
|
+
const nextId = getIdFromEl()(nextEl);
|
|
193
|
+
if (nextId) {
|
|
194
|
+
await editorService.select(nextId);
|
|
195
|
+
editorService.get('stage')?.select(nextId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
112
200
|
editorService.set('stage', markRaw(stage));
|
|
113
201
|
|
|
114
202
|
stage.mount(stageContainerEl.value);
|
|
@@ -46,12 +46,7 @@ const style = computed(() => ({
|
|
|
46
46
|
}));
|
|
47
47
|
|
|
48
48
|
watch(stage, (stage) => {
|
|
49
|
-
if (stage) {
|
|
50
|
-
stage.on('dblclick', async (event: MouseEvent) => {
|
|
51
|
-
const el = (await stage.actionManager?.getElementFromPoint(event)) || null;
|
|
52
|
-
stageOverlayService.openOverlay(el);
|
|
53
|
-
});
|
|
54
|
-
} else {
|
|
49
|
+
if (!stage) {
|
|
55
50
|
stageOverlayService.closeOverlay();
|
|
56
51
|
}
|
|
57
52
|
});
|