@tmagic/editor 1.5.0-beta.11 → 1.5.0-beta.13
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/style.css +15 -11
- package/dist/tmagic-editor.js +308 -288
- package/dist/tmagic-editor.umd.cjs +304 -284
- package/package.json +7 -7
- package/src/Editor.vue +6 -1
- package/src/editorProps.ts +3 -1
- package/src/initService.ts +94 -70
- package/src/layouts/Framework.vue +8 -1
- package/src/layouts/page-bar/AddButton.vue +29 -9
- package/src/layouts/page-bar/PageBar.vue +35 -65
- package/src/layouts/page-bar/PageBarScrollContainer.vue +45 -84
- package/src/layouts/page-bar/PageList.vue +1 -1
- package/src/layouts/page-bar/Search.vue +67 -0
- package/src/layouts/workspace/viewer/Stage.vue +11 -0
- package/src/services/dep.ts +12 -2
- package/src/services/editor.ts +20 -40
- package/src/theme/page-bar.scss +19 -12
- package/src/type.ts +2 -1
- package/src/utils/idle-task.ts +3 -2
- package/types/index.d.ts +38 -2
- package/src/layouts/page-bar/SwitchTypeButton.vue +0 -45
|
@@ -6,12 +6,7 @@
|
|
|
6
6
|
<Icon :icon="ArrowLeftBold"></Icon>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
<div
|
|
10
|
-
v-if="(type === NodeType.PAGE && pageLength) || (type === NodeType.PAGE_FRAGMENT && pageFragmentLength)"
|
|
11
|
-
class="m-editor-page-bar-items"
|
|
12
|
-
ref="itemsContainer"
|
|
13
|
-
:style="`width: ${itemsContainerWidth}px`"
|
|
14
|
-
>
|
|
9
|
+
<div v-if="length" class="m-editor-page-bar-items" ref="itemsContainer" :style="`width: ${itemsContainerWidth}px`">
|
|
15
10
|
<slot></slot>
|
|
16
11
|
</div>
|
|
17
12
|
|
|
@@ -22,21 +17,11 @@
|
|
|
22
17
|
</template>
|
|
23
18
|
|
|
24
19
|
<script setup lang="ts">
|
|
25
|
-
import {
|
|
26
|
-
computed,
|
|
27
|
-
type ComputedRef,
|
|
28
|
-
inject,
|
|
29
|
-
nextTick,
|
|
30
|
-
onBeforeUnmount,
|
|
31
|
-
onMounted,
|
|
32
|
-
ref,
|
|
33
|
-
watch,
|
|
34
|
-
type WatchStopHandle,
|
|
35
|
-
} from 'vue';
|
|
20
|
+
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
36
21
|
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
|
|
37
|
-
import Sortable, { SortableEvent } from 'sortablejs';
|
|
22
|
+
import Sortable, { type SortableEvent } from 'sortablejs';
|
|
38
23
|
|
|
39
|
-
import { Id
|
|
24
|
+
import type { Id } from '@tmagic/core';
|
|
40
25
|
|
|
41
26
|
import Icon from '@editor/components/Icon.vue';
|
|
42
27
|
import type { PageBarSortOptions, Services } from '@editor/type';
|
|
@@ -46,8 +31,8 @@ defineOptions({
|
|
|
46
31
|
});
|
|
47
32
|
|
|
48
33
|
const props = defineProps<{
|
|
49
|
-
type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
|
|
50
34
|
pageBarSortOptions?: PageBarSortOptions;
|
|
35
|
+
length: number;
|
|
51
36
|
}>();
|
|
52
37
|
|
|
53
38
|
const services = inject<Services>('services');
|
|
@@ -63,11 +48,12 @@ const showPageListButton = computed(() => uiService?.get('showPageListButton'));
|
|
|
63
48
|
const itemsContainerWidth = ref(0);
|
|
64
49
|
|
|
65
50
|
const setCanScroll = () => {
|
|
66
|
-
//
|
|
51
|
+
// 减去新增、搜索、页面列表、左移、右移5个按钮的宽度
|
|
67
52
|
// 37 = icon width 16 + padding 10 * 2 + border-right 1
|
|
68
53
|
itemsContainerWidth.value =
|
|
69
54
|
(pageBar.value?.clientWidth || 0) -
|
|
70
55
|
37 * 2 -
|
|
56
|
+
37 -
|
|
71
57
|
(showAddPageButton.value ? 37 : 21) -
|
|
72
58
|
(showPageListButton.value ? 37 : 0);
|
|
73
59
|
|
|
@@ -119,71 +105,46 @@ const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
|
|
|
119
105
|
itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
|
|
120
106
|
};
|
|
121
107
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
|
|
144
|
-
await props.pageBarSortOptions.beforeStart(event, sortable);
|
|
145
|
-
}
|
|
146
|
-
beforeDragList = sortable.toArray();
|
|
147
|
-
},
|
|
148
|
-
onUpdate: async (event: SortableEvent) => {
|
|
149
|
-
await editorService?.sort(
|
|
150
|
-
beforeDragList[event.oldIndex as number],
|
|
151
|
-
beforeDragList[event.newIndex as number],
|
|
152
|
-
);
|
|
153
|
-
if (typeof props.pageBarSortOptions?.afterUpdate === 'function') {
|
|
154
|
-
await props.pageBarSortOptions.afterUpdate(event, sortable);
|
|
155
|
-
}
|
|
156
|
-
},
|
|
108
|
+
watch(
|
|
109
|
+
() => props.length,
|
|
110
|
+
(length = 0, preLength = 0) => {
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
setCanScroll();
|
|
113
|
+
if (length < preLength) {
|
|
114
|
+
scroll('start');
|
|
115
|
+
} else {
|
|
116
|
+
scroll('end');
|
|
117
|
+
}
|
|
118
|
+
if (length > 1) {
|
|
119
|
+
const el = document.querySelector('.m-editor-page-bar-items') as HTMLElement;
|
|
120
|
+
let beforeDragList: Id[] = [];
|
|
121
|
+
const options = {
|
|
122
|
+
...{
|
|
123
|
+
dataIdAttr: 'page-id', // 获取排序后的数据
|
|
124
|
+
onStart: async (event: SortableEvent) => {
|
|
125
|
+
if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
|
|
126
|
+
await props.pageBarSortOptions.beforeStart(event, sortable);
|
|
127
|
+
}
|
|
128
|
+
beforeDragList = sortable.toArray();
|
|
157
129
|
},
|
|
158
|
-
|
|
159
|
-
|
|
130
|
+
onUpdate: async (event: SortableEvent) => {
|
|
131
|
+
await editorService?.sort(
|
|
132
|
+
beforeDragList[event.oldIndex as number],
|
|
133
|
+
beforeDragList[event.newIndex as number],
|
|
134
|
+
);
|
|
135
|
+
if (typeof props.pageBarSortOptions?.afterUpdate === 'function') {
|
|
136
|
+
await props.pageBarSortOptions.afterUpdate(event, sortable);
|
|
137
|
+
}
|
|
160
138
|
},
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
let unWatchPageLength: WatchStopHandle | null;
|
|
173
|
-
let unWatchPageFragmentLength: WatchStopHandle | null;
|
|
174
|
-
|
|
175
|
-
watch(
|
|
176
|
-
() => props.type,
|
|
177
|
-
(type) => {
|
|
178
|
-
if (type === NodeType.PAGE) {
|
|
179
|
-
unWatchPageFragmentLength?.();
|
|
180
|
-
unWatchPageFragmentLength = null;
|
|
181
|
-
unWatchPageLength = crateWatchLength(pageLength);
|
|
182
|
-
} else {
|
|
183
|
-
unWatchPageLength?.();
|
|
184
|
-
unWatchPageLength = null;
|
|
185
|
-
unWatchPageFragmentLength = crateWatchLength(pageFragmentLength);
|
|
186
|
-
}
|
|
139
|
+
},
|
|
140
|
+
...{
|
|
141
|
+
...(props.pageBarSortOptions ? props.pageBarSortOptions : {}),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
if (!el) return;
|
|
145
|
+
const sortable = new Sortable(el, options);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
187
148
|
},
|
|
188
149
|
{
|
|
189
150
|
immediate: true,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="m-editor-page-bar-item m-editor-page-bar-item-icon m-editor-page-bar-search">
|
|
3
|
+
<Icon :icon="Search" @click="visible = !visible" :class="{ 'icon-active': visible }"></Icon>
|
|
4
|
+
<Teleport to=".m-editor-page-bar-tabs" v-if="visible">
|
|
5
|
+
<MForm
|
|
6
|
+
v-if="query"
|
|
7
|
+
class="m-editor-page-bar-search-panel"
|
|
8
|
+
:inline="true"
|
|
9
|
+
:config="formConfig"
|
|
10
|
+
:init-values="query"
|
|
11
|
+
:prevent-submit-default="true"
|
|
12
|
+
@change="onFormChange"
|
|
13
|
+
></MForm>
|
|
14
|
+
</Teleport>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { ref } from 'vue';
|
|
20
|
+
import { Search } from '@element-plus/icons-vue';
|
|
21
|
+
|
|
22
|
+
import { NodeType } from '@tmagic/core';
|
|
23
|
+
import { createForm, MForm } from '@tmagic/form';
|
|
24
|
+
|
|
25
|
+
import Icon from '@editor/components/Icon.vue';
|
|
26
|
+
|
|
27
|
+
interface Query {
|
|
28
|
+
pageType: NodeType[];
|
|
29
|
+
keyword: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits<{
|
|
33
|
+
search: [value: Query];
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
const query = defineModel<Query>('query');
|
|
37
|
+
|
|
38
|
+
const formConfig = createForm([
|
|
39
|
+
{
|
|
40
|
+
type: 'checkbox-group',
|
|
41
|
+
name: 'pageType',
|
|
42
|
+
options: [
|
|
43
|
+
{
|
|
44
|
+
value: NodeType.PAGE,
|
|
45
|
+
text: '页面',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
value: NodeType.PAGE_FRAGMENT,
|
|
49
|
+
text: '页面片段',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'keyword',
|
|
55
|
+
type: 'text',
|
|
56
|
+
placeholder: '请输入关键字',
|
|
57
|
+
clearable: true,
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
const visible = ref(false);
|
|
62
|
+
|
|
63
|
+
const onFormChange = (values: Query) => {
|
|
64
|
+
query.value = values;
|
|
65
|
+
emit('search', values);
|
|
66
|
+
};
|
|
67
|
+
</script>
|
|
@@ -132,9 +132,20 @@ watch(zoom, (zoom) => {
|
|
|
132
132
|
stage.setZoom(zoom);
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
let timeoutId: NodeJS.Timeout | null = null;
|
|
135
136
|
watch(page, (page) => {
|
|
136
137
|
if (runtime && page) {
|
|
137
138
|
services?.editorService.set('stageLoading', true);
|
|
139
|
+
|
|
140
|
+
if (timeoutId) {
|
|
141
|
+
globalThis.clearTimeout(timeoutId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
timeoutId = globalThis.setTimeout(() => {
|
|
145
|
+
services?.editorService.set('stageLoading', false);
|
|
146
|
+
timeoutId = null;
|
|
147
|
+
}, 3000);
|
|
148
|
+
|
|
138
149
|
runtime.updatePageId?.(page.id);
|
|
139
150
|
nextTick(() => {
|
|
140
151
|
stage?.select(page.id);
|
package/src/services/dep.ts
CHANGED
|
@@ -79,7 +79,9 @@ class Dep extends BaseService {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
public collectIdle(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
|
|
82
|
+
let startTask = false;
|
|
82
83
|
this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
|
|
84
|
+
startTask = true;
|
|
83
85
|
idleTask.enqueueTask(
|
|
84
86
|
({ node, deep, target }) => {
|
|
85
87
|
this.collectNode(node, target, depExtendedData, deep);
|
|
@@ -92,8 +94,16 @@ class Dep extends BaseService {
|
|
|
92
94
|
);
|
|
93
95
|
});
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
return new Promise<void>((resolve) => {
|
|
98
|
+
if (!startTask) {
|
|
99
|
+
this.emit('collected', nodes, deep);
|
|
100
|
+
resolve();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
idleTask.once('finish', () => {
|
|
104
|
+
this.emit('collected', nodes, deep);
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
97
107
|
});
|
|
98
108
|
}
|
|
99
109
|
|
package/src/services/editor.ts
CHANGED
|
@@ -20,10 +20,19 @@ import { reactive, toRaw } from 'vue';
|
|
|
20
20
|
import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
|
|
21
21
|
import type { Writable } from 'type-fest';
|
|
22
22
|
|
|
23
|
-
import type { Id, MApp,
|
|
23
|
+
import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
|
|
24
24
|
import { NodeType, Target, Watcher } from '@tmagic/core';
|
|
25
25
|
import { isFixed } from '@tmagic/stage';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
calcValueByFontsize,
|
|
28
|
+
getElById,
|
|
29
|
+
getNodeInfo,
|
|
30
|
+
getNodePath,
|
|
31
|
+
isNumber,
|
|
32
|
+
isPage,
|
|
33
|
+
isPageFragment,
|
|
34
|
+
isPop,
|
|
35
|
+
} from '@tmagic/utils';
|
|
27
36
|
|
|
28
37
|
import BaseService from '@editor/services//BaseService';
|
|
29
38
|
import propsService from '@editor/services//props';
|
|
@@ -175,36 +184,7 @@ class Editor extends BaseService {
|
|
|
175
184
|
root = toRaw(root);
|
|
176
185
|
}
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
node: null,
|
|
180
|
-
parent: null,
|
|
181
|
-
page: null,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
if (!root) return info;
|
|
185
|
-
|
|
186
|
-
if (id === root.id) {
|
|
187
|
-
info.node = root;
|
|
188
|
-
return info;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const path = getNodePath(id, root.items);
|
|
192
|
-
|
|
193
|
-
if (!path.length) return info;
|
|
194
|
-
|
|
195
|
-
path.unshift(root);
|
|
196
|
-
|
|
197
|
-
info.node = path[path.length - 1] as MComponent;
|
|
198
|
-
info.parent = path[path.length - 2] as MContainer;
|
|
199
|
-
|
|
200
|
-
path.forEach((item) => {
|
|
201
|
-
if (isPage(item) || isPageFragment(item)) {
|
|
202
|
-
info.page = item as MPage | MPageFragment;
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
return info;
|
|
187
|
+
return getNodeInfo(id, root);
|
|
208
188
|
}
|
|
209
189
|
|
|
210
190
|
/**
|
|
@@ -494,11 +474,11 @@ class Editor extends BaseService {
|
|
|
494
474
|
if (isPage(node)) {
|
|
495
475
|
this.state.pageLength -= 1;
|
|
496
476
|
|
|
497
|
-
await selectDefault(
|
|
477
|
+
await selectDefault(rootItems);
|
|
498
478
|
} else if (isPageFragment(node)) {
|
|
499
479
|
this.state.pageFragmentLength -= 1;
|
|
500
480
|
|
|
501
|
-
await selectDefault(
|
|
481
|
+
await selectDefault(rootItems);
|
|
502
482
|
} else {
|
|
503
483
|
await this.select(parent);
|
|
504
484
|
stage?.select(parent.id);
|
|
@@ -586,11 +566,7 @@ class Editor extends BaseService {
|
|
|
586
566
|
nodes.splice(targetIndex, 1, newConfig);
|
|
587
567
|
this.set('nodes', [...nodes]);
|
|
588
568
|
|
|
589
|
-
|
|
590
|
-
config: cloneDeep(newConfig),
|
|
591
|
-
parentId: parent.id,
|
|
592
|
-
root: cloneDeep(root),
|
|
593
|
-
});
|
|
569
|
+
// update后会触发依赖收集,收集完后会掉stage.update方法
|
|
594
570
|
|
|
595
571
|
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
|
596
572
|
this.set('page', newConfig as MPage | MPageFragment);
|
|
@@ -875,7 +851,11 @@ class Editor extends BaseService {
|
|
|
875
851
|
await stage.select(targetId);
|
|
876
852
|
|
|
877
853
|
const targetParent = this.getParentById(target.id);
|
|
878
|
-
await stage.update({
|
|
854
|
+
await stage.update({
|
|
855
|
+
config: cloneDeep(target),
|
|
856
|
+
parentId: targetParent?.id,
|
|
857
|
+
root: cloneDeep(root),
|
|
858
|
+
});
|
|
879
859
|
|
|
880
860
|
await this.select(newConfig);
|
|
881
861
|
stage.select(newConfig.id);
|
package/src/theme/page-bar.scss
CHANGED
|
@@ -3,19 +3,13 @@
|
|
|
3
3
|
bottom: 0;
|
|
4
4
|
left: 0;
|
|
5
5
|
width: 100%;
|
|
6
|
+
user-select: none;
|
|
7
|
+
}
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
border-radius: 3px 3px 0 0;
|
|
12
|
-
border: 1px solid $--border-color;
|
|
13
|
-
border-bottom: 1px solid transparent;
|
|
14
|
-
padding: 5px 10px;
|
|
15
|
-
|
|
16
|
-
&.active {
|
|
17
|
-
background-color: #f3f3f3;
|
|
18
|
-
}
|
|
9
|
+
.m-editor-page-bar-item-icon {
|
|
10
|
+
.icon-active {
|
|
11
|
+
font-weight: bold;
|
|
12
|
+
color: $--theme-color;
|
|
19
13
|
}
|
|
20
14
|
}
|
|
21
15
|
|
|
@@ -101,3 +95,16 @@
|
|
|
101
95
|
}
|
|
102
96
|
}
|
|
103
97
|
}
|
|
98
|
+
|
|
99
|
+
.m-editor-page-bar-search-panel {
|
|
100
|
+
position: absolute;
|
|
101
|
+
bottom: $--page-bar-height;
|
|
102
|
+
border: 1px solid $--border-color;
|
|
103
|
+
padding: 6px 10px;
|
|
104
|
+
width: 100%;
|
|
105
|
+
box-sizing: border-box;
|
|
106
|
+
|
|
107
|
+
.tmagic-design-form-item {
|
|
108
|
+
margin-bottom: 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/type.ts
CHANGED
|
@@ -77,9 +77,10 @@ export interface FrameworkSlots {
|
|
|
77
77
|
'props-panel'(props: {}): any;
|
|
78
78
|
'footer'(props: {}): any;
|
|
79
79
|
'page-bar'(props: {}): any;
|
|
80
|
+
'page-bar-add-button'(props: {}): any;
|
|
80
81
|
'page-bar-title'(props: { page: MPage | MPageFragment }): any;
|
|
81
82
|
'page-bar-popover'(props: { page: MPage | MPageFragment }): any;
|
|
82
|
-
'page-list-popover'(props: { list: MPage
|
|
83
|
+
'page-list-popover'(props: { list: (MPage | MPageFragment)[] }): any;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
export interface WorkspaceSlots {
|
package/src/utils/idle-task.ts
CHANGED
|
@@ -56,13 +56,14 @@ export class IdleTask<T = any> extends EventEmitter {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
private runTaskQueue(deadline: IdleDeadline) {
|
|
59
|
-
|
|
59
|
+
// 动画会占用空闲时间,当任务一直无法执行时,看看是否有动画正在播放
|
|
60
|
+
while ((deadline.timeRemaining() > 10 || deadline.didTimeout) && this.taskList.length) {
|
|
60
61
|
const task = this.taskList.shift();
|
|
61
62
|
task!.handler(task!.data);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (this.taskList.length) {
|
|
65
|
-
this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout:
|
|
66
|
+
this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 300 });
|
|
66
67
|
} else {
|
|
67
68
|
this.taskHandle = 0;
|
|
68
69
|
|