feat(editor): 使用 floatbox 替换原抽屉栏

This commit is contained in:
moonszhang 2024-03-11 20:11:13 +08:00 committed by roymondchen
parent 260286f9cf
commit a035f02f83
13 changed files with 128 additions and 66 deletions

View File

@ -1,6 +1,5 @@
<template> <template>
<component <MFormBox
:is="slideType === 'box' ? MFormBox : MFormDrawer"
class="m-editor-code-block-editor" class="m-editor-code-block-editor"
ref="fomDrawer" ref="fomDrawer"
label-width="80px" label-width="80px"
@ -20,7 +19,7 @@
<template #left> <template #left>
<TMagicButton type="primary" link @click="difVisible = true">查看修改</TMagicButton> <TMagicButton type="primary" link @click="difVisible = true">查看修改</TMagicButton>
</template> </template>
</component> </MFormBox>
<TMagicDialog v-model="difVisible" title="查看修改" fullscreen> <TMagicDialog v-model="difVisible" title="查看修改" fullscreen>
<div style="display: flex; margin-bottom: 10px"> <div style="display: flex; margin-bottom: 10px">
@ -55,7 +54,7 @@ import { ColumnConfig, FormConfig, FormState, MFormBox, MFormDrawer } from '@tma
import type { CodeBlockContent } from '@tmagic/schema'; import type { CodeBlockContent } from '@tmagic/schema';
import CodeEditor from '@editor/layouts/CodeEditor.vue'; import CodeEditor from '@editor/layouts/CodeEditor.vue';
import type { Services, SlideType } from '@editor/type'; import type { Services } from '@editor/type';
import { getConfig } from '@editor/utils/config'; import { getConfig } from '@editor/utils/config';
defineOptions({ defineOptions({
@ -67,7 +66,6 @@ const props = defineProps<{
disabled?: boolean; disabled?: boolean;
isDataSource?: boolean; isDataSource?: boolean;
dataSourceType?: string; dataSourceType?: string;
slideType?: SlideType;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -1,6 +1,6 @@
<template> <template>
<Teleport to="body" v-if="visible"> <Teleport to="body" v-if="visible">
<div ref="target" class="m-editor-float-box" :style="style"> <div ref="target" class="m-editor-float-box" :style="style" @mousedown="nextZIndex">
<div ref="dragTarget" class="m-editor-float-box-title"> <div ref="dragTarget" class="m-editor-float-box-title">
<slot name="title"> <slot name="title">
<span>{{ title }}</span> <span>{{ title }}</span>
@ -21,7 +21,7 @@ import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { Close } from '@element-plus/icons-vue'; import { Close } from '@element-plus/icons-vue';
import VanillaMoveable from 'moveable'; import VanillaMoveable from 'moveable';
import { TMagicButton } from '@tmagic/design'; import { TMagicButton, useZIndex } from '@tmagic/design';
interface Position { interface Position {
left: number; left: number;
@ -47,11 +47,15 @@ const emit = defineEmits<{
const target = ref<HTMLDivElement>(); const target = ref<HTMLDivElement>();
const dragTarget = ref<HTMLDivElement>(); const dragTarget = ref<HTMLDivElement>();
const zIndex = useZIndex();
const curZIndex = ref<number>(zIndex.nextZIndex());
const style = computed(() => ({ const style = computed(() => ({
left: `${props.position.left}px`, left: `${props.position.left}px`,
top: `${props.position.top}px`, top: `${props.position.top}px`,
width: typeof props.rect.width === 'string' ? props.rect.width : `${props.rect.width}px`, width: typeof props.rect.width === 'string' ? props.rect.width : `${props.rect.width}px`,
height: typeof props.rect.height === 'string' ? props.rect.height : `${props.rect.height}px`, height: typeof props.rect.height === 'string' ? props.rect.height : `${props.rect.height}px`,
zIndex: curZIndex.value,
})); }));
let moveable: VanillaMoveable | null = null; let moveable: VanillaMoveable | null = null;
@ -70,6 +74,7 @@ const initMoveable = () => {
dragTargetSelf: false, dragTargetSelf: false,
linePadding: 10, linePadding: 10,
controlPadding: 10, controlPadding: 10,
bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' },
}); });
moveable.on('drag', (e) => { moveable.on('drag', (e) => {
@ -111,6 +116,10 @@ const closeHandler = () => {
emit('update:visible', false); emit('update:visible', false);
}; };
const nextZIndex = () => {
curZIndex.value = zIndex.nextZIndex();
};
defineExpose({ defineExpose({
target, target,
}); });

View File

@ -54,7 +54,6 @@ export const useCodeBlockEdit = (codeBlockService?: CodeBlockService) => {
codeId.value = id; codeId.value = id;
await nextTick(); await nextTick();
codeBlockEditor.value?.show(); codeBlockEditor.value?.show();
}; };

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="m-editor-nav-menu" :style="{ height: `${height}px` }"> <div class="m-editor-nav-menu" :style="{ height: `${height}px` }" ref="navMenu">
<div v-for="key in keys" :class="`menu-${key}`" :key="key" :style="`width: ${columnWidth?.[key]}px`"> <div v-for="key in keys" :class="`menu-${key}`" :key="key" :style="`width: ${columnWidth?.[key]}px`">
<ToolButton :data="item" v-for="(item, index) in buttons[key]" :key="index"></ToolButton> <ToolButton :data="item" v-for="(item, index) in buttons[key]" :key="index"></ToolButton>
</div> </div>
@ -7,7 +7,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, markRaw } from 'vue'; import { computed, inject, markRaw, onBeforeUnmount, onMounted, ref } from 'vue';
import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, ZoomOut } from '@element-plus/icons-vue'; import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, ZoomOut } from '@element-plus/icons-vue';
import { NodeType } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema';
@ -178,4 +178,23 @@ const buttons = computed(() => {
}); });
return data; return data;
}); });
const resizeObserver = new ResizeObserver(() => {
const rect = navMenu.value?.getBoundingClientRect();
if (rect) {
uiService?.set('navMenuRect', {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
});
}
});
const navMenu = ref<HTMLDivElement>();
onMounted(() => {
navMenu.value && resizeObserver.observe(navMenu.value);
});
onBeforeUnmount(() => {
resizeObserver.disconnect();
});
</script> </script>

View File

@ -204,6 +204,11 @@ const getItemConfig = (data: SideItem): SideComponent => {
text: '代码编辑', text: '代码编辑',
component: CodeBlockListPanel, component: CodeBlockListPanel,
slots: {}, slots: {},
boxComponentConfig: {
props: {
slideType: 'box',
},
},
}, },
'data-source': { 'data-source': {
$key: 'data-source', $key: 'data-source',

View File

@ -3,7 +3,7 @@
<slot name="code-block-panel-header"> <slot name="code-block-panel-header">
<div class="search-wrapper"> <div class="search-wrapper">
<SearchInput @search="filterTextChangeHandler"></SearchInput> <SearchInput @search="filterTextChangeHandler"></SearchInput>
<TMagicButton v-if="editable" class="create-code-button" type="primary" size="small" @click="createCodeBlock" <TMagicButton v-if="editable" class="create-code-button" type="primary" size="small" @click="showCreate"
>新增</TMagicButton >新增</TMagicButton
> >
<slot name="code-block-panel-search"></slot> <slot name="code-block-panel-search"></slot>
@ -11,7 +11,7 @@
</slot> </slot>
<!-- 代码块列表 --> <!-- 代码块列表 -->
<CodeBlockList ref="codeBlockList" :custom-error="customError" @edit="editCode" @remove="deleteCode"> <CodeBlockList ref="codeBlockList" :custom-error="customError" @edit="showEdit" @remove="deleteCode">
<template #code-block-panel-tool="{ id, data }"> <template #code-block-panel-tool="{ id, data }">
<slot name="code-block-panel-tool" :id="id" :data="data"></slot> <slot name="code-block-panel-tool" :id="id" :data="data"></slot>
</template> </template>
@ -19,23 +19,31 @@
</TMagicScrollbar> </TMagicScrollbar>
<!-- 代码块编辑区 --> <!-- 代码块编辑区 -->
<CodeBlockEditor <FloatingBox v-model:visible="popVisible" title="代码编辑" :position="boxPosition">
v-if="codeConfig" <template #body>
ref="codeBlockEditor" <div ref="scrollBar"></div>
:disabled="!editable" </template>
:content="codeConfig" </FloatingBox>
:slideType="slideType"
@submit="submitCodeBlockHandler" <Teleport :to="scrollBar" :disabled="slideType === 'box'" v-if="editVisible">
></CodeBlockEditor> <CodeBlockEditor
v-if="codeConfig"
ref="codeBlockEditor"
:disabled="!editable"
:content="codeConfig"
@submit="submitCodeBlockHandler"
></CodeBlockEditor>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, ref } from 'vue'; import { computed, inject, nextTick, ref } from 'vue';
import { TMagicButton, TMagicScrollbar } from '@tmagic/design'; import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
import type { Id } from '@tmagic/schema'; import type { Id } from '@tmagic/schema';
import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue'; import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
import FloatingBox from '@editor/components/FloatingBox.vue';
import SearchInput from '@editor/components/SearchInput.vue'; import SearchInput from '@editor/components/SearchInput.vue';
import { useCodeBlockEdit } from '@editor/hooks/use-code-block-edit'; import { useCodeBlockEdit } from '@editor/hooks/use-code-block-edit';
import type { CodeBlockListPanelSlots, CodeDeleteErrorType, Services, SlideType } from '@editor/type'; import type { CodeBlockListPanelSlots, CodeDeleteErrorType, Services, SlideType } from '@editor/type';
@ -48,12 +56,12 @@ defineOptions({
name: 'MEditorCodeBlockListPanel', name: 'MEditorCodeBlockListPanel',
}); });
defineProps<{ const props = defineProps<{
customError?: (id: Id, errorType: CodeDeleteErrorType) => any; customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
slideType?: SlideType; slideType?: SlideType;
}>(); }>();
const { codeBlockService } = inject<Services>('services') || {}; const { codeBlockService, uiService } = inject<Services>('services') || {};
const editable = computed(() => codeBlockService?.getEditStatus()); const editable = computed(() => codeBlockService?.getEditStatus());
@ -65,4 +73,35 @@ const codeBlockList = ref<InstanceType<typeof CodeBlockList>>();
const filterTextChangeHandler = (val: string) => { const filterTextChangeHandler = (val: string) => {
codeBlockList.value?.filter(val); codeBlockList.value?.filter(val);
}; };
const boxPosition = computed(() => {
const columnWidth = uiService?.get('columnWidth');
const navMenuRect = uiService?.get('navMenuRect');
return {
left: columnWidth?.left ?? 0,
top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
};
});
const scrollBar = ref<HTMLDivElement>();
const popVisible = ref<boolean>(false);
const editVisible = ref<boolean>(false);
const beforeShowEdit = async () => {
if (props.slideType !== 'box') {
popVisible.value = true;
}
await nextTick();
editVisible.value = true;
};
const showEdit = async (id: string) => {
await beforeShowEdit();
editCode(id);
};
const showCreate = async () => {
await beforeShowEdit();
createCodeBlock();
};
</script> </script>

View File

@ -47,8 +47,13 @@ const state = reactive<UiState>({
showRule: true, showRule: true,
propsPanelSize: 'small', propsPanelSize: 'small',
showAddPageButton: true, showAddPageButton: true,
floatBox: new Map(),
hideSlideBar: false, hideSlideBar: false,
navMenuRect: {
left: 0,
top: 0,
width: 0,
height: 0,
},
}); });
const canUsePluginMethods = { const canUsePluginMethods = {
@ -110,19 +115,6 @@ class Ui extends BaseService {
return Math.min((width - 60) / stageWidth || 1, (height - 80) / stageHeight || 1); return Math.min((width - 60) / stageWidth || 1, (height - 80) / stageHeight || 1);
} }
public async setFloatBox(keys: string[]) {
const map = state.floatBox;
for (const key of keys) {
if (map.get(key)) continue;
map.set(key, {
status: false,
zIndex: 99,
top: 0,
left: 0,
});
}
}
public resetState() { public resetState() {
this.set('showSrc', false); this.set('showSrc', false);
this.set('uiSelectMode', false); this.set('uiSelectMode', false);

View File

@ -1,3 +1,9 @@
.m-container-vs-code {
.el-form-item {
margin-bottom: 0;
}
}
.magic-code-editor { .magic-code-editor {
width: 100%; width: 100%;
} }

View File

@ -72,12 +72,15 @@
} }
.m-editor-slide-list-box { .m-editor-slide-list-box {
min-width: 270px; display: flex;
min-width: 240px;
min-height: 500px; min-height: 500px;
max-height: 1024px;
> div { > div {
&:first-child { &:first-child {
width: 100%; min-width: 240px;
} }
} }
.m-form-box {
border-left: 1px solid #e0e0e0;
}
} }

View File

@ -236,20 +236,16 @@ export interface UiState {
propsPanelSize: 'large' | 'default' | 'small'; propsPanelSize: 'large' | 'default' | 'small';
/** 是否显示新增页面按钮 */ /** 是否显示新增页面按钮 */
showAddPageButton: boolean; showAddPageButton: boolean;
/** slide 拖拽悬浮窗 state */
floatBox: Map<
string,
{
status: boolean;
zIndex: number;
top: number;
left: number;
}
>;
/** 是否隐藏侧边栏 */ /** 是否隐藏侧边栏 */
hideSlideBar: boolean; hideSlideBar: boolean;
// navMenu 的宽高
navMenuRect: {
left: number;
top: number;
width: number;
height: number;
};
} }
export interface EditorNodeInfo { export interface EditorNodeInfo {

View File

@ -2,7 +2,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 16px; padding: 16px;
width: 100%; max-width: 90%;
.el-box__header { .el-box__header {
margin: 0; margin: 0;
} }

View File

@ -1,4 +1,3 @@
import React, { constructor, useEffect, useMemo, useState } from 'react'; import React, { constructor, useEffect, useMemo, useState } from 'react';
import type { MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema'; import type { MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
@ -16,24 +15,24 @@ const PageFragmentContainer: React.FC<PageFragmentContainerProps> = ({ config })
if (!app) return null; if (!app) return null;
const MagicUiContainer = app.resolveComponent('container'); const MagicUiContainer = app.resolveComponent('container');
let containerConfig = {} let containerConfig = {};
const fragment = app?.dsl?.items?.find((page) => page.id === config.pageFragmentId) const fragment = app?.dsl?.items?.find((page) => page.id === config.pageFragmentId);
if(fragment) { if (fragment) {
const { id, type, items, ...others } = fragment; const { id, type, items, ...others } = fragment;
const itemsWithoutId = items.map((item: MNode) => { const itemsWithoutId = items.map((item: MNode) => {
const { id, ...otherConfig } = item; const { id, ...otherConfig } = item;
return otherConfig; return otherConfig;
}); });
if (app?.platform === 'editor') { if (app?.platform === 'editor') {
containerConfig ={ containerConfig = {
...others, ...others,
items: itemsWithoutId, items: itemsWithoutId,
}; };
}else { } else {
containerConfig = { containerConfig = {
...others, ...others,
items items,
} };
} }
} }
@ -43,13 +42,11 @@ const PageFragmentContainer: React.FC<PageFragmentContainerProps> = ({ config })
className="magic-ui-page-fragment-container" className="magic-ui-page-fragment-container"
style={app.transformStyle(config.style || {})} style={app.transformStyle(config.style || {})}
> >
<MagicUiContainer <MagicUiContainer config={containerConfig}></MagicUiContainer>
config={containerConfig}
></MagicUiContainer>
</div> </div>
); );
}; };
PageFragmentContainer.displayName = 'magic-ui-page-fragment-container'; PageFragmentContainer.displayName = 'magic-ui-page-fragment-container';
export default PageFragmentContainer; export default PageFragmentContainer;

View File

@ -1,4 +1,3 @@
import React from 'react'; import React from 'react';
import type { MComponent, MContainer, MPageFragment } from '@tmagic/schema'; import type { MComponent, MContainer, MPageFragment } from '@tmagic/schema';