@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.
@@ -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, NodeType } from '@tmagic/core';
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
- const pageLength = computed(() => editorService?.get('pageLength') || 0);
123
- const pageFragmentLength = computed(() => editorService?.get('pageFragmentLength') || 0);
124
-
125
- const crateWatchLength = (length: ComputedRef<number>) =>
126
- watch(
127
- length,
128
- (length = 0, preLength = 0) => {
129
- setTimeout(() => {
130
- setCanScroll();
131
- if (length < preLength) {
132
- scroll('start');
133
- } else {
134
- scroll('end');
135
- }
136
- if (length > 1) {
137
- const el = document.querySelector('.m-editor-page-bar-items') as HTMLElement;
138
- let beforeDragList: Id[] = [];
139
- const options = {
140
- ...{
141
- dataIdAttr: 'page-id', // 获取排序后的数据
142
- onStart: async (event: SortableEvent) => {
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
- ...(props.pageBarSortOptions ? props.pageBarSortOptions : {}),
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
- if (!el) return;
163
- const sortable = new Sortable(el, options);
164
- }
165
- });
166
- },
167
- {
168
- immediate: true,
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,
@@ -47,7 +47,7 @@ defineOptions({
47
47
  });
48
48
 
49
49
  defineProps<{
50
- list: MPage[] | MPageFragment[];
50
+ list: (MPage | MPageFragment)[];
51
51
  }>();
52
52
 
53
53
  const services = inject<Services>('services');
@@ -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);
@@ -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
- idleTask.once('finish', () => {
96
- this.emit('collected', nodes, deep);
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
 
@@ -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, MComponent, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
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 { calcValueByFontsize, getElById, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
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
- const info: EditorNodeInfo = {
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(getPageList(root));
477
+ await selectDefault(rootItems);
498
478
  } else if (isPageFragment(node)) {
499
479
  this.state.pageFragmentLength -= 1;
500
480
 
501
- await selectDefault(getPageFragmentList(root));
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
- this.get('stage')?.update({
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({ config: cloneDeep(target), parentId: targetParent?.id, root: cloneDeep(root) });
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);
@@ -3,19 +3,13 @@
3
3
  bottom: 0;
4
4
  left: 0;
5
5
  width: 100%;
6
+ user-select: none;
7
+ }
6
8
 
7
- .tmagic-design-button.m-editor-page-bar-switch-type-button {
8
- margin-left: 10px;
9
- position: relative;
10
- top: 1px;
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[] | MPageFragment[] }): any;
83
+ 'page-list-popover'(props: { list: (MPage | MPageFragment)[] }): any;
83
84
  }
84
85
 
85
86
  export interface WorkspaceSlots {
@@ -56,13 +56,14 @@ export class IdleTask<T = any> extends EventEmitter {
56
56
  }
57
57
 
58
58
  private runTaskQueue(deadline: IdleDeadline) {
59
- while ((deadline.timeRemaining() > 15 || deadline.didTimeout) && this.taskList.length) {
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: 10000 });
66
+ this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 300 });
66
67
  } else {
67
68
  this.taskHandle = 0;
68
69