chore(editor): 拆分Editor.vue

This commit is contained in:
roymondchen 2023-03-28 17:26:34 +08:00
parent 3b6ca97f4c
commit 899991794b
3 changed files with 341 additions and 298 deletions

View File

@ -64,18 +64,7 @@
</template>
<script lang="ts">
import { defineComponent, onUnmounted, PropType, provide, reactive, toRaw, watch } from 'vue';
import { EventOption } from '@tmagic/core';
import type { FormConfig } from '@tmagic/form';
import { CodeBlockContent, Id, MApp, MNode, MPage } from '@tmagic/schema';
import StageCore, {
CONTAINER_HIGHLIGHT_CLASS_NAME,
ContainerHighlightType,
CustomizeMoveableOptionsCallbackConfig,
MoveableOptions,
UpdateDragEl,
} from '@tmagic/stage';
import { defineComponent, provide, reactive } from 'vue';
import Framework from './layouts/Framework.vue';
import NavMenu from './layouts/NavMenu.vue';
@ -91,8 +80,9 @@ import historyService from './services/history';
import propsService from './services/props';
import storageService from './services/storage';
import uiService from './services/ui';
import { createCodeBlockTarget } from './utils/dep';
import type { ComponentGroup, MenuBarData, MenuButton, MenuComponent, Services, SideBarData, StageRect } from './type';
import editorProps from './editorProps';
import { initServiceEvents, initServiceState } from './initService';
import type { Services } from './type';
export default defineComponent({
name: 'm-editor',
@ -105,276 +95,11 @@ export default defineComponent({
Framework,
},
props: {
/** 页面初始值 */
modelValue: {
type: Object as PropType<MApp>,
default: () => ({}),
require: true,
},
/** 左侧面板中的组件列表 */
componentGroupList: {
type: Array as PropType<ComponentGroup[]>,
default: () => [],
},
/** 左侧面板配置 */
sidebar: {
type: Object as PropType<SideBarData>,
},
/** 顶部工具栏配置 */
menu: {
type: Object as PropType<MenuBarData>,
default: () => ({ left: [], right: [] }),
},
/** 组件树右键菜单 */
layerContentMenu: {
type: Array as PropType<(MenuButton | MenuComponent)[]>,
default: () => [],
},
/** 画布右键菜单 */
stageContentMenu: {
type: Array as PropType<(MenuButton | MenuComponent)[]>,
default: () => [],
},
/** 中间工作区域中画布渲染的内容 */
render: {
type: Function as PropType<(stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>>,
},
/** 中间工作区域中画布通过iframe渲染时的页面url */
runtimeUrl: String,
/** 选中时是否自动滚动到可视区域 */
autoScrollIntoView: Boolean,
/** 组件的属性配置表单的dsl */
propsConfigs: {
type: Object as PropType<Record<string, FormConfig>>,
default: () => ({}),
},
/** 添加组件时的默认值 */
propsValues: {
type: Object as PropType<Record<string, MNode>>,
default: () => ({}),
},
/** 组件联动事件选项列表 */
eventMethodList: {
type: Object as PropType<Record<string, { events: EventOption[]; methods: EventOption[] }>>,
default: () => ({}),
},
/** 画布中组件选中框的移动范围 */
moveableOptions: {
type: [Object, Function] as PropType<
MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions)
>,
},
/** 编辑器初始化时默认选中的组件ID */
defaultSelected: {
type: [Number, String],
},
canSelect: {
type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
default: (el: HTMLElement) => Boolean(el.id),
},
isContainer: {
type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
default: (el: HTMLElement) => el.classList.contains('magic-ui-container'),
},
containerHighlightClassName: {
type: String,
default: CONTAINER_HIGHLIGHT_CLASS_NAME,
},
containerHighlightDuration: {
type: Number,
default: 800,
},
containerHighlightType: {
type: String as PropType<ContainerHighlightType>,
default: ContainerHighlightType.DEFAULT,
},
stageRect: {
type: [String, Object] as PropType<StageRect>,
},
codeOptions: {
type: Object,
default: () => ({}),
},
updateDragEl: {
type: Function as PropType<UpdateDragEl>,
},
disabledDragStart: {
type: Boolean,
},
},
props: editorProps,
emits: ['props-panel-mounted', 'update:modelValue'],
setup(props, { emit }) {
const rootChangeHandler = async (value: MApp, preValue?: MApp | null) => {
const nodeId = editorService.get('node')?.id || props.defaultSelected;
let node;
if (nodeId) {
node = editorService.getNodeById(nodeId);
}
if (node && node !== value) {
await editorService.select(node.id);
} else if (value?.items?.length) {
await editorService.select(value.items[0]);
} else if (value?.id) {
editorService.set('nodes', [value]);
editorService.set('parent', null);
editorService.set('page', null);
}
if (toRaw(value) !== toRaw(preValue)) {
emit('update:modelValue', value);
}
value.codeBlocks = value.codeBlocks || {};
codeBlockService.setCodeDsl(value.codeBlocks);
depService.removeTargets('code-block');
Object.entries(value.codeBlocks).forEach(([id, code]) => {
depService.addTarget(createCodeBlockTarget(id, code));
});
if (value && Array.isArray(value.items)) {
depService.collect(value.items, true);
} else {
depService.clear();
}
};
const nodeAddHandler = (nodes: MNode[]) => {
depService.collect(nodes);
};
const nodeUpdateHandler = (nodes: MNode[]) => {
depService.collect(nodes);
};
const nodeRemoveHandler = (nodes: MNode[]) => {
depService.clear(nodes);
};
const historyChangeHandler = (page: MPage) => {
depService.collect([page], true);
};
editorService.on('history-change', historyChangeHandler);
editorService.on('root-change', rootChangeHandler);
editorService.on('add', nodeAddHandler);
editorService.on('remove', nodeRemoveHandler);
editorService.on('update', nodeUpdateHandler);
const codeBlockAddOrUpdateHandler = (id: Id, codeBlock: CodeBlockContent) => {
if (depService.hasTarget(id)) {
depService.getTarget(id)!.name = codeBlock.name;
return;
}
depService.addTarget(createCodeBlockTarget(id, codeBlock));
};
const codeBlockRemoveHandler = (id: Id) => {
depService.removeTarget(id);
};
codeBlockService.on('addOrUpdate', codeBlockAddOrUpdateHandler);
codeBlockService.on('remove', codeBlockRemoveHandler);
//
watch(
() => props.modelValue,
(modelValue) => {
editorService.set('root', modelValue);
},
{
immediate: true,
},
);
watch(
() => props.componentGroupList,
(componentGroupList) => componentListService.setList(componentGroupList),
{
immediate: true,
},
);
watch(
() => props.propsConfigs,
(configs) => propsService.setPropsConfigs(configs),
{
immediate: true,
},
);
watch(
() => props.propsValues,
(values) => propsService.setPropsValues(values),
{
immediate: true,
},
);
watch(
() => props.eventMethodList,
(eventMethodList) => {
const eventsList: Record<string, EventOption[]> = {};
const methodsList: Record<string, EventOption[]> = {};
Object.keys(eventMethodList).forEach((type: string) => {
eventsList[type] = eventMethodList[type].events;
methodsList[type] = eventMethodList[type].methods;
});
eventsService.setEvents(eventsList);
eventsService.setMethods(methodsList);
},
{
immediate: true,
},
);
watch(
() => props.defaultSelected,
(defaultSelected) => defaultSelected && editorService.select(defaultSelected),
{
immediate: true,
},
);
watch(
() => props.stageRect,
(stageRect) => stageRect && uiService.set('stageRect', stageRect),
{
immediate: true,
},
);
const services: Services = {
componentListService,
eventsService,
@ -387,6 +112,9 @@ export default defineComponent({
depService,
};
initServiceEvents(props, emit, services);
initServiceState(props, services);
provide('services', services);
provide('codeOptions', props.codeOptions);
@ -407,24 +135,6 @@ export default defineComponent({
}),
);
onUnmounted(() => {
editorService.resetState();
historyService.resetState();
propsService.resetState();
uiService.resetState();
componentListService.resetState();
codeBlockService.resetState();
editorService.off('history-change', historyChangeHandler);
editorService.off('root-change', rootChangeHandler);
editorService.off('add', nodeAddHandler);
editorService.off('remove', nodeRemoveHandler);
editorService.off('update', nodeUpdateHandler);
codeBlockService.off('addOrUpdate', codeBlockAddOrUpdateHandler);
codeBlockService.off('remove', codeBlockRemoveHandler);
});
return services;
},
});

View File

@ -0,0 +1,135 @@
import type { PropType } from 'vue';
import type { EventOption } from '@tmagic/core';
import type { FormConfig } from '@tmagic/form';
import type { MApp, MNode } from '@tmagic/schema';
import StageCore, {
CONTAINER_HIGHLIGHT_CLASS_NAME,
ContainerHighlightType,
CustomizeMoveableOptionsCallbackConfig,
MoveableOptions,
UpdateDragEl,
} from '@tmagic/stage';
import type { ComponentGroup, MenuBarData, MenuButton, MenuComponent, SideBarData, StageRect } from './type';
export default {
/** 页面初始值 */
modelValue: {
type: Object as PropType<MApp>,
default: () => ({}),
require: true,
},
/** 左侧面板中的组件列表 */
componentGroupList: {
type: Array as PropType<ComponentGroup[]>,
default: () => [],
},
/** 左侧面板配置 */
sidebar: {
type: Object as PropType<SideBarData>,
},
/** 顶部工具栏配置 */
menu: {
type: Object as PropType<MenuBarData>,
default: () => ({ left: [], right: [] }),
},
/** 组件树右键菜单 */
layerContentMenu: {
type: Array as PropType<(MenuButton | MenuComponent)[]>,
default: () => [],
},
/** 画布右键菜单 */
stageContentMenu: {
type: Array as PropType<(MenuButton | MenuComponent)[]>,
default: () => [],
},
/** 中间工作区域中画布渲染的内容 */
render: {
type: Function as PropType<(stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>>,
},
/** 中间工作区域中画布通过iframe渲染时的页面url */
runtimeUrl: String,
/** 选中时是否自动滚动到可视区域 */
autoScrollIntoView: Boolean,
/** 组件的属性配置表单的dsl */
propsConfigs: {
type: Object as PropType<Record<string, FormConfig>>,
default: () => ({}),
},
/** 添加组件时的默认值 */
propsValues: {
type: Object as PropType<Record<string, MNode>>,
default: () => ({}),
},
/** 组件联动事件选项列表 */
eventMethodList: {
type: Object as PropType<Record<string, { events: EventOption[]; methods: EventOption[] }>>,
default: () => ({}),
},
/** 画布中组件选中框的移动范围 */
moveableOptions: {
type: [Object, Function] as PropType<
MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions)
>,
},
/** 编辑器初始化时默认选中的组件ID */
defaultSelected: {
type: [Number, String],
},
canSelect: {
type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
default: (el: HTMLElement) => Boolean(el.id),
},
isContainer: {
type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
default: (el: HTMLElement) => el.classList.contains('magic-ui-container'),
},
containerHighlightClassName: {
type: String,
default: CONTAINER_HIGHLIGHT_CLASS_NAME,
},
containerHighlightDuration: {
type: Number,
default: 800,
},
containerHighlightType: {
type: String as PropType<ContainerHighlightType>,
default: ContainerHighlightType.DEFAULT,
},
stageRect: {
type: [String, Object] as PropType<StageRect>,
},
codeOptions: {
type: Object,
default: () => ({}),
},
updateDragEl: {
type: Function as PropType<UpdateDragEl>,
},
disabledDragStart: {
type: Boolean,
},
};

View File

@ -0,0 +1,198 @@
import type { ExtractPropTypes } from 'vue';
import { onUnmounted, toRaw, watch } from 'vue';
import type { EventOption } from '@tmagic/core';
import type { CodeBlockContent, Id, MApp, MNode, MPage } from '@tmagic/schema';
import { createCodeBlockTarget } from './utils/dep';
import editorProps from './editorProps';
import type { Services } from './type';
export declare type LooseRequired<T> = {
[P in string & keyof T]: T[P];
};
export const initServiceState = (
props: Readonly<LooseRequired<Readonly<ExtractPropTypes<typeof editorProps>>>>,
{
editorService,
historyService,
componentListService,
propsService,
eventsService,
uiService,
codeBlockService,
}: Services,
) => {
// 初始值变化,重新设置节点信息
watch(
() => props.modelValue,
(modelValue) => {
editorService.set('root', modelValue);
},
{
immediate: true,
},
);
watch(
() => props.componentGroupList,
(componentGroupList) => componentListService.setList(componentGroupList),
{
immediate: true,
},
);
watch(
() => props.propsConfigs,
(configs) => propsService.setPropsConfigs(configs),
{
immediate: true,
},
);
watch(
() => props.propsValues,
(values) => propsService.setPropsValues(values),
{
immediate: true,
},
);
watch(
() => props.eventMethodList,
(eventMethodList) => {
const eventsList: Record<string, EventOption[]> = {};
const methodsList: Record<string, EventOption[]> = {};
Object.keys(eventMethodList).forEach((type: string) => {
eventsList[type] = eventMethodList[type].events;
methodsList[type] = eventMethodList[type].methods;
});
eventsService.setEvents(eventsList);
eventsService.setMethods(methodsList);
},
{
immediate: true,
},
);
watch(
() => props.defaultSelected,
(defaultSelected) => defaultSelected && editorService.select(defaultSelected),
{
immediate: true,
},
);
watch(
() => props.stageRect,
(stageRect) => stageRect && uiService.set('stageRect', stageRect),
{
immediate: true,
},
);
onUnmounted(() => {
editorService.resetState();
historyService.resetState();
propsService.resetState();
uiService.resetState();
componentListService.resetState();
codeBlockService.resetState();
});
};
export const initServiceEvents = (
props: Readonly<LooseRequired<Readonly<ExtractPropTypes<typeof editorProps>>>>,
emit: (event: 'props-panel-mounted' | 'update:modelValue', ...args: any[]) => void,
{ editorService, codeBlockService, depService }: Services,
) => {
const rootChangeHandler = async (value: MApp, preValue?: MApp | null) => {
const nodeId = editorService.get('node')?.id || props.defaultSelected;
let node;
if (nodeId) {
node = editorService.getNodeById(nodeId);
}
if (node && node !== value) {
await editorService.select(node.id);
} else if (value?.items?.length) {
await editorService.select(value.items[0]);
} else if (value?.id) {
editorService.set('nodes', [value]);
editorService.set('parent', null);
editorService.set('page', null);
}
if (toRaw(value) !== toRaw(preValue)) {
emit('update:modelValue', value);
}
value.codeBlocks = value.codeBlocks || {};
codeBlockService.setCodeDsl(value.codeBlocks);
depService.removeTargets('code-block');
Object.entries(value.codeBlocks).forEach(([id, code]) => {
depService.addTarget(createCodeBlockTarget(id, code));
});
if (value && Array.isArray(value.items)) {
depService.collect(value.items, true);
} else {
depService.clear();
}
};
const nodeAddHandler = (nodes: MNode[]) => {
depService.collect(nodes);
};
const nodeUpdateHandler = (nodes: MNode[]) => {
depService.collect(nodes);
};
const nodeRemoveHandler = (nodes: MNode[]) => {
depService.clear(nodes);
};
const historyChangeHandler = (page: MPage) => {
depService.collect([page], true);
};
editorService.on('history-change', historyChangeHandler);
editorService.on('root-change', rootChangeHandler);
editorService.on('add', nodeAddHandler);
editorService.on('remove', nodeRemoveHandler);
editorService.on('update', nodeUpdateHandler);
const codeBlockAddOrUpdateHandler = (id: Id, codeBlock: CodeBlockContent) => {
if (depService.hasTarget(id)) {
depService.getTarget(id)!.name = codeBlock.name;
return;
}
depService.addTarget(createCodeBlockTarget(id, codeBlock));
};
const codeBlockRemoveHandler = (id: Id) => {
depService.removeTarget(id);
};
codeBlockService.on('addOrUpdate', codeBlockAddOrUpdateHandler);
codeBlockService.on('remove', codeBlockRemoveHandler);
onUnmounted(() => {
editorService.off('history-change', historyChangeHandler);
editorService.off('root-change', rootChangeHandler);
editorService.off('add', nodeAddHandler);
editorService.off('remove', nodeRemoveHandler);
editorService.off('update', nodeUpdateHandler);
codeBlockService.off('addOrUpdate', codeBlockAddOrUpdateHandler);
codeBlockService.off('remove', codeBlockRemoveHandler);
});
};