@tmagic/editor 1.0.0-beta.1 → 1.0.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/LICENSE +5432 -0
- package/README.md +1 -0
- package/dist/style.css +53 -24
- package/dist/tmagic-editor.es.js +901 -412
- package/dist/tmagic-editor.es.js.map +1 -1
- package/dist/tmagic-editor.umd.js +918 -412
- package/dist/tmagic-editor.umd.js.map +1 -1
- package/dist/types/src/Editor.vue.d.ts +5 -5
- package/dist/types/src/components/ScrollViewer.vue.d.ts +23 -0
- package/dist/types/src/layouts/CodeEditor.vue.d.ts +5 -4
- package/dist/types/src/layouts/NavMenu.vue.d.ts +2 -1
- package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +1 -1
- package/dist/types/src/layouts/sidebar/LayerPanel.vue.d.ts +10 -3
- package/dist/types/src/layouts/workspace/PageBar.vue.d.ts +8 -3
- package/dist/types/src/layouts/workspace/Stage.vue.d.ts +77 -42
- package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +4 -10
- package/dist/types/src/services/BaseService.d.ts +4 -1
- package/dist/types/src/services/editor.d.ts +11 -4
- package/dist/types/src/services/props.d.ts +1 -1
- package/dist/types/src/services/ui.d.ts +10 -3
- package/dist/types/src/type.d.ts +23 -7
- package/dist/types/src/utils/editor.d.ts +3 -3
- package/dist/types/src/utils/scroll-viewer.d.ts +35 -0
- package/package.json +25 -12
- package/src/Editor.vue +5 -5
- package/src/components/ScrollViewer.vue +66 -0
- package/src/components/ToolButton.vue +5 -3
- package/src/layouts/AddPageBox.vue +3 -1
- package/src/layouts/CodeEditor.vue +35 -59
- package/src/layouts/NavMenu.vue +7 -3
- package/src/layouts/PropsPanel.vue +6 -10
- package/src/layouts/sidebar/ComponentListPanel.vue +2 -2
- package/src/layouts/sidebar/LayerMenu.vue +15 -39
- package/src/layouts/sidebar/LayerPanel.vue +63 -16
- package/src/layouts/workspace/PageBar.vue +113 -14
- package/src/layouts/workspace/Stage.vue +67 -61
- package/src/layouts/workspace/ViewerMenu.vue +5 -3
- package/src/layouts/workspace/Workspace.vue +45 -34
- package/src/services/BaseService.ts +70 -22
- package/src/services/editor.ts +111 -55
- package/src/services/props.ts +2 -1
- package/src/services/ui.ts +42 -5
- package/src/theme/common/var.scss +2 -2
- package/src/theme/layer-panel.scss +9 -0
- package/src/theme/nav-menu.scss +3 -3
- package/src/theme/page-bar.scss +21 -2
- package/src/theme/props-panel.scss +8 -0
- package/src/theme/stage.scss +8 -7
- package/src/type.ts +26 -7
- package/src/utils/editor.ts +30 -27
- package/src/utils/props.ts +1 -1
- package/src/utils/scroll-viewer.ts +216 -0
- package/dist/types/src/layouts/sidebar/LayerMenu.vue.d.ts +0 -31
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="m-editor-page-bar">
|
|
3
|
-
<div class="m-editor-page-bar-item" @click="addPage">
|
|
4
|
-
<el-icon
|
|
2
|
+
<div class="m-editor-page-bar" ref="pageBar">
|
|
3
|
+
<div id="m-editor-page-bar-add-icon" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="addPage">
|
|
4
|
+
<el-icon><plus></plus></el-icon>
|
|
5
5
|
</div>
|
|
6
|
-
<
|
|
6
|
+
<div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('left')">
|
|
7
|
+
<el-icon><arrow-left-bold></arrow-left-bold></el-icon>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-if="root" class="m-editor-page-bar-items" ref="itemsContainer" :style="`width: ${itemsContainerWidth}px`">
|
|
7
10
|
<div
|
|
8
11
|
v-for="item in root.items"
|
|
9
12
|
:key="item.key"
|
|
@@ -11,9 +14,13 @@
|
|
|
11
14
|
:class="{ active: page?.id === item.id }"
|
|
12
15
|
@click="switchPage(item)"
|
|
13
16
|
>
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
+
<div class="m-editor-page-bar-title">
|
|
18
|
+
<slot name="page-bar-title" :page="item">
|
|
19
|
+
<el-tooltip effect="dark" placement="top-start" :content="item.name">
|
|
20
|
+
<span>{{ item.name }}</span>
|
|
21
|
+
</el-tooltip>
|
|
22
|
+
</slot>
|
|
23
|
+
</div>
|
|
17
24
|
|
|
18
25
|
<el-popover placement="top" :width="160" trigger="hover">
|
|
19
26
|
<div>
|
|
@@ -29,28 +36,120 @@
|
|
|
29
36
|
</template>
|
|
30
37
|
</el-popover>
|
|
31
38
|
</div>
|
|
32
|
-
</
|
|
39
|
+
</div>
|
|
40
|
+
<div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('right')">
|
|
41
|
+
<el-icon><arrow-right-bold></arrow-right-bold></el-icon>
|
|
42
|
+
</div>
|
|
33
43
|
</div>
|
|
34
44
|
</template>
|
|
35
45
|
|
|
36
46
|
<script lang="ts">
|
|
37
|
-
import { computed, defineComponent, inject, toRaw } from 'vue';
|
|
38
|
-
import { CaretBottom, Plus } from '@element-plus/icons';
|
|
47
|
+
import { computed, ComputedRef, defineComponent, inject, onMounted, onUnmounted, ref, toRaw, watch } from 'vue';
|
|
48
|
+
import { ArrowLeftBold, ArrowRightBold, CaretBottom, Plus } from '@element-plus/icons';
|
|
39
49
|
|
|
40
|
-
import type { MPage } from '@tmagic/schema';
|
|
50
|
+
import type { MApp, MPage } from '@tmagic/schema';
|
|
51
|
+
import { NodeType } from '@tmagic/schema';
|
|
41
52
|
|
|
42
53
|
import type { Services } from '@editor/type';
|
|
43
54
|
import { generatePageNameByApp } from '@editor/utils/editor';
|
|
44
55
|
|
|
56
|
+
const useScroll = (root: ComputedRef<MApp | undefined>) => {
|
|
57
|
+
const pageBar = ref<HTMLDivElement>();
|
|
58
|
+
const itemsContainer = ref<HTMLDivElement>();
|
|
59
|
+
|
|
60
|
+
const pageBarWidth = ref(0);
|
|
61
|
+
const canScroll = ref(false);
|
|
62
|
+
|
|
63
|
+
const itemsContainerWidth = computed(() => pageBarWidth.value - 105);
|
|
64
|
+
|
|
65
|
+
let translateLeft = 0;
|
|
66
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
67
|
+
for (const { contentRect } of entries) {
|
|
68
|
+
const { width } = contentRect;
|
|
69
|
+
pageBarWidth.value = width || 0;
|
|
70
|
+
|
|
71
|
+
setCanScroll();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const setCanScroll = () => {
|
|
76
|
+
if (itemsContainer.value) {
|
|
77
|
+
canScroll.value = itemsContainer.value.scrollWidth > pageBarWidth.value - 105;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
|
|
82
|
+
if (!itemsContainer.value) return;
|
|
83
|
+
|
|
84
|
+
const maxScrollLeft = itemsContainer.value.scrollWidth - itemsContainerWidth.value;
|
|
85
|
+
|
|
86
|
+
if (type === 'left') {
|
|
87
|
+
translateLeft += 100;
|
|
88
|
+
|
|
89
|
+
if (translateLeft > 0) {
|
|
90
|
+
translateLeft = 0;
|
|
91
|
+
}
|
|
92
|
+
} else if (type === 'right') {
|
|
93
|
+
translateLeft -= 100;
|
|
94
|
+
|
|
95
|
+
if (-translateLeft > maxScrollLeft) {
|
|
96
|
+
translateLeft = -maxScrollLeft;
|
|
97
|
+
}
|
|
98
|
+
} else if (type === 'start') {
|
|
99
|
+
translateLeft = 0;
|
|
100
|
+
} else if (type === 'end') {
|
|
101
|
+
translateLeft = -maxScrollLeft;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
onMounted(() => {
|
|
108
|
+
pageBar.value && resizeObserver.observe(pageBar.value);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
onUnmounted(() => {
|
|
112
|
+
resizeObserver.disconnect();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
watch(
|
|
116
|
+
() => root.value?.items.length,
|
|
117
|
+
(length = 0, preLength = 0) => {
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
setCanScroll();
|
|
120
|
+
if (length < preLength) {
|
|
121
|
+
scroll('start');
|
|
122
|
+
} else {
|
|
123
|
+
scroll('end');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
pageBar,
|
|
131
|
+
itemsContainer,
|
|
132
|
+
canScroll,
|
|
133
|
+
|
|
134
|
+
itemsContainerWidth,
|
|
135
|
+
|
|
136
|
+
scroll,
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
45
140
|
export default defineComponent({
|
|
46
|
-
components: { CaretBottom, Plus },
|
|
141
|
+
components: { ArrowLeftBold, ArrowRightBold, CaretBottom, Plus },
|
|
47
142
|
|
|
48
143
|
setup() {
|
|
49
144
|
const services = inject<Services>('services');
|
|
50
145
|
const editorService = services?.editorService;
|
|
51
146
|
|
|
147
|
+
const root = computed(() => editorService?.get<MApp>('root'));
|
|
148
|
+
|
|
52
149
|
return {
|
|
53
|
-
|
|
150
|
+
...useScroll(root),
|
|
151
|
+
|
|
152
|
+
root,
|
|
54
153
|
page: computed(() => editorService?.get('page')),
|
|
55
154
|
|
|
56
155
|
switchPage(page: MPage) {
|
|
@@ -60,7 +159,7 @@ export default defineComponent({
|
|
|
60
159
|
addPage() {
|
|
61
160
|
if (!editorService) return;
|
|
62
161
|
const pageConfig = {
|
|
63
|
-
type:
|
|
162
|
+
type: NodeType.PAGE,
|
|
64
163
|
name: generatePageNameByApp(toRaw(editorService.get('root'))),
|
|
65
164
|
};
|
|
66
165
|
editorService.add(pageConfig);
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<scroll-viewer
|
|
3
|
+
class="m-editor-stage"
|
|
4
|
+
ref="stageWrap"
|
|
5
|
+
:width="stageRect?.width"
|
|
6
|
+
:height="stageRect?.height"
|
|
7
|
+
:zoom="zoom"
|
|
8
|
+
>
|
|
3
9
|
<div
|
|
4
10
|
class="m-editor-stage-container"
|
|
5
11
|
ref="stageContainer"
|
|
6
|
-
:style="stageStyle"
|
|
7
12
|
@contextmenu="contextmenuHandler"
|
|
13
|
+
:style="`transform: scale(${zoom})`"
|
|
8
14
|
></div>
|
|
9
15
|
<teleport to="body">
|
|
10
16
|
<viewer-menu ref="menu" :style="menuStyle"></viewer-menu>
|
|
11
17
|
</teleport>
|
|
12
|
-
</
|
|
18
|
+
</scroll-viewer>
|
|
13
19
|
</template>
|
|
14
20
|
|
|
15
21
|
<script lang="ts">
|
|
@@ -17,6 +23,7 @@ import {
|
|
|
17
23
|
computed,
|
|
18
24
|
defineComponent,
|
|
19
25
|
inject,
|
|
26
|
+
markRaw,
|
|
20
27
|
onMounted,
|
|
21
28
|
onUnmounted,
|
|
22
29
|
PropType,
|
|
@@ -31,7 +38,8 @@ import type { MApp, MNode, MPage } from '@tmagic/schema';
|
|
|
31
38
|
import type { MoveableOptions, Runtime, SortEventData, UpdateEventData } from '@tmagic/stage';
|
|
32
39
|
import StageCore from '@tmagic/stage';
|
|
33
40
|
|
|
34
|
-
import
|
|
41
|
+
import ScrollViewer from '@editor/components/ScrollViewer.vue';
|
|
42
|
+
import type { Services, StageRect } from '@editor/type';
|
|
35
43
|
|
|
36
44
|
import ViewerMenu from './ViewerMenu.vue';
|
|
37
45
|
|
|
@@ -79,6 +87,7 @@ export default defineComponent({
|
|
|
79
87
|
|
|
80
88
|
components: {
|
|
81
89
|
ViewerMenu,
|
|
90
|
+
ScrollViewer,
|
|
82
91
|
},
|
|
83
92
|
|
|
84
93
|
props: {
|
|
@@ -88,26 +97,6 @@ export default defineComponent({
|
|
|
88
97
|
|
|
89
98
|
runtimeUrl: String,
|
|
90
99
|
|
|
91
|
-
root: {
|
|
92
|
-
type: Object as PropType<MApp>,
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
page: {
|
|
96
|
-
type: Object as PropType<MPage>,
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
node: {
|
|
100
|
-
type: Object as PropType<MNode>,
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
uiSelectMode: {
|
|
104
|
-
type: Boolean,
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
zoom: {
|
|
108
|
-
type: Number,
|
|
109
|
-
},
|
|
110
|
-
|
|
111
100
|
canSelect: {
|
|
112
101
|
type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
|
|
113
102
|
default: (el: HTMLElement) => Boolean(el.id),
|
|
@@ -121,16 +110,18 @@ export default defineComponent({
|
|
|
121
110
|
},
|
|
122
111
|
},
|
|
123
112
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
setup(props, { emit }) {
|
|
113
|
+
setup(props) {
|
|
127
114
|
const services = inject<Services>('services');
|
|
115
|
+
|
|
116
|
+
const stageWrap = ref<InstanceType<typeof ScrollViewer>>();
|
|
128
117
|
const stageContainer = ref<HTMLDivElement>();
|
|
129
118
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
119
|
+
const stageRect = computed(() => services?.uiService.get<StageRect>('stageRect'));
|
|
120
|
+
const uiSelectMode = computed(() => services?.uiService.get<boolean>('uiSelectMode'));
|
|
121
|
+
const root = computed(() => services?.editorService.get<MApp>('root'));
|
|
122
|
+
const page = computed(() => services?.editorService.get<MPage>('page'));
|
|
123
|
+
const zoom = computed(() => services?.uiService.get<number>('zoom'));
|
|
124
|
+
const node = computed(() => services?.editorService.get<MNode>('node'));
|
|
134
125
|
|
|
135
126
|
let stage: StageCore | null = null;
|
|
136
127
|
let runtime: Runtime | null = null;
|
|
@@ -139,16 +130,16 @@ export default defineComponent({
|
|
|
139
130
|
if (stage) return;
|
|
140
131
|
|
|
141
132
|
if (!stageContainer.value) return;
|
|
142
|
-
if (!(props.runtimeUrl || props.render) || !
|
|
133
|
+
if (!(props.runtimeUrl || props.render) || !root.value) return;
|
|
143
134
|
|
|
144
135
|
stage = new StageCore({
|
|
145
136
|
render: props.render,
|
|
146
137
|
runtimeUrl: props.runtimeUrl,
|
|
147
|
-
zoom:
|
|
148
|
-
canSelect: (el, stop) => {
|
|
138
|
+
zoom: zoom.value,
|
|
139
|
+
canSelect: (el, event, stop) => {
|
|
149
140
|
const elCanSelect = props.canSelect(el);
|
|
150
141
|
// 在组件联动过程中不能再往下选择,返回并触发 ui-select
|
|
151
|
-
if (
|
|
142
|
+
if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
|
|
152
143
|
document.dispatchEvent(new CustomEvent('ui-select', { detail: el }));
|
|
153
144
|
return stop();
|
|
154
145
|
}
|
|
@@ -158,63 +149,78 @@ export default defineComponent({
|
|
|
158
149
|
moveableOptions: props.moveableOptions,
|
|
159
150
|
});
|
|
160
151
|
|
|
161
|
-
services?.editorService.set('stage', stage);
|
|
152
|
+
services?.editorService.set('stage', markRaw(stage));
|
|
162
153
|
|
|
163
154
|
stage?.mount(stageContainer.value);
|
|
164
155
|
|
|
165
|
-
stage?.on('select', (el: HTMLElement) =>
|
|
156
|
+
stage?.on('select', (el: HTMLElement) => {
|
|
157
|
+
services?.editorService.select(el.id);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
stage?.on('highlight', (el: HTMLElement) => {
|
|
161
|
+
services?.editorService.highlight(el.id);
|
|
162
|
+
});
|
|
166
163
|
|
|
167
164
|
stage?.on('update', (ev: UpdateEventData) => {
|
|
168
|
-
|
|
165
|
+
services?.editorService.update({ id: ev.el.id, style: ev.style });
|
|
169
166
|
});
|
|
170
167
|
|
|
171
168
|
stage?.on('sort', (ev: SortEventData) => {
|
|
172
|
-
|
|
169
|
+
services?.editorService.sort(ev.src, ev.dist);
|
|
173
170
|
});
|
|
174
171
|
|
|
175
172
|
stage?.on('changeGuides', () => {
|
|
176
173
|
services?.uiService.set('showGuides', true);
|
|
177
174
|
});
|
|
178
175
|
|
|
179
|
-
if (!
|
|
176
|
+
if (!node.value?.id) return;
|
|
180
177
|
stage?.on('runtime-ready', (rt) => {
|
|
181
178
|
runtime = rt;
|
|
182
179
|
// toRaw返回的值是一个引用而非快照,需要cloneDeep
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
root.value && runtime?.updateRootConfig(cloneDeep(toRaw(root.value)));
|
|
181
|
+
page.value?.id && runtime?.updatePageId?.(page.value.id);
|
|
185
182
|
setTimeout(() => {
|
|
186
|
-
|
|
183
|
+
node.value && stage?.select(toRaw(node.value.id));
|
|
187
184
|
});
|
|
188
185
|
});
|
|
189
186
|
});
|
|
190
187
|
|
|
191
|
-
watch(
|
|
192
|
-
(
|
|
193
|
-
(zoom)
|
|
194
|
-
|
|
195
|
-
stage?.setZoom(zoom);
|
|
196
|
-
},
|
|
197
|
-
);
|
|
188
|
+
watch(zoom, (zoom) => {
|
|
189
|
+
if (!stage || !zoom) return;
|
|
190
|
+
stage.setZoom(zoom);
|
|
191
|
+
});
|
|
198
192
|
|
|
199
|
-
watch(
|
|
200
|
-
(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
193
|
+
watch(root, (root) => {
|
|
194
|
+
if (runtime && root) {
|
|
195
|
+
runtime.updateRootConfig(cloneDeep(toRaw(root)));
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
200
|
+
for (const { contentRect } of entries) {
|
|
201
|
+
services?.uiService.set('stageContainerRect', {
|
|
202
|
+
width: contentRect.width,
|
|
203
|
+
height: contentRect.height,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
onMounted(() => {
|
|
209
|
+
stageWrap.value?.container && resizeObserver.observe(stageWrap.value.container);
|
|
210
|
+
});
|
|
207
211
|
|
|
208
212
|
onUnmounted(() => {
|
|
209
213
|
stage?.destroy();
|
|
214
|
+
resizeObserver.disconnect();
|
|
210
215
|
services?.editorService.set('stage', null);
|
|
211
216
|
});
|
|
212
217
|
|
|
213
218
|
return {
|
|
214
|
-
|
|
215
|
-
...useMenu(),
|
|
216
|
-
|
|
219
|
+
stageWrap,
|
|
217
220
|
stageContainer,
|
|
221
|
+
stageRect,
|
|
222
|
+
zoom,
|
|
223
|
+
...useMenu(),
|
|
218
224
|
};
|
|
219
225
|
},
|
|
220
226
|
});
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
<script lang="ts">
|
|
25
25
|
import { computed, defineComponent, inject, onMounted, ref, watch } from 'vue';
|
|
26
26
|
|
|
27
|
+
import { NodeType } from '@tmagic/schema';
|
|
27
28
|
import type StageCore from '@tmagic/stage';
|
|
28
29
|
|
|
29
30
|
import { LayerOffset, Layout, Services } from '@editor/type';
|
|
@@ -53,7 +54,8 @@ export default defineComponent({
|
|
|
53
54
|
if (!parent.value || !editorService) return (canCenter.value = false);
|
|
54
55
|
const layout = await editorService.getLayout(parent.value);
|
|
55
56
|
canCenter.value =
|
|
56
|
-
[Layout.ABSOLUTE, Layout.FIXED].includes(layout) &&
|
|
57
|
+
[Layout.ABSOLUTE, Layout.FIXED].includes(layout) &&
|
|
58
|
+
![NodeType.ROOT, NodeType.PAGE, 'pop'].includes(`${node.value?.type}`);
|
|
57
59
|
},
|
|
58
60
|
{ immediate: true },
|
|
59
61
|
);
|
|
@@ -62,8 +64,8 @@ export default defineComponent({
|
|
|
62
64
|
menu,
|
|
63
65
|
canPaste,
|
|
64
66
|
|
|
65
|
-
canDelete: computed(() => node.value?.type !==
|
|
66
|
-
canMoveZPos: computed(() => node.value?.type !==
|
|
67
|
+
canDelete: computed(() => node.value?.type !== NodeType.PAGE),
|
|
68
|
+
canMoveZPos: computed(() => node.value?.type !== NodeType.PAGE),
|
|
67
69
|
canCenter,
|
|
68
70
|
|
|
69
71
|
center() {
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="m-editor-workspace">
|
|
2
|
+
<div class="m-editor-workspace" tabindex="1" ref="workspace">
|
|
3
3
|
<magic-stage
|
|
4
4
|
:key="page?.id"
|
|
5
5
|
:runtime-url="runtimeUrl"
|
|
6
6
|
:render="render"
|
|
7
|
-
:ui-select-mode="uiSelectMode"
|
|
8
|
-
:root="root"
|
|
9
|
-
:page="page"
|
|
10
|
-
:node="node"
|
|
11
|
-
:zoom="zoom"
|
|
12
7
|
:moveable-options="moveableOptions"
|
|
13
8
|
:can-select="canSelect"
|
|
14
|
-
@select="selectHandler"
|
|
15
|
-
@update="updateNodeHandler"
|
|
16
|
-
@sort="sortNodeHandler"
|
|
17
9
|
></magic-stage>
|
|
18
10
|
|
|
19
11
|
<slot name="workspace-content"></slot>
|
|
@@ -26,10 +18,11 @@
|
|
|
26
18
|
</template>
|
|
27
19
|
|
|
28
20
|
<script lang="ts">
|
|
29
|
-
import { computed, defineComponent, inject,
|
|
21
|
+
import { computed, defineComponent, inject, onMounted, onUnmounted, PropType, ref } from 'vue';
|
|
22
|
+
import KeyController from 'keycon';
|
|
30
23
|
|
|
31
|
-
import type {
|
|
32
|
-
import type { MoveableOptions
|
|
24
|
+
import type { MNode, MPage } from '@tmagic/schema';
|
|
25
|
+
import type { MoveableOptions } from '@tmagic/stage';
|
|
33
26
|
import StageCore from '@tmagic/stage';
|
|
34
27
|
|
|
35
28
|
import type { Services } from '@editor/type';
|
|
@@ -63,35 +56,53 @@ export default defineComponent({
|
|
|
63
56
|
|
|
64
57
|
setup() {
|
|
65
58
|
const services = inject<Services>('services');
|
|
59
|
+
const workspace = ref<HTMLDivElement>();
|
|
66
60
|
const node = computed(() => services?.editorService.get<MNode>('node'));
|
|
67
|
-
|
|
61
|
+
let keycon: KeyController;
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
id && stage?.select(id);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
63
|
+
const mouseenterHandler = () => {
|
|
64
|
+
workspace.value?.focus();
|
|
65
|
+
};
|
|
75
66
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
page: computed(() => services?.editorService.get<MPage>('page')),
|
|
80
|
-
zoom: computed(() => services?.uiService.get<number>('zoom')),
|
|
67
|
+
const mouseleaveHandler = () => {
|
|
68
|
+
workspace.value?.blur();
|
|
69
|
+
};
|
|
81
70
|
|
|
82
|
-
|
|
71
|
+
onMounted(() => {
|
|
72
|
+
workspace.value?.addEventListener('mouseenter', mouseenterHandler);
|
|
73
|
+
|
|
74
|
+
workspace.value?.addEventListener('mouseleave', mouseleaveHandler);
|
|
75
|
+
|
|
76
|
+
keycon = new KeyController(workspace.value);
|
|
77
|
+
|
|
78
|
+
keycon
|
|
79
|
+
.keyup('delete', () => {
|
|
80
|
+
node.value && services?.editorService.remove(node.value);
|
|
81
|
+
})
|
|
82
|
+
.keyup(['ctrl', 'c'], () => {
|
|
83
|
+
node.value && services?.editorService.copy(node.value);
|
|
84
|
+
})
|
|
85
|
+
.keyup(['ctrl', 'v'], () => {
|
|
86
|
+
node.value && services?.editorService.paste();
|
|
87
|
+
})
|
|
88
|
+
.keyup(['ctrl', 'x'], () => {
|
|
89
|
+
if (node.value && services) {
|
|
90
|
+
services.editorService.copy(node.value);
|
|
91
|
+
services.editorService.remove(node.value);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
onUnmounted(() => {
|
|
97
|
+
workspace.value?.removeEventListener('mouseenter', mouseenterHandler);
|
|
98
|
+
workspace.value?.removeEventListener('mouseleave', mouseleaveHandler);
|
|
99
|
+
keycon.destroy();
|
|
100
|
+
});
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
102
|
+
return {
|
|
103
|
+
workspace,
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
services?.editorService.sort(ev.src, ev.dist);
|
|
94
|
-
},
|
|
105
|
+
page: computed(() => services?.editorService.get<MPage>('page')),
|
|
95
106
|
};
|
|
96
107
|
},
|
|
97
108
|
});
|
|
@@ -24,6 +24,46 @@ const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpper
|
|
|
24
24
|
|
|
25
25
|
const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
|
|
26
26
|
|
|
27
|
+
const doAction = async (
|
|
28
|
+
args: any[],
|
|
29
|
+
scope: any,
|
|
30
|
+
sourceMethod: any,
|
|
31
|
+
beforeMethodName: string,
|
|
32
|
+
afterMethodName: string,
|
|
33
|
+
fn: (args: any[], next?: Function | undefined) => Promise<void>,
|
|
34
|
+
) => {
|
|
35
|
+
try {
|
|
36
|
+
let beforeArgs = args;
|
|
37
|
+
|
|
38
|
+
for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
|
|
39
|
+
let beforeReturnValue = (await beforeMethod(...beforeArgs)) || [];
|
|
40
|
+
|
|
41
|
+
if (isError(beforeReturnValue)) throw beforeReturnValue;
|
|
42
|
+
|
|
43
|
+
if (!Array.isArray(beforeReturnValue)) {
|
|
44
|
+
beforeReturnValue = [beforeReturnValue];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
beforeArgs = beforeArgs.map((v: any, index: number) => {
|
|
48
|
+
if (typeof beforeReturnValue[index] === 'undefined') return v;
|
|
49
|
+
return beforeReturnValue[index];
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let returnValue: any = await fn(beforeArgs, sourceMethod.bind(scope));
|
|
54
|
+
|
|
55
|
+
for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
|
|
56
|
+
returnValue = await afterMethod(...beforeArgs, returnValue);
|
|
57
|
+
|
|
58
|
+
if (isError(returnValue)) throw returnValue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return returnValue;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
27
67
|
/**
|
|
28
68
|
* 提供两种方式对Class进行扩展
|
|
29
69
|
* 方法1:
|
|
@@ -74,8 +114,10 @@ const isError = (error: any): boolean => Object.prototype.toString.call(error) =
|
|
|
74
114
|
export default class extends EventEmitter {
|
|
75
115
|
private pluginOptionsList: Record<string, Function[]> = {};
|
|
76
116
|
private middleware: Record<string, Function[]> = {};
|
|
117
|
+
private taskList: (() => Promise<void>)[] = [];
|
|
118
|
+
private doingTask = false;
|
|
77
119
|
|
|
78
|
-
constructor(methods: string[]) {
|
|
120
|
+
constructor(methods: string[] = [], serialMethods: string[] = []) {
|
|
79
121
|
super();
|
|
80
122
|
|
|
81
123
|
methods.forEach((propertyName: string) => {
|
|
@@ -93,32 +135,28 @@ export default class extends EventEmitter {
|
|
|
93
135
|
const fn = compose(this.middleware[propertyName]);
|
|
94
136
|
Object.defineProperty(scope, propertyName, {
|
|
95
137
|
value: async (...args: any[]) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
for (const beforeMethod of this.pluginOptionsList[beforeMethodName]) {
|
|
99
|
-
let beforeReturnValue = (await beforeMethod(...beforeArgs)) || [];
|
|
100
|
-
|
|
101
|
-
if (isError(beforeReturnValue)) throw beforeReturnValue;
|
|
102
|
-
|
|
103
|
-
if (!Array.isArray(beforeReturnValue)) {
|
|
104
|
-
beforeReturnValue = [beforeReturnValue];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
beforeArgs = beforeArgs.map((v: any, index: number) => {
|
|
108
|
-
if (typeof beforeReturnValue[index] === 'undefined') return v;
|
|
109
|
-
return beforeReturnValue[index];
|
|
110
|
-
});
|
|
138
|
+
if (!serialMethods.includes(propertyName)) {
|
|
139
|
+
return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
|
111
140
|
}
|
|
112
141
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
// 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
|
|
143
|
+
// 这里保证函数调用严格按顺序执行;
|
|
144
|
+
const promise = new Promise<any>((resolve, reject) => {
|
|
145
|
+
this.taskList.push(async () => {
|
|
146
|
+
try {
|
|
147
|
+
const value = await doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
|
148
|
+
resolve(value);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
reject(e);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
117
154
|
|
|
118
|
-
|
|
155
|
+
if (!this.doingTask) {
|
|
156
|
+
this.doTask();
|
|
119
157
|
}
|
|
120
158
|
|
|
121
|
-
return
|
|
159
|
+
return promise;
|
|
122
160
|
},
|
|
123
161
|
});
|
|
124
162
|
});
|
|
@@ -135,4 +173,14 @@ export default class extends EventEmitter {
|
|
|
135
173
|
if (typeof method === 'function') this.pluginOptionsList[methodName].push(method);
|
|
136
174
|
});
|
|
137
175
|
}
|
|
176
|
+
|
|
177
|
+
private async doTask() {
|
|
178
|
+
this.doingTask = true;
|
|
179
|
+
let task = this.taskList.shift();
|
|
180
|
+
while (task) {
|
|
181
|
+
await task();
|
|
182
|
+
task = this.taskList.shift();
|
|
183
|
+
}
|
|
184
|
+
this.doingTask = false;
|
|
185
|
+
}
|
|
138
186
|
}
|