@tmagic/editor 1.4.8 → 1.4.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.
Files changed (166) hide show
  1. package/dist/style.css +26 -0
  2. package/dist/tmagic-editor.js +3213 -2820
  3. package/dist/tmagic-editor.umd.cjs +3237 -2839
  4. package/package.json +14 -12
  5. package/src/Editor.vue +212 -0
  6. package/src/components/CodeBlockEditor.vue +268 -0
  7. package/src/components/CodeParams.vue +60 -0
  8. package/src/components/ContentMenu.vue +193 -0
  9. package/src/components/FloatingBox.vue +178 -0
  10. package/src/components/Icon.vue +27 -0
  11. package/src/components/Resizer.vue +23 -0
  12. package/src/components/ScrollBar.vue +151 -0
  13. package/src/components/ScrollViewer.vue +128 -0
  14. package/src/components/SearchInput.vue +37 -0
  15. package/src/components/SplitView.vue +186 -0
  16. package/src/components/ToolButton.vue +138 -0
  17. package/src/components/Tree.vue +71 -0
  18. package/src/components/TreeNode.vue +150 -0
  19. package/src/editorProps.ts +120 -0
  20. package/src/fields/Code.vue +50 -0
  21. package/src/fields/CodeLink.vue +80 -0
  22. package/src/fields/CodeSelect.vue +132 -0
  23. package/src/fields/CodeSelectCol.vue +139 -0
  24. package/src/fields/CondOpSelect.vue +87 -0
  25. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +152 -0
  26. package/src/fields/DataSourceFieldSelect/Index.vue +168 -0
  27. package/src/fields/DataSourceFields.vue +329 -0
  28. package/src/fields/DataSourceInput.vue +339 -0
  29. package/src/fields/DataSourceMethodSelect.vue +153 -0
  30. package/src/fields/DataSourceMethods.vue +104 -0
  31. package/src/fields/DataSourceMocks.vue +255 -0
  32. package/src/fields/DataSourceSelect.vue +98 -0
  33. package/src/fields/DisplayConds.vue +145 -0
  34. package/src/fields/EventSelect.vue +375 -0
  35. package/src/fields/KeyValue.vue +137 -0
  36. package/src/fields/PageFragmentSelect.vue +63 -0
  37. package/src/fields/UISelect.vue +135 -0
  38. package/src/hooks/index.ts +24 -0
  39. package/src/hooks/use-code-block-edit.ts +83 -0
  40. package/src/hooks/use-data-source-edit.ts +46 -0
  41. package/src/hooks/use-data-source-method.ts +100 -0
  42. package/src/hooks/use-editor-content-height.ts +26 -0
  43. package/src/hooks/use-filter.ts +55 -0
  44. package/src/hooks/use-float-box.ts +76 -0
  45. package/src/hooks/use-getso.ts +35 -0
  46. package/src/hooks/use-next-float-box-position.ts +29 -0
  47. package/src/hooks/use-node-status.ts +48 -0
  48. package/src/hooks/use-stage.ts +129 -0
  49. package/src/hooks/use-window-rect.ts +20 -0
  50. package/src/icons/AppManageIcon.vue +15 -0
  51. package/src/icons/CenterIcon.vue +13 -0
  52. package/src/icons/CodeIcon.vue +28 -0
  53. package/src/icons/FolderMinusIcon.vue +22 -0
  54. package/src/icons/PinIcon.vue +25 -0
  55. package/src/icons/PinnedIcon.vue +25 -0
  56. package/src/index.ts +124 -0
  57. package/src/initService.ts +448 -0
  58. package/src/layouts/AddPageBox.vue +55 -0
  59. package/src/layouts/CodeEditor.vue +226 -0
  60. package/src/layouts/Framework.vue +165 -0
  61. package/src/layouts/NavMenu.vue +200 -0
  62. package/src/layouts/PropsPanel.vue +131 -0
  63. package/src/layouts/page-bar/AddButton.vue +48 -0
  64. package/src/layouts/page-bar/PageBar.vue +165 -0
  65. package/src/layouts/page-bar/PageBarScrollContainer.vue +192 -0
  66. package/src/layouts/page-bar/PageList.vue +55 -0
  67. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  68. package/src/layouts/sidebar/ComponentListPanel.vue +134 -0
  69. package/src/layouts/sidebar/Sidebar.vue +278 -0
  70. package/src/layouts/sidebar/code-block/CodeBlockList.vue +160 -0
  71. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +70 -0
  72. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +84 -0
  73. package/src/layouts/sidebar/data-source/DataSourceList.vue +172 -0
  74. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +106 -0
  75. package/src/layouts/sidebar/layer/LayerMenu.vue +128 -0
  76. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  77. package/src/layouts/sidebar/layer/LayerPanel.vue +119 -0
  78. package/src/layouts/sidebar/layer/use-click.ts +113 -0
  79. package/src/layouts/sidebar/layer/use-drag.ts +167 -0
  80. package/src/layouts/sidebar/layer/use-keybinding.ts +54 -0
  81. package/src/layouts/sidebar/layer/use-node-status.ts +114 -0
  82. package/src/layouts/workspace/Breadcrumb.vue +35 -0
  83. package/src/layouts/workspace/Workspace.vue +46 -0
  84. package/src/layouts/workspace/viewer/NodeListMenu.vue +115 -0
  85. package/src/layouts/workspace/viewer/Stage.vue +246 -0
  86. package/src/layouts/workspace/viewer/StageOverlay.vue +68 -0
  87. package/src/layouts/workspace/viewer/ViewerMenu.vue +145 -0
  88. package/src/services/BaseService.ts +230 -0
  89. package/src/services/codeBlock.ts +327 -0
  90. package/src/services/componentList.ts +58 -0
  91. package/src/services/dataSource.ts +216 -0
  92. package/src/services/dep.ts +152 -0
  93. package/src/services/editor.ts +1135 -0
  94. package/src/services/events.ts +93 -0
  95. package/src/services/history.ts +126 -0
  96. package/src/services/keybinding.ts +220 -0
  97. package/src/services/props.ts +286 -0
  98. package/src/services/stageOverlay.ts +212 -0
  99. package/src/services/storage.ts +155 -0
  100. package/src/services/ui.ts +157 -0
  101. package/src/shims-vue.d.ts +6 -0
  102. package/src/theme/breadcrumb.scss +6 -0
  103. package/src/theme/code-block.scss +6 -0
  104. package/src/theme/code-editor.scss +38 -0
  105. package/src/theme/common/var.scss +14 -0
  106. package/src/theme/component-list-panel.scss +95 -0
  107. package/src/theme/content-menu.scss +76 -0
  108. package/src/theme/data-source-field-select.scss +16 -0
  109. package/src/theme/data-source-field.scss +12 -0
  110. package/src/theme/data-source-fields.scss +13 -0
  111. package/src/theme/data-source-input.scss +18 -0
  112. package/src/theme/data-source-methods.scss +13 -0
  113. package/src/theme/data-source.scss +28 -0
  114. package/src/theme/event.scss +37 -0
  115. package/src/theme/floating-box.scss +32 -0
  116. package/src/theme/framework.scss +69 -0
  117. package/src/theme/icon.scss +12 -0
  118. package/src/theme/index.scss +10 -0
  119. package/src/theme/key-value.scss +20 -0
  120. package/src/theme/layer-panel.scss +26 -0
  121. package/src/theme/layout.scss +9 -0
  122. package/src/theme/nav-menu.scss +78 -0
  123. package/src/theme/page-bar.scss +102 -0
  124. package/src/theme/page-fragment-select.scss +14 -0
  125. package/src/theme/props-panel.scss +55 -0
  126. package/src/theme/resizer.scss +66 -0
  127. package/src/theme/ruler.scss +38 -0
  128. package/src/theme/search-input.scss +14 -0
  129. package/src/theme/sidebar.scss +79 -0
  130. package/src/theme/stage.scss +76 -0
  131. package/src/theme/theme.scss +28 -0
  132. package/src/theme/tree.scss +89 -0
  133. package/src/theme/workspace.scss +9 -0
  134. package/src/type.ts +780 -0
  135. package/src/utils/compose.ts +52 -0
  136. package/src/utils/config.ts +29 -0
  137. package/src/utils/content-menu.ts +95 -0
  138. package/src/utils/data-source/formConfigs/base.ts +30 -0
  139. package/src/utils/data-source/formConfigs/http.ts +60 -0
  140. package/src/utils/data-source/index.ts +249 -0
  141. package/src/utils/editor.ts +313 -0
  142. package/src/utils/idle-task.ts +72 -0
  143. package/src/utils/index.ts +24 -0
  144. package/src/utils/keybinding-config.ts +125 -0
  145. package/src/utils/logger.ts +47 -0
  146. package/src/utils/monaco-editor.ts +8 -0
  147. package/src/utils/operator.ts +106 -0
  148. package/src/utils/props.ts +407 -0
  149. package/src/utils/scroll-viewer.ts +165 -0
  150. package/src/utils/tree.ts +15 -0
  151. package/src/utils/undo-redo.ts +76 -0
  152. package/types/editorProps.d.ts +3 -1
  153. package/types/fields/CondOpSelect.vue.d.ts +17 -0
  154. package/types/fields/DataSourceFieldSelect/FieldSelect.vue.d.ts +36 -0
  155. package/types/fields/{DataSourceFieldSelect.vue.d.ts → DataSourceFieldSelect/Index.vue.d.ts} +1 -1
  156. package/types/fields/DisplayConds.vue.d.ts +32 -0
  157. package/types/index.d.ts +3 -1
  158. package/types/layouts/Framework.vue.d.ts +3 -1
  159. package/types/layouts/PropsPanel.vue.d.ts +21 -21
  160. package/types/layouts/page-bar/PageBar.vue.d.ts +6 -0
  161. package/types/layouts/page-bar/PageBarScrollContainer.vue.d.ts +3 -0
  162. package/types/layouts/page-bar/PageList.vue.d.ts +25 -0
  163. package/types/services/ui.d.ts +1 -0
  164. package/types/type.d.ts +30 -4
  165. package/types/utils/data-source/index.d.ts +1 -0
  166. package/types/utils/props.d.ts +12 -0
@@ -0,0 +1,131 @@
1
+ <template>
2
+ <div class="m-editor-props-panel" v-show="nodes.length === 1">
3
+ <slot name="props-panel-header"></slot>
4
+ <MForm
5
+ ref="configForm"
6
+ :class="propsPanelSize"
7
+ :popper-class="`m-editor-props-panel-popper ${propsPanelSize}`"
8
+ :size="propsPanelSize"
9
+ :init-values="values"
10
+ :config="curFormConfig"
11
+ :extend-state="extendState"
12
+ @change="submit"
13
+ @error="errorHandler"
14
+ ></MForm>
15
+
16
+ <TMagicButton
17
+ v-if="!disabledShowSrc"
18
+ class="m-editor-props-panel-src-icon"
19
+ circle
20
+ size="large"
21
+ title="源码"
22
+ :type="showSrc ? 'primary' : ''"
23
+ @click="showSrc = !showSrc"
24
+ >
25
+ <MIcon :icon="DocumentIcon"></MIcon>
26
+ </TMagicButton>
27
+
28
+ <CodeEditor
29
+ v-if="showSrc"
30
+ class="m-editor-props-panel-src-code"
31
+ :height="`${editorContentHeight}px`"
32
+ :init-values="values"
33
+ :options="codeOptions"
34
+ :parse="true"
35
+ @save="saveCode"
36
+ ></CodeEditor>
37
+ </div>
38
+ </template>
39
+
40
+ <script lang="ts" setup>
41
+ import { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue';
42
+ import { Document as DocumentIcon } from '@element-plus/icons-vue';
43
+
44
+ import { TMagicButton } from '@tmagic/design';
45
+ import type { FormState, FormValue } from '@tmagic/form';
46
+ import { MForm } from '@tmagic/form';
47
+ import type { MNode } from '@tmagic/schema';
48
+
49
+ import MIcon from '@editor/components/Icon.vue';
50
+ import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
51
+ import type { PropsPanelSlots, Services } from '@editor/type';
52
+
53
+ import CodeEditor from './CodeEditor.vue';
54
+
55
+ defineSlots<PropsPanelSlots>();
56
+
57
+ defineOptions({
58
+ name: 'MEditorPropsPanel',
59
+ });
60
+
61
+ defineProps<{
62
+ disabledShowSrc?: boolean;
63
+ extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
64
+ }>();
65
+
66
+ const codeOptions = inject('codeOptions', {});
67
+
68
+ const emit = defineEmits(['mounted', 'submit-error', 'form-error']);
69
+
70
+ const showSrc = ref(false);
71
+
72
+ const internalInstance = getCurrentInstance();
73
+ const values = ref<FormValue>({});
74
+ const configForm = ref<InstanceType<typeof MForm>>();
75
+ // ts类型应该是FormConfig, 但是打包时会出错,所以暂时用any
76
+ const curFormConfig = ref<any>([]);
77
+ const services = inject<Services>('services');
78
+ const node = computed(() => services?.editorService.get('node'));
79
+ const nodes = computed(() => services?.editorService.get('nodes') || []);
80
+ const propsPanelSize = computed(() => services?.uiService.get('propsPanelSize') || 'small');
81
+ const stage = computed(() => services?.editorService.get('stage'));
82
+
83
+ const { height: editorContentHeight } = useEditorContentHeight();
84
+
85
+ const init = async () => {
86
+ if (!node.value) {
87
+ curFormConfig.value = [];
88
+ return;
89
+ }
90
+
91
+ const type = node.value.type || (node.value.items ? 'container' : 'text');
92
+ curFormConfig.value = (await services?.propsService.getPropsConfig(type)) || [];
93
+ values.value = node.value;
94
+ };
95
+
96
+ watchEffect(init);
97
+ services?.propsService.on('props-configs-change', init);
98
+
99
+ onMounted(() => {
100
+ emit('mounted', internalInstance);
101
+ });
102
+
103
+ onBeforeUnmount(() => {
104
+ services?.propsService.off('props-configs-change', init);
105
+ });
106
+
107
+ watchEffect(() => {
108
+ if (configForm.value && stage.value) {
109
+ configForm.value.formState.stage = stage.value;
110
+ }
111
+ });
112
+
113
+ const submit = async () => {
114
+ try {
115
+ const values = await configForm.value?.submitForm();
116
+ services?.editorService.update(values);
117
+ } catch (e: any) {
118
+ emit('submit-error', e);
119
+ }
120
+ };
121
+
122
+ const errorHandler = (e: any) => {
123
+ emit('form-error', e);
124
+ };
125
+
126
+ const saveCode = (values: MNode) => {
127
+ services?.editorService.update(values);
128
+ };
129
+
130
+ defineExpose({ configForm, submit });
131
+ </script>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div
3
+ v-if="showAddPageButton"
4
+ id="m-editor-page-bar-add-icon"
5
+ class="m-editor-page-bar-item m-editor-page-bar-item-icon"
6
+ @click="addPage"
7
+ >
8
+ <Icon :icon="Plus"></Icon>
9
+ </div>
10
+ <div v-else style="width: 21px"></div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed, inject, toRaw } from 'vue';
15
+ import { Plus } from '@element-plus/icons-vue';
16
+
17
+ import { NodeType } from '@tmagic/schema';
18
+
19
+ import Icon from '@editor/components/Icon.vue';
20
+ import type { Services } from '@editor/type';
21
+ import { generatePageNameByApp } from '@editor/utils/editor';
22
+
23
+ defineOptions({
24
+ name: 'MEditorPageBarAddButton',
25
+ });
26
+
27
+ const props = defineProps<{
28
+ type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
29
+ }>();
30
+
31
+ const services = inject<Services>('services');
32
+ const uiService = services?.uiService;
33
+ const editorService = services?.editorService;
34
+
35
+ const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
36
+
37
+ const addPage = () => {
38
+ if (!editorService) return;
39
+ const root = toRaw(editorService.get('root'));
40
+ if (!root) throw new Error('root 不能为空');
41
+ const pageConfig = {
42
+ type: props.type,
43
+ name: generatePageNameByApp(root, props.type),
44
+ items: [],
45
+ };
46
+ editorService.add(pageConfig);
47
+ };
48
+ </script>
@@ -0,0 +1,165 @@
1
+ <template>
2
+ <div class="m-editor-page-bar-tabs">
3
+ <SwitchTypeButton v-if="!disabledPageFragment" v-model="active" />
4
+
5
+ <PageBarScrollContainer :type="active" :page-bar-sort-options="pageBarSortOptions">
6
+ <template #prepend>
7
+ <AddButton :type="active"></AddButton>
8
+ <PageList :list="list">
9
+ <template #page-list-popover="{ list }"><slot name="page-list-popover" :list="list"></slot></template>
10
+ </PageList>
11
+ </template>
12
+
13
+ <div
14
+ v-for="item in list"
15
+ class="m-editor-page-bar-item"
16
+ :key="item.id"
17
+ :page-id="item.id"
18
+ :class="{ active: page?.id === item.id }"
19
+ @click="switchPage(item.id)"
20
+ >
21
+ <div class="m-editor-page-bar-title">
22
+ <slot name="page-bar-title" :page="item">
23
+ <span :title="item.name">{{ item.name || item.id }}</span>
24
+ </slot>
25
+ </div>
26
+
27
+ <TMagicPopover popper-class="page-bar-popover" placement="top" :width="160" trigger="hover">
28
+ <div>
29
+ <slot name="page-bar-popover" :page="item">
30
+ <ToolButton
31
+ :data="{
32
+ type: 'button',
33
+ text: '复制',
34
+ icon: DocumentCopy,
35
+ handler: () => copy(item),
36
+ }"
37
+ ></ToolButton>
38
+ <ToolButton
39
+ :data="{
40
+ type: 'button',
41
+ text: '删除',
42
+ icon: Delete,
43
+ handler: () => remove(item),
44
+ }"
45
+ ></ToolButton>
46
+ </slot>
47
+ </div>
48
+ <template #reference>
49
+ <TMagicIcon class="m-editor-page-bar-menu-icon">
50
+ <CaretBottom></CaretBottom>
51
+ </TMagicIcon>
52
+ </template>
53
+ </TMagicPopover>
54
+ </div>
55
+ </PageBarScrollContainer>
56
+ </div>
57
+ </template>
58
+
59
+ <script lang="ts" setup>
60
+ import { computed, inject, ref, watch } from 'vue';
61
+ import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue';
62
+
63
+ import { TMagicIcon, TMagicPopover } from '@tmagic/design';
64
+ import { Id, type MPage, type MPageFragment, NodeType } from '@tmagic/schema';
65
+ import { isPage, isPageFragment } from '@tmagic/utils';
66
+
67
+ import ToolButton from '@editor/components/ToolButton.vue';
68
+ import type { PageBarSortOptions, Services } from '@editor/type';
69
+ import { getPageFragmentList, getPageList } from '@editor/utils';
70
+
71
+ import AddButton from './AddButton.vue';
72
+ import PageBarScrollContainer from './PageBarScrollContainer.vue';
73
+ import PageList from './PageList.vue';
74
+ import SwitchTypeButton from './SwitchTypeButton.vue';
75
+
76
+ defineOptions({
77
+ name: 'MEditorPageBar',
78
+ });
79
+
80
+ defineProps<{
81
+ disabledPageFragment: boolean;
82
+ pageBarSortOptions?: PageBarSortOptions;
83
+ }>();
84
+
85
+ const active = ref<NodeType.PAGE | NodeType.PAGE_FRAGMENT>(NodeType.PAGE);
86
+
87
+ const services = inject<Services>('services');
88
+ const editorService = services?.editorService;
89
+
90
+ const root = computed(() => editorService?.get('root'));
91
+ const page = computed(() => editorService?.get('page'));
92
+ const pageList = computed(() => getPageList(root.value));
93
+ const pageFragmentList = computed(() => getPageFragmentList(root.value));
94
+
95
+ const list = computed(() => (active.value === NodeType.PAGE ? pageList.value : pageFragmentList.value));
96
+
97
+ const activePage = ref<Id>('');
98
+ const activePageFragment = ref<Id>('');
99
+
100
+ watch(
101
+ page,
102
+ (page) => {
103
+ if (!page) {
104
+ if (active.value === NodeType.PAGE) {
105
+ activePage.value = '';
106
+ }
107
+ if (active.value === NodeType.PAGE_FRAGMENT) {
108
+ activePageFragment.value = '';
109
+ }
110
+ return;
111
+ }
112
+
113
+ if (isPage(page)) {
114
+ activePage.value = page?.id;
115
+ if (active.value !== NodeType.PAGE) {
116
+ active.value = NodeType.PAGE;
117
+ }
118
+ } else if (isPageFragment(page)) {
119
+ activePageFragment.value = page?.id;
120
+ if (active.value !== NodeType.PAGE_FRAGMENT) {
121
+ active.value = NodeType.PAGE_FRAGMENT;
122
+ }
123
+ }
124
+ },
125
+ {
126
+ immediate: true,
127
+ },
128
+ );
129
+
130
+ watch(active, (active) => {
131
+ if (active === NodeType.PAGE) {
132
+ if (!activePage.value && !pageList.value.length) {
133
+ editorService?.selectRoot();
134
+ return;
135
+ }
136
+ switchPage(activePage.value);
137
+ return;
138
+ }
139
+
140
+ if (active === NodeType.PAGE_FRAGMENT) {
141
+ // 之前没有选中过页面片并且当前没有页面片
142
+ if (!activePageFragment.value && !pageFragmentList.value.length) {
143
+ editorService?.selectRoot();
144
+ return;
145
+ }
146
+ switchPage(activePageFragment.value || pageFragmentList.value[0].id);
147
+ }
148
+ });
149
+
150
+ const switchPage = (id: Id) => {
151
+ editorService?.select(id);
152
+ };
153
+
154
+ const copy = (node: MPage | MPageFragment) => {
155
+ node && editorService?.copy(node);
156
+ editorService?.paste({
157
+ left: 0,
158
+ top: 0,
159
+ });
160
+ };
161
+
162
+ const remove = (node: MPage | MPageFragment) => {
163
+ editorService?.remove(node);
164
+ };
165
+ </script>
@@ -0,0 +1,192 @@
1
+ <template>
2
+ <div class="m-editor-page-bar" ref="pageBar">
3
+ <slot name="prepend"></slot>
4
+
5
+ <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('left')">
6
+ <Icon :icon="ArrowLeftBold"></Icon>
7
+ </div>
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
+ >
15
+ <slot></slot>
16
+ </div>
17
+
18
+ <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('right')">
19
+ <Icon :icon="ArrowRightBold"></Icon>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <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';
36
+ import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
37
+ import Sortable, { SortableEvent } from 'sortablejs';
38
+
39
+ import { Id, NodeType } from '@tmagic/schema';
40
+
41
+ import Icon from '@editor/components/Icon.vue';
42
+ import type { PageBarSortOptions, Services } from '@editor/type';
43
+
44
+ defineOptions({
45
+ name: 'MEditorPageBarScrollContainer',
46
+ });
47
+
48
+ const props = defineProps<{
49
+ type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
50
+ pageBarSortOptions?: PageBarSortOptions;
51
+ }>();
52
+
53
+ const services = inject<Services>('services');
54
+ const editorService = services?.editorService;
55
+ const uiService = services?.uiService;
56
+
57
+ const itemsContainer = ref<HTMLElement>();
58
+ const canScroll = ref(false);
59
+
60
+ const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
61
+ const showPageListButton = computed(() => uiService?.get('showPageListButton'));
62
+
63
+ const itemsContainerWidth = ref(0);
64
+
65
+ const setCanScroll = () => {
66
+ // 减去新增、左移、右移三个按钮的宽度
67
+ // 37 = icon width 16 + padding 10 * 2 + border-right 1
68
+ itemsContainerWidth.value =
69
+ (pageBar.value?.clientWidth || 0) -
70
+ 37 * 2 -
71
+ (showAddPageButton.value ? 37 : 21) -
72
+ (showPageListButton.value ? 37 : 0);
73
+
74
+ nextTick(() => {
75
+ if (itemsContainer.value) {
76
+ canScroll.value = itemsContainer.value.scrollWidth - itemsContainerWidth.value > 1;
77
+ }
78
+ });
79
+ };
80
+
81
+ const resizeObserver = new ResizeObserver(() => {
82
+ setCanScroll();
83
+ });
84
+
85
+ const pageBar = ref<HTMLDivElement>();
86
+ onMounted(() => {
87
+ pageBar.value && resizeObserver.observe(pageBar.value);
88
+ });
89
+
90
+ onBeforeUnmount(() => {
91
+ resizeObserver.disconnect();
92
+ });
93
+
94
+ let translateLeft = 0;
95
+
96
+ const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
97
+ if (!itemsContainer.value) return;
98
+
99
+ const maxScrollLeft = itemsContainer.value.scrollWidth - itemsContainerWidth.value;
100
+
101
+ if (type === 'left') {
102
+ translateLeft += 100;
103
+
104
+ if (translateLeft > 0) {
105
+ translateLeft = 0;
106
+ }
107
+ } else if (type === 'right') {
108
+ translateLeft -= 100;
109
+
110
+ if (-translateLeft > maxScrollLeft) {
111
+ translateLeft = -maxScrollLeft;
112
+ }
113
+ } else if (type === 'start') {
114
+ translateLeft = 0;
115
+ } else if (type === 'end') {
116
+ translateLeft = -maxScrollLeft;
117
+ }
118
+
119
+ itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
120
+ };
121
+
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
+ },
157
+ },
158
+ ...{
159
+ ...(props.pageBarSortOptions ? props.pageBarSortOptions : {}),
160
+ },
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
+ }
187
+ },
188
+ {
189
+ immediate: true,
190
+ },
191
+ );
192
+ </script>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <div
3
+ v-if="showPageListButton"
4
+ id="m-editor-page-bar-list-icon"
5
+ class="m-editor-page-bar-item m-editor-page-bar-item-icon"
6
+ >
7
+ <TMagicPopover popper-class="page-bar-popover" placement="top" :width="160" trigger="hover">
8
+ <div>
9
+ <slot name="page-list-popover" :list="list">
10
+ <ToolButton
11
+ v-for="(item, index) in list"
12
+ :data="{
13
+ type: 'button',
14
+ text: item.devconfig?.tabName || item.name || item.id,
15
+ handler: () => switchPage(item.id),
16
+ }"
17
+ :key="index"
18
+ ></ToolButton>
19
+ </slot>
20
+ </div>
21
+ <template #reference>
22
+ <TMagicIcon class="m-editor-page-list-menu-icon">
23
+ <Files></Files>
24
+ </TMagicIcon>
25
+ </template>
26
+ </TMagicPopover>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { computed, inject } from 'vue';
32
+ import { Files } from '@element-plus/icons-vue';
33
+
34
+ import { TMagicIcon, TMagicPopover } from '@tmagic/design';
35
+ import { Id, MPage, MPageFragment } from '@tmagic/schema';
36
+
37
+ import ToolButton from '@editor/components/ToolButton.vue';
38
+ import type { Services } from '@editor/type';
39
+ defineOptions({
40
+ name: 'MEditorPageList',
41
+ });
42
+
43
+ defineProps<{
44
+ list: MPage[] | MPageFragment[];
45
+ }>();
46
+
47
+ const services = inject<Services>('services');
48
+ const uiService = services?.uiService;
49
+ const editorService = services?.editorService;
50
+
51
+ const showPageListButton = computed(() => uiService?.get('showPageListButton'));
52
+ const switchPage = (id: Id) => {
53
+ editorService?.select(id);
54
+ };
55
+ </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <TMagicButton
3
+ v-for="item in data"
4
+ class="m-editor-page-bar-switch-type-button"
5
+ size="small"
6
+ :key="item.type"
7
+ link
8
+ :class="{ active: modelValue === item.type }"
9
+ :type="modelValue === item.type ? 'primary' : ''"
10
+ @click="clickHandler(item.type)"
11
+ >{{ item.text }}</TMagicButton
12
+ >
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { TMagicButton } from '@tmagic/design';
17
+ import { NodeType } from '@tmagic/schema';
18
+
19
+ defineOptions({
20
+ name: 'MEditorPageBarSwitchTypeButton',
21
+ });
22
+
23
+ defineProps<{
24
+ modelValue: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
25
+ }>();
26
+
27
+ const data: { type: NodeType.PAGE | NodeType.PAGE_FRAGMENT; text: string }[] = [
28
+ {
29
+ type: NodeType.PAGE,
30
+ text: '页面',
31
+ },
32
+ {
33
+ type: NodeType.PAGE_FRAGMENT,
34
+ text: '页面片',
35
+ },
36
+ ];
37
+
38
+ const emit = defineEmits<{
39
+ 'update:modelValue': [value: NodeType.PAGE | NodeType.PAGE_FRAGMENT];
40
+ }>();
41
+
42
+ const clickHandler = (value: NodeType.PAGE | NodeType.PAGE_FRAGMENT) => {
43
+ emit('update:modelValue', value);
44
+ };
45
+ </script>