diff --git a/packages/editor/package.json b/packages/editor/package.json index 51ffc316..028d0843 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -61,6 +61,7 @@ "keycon": "^1.4.0", "lodash-es": "^4.17.21", "monaco-editor": "^0.41.0", + "moveable": "^0.51.1", "serialize-javascript": "^6.0.0", "vue": "^3.3.4" }, diff --git a/packages/editor/src/components/CodeBlockEditor.vue b/packages/editor/src/components/CodeBlockEditor.vue index a473ea2c..14bb10b3 100644 --- a/packages/editor/src/components/CodeBlockEditor.vue +++ b/packages/editor/src/components/CodeBlockEditor.vue @@ -1,5 +1,6 @@ - +
@@ -50,11 +51,11 @@ import { computed, inject, onUnmounted, ref } from 'vue'; import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design'; -import { ColumnConfig, FormConfig, FormState, MFormDrawer } from '@tmagic/form'; +import { ColumnConfig, FormConfig, FormState, MFormBox, MFormDrawer } from '@tmagic/form'; import type { CodeBlockContent } from '@tmagic/schema'; import CodeEditor from '@editor/layouts/CodeEditor.vue'; -import type { Services } from '@editor/type'; +import type { Services, SlideType } from '@editor/type'; import { getConfig } from '@editor/utils/config'; defineOptions({ @@ -66,6 +67,7 @@ const props = defineProps<{ disabled?: boolean; isDataSource?: boolean; dataSourceType?: string; + slideType?: SlideType; }>(); const emit = defineEmits<{ diff --git a/packages/editor/src/hooks/index.ts b/packages/editor/src/hooks/index.ts index 5eab9d68..7c1396f4 100644 --- a/packages/editor/src/hooks/index.ts +++ b/packages/editor/src/hooks/index.ts @@ -19,3 +19,4 @@ export * from './use-code-block-edit'; export * from './use-data-source-method'; export * from './use-stage'; +export * from './use-float-box'; diff --git a/packages/editor/src/hooks/use-code-block-edit.ts b/packages/editor/src/hooks/use-code-block-edit.ts index 64fa7eb2..a3d0feeb 100644 --- a/packages/editor/src/hooks/use-code-block-edit.ts +++ b/packages/editor/src/hooks/use-code-block-edit.ts @@ -68,6 +68,8 @@ export const useCodeBlockEdit = (codeBlockService?: CodeBlockService) => { await codeBlockService?.setCodeDslById(codeId.value, values); + tMagicMessage.success('代码块保存成功'); + codeBlockEditor.value?.hide(); }; diff --git a/packages/editor/src/hooks/use-float-box.ts b/packages/editor/src/hooks/use-float-box.ts new file mode 100644 index 00000000..b6cfad17 --- /dev/null +++ b/packages/editor/src/hooks/use-float-box.ts @@ -0,0 +1,153 @@ +import { computed, ComputedRef, inject, nextTick, ref, watch } from 'vue'; +import Moveable from 'moveable'; + +import { type Services } from '@editor/type'; + +export const useFloatBox = (slideKeys: ComputedRef) => { + const services = inject('services'); + const moveable = ref(); + const floatBox = ref(); + + const floatBoxStates = computed(() => services?.uiService.get('floatBox')); + + const curKey = ref(''); + const target = computed(() => + floatBox.value + ? floatBox.value.find((item) => item.classList.contains(`m-editor-float-box-${curKey.value}`)) + : undefined, + ); + + const showingBoxKeys = computed(() => + [...(floatBoxStates.value?.keys() ?? [])].filter((key) => floatBoxStates.value?.get(key)?.status), + ); + + const isDraging = ref(false); + + const showFloatBox = async (key: string) => { + const curBoxStatus = floatBoxStates.value?.get(curKey.value)?.status; + if (curKey.value === key && curBoxStatus) return; + curKey.value = key; + setSlideState(key, { + zIndex: getMaxZIndex() + 1, + status: true, + }); + + await nextTick(); + if (moveable.value) { + moveable.value.target = target.value; + moveable.value.dragTarget = getDragTarget(); + moveable.value.updateRect(); + } else { + initFloatBoxMoveable(); + } + }; + + const setSlideState = (key: string, data: Record) => { + const slideState = floatBoxStates.value?.get(key); + if (!slideState) return; + floatBoxStates.value?.set(key, { + ...slideState, + ...data, + }); + }; + + const getDragTarget = (key?: string) => `.m-editor-float-box-header-${key ?? curKey.value}`; + + const closeFloatBox = (key: string) => { + setSlideState(key, { + status: false, + }); + + // 如果只有一个,关掉后需要销毁moveable实例 + if (!floatBoxStates.value?.values) return; + const keys = [...floatBoxStates.value?.keys()]; + const values = [...floatBoxStates.value?.values()]; + const lastFloatBoxLen = values.filter((state) => state.status).length; + if (lastFloatBoxLen === 0) { + moveable.value?.destroy(); + moveable.value = undefined; + return; + } + + // 如果关掉的 box 是最大的,需要选中下面的一层 + if (key === curKey.value) { + // 查找显示的最大 zIndex 对应的 index + const zIndexList = values.filter((item) => item.status).map((item) => item.zIndex); + const maxZIndex = Math.max(...zIndexList); + const key = keys.find((key) => floatBoxStates.value?.get(key)?.zIndex === maxZIndex); + if (!key) return; + showFloatBox(key); + } + }; + + const getMaxZIndex = () => { + if (!floatBoxStates.value?.values()) return 0; + const list = [...floatBoxStates.value?.values()].map((state) => state.zIndex); + return Math.max(...list) ?? 0; + }; + + const initFloatBoxMoveable = () => { + const dragTarget = getDragTarget(); + moveable.value = new Moveable(document.body, { + target: target.value, + draggable: true, + resizable: true, + edge: true, + keepRatio: false, + origin: false, + snappable: true, + dragTarget, + dragTargetSelf: false, + linePadding: 10, + controlPadding: 10, + elementGuidelines: [...(floatBoxStates.value?.keys() ?? [])].map((key) => getDragTarget(key)), + bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' }, + }); + moveable.value.on('drag', (e) => { + e.target.style.transform = e.transform; + }); + moveable.value.on('resize', (e) => { + e.target.style.width = `${e.width}px`; + e.target.style.height = `${e.height}px`; + e.target.style.transform = e.drag.transform; + }); + }; + + const dragendHandler = (key: string, e: DragEvent) => { + setSlideState(key, { + left: e.clientX, + top: e.clientY, + }); + showFloatBox(key); + isDraging.value = false; + }; + + document.body.addEventListener('dragover', (e: DragEvent) => { + if (!isDraging.value) return; + e.preventDefault(); + }); + + const dragstartHandler = () => (isDraging.value = true); + + // 监听 slide 长度变化,更新 ui serice map + watch( + () => slideKeys.value, + () => { + services?.uiService.setFloatBox(slideKeys.value); + }, + { + deep: true, + immediate: true, + }, + ); + + return { + showFloatBox, + closeFloatBox, + dragstartHandler, + dragendHandler, + floatBoxStates, + floatBox, + showingBoxKeys, + }; +}; diff --git a/packages/editor/src/layouts/Framework.vue b/packages/editor/src/layouts/Framework.vue index 19eb9190..5cd90b49 100644 --- a/packages/editor/src/layouts/Framework.vue +++ b/packages/editor/src/layouts/Framework.vue @@ -82,8 +82,10 @@ const showSrc = computed(() => uiService?.get('showSrc')); const LEFT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorLeftColumnWidthData'; const RIGHT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorRightColumnWidthData'; -const leftColumnWidthCacheData = +const getLeftColumnWidthCacheData = () => Number(globalThis.localStorage.getItem(LEFT_COLUMN_WIDTH_STORAGE_KEY)) || DEFAULT_LEFT_COLUMN_WIDTH; + +const leftColumnWidthCacheData = getLeftColumnWidthCacheData(); const RightColumnWidthCacheData = Number(globalThis.localStorage.getItem(RIGHT_COLUMN_WIDTH_STORAGE_KEY)) || DEFAULT_RIGHT_COLUMN_WIDTH; @@ -118,6 +120,13 @@ watch( }, ); +watch( + () => uiService?.get('hideSlideBar'), + (hideSlideBar) => { + columnWidth.value.left = hideSlideBar ? 0 : getLeftColumnWidthCacheData(); + }, +); + const columnWidthChange = (columnW: GetColumnWidth) => { columnWidth.value.left = columnW.left; columnWidth.value.center = columnW.center; diff --git a/packages/editor/src/layouts/sidebar/Sidebar.vue b/packages/editor/src/layouts/sidebar/Sidebar.vue index db79ee25..8039995c 100644 --- a/packages/editor/src/layouts/sidebar/Sidebar.vue +++ b/packages/editor/src/layouts/sidebar/Sidebar.vue @@ -1,5 +1,5 @@