feat(editor): 修改service get set 方法的ts定义,不再需要传入泛型参数

This commit is contained in:
roymondchen 2023-02-06 19:25:37 +08:00
parent 23708e4644
commit 0491487385
25 changed files with 244 additions and 206 deletions

View File

@ -228,7 +228,7 @@ export default defineComponent({
setup(props, { emit }) { setup(props, { emit }) {
const rootChangeHandler = (value: MApp, preValue?: MApp | null) => { const rootChangeHandler = (value: MApp, preValue?: MApp | null) => {
const nodeId = editorService.get<MNode | null>('node')?.id || props.defaultSelected; const nodeId = editorService.get('node')?.id || props.defaultSelected;
let node; let node;
if (nodeId) { if (nodeId) {
node = editorService.getNodeById(nodeId); node = editorService.getNodeById(nodeId);

View File

@ -37,7 +37,7 @@ const uiSelectMode = ref(false);
const cancelHandler = () => { const cancelHandler = () => {
if (!services?.uiService) return; if (!services?.uiService) return;
services.uiService.set<boolean>('uiSelectMode', false); services.uiService.set('uiSelectMode', false);
uiSelectMode.value = false; uiSelectMode.value = false;
globalThis.document.removeEventListener('ui-select', clickHandler as EventListener); globalThis.document.removeEventListener('ui-select', clickHandler as EventListener);
}; };
@ -61,7 +61,7 @@ const toName = computed(() => {
const startSelect = () => { const startSelect = () => {
if (!services?.uiService) return; if (!services?.uiService) return;
services.uiService.set<boolean>('uiSelectMode', true); services.uiService.set('uiSelectMode', true);
uiSelectMode.value = true; uiSelectMode.value = true;
globalThis.document.addEventListener('ui-select', clickHandler as EventListener); globalThis.document.addEventListener('ui-select', clickHandler as EventListener);
}; };

View File

@ -28,9 +28,12 @@ const clickHandler = () => {
if (!editorService) return; if (!editorService) return;
const root = toRaw(editorService.get('root'));
if (!root) throw new Error('root 不能为空');
editorService.add({ editorService.add({
type: NodeType.PAGE, type: NodeType.PAGE,
name: generatePageNameByApp(toRaw(editorService.get('root'))), name: generatePageNameByApp(root),
}); });
}; };
</script> </script>

View File

@ -46,7 +46,6 @@
import { computed, inject, ref, watch } from 'vue'; import { computed, inject, ref, watch } from 'vue';
import { TMagicScrollbar } from '@tmagic/design'; import { TMagicScrollbar } from '@tmagic/design';
import type { MApp } from '@tmagic/schema';
import Layout from '../components/Layout.vue'; import Layout from '../components/Layout.vue';
import { GetColumnWidth, Services } from '../type'; import { GetColumnWidth, Services } from '../type';
@ -67,11 +66,11 @@ withDefaults(
const { editorService, uiService } = inject<Services>('services') || {}; const { editorService, uiService } = inject<Services>('services') || {};
const root = computed(() => editorService?.get<MApp>('root')); const root = computed(() => editorService?.get('root'));
const nodes = computed(() => editorService?.get<Node[]>('nodes') || []); const nodes = computed(() => editorService?.get('nodes') || []);
const pageLength = computed(() => editorService?.get<number>('pageLength') || 0); const pageLength = computed(() => editorService?.get('pageLength') || 0);
const showSrc = computed(() => uiService?.get<boolean>('showSrc')); const showSrc = computed(() => uiService?.get('showSrc'));
const LEFT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorLeftColumnWidthData'; const LEFT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorLeftColumnWidthData';
const RIGHT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorRightColumnWidthData'; const RIGHT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorRightColumnWidthData';
@ -101,7 +100,7 @@ watch(
columnWidth.value.center = globalThis.document.body.clientWidth - left - right; columnWidth.value.center = globalThis.document.body.clientWidth - left - right;
} }
uiService?.set('columnWidth', columnWidth); uiService?.set('columnWidth', columnWidth.value as GetColumnWidth);
}, },
{ {
immediate: true, immediate: true,

View File

@ -13,7 +13,7 @@ import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, Z
import { NodeType } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema';
import ToolButton from '../components/ToolButton.vue'; import ToolButton from '../components/ToolButton.vue';
import { ColumnLayout, GetColumnWidth, MenuBarData, MenuButton, MenuComponent, MenuItem, Services } from '../type'; import { ColumnLayout, MenuBarData, MenuButton, MenuComponent, MenuItem, Services } from '../type';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -29,12 +29,12 @@ const props = withDefaults(
const services = inject<Services>('services'); const services = inject<Services>('services');
const uiService = services?.uiService; const uiService = services?.uiService;
const columnWidth = computed(() => services?.uiService.get<GetColumnWidth>('columnWidth')); const columnWidth = computed(() => services?.uiService.get('columnWidth'));
const keys = Object.values(ColumnLayout); const keys = Object.values(ColumnLayout);
const showGuides = computed((): boolean => uiService?.get<boolean>('showGuides') ?? true); const showGuides = computed((): boolean => uiService?.get('showGuides') ?? true);
const showRule = computed((): boolean => uiService?.get<boolean>('showRule') ?? true); const showRule = computed((): boolean => uiService?.get('showRule') ?? true);
const zoom = computed((): number => uiService?.get<number>('zoom') ?? 1); const zoom = computed((): number => uiService?.get('zoom') ?? 1);
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase()); const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
const ctrl = isMac ? 'Command' : 'Ctrl'; const ctrl = isMac ? 'Command' : 'Ctrl';
@ -67,7 +67,10 @@ const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => {
icon: markRaw(Delete), icon: markRaw(Delete),
tooltip: `刪除(Delete)`, tooltip: `刪除(Delete)`,
disabled: () => services?.editorService.get('node')?.type === NodeType.PAGE, disabled: () => services?.editorService.get('node')?.type === NodeType.PAGE,
handler: () => services?.editorService.remove(services?.editorService.get('node')), handler: () => {
const node = services?.editorService.get('node');
node && services?.editorService.remove(node);
},
}); });
break; break;
case 'undo': case 'undo':

View File

@ -19,8 +19,6 @@ import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, watc
import { tMagicMessage } from '@tmagic/design'; import { tMagicMessage } from '@tmagic/design';
import type { FormValue } from '@tmagic/form'; import type { FormValue } from '@tmagic/form';
import { MForm } from '@tmagic/form'; import { MForm } from '@tmagic/form';
import type { MNode } from '@tmagic/schema';
import type StageCore from '@tmagic/stage';
import type { Services } from '../type'; import type { Services } from '../type';
@ -32,11 +30,9 @@ const configForm = ref<InstanceType<typeof MForm>>();
// tsFormConfig any // tsFormConfig any
const curFormConfig = ref<any>([]); const curFormConfig = ref<any>([]);
const services = inject<Services>('services'); const services = inject<Services>('services');
const node = computed(() => services?.editorService.get<MNode | null>('node')); const node = computed(() => services?.editorService.get('node'));
const propsPanelSize = computed( const propsPanelSize = computed(() => services?.uiService.get('propsPanelSize') || 'small');
() => services?.uiService.get<'large' | 'default' | 'small'>('propsPanelSize') || 'small', const stage = computed(() => services?.editorService.get('stage'));
);
const stage = computed(() => services?.editorService.get<StageCore>('stage'));
const init = async () => { const init = async () => {
if (!node.value) { if (!node.value) {

View File

@ -44,7 +44,6 @@ import { Grid, Search } from '@element-plus/icons-vue';
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
import { TMagicCollapse, TMagicCollapseItem, TMagicInput, TMagicScrollbar, TMagicTooltip } from '@tmagic/design'; import { TMagicCollapse, TMagicCollapseItem, TMagicInput, TMagicScrollbar, TMagicTooltip } from '@tmagic/design';
import type StageCore from '@tmagic/stage';
import { removeClassNameByClassName } from '@tmagic/utils'; import { removeClassNameByClassName } from '@tmagic/utils';
import MIcon from '../../components/Icon.vue'; import MIcon from '../../components/Icon.vue';
@ -54,7 +53,7 @@ const searchText = ref('');
const services = inject<Services>('services'); const services = inject<Services>('services');
const stageOptions = inject<StageOptions>('stageOptions'); const stageOptions = inject<StageOptions>('stageOptions');
const stage = computed(() => services?.editorService.get<StageCore>('stage')); const stage = computed(() => services?.editorService.get('stage'));
const list = computed(() => const list = computed(() =>
services?.componentListService.getList().map((group: ComponentGroup) => ({ services?.componentListService.getList().map((group: ComponentGroup) => ({
...group, ...group,

View File

@ -65,7 +65,6 @@ import { difference, throttle, union } from 'lodash-es';
import { TMagicInput, TMagicScrollbar, TMagicTree } from '@tmagic/design'; import { TMagicInput, TMagicScrollbar, TMagicTree } from '@tmagic/design';
import type { Id, MNode, MPage } from '@tmagic/schema'; import type { Id, MNode, MPage } from '@tmagic/schema';
import { MContainer, NodeType } from '@tmagic/schema'; import { MContainer, NodeType } from '@tmagic/schema';
import StageCore from '@tmagic/stage';
import { getNodePath, isPage } from '@tmagic/utils'; import { getNodePath, isPage } from '@tmagic/utils';
import type { MenuButton, MenuComponent, Services } from '../../type'; import type { MenuButton, MenuComponent, Services } from '../../type';
@ -108,8 +107,8 @@ const treeProps = {
const isMultiSelect = computed(() => isCtrlKeyDown.value || checkedKeys.value.length > 1); const isMultiSelect = computed(() => isCtrlKeyDown.value || checkedKeys.value.length > 1);
const nodes = computed(() => editorService?.get<MNode[]>('nodes') || []); const nodes = computed(() => editorService?.get('nodes') || []);
const page = computed(() => editorService?.get<MPage>('page')); const page = computed(() => editorService?.get('page'));
const values = computed(() => (page.value ? [page.value] : [])); const values = computed(() => (page.value ? [page.value] : []));
// //
const highlightNode = computed(() => editorService?.get('highlightNode')); const highlightNode = computed(() => editorService?.get('highlightNode'));
@ -121,13 +120,13 @@ const select = async (data: MNode) => {
} }
await editorService?.select(data); await editorService?.select(data);
editorService?.get<StageCore>('stage')?.select(data.id); editorService?.get('stage')?.select(data.id);
}; };
// //
const multiSelect = async (data: Id[]) => { const multiSelect = async (data: Id[]) => {
await editorService?.multiSelect(data); await editorService?.multiSelect(data);
editorService?.get<StageCore>('stage')?.multiSelect(data); editorService?.get('stage')?.multiSelect(data);
}; };
// //
@ -136,7 +135,7 @@ const highlight = (data: MNode) => {
throw new Error('没有id'); throw new Error('没有id');
} }
editorService?.highlight(data); editorService?.highlight(data);
editorService?.get<StageCore>('stage')?.highlight(data.id); editorService?.get('stage')?.highlight(data.id);
}; };
// tree // tree
@ -230,14 +229,14 @@ watch(nodes, (nodes) => {
watch(isMultiSelect, (isMultiSelect) => { watch(isMultiSelect, (isMultiSelect) => {
if (!isMultiSelect) { if (!isMultiSelect) {
currentNodeKey.value = editorService?.get<MNode>('node').id; currentNodeKey.value = editorService?.get('node')?.id;
tree.value?.setCurrentKey(currentNodeKey.value); tree.value?.setCurrentKey(currentNodeKey.value);
} }
}); });
const editorServiceRemoveHandler = () => { const editorServiceRemoveHandler = () => {
setTimeout(() => { setTimeout(() => {
tree.value?.getNode(editorService?.get('node').id)?.updateChildren(); tree.value?.getNode(editorService?.get('node')?.id)?.updateChildren();
}, 0); }, 0);
}; };
@ -300,7 +299,7 @@ const clickHandler = (data: MNode): void => {
if (isCtrlKeyDown.value) { if (isCtrlKeyDown.value) {
return; return;
} }
if (services?.uiService.get<boolean>('uiSelectMode')) { if (services?.uiService.get('uiSelectMode')) {
document.dispatchEvent(new CustomEvent('ui-select', { detail: data })); document.dispatchEvent(new CustomEvent('ui-select', { detail: data }));
return; return;
} }

View File

@ -93,7 +93,6 @@ import { cloneDeep, forIn, isEmpty } from 'lodash-es';
import { TMagicButton, TMagicInput, tMagicMessage, TMagicScrollbar, TMagicTooltip, TMagicTree } from '@tmagic/design'; import { TMagicButton, TMagicInput, tMagicMessage, TMagicScrollbar, TMagicTooltip, TMagicTree } from '@tmagic/design';
import { CodeBlockContent, Id } from '@tmagic/schema'; import { CodeBlockContent, Id } from '@tmagic/schema';
import StageCore from '@tmagic/stage';
import Icon from '../../../components/Icon.vue'; import Icon from '../../../components/Icon.vue';
import type { CodeRelation, Services } from '../../../type'; import type { CodeRelation, Services } from '../../../type';
@ -228,7 +227,7 @@ const getCompName = (compId: Id): string => {
// //
const selectComp = (compId: Id) => { const selectComp = (compId: Id) => {
const stage = services?.editorService.get<StageCore | null>('stage'); const stage = services?.editorService.get('stage');
services?.editorService.select(compId); services?.editorService.select(compId);
stage?.select(compId); stage?.select(compId);
}; };

View File

@ -11,8 +11,7 @@
import { computed, inject } from 'vue'; import { computed, inject } from 'vue';
import { TMagicButton } from '@tmagic/design'; import { TMagicButton } from '@tmagic/design';
import type { MApp, MNode } from '@tmagic/schema'; import type { MNode } from '@tmagic/schema';
import type StageCore from '@tmagic/stage';
import { getNodePath } from '@tmagic/utils'; import { getNodePath } from '@tmagic/utils';
import type { Services } from '../../type'; import type { Services } from '../../type';
@ -20,13 +19,13 @@ import type { Services } from '../../type';
const services = inject<Services>('services'); const services = inject<Services>('services');
const editorService = services?.editorService; const editorService = services?.editorService;
const node = computed(() => editorService?.get<MNode>('node')); const node = computed(() => editorService?.get('node'));
const nodes = computed(() => editorService?.get<MNode[]>('nodes') || []); const nodes = computed(() => editorService?.get('nodes') || []);
const root = computed(() => editorService?.get<MApp>('root')); const root = computed(() => editorService?.get('root'));
const path = computed(() => getNodePath(node.value?.id || '', root.value?.items || [])); const path = computed(() => getNodePath(node.value?.id || '', root.value?.items || []));
const select = async (node: MNode) => { const select = async (node: MNode) => {
await editorService?.select(node); await editorService?.select(node);
editorService?.get<StageCore>('stage')?.select(node.id); editorService?.get('stage')?.select(node.id);
}; };
</script> </script>

View File

@ -51,7 +51,7 @@ import { computed, inject } from 'vue';
import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue'; import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue';
import { TMagicIcon, TMagicPopover, TMagicTooltip } from '@tmagic/design'; import { TMagicIcon, TMagicPopover, TMagicTooltip } from '@tmagic/design';
import type { MApp, MPage } from '@tmagic/schema'; import type { MPage } from '@tmagic/schema';
import ToolButton from '../../components/ToolButton.vue'; import ToolButton from '../../components/ToolButton.vue';
import type { Services } from '../../type'; import type { Services } from '../../type';
@ -61,7 +61,7 @@ import PageBarScrollContainer from './PageBarScrollContainer.vue';
const services = inject<Services>('services'); const services = inject<Services>('services');
const editorService = services?.editorService; const editorService = services?.editorService;
const root = computed(() => editorService?.get<MApp>('root')); const root = computed(() => editorService?.get('root'));
const page = computed(() => editorService?.get('page')); const page = computed(() => editorService?.get('page'));

View File

@ -100,7 +100,7 @@ const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`; itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
}; };
const pageLength = computed(() => editorService?.get<number>('pageLength')); const pageLength = computed(() => editorService?.get('pageLength'));
watch(pageLength, (length = 0, preLength = 0) => { watch(pageLength, (length = 0, preLength = 0) => {
setTimeout(() => { setTimeout(() => {
@ -115,9 +115,11 @@ watch(pageLength, (length = 0, preLength = 0) => {
const addPage = () => { const addPage = () => {
if (!editorService) return; if (!editorService) return;
const root = toRaw(editorService.get('root'));
if (!root) throw new Error('root 不能为空');
const pageConfig = { const pageConfig = {
type: NodeType.PAGE, type: NodeType.PAGE,
name: generatePageNameByApp(toRaw(editorService.get('root'))), name: generatePageNameByApp(root),
}; };
editorService.add(pageConfig); editorService.add(pageConfig);
}; };

View File

@ -30,11 +30,11 @@
import { computed, inject, markRaw, nextTick, onMounted, onUnmounted, ref, toRaw, watch, watchEffect } from 'vue'; import { computed, inject, markRaw, nextTick, onMounted, onUnmounted, ref, toRaw, watch, watchEffect } from 'vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { MApp, MContainer, MNode, MPage } from '@tmagic/schema'; import type { MContainer } from '@tmagic/schema';
import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stage'; import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stage';
import ScrollViewer from '../../components/ScrollViewer.vue'; import ScrollViewer from '../../components/ScrollViewer.vue';
import { Layout, MenuButton, MenuComponent, Services, StageOptions, StageRect } from '../../type'; import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '../../type';
import { useStage } from '../../utils/stage'; import { useStage } from '../../utils/stage';
import ViewerMenu from './ViewerMenu.vue'; import ViewerMenu from './ViewerMenu.vue';
@ -53,13 +53,14 @@ const stageWrap = ref<InstanceType<typeof ScrollViewer>>();
const stageContainer = ref<HTMLDivElement>(); const stageContainer = ref<HTMLDivElement>();
const menu = ref<InstanceType<typeof ViewerMenu>>(); const menu = ref<InstanceType<typeof ViewerMenu>>();
const isMultiSelect = computed(() => services?.editorService.get('nodes')?.length > 1); const nodes = computed(() => services?.editorService.get('nodes') || []);
const stageRect = computed(() => services?.uiService.get<StageRect>('stageRect')); const isMultiSelect = computed(() => nodes.value.length > 1);
const stageContainerRect = computed(() => services?.uiService.get<StageRect>('stageContainerRect')); const stageRect = computed(() => services?.uiService.get('stageRect'));
const root = computed(() => services?.editorService.get<MApp>('root')); const stageContainerRect = computed(() => services?.uiService.get('stageContainerRect'));
const page = computed(() => services?.editorService.get<MPage>('page')); const root = computed(() => services?.editorService.get('root'));
const zoom = computed(() => services?.uiService.get<number>('zoom') || 1); const page = computed(() => services?.editorService.get('page'));
const node = computed(() => services?.editorService.get<MNode>('node')); const zoom = computed(() => services?.uiService.get('zoom') || 1);
const node = computed(() => services?.editorService.get('node'));
watchEffect(() => { watchEffect(() => {
if (stage || !page.value) return; if (stage || !page.value) return;
@ -142,7 +143,7 @@ const dropHandler = async (e: DragEvent) => {
const doc = stage?.renderer.contentWindow?.document; const doc = stage?.renderer.contentWindow?.document;
const parentEl: HTMLElement | null | undefined = doc?.querySelector(`.${stageOptions?.containerHighlightClassName}`); const parentEl: HTMLElement | null | undefined = doc?.querySelector(`.${stageOptions?.containerHighlightClassName}`);
let parent: MContainer | undefined = page.value; let parent: MContainer | undefined | null = page.value;
if (parentEl) { if (parentEl) {
parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer; parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer;
} }

View File

@ -6,8 +6,7 @@
import { computed, inject, markRaw, ref, watch } from 'vue'; import { computed, inject, markRaw, ref, watch } from 'vue';
import { Bottom, CopyDocument, Delete, DocumentCopy, Top } from '@element-plus/icons-vue'; import { Bottom, CopyDocument, Delete, DocumentCopy, Top } from '@element-plus/icons-vue';
import { MNode, NodeType } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema';
import StageCore from '@tmagic/stage';
import { isPage } from '@tmagic/utils'; import { isPage } from '@tmagic/utils';
import ContentMenu from '../../components/ContentMenu.vue'; import ContentMenu from '../../components/ContentMenu.vue';
@ -26,10 +25,10 @@ const menu = ref<InstanceType<typeof ContentMenu>>();
const canPaste = ref(false); const canPaste = ref(false);
const canCenter = ref(false); const canCenter = ref(false);
const node = computed(() => editorService?.get<MNode>('node')); const node = computed(() => editorService?.get('node'));
const nodes = computed(() => editorService?.get<MNode[]>('nodes')); const nodes = computed(() => editorService?.get('nodes'));
const parent = computed(() => editorService?.get('parent')); const parent = computed(() => editorService?.get('parent'));
const stage = computed(() => editorService?.get<StageCore>('stage')); const stage = computed(() => editorService?.get('stage'));
const menuData = computed<(MenuButton | MenuComponent)[]>(() => [ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
{ {
@ -129,7 +128,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
type: 'button', type: 'button',
text: '清空参考线', text: '清空参考线',
handler: () => { handler: () => {
editorService?.get<StageCore>('stage').clearGuides(); editorService?.get('stage')?.clearGuides();
}, },
}, },
...props.stageContentMenu, ...props.stageContentMenu,

View File

@ -19,7 +19,6 @@
import { computed, inject, onMounted, onUnmounted, ref } from 'vue'; import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import KeyController from 'keycon'; import KeyController from 'keycon';
import type { MNode, MPage } from '@tmagic/schema';
import { isPage } from '@tmagic/utils'; import { isPage } from '@tmagic/utils';
import type { MenuButton, MenuComponent, Services } from '../../type'; import type { MenuButton, MenuComponent, Services } from '../../type';
@ -34,8 +33,8 @@ defineProps<{
const services = inject<Services>('services'); const services = inject<Services>('services');
const workspace = ref<HTMLDivElement>(); const workspace = ref<HTMLDivElement>();
const nodes = computed(() => services?.editorService.get<MNode[]>('nodes')); const nodes = computed(() => services?.editorService.get('nodes'));
const page = computed(() => services?.editorService.get<MPage>('page')); const page = computed(() => services?.editorService.get('page'));
const mouseenterHandler = () => { const mouseenterHandler = () => {
workspace.value?.focus(); workspace.value?.focus();

View File

@ -19,7 +19,7 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { cloneDeep, forIn, isEmpty, keys, omit, pick } from 'lodash-es'; import { cloneDeep, forIn, isEmpty, keys, omit, pick } from 'lodash-es';
import { CodeBlockContent, CodeBlockDSL, HookType, Id, MApp, MNode } from '@tmagic/schema'; import { CodeBlockContent, CodeBlockDSL, HookType, Id, MNode } from '@tmagic/schema';
import editorService from '../services/editor'; import editorService from '../services/editor';
import type { CodeRelation, CodeState, HookData } from '../type'; import type { CodeRelation, CodeState, HookData } from '../type';
@ -253,7 +253,7 @@ class CodeBlock extends BaseService {
* @returns {CodeRelation | null} * @returns {CodeRelation | null}
*/ */
public refreshCombineInfo(): CodeRelation | null { public refreshCombineInfo(): CodeRelation | null {
const root = editorService.get<MApp | null>('root'); const root = editorService.get('root');
if (!root) return null; if (!root) return null;
const relations = {}; const relations = {};
this.recurseMNode(root, relations); this.recurseMNode(root, relations);

View File

@ -27,7 +27,7 @@ import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
import codeBlockService from '../services/codeBlock'; import codeBlockService from '../services/codeBlock';
import historyService from '../services/history'; import historyService from '../services/history';
import storageService, { Protocol } from '../services/storage'; import storageService, { Protocol } from '../services/storage';
import type { AddMNode, EditorNodeInfo, PastePosition, StepValue, StoreState } from '../type'; import type { AddMNode, EditorNodeInfo, PastePosition, StepValue, StoreState, StoreStateKey } from '../type';
import { LayerOffset, Layout } from '../type'; import { LayerOffset, Layout } from '../type';
import { import {
change2Fixed, change2Fixed,
@ -94,15 +94,26 @@ class Editor extends BaseService {
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength'
* @param value MNode * @param value MNode
*/ */
public set<T = MNode>(name: keyof StoreState, value: T) { public set<K extends StoreStateKey, T extends StoreState[K]>(name: K, value: T) {
const preValue = this.state[name]; const preValue = this.state[name];
this.state[name] = value as any; this.state[name] = value;
// set nodes时将node设置为nodes第一个元素 // set nodes时将node设置为nodes第一个元素
if (name === 'nodes') { if (name === 'nodes' && Array.isArray(value)) {
this.set('node', (value as unknown as MNode[])[0]); this.set('node', value[0]);
} }
if (name === 'root') { if (name === 'root') {
this.state.pageLength = (value as unknown as MApp)?.items?.length || 0; if (Array.isArray(value)) {
throw new Error('root 不能为数组');
}
if (isObject(value) && !(value instanceof StageCore) && !(value instanceof Map)) {
this.state.pageLength = value.items.length;
} else {
this.state.pageLength = 0;
}
this.emit('root-change', value, preValue); this.emit('root-change', value, preValue);
} }
} }
@ -112,8 +123,8 @@ class Editor extends BaseService {
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength'
* @returns MNode * @returns MNode
*/ */
public get<T = MNode>(name: keyof StoreState): T { public get<K extends StoreStateKey>(name: K): StoreState[K] {
return (this.state as any)[name]; return this.state[name];
} }
/** /**
@ -123,22 +134,29 @@ class Editor extends BaseService {
* @returns {EditorNodeInfo} * @returns {EditorNodeInfo}
*/ */
public getNodeInfo(id: Id, raw = true): EditorNodeInfo { public getNodeInfo(id: Id, raw = true): EditorNodeInfo {
let root = this.get<MApp | null>('root'); let root = this.get('root');
if (raw) { if (raw) {
root = toRaw(root); root = toRaw(root);
} }
if (!root) return {};
const info: EditorNodeInfo = {
node: null,
parent: null,
page: null,
};
if (!root) return info;
if (id === root.id) { if (id === root.id) {
return { node: root }; info.node = root;
return info;
} }
const path = getNodePath(id, root.items); const path = getNodePath(id, root.items);
if (!path.length) return {}; if (!path.length) return info;
path.unshift(root); path.unshift(root);
const info: EditorNodeInfo = {};
info.node = path[path.length - 1] as MComponent; info.node = path[path.length - 1] as MComponent;
info.parent = path[path.length - 2] as MContainer; info.parent = path[path.length - 2] as MContainer;
@ -159,7 +177,7 @@ class Editor extends BaseService {
* @param {boolean} raw 使toRaw * @param {boolean} raw 使toRaw
* @returns * @returns
*/ */
public getNodeById(id: Id, raw = true): MNode | undefined { public getNodeById(id: Id, raw = true): MNode | null {
const { node } = this.getNodeInfo(id, raw); const { node } = this.getNodeInfo(id, raw);
return node; return node;
} }
@ -170,8 +188,7 @@ class Editor extends BaseService {
* @param {boolean} raw 使toRaw * @param {boolean} raw 使toRaw
* @returns * @returns
*/ */
public getParentById(id: Id, raw = true): MContainer | undefined { public getParentById(id: Id, raw = true): MContainer | null {
if (!this.get<MApp | null>('root')) return;
const { parent } = this.getNodeInfo(id, raw); const { parent } = this.getNodeInfo(id, raw);
return parent; return parent;
} }
@ -201,9 +218,9 @@ class Editor extends BaseService {
*/ */
public async select(config: MNode | Id): Promise<MNode> | never { public async select(config: MNode | Id): Promise<MNode> | never {
const { node, page, parent } = this.selectedConfigExceptionHandler(config); const { node, page, parent } = this.selectedConfigExceptionHandler(config);
this.set('nodes', [node]); this.set('nodes', node ? [node] : []);
this.set('page', page || null); this.set('page', page);
this.set('parent', parent || null); this.set('parent', parent);
if (page) { if (page) {
historyService.changePage(toRaw(page)); historyService.changePage(toRaw(page));
@ -212,7 +229,7 @@ class Editor extends BaseService {
} }
if (node?.id) { if (node?.id) {
this.get<StageCore>('stage') this.get('stage')
?.renderer.runtime?.getApp?.() ?.renderer.runtime?.getApp?.()
?.emit( ?.emit(
'editor:select', 'editor:select',
@ -221,7 +238,7 @@ class Editor extends BaseService {
page, page,
parent, parent,
}, },
getNodePath(node.id, this.get<MApp>('root').items), getNodePath(node.id, this.get('root')?.items),
); );
} }
@ -230,7 +247,7 @@ class Editor extends BaseService {
return node!; return node!;
} }
public async selectNextNode(): Promise<MNode> | never { public async selectNextNode(): Promise<MNode | null> | never {
const node = toRaw(this.get('node')); const node = toRaw(this.get('node'));
if (!node || isPage(node) || node.type === NodeType.ROOT) return node; if (!node || isPage(node) || node.type === NodeType.ROOT) return node;
@ -244,21 +261,24 @@ class Editor extends BaseService {
const nextNode = parent.items[index + 1] || parent.items[0]; const nextNode = parent.items[index + 1] || parent.items[0];
await this.select(nextNode); await this.select(nextNode);
this.get<StageCore>('stage')?.select(nextNode.id); this.get('stage')?.select(nextNode.id);
return nextNode; return nextNode;
} }
public async selectNextPage(): Promise<MNode> | never { public async selectNextPage(): Promise<MNode> | never {
const root = toRaw(this.get<MApp>('root')); const root = toRaw(this.get('root'));
const page = toRaw(this.get('page')); const page = toRaw(this.get('page'));
if (!page) throw new Error('page不能为空');
if (!root) throw new Error('root不能为空');
const index = getNodeIndex(page, root); const index = getNodeIndex(page, root);
const nextPage = root.items[index + 1] || root.items[0]; const nextPage = root.items[index + 1] || root.items[0];
await this.select(nextPage); await this.select(nextPage);
this.get<StageCore>('stage')?.select(nextPage.id); this.get('stage')?.select(nextPage.id);
return nextPage; return nextPage;
} }
@ -292,20 +312,25 @@ class Editor extends BaseService {
} }
public async doAdd(node: MNode, parent: MContainer): Promise<MNode> { public async doAdd(node: MNode, parent: MContainer): Promise<MNode> {
const root = this.get<MApp>('root'); const root = this.get('root');
const curNode = this.get<MNode>('node');
const stage = this.get<StageCore | null>('stage');
if ((parent?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && node.type !== NodeType.PAGE) { if (!root) throw new Error('root为空');
const curNode = this.get('node');
const stage = this.get('stage');
if (!curNode) throw new Error('当前选中节点为空');
if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && node.type !== NodeType.PAGE) {
throw new Error('app下不能添加组件'); throw new Error('app下不能添加组件');
} }
if (parent.id !== curNode.id && node.type !== NodeType.PAGE) { if (parent.id !== curNode.id && node.type !== NodeType.PAGE) {
const index = parent.items.indexOf(curNode); const index = parent.items.indexOf(curNode);
parent?.items?.splice(index + 1, 0, node); parent.items?.splice(index + 1, 0, node);
} else { } else {
// 新增节点添加到配置中 // 新增节点添加到配置中
parent?.items?.push(node); parent.items?.push(node);
} }
const layout = await this.getLayout(toRaw(parent), node as MNode); const layout = await this.getLayout(toRaw(parent), node as MNode);
@ -337,7 +362,7 @@ class Editor extends BaseService {
* @returns * @returns
*/ */
public async add(addNode: AddMNode | MNode[], parent?: MContainer | null): Promise<MNode | MNode[]> { public async add(addNode: AddMNode | MNode[], parent?: MContainer | null): Promise<MNode | MNode[]> {
const stage = this.get<StageCore | null>('stage'); const stage = this.get('stage');
// 新增多个组件只存在于粘贴多个组件,粘贴的是一个完整的config,所以不再需要getPropsValue // 新增多个组件只存在于粘贴多个组件,粘贴的是一个完整的config,所以不再需要getPropsValue
const addNodes = []; const addNodes = [];
@ -353,8 +378,9 @@ class Editor extends BaseService {
const newNodes = await Promise.all( const newNodes = await Promise.all(
addNodes.map((node) => { addNodes.map((node) => {
if (isPage(node)) { const root = this.get('root');
return this.doAdd(node, this.get('root')); if (isPage(node) && root) {
return this.doAdd(node, root);
} }
const parentNode = parent && typeof parent !== 'function' ? parent : getAddParent(node); const parentNode = parent && typeof parent !== 'function' ? parent : getAddParent(node);
if (!parentNode) throw new Error('未找到父元素'); if (!parentNode) throw new Error('未找到父元素');
@ -388,9 +414,8 @@ class Editor extends BaseService {
} }
public async doRemove(node: MNode): Promise<void> { public async doRemove(node: MNode): Promise<void> {
const root = this.get<MApp | null>('root'); const root = this.get('root');
if (!root) throw new Error('root不能为空');
if (!root) throw new Error('没有root');
const { parent, node: curNode } = this.getNodeInfo(node.id); const { parent, node: curNode } = this.getNodeInfo(node.id);
@ -401,7 +426,7 @@ class Editor extends BaseService {
if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点'); if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
parent.items?.splice(index, 1); parent.items?.splice(index, 1);
const stage = this.get<StageCore | null>('stage'); const stage = this.get('stage');
stage?.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) }); stage?.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) });
if (node.type === NodeType.PAGE) { if (node.type === NodeType.PAGE) {
@ -450,6 +475,9 @@ class Editor extends BaseService {
} }
public async doUpdate(config: MNode) { public async doUpdate(config: MNode) {
const root = this.get('root');
if (!root) throw new Error('root为空');
if (!config?.id) throw new Error('没有配置或者配置缺少id值'); if (!config?.id) throw new Error('没有配置或者配置缺少id值');
const info = this.getNodeInfo(config.id, false); const info = this.getNodeInfo(config.id, false);
@ -458,7 +486,7 @@ class Editor extends BaseService {
const node = cloneDeep(toRaw(info.node)); const node = cloneDeep(toRaw(info.node));
let newConfig = await this.toggleFixedPosition(toRaw(config), node, this.get<MApp>('root')); let newConfig = await this.toggleFixedPosition(toRaw(config), node, root);
newConfig = mergeWith(cloneDeep(node), newConfig, (objValue, srcValue) => { newConfig = mergeWith(cloneDeep(node), newConfig, (objValue, srcValue) => {
if (isObject(srcValue) && Array.isArray(objValue)) { if (isObject(srcValue) && Array.isArray(objValue)) {
@ -473,7 +501,7 @@ class Editor extends BaseService {
if (!newConfig.type) throw new Error('配置缺少type值'); if (!newConfig.type) throw new Error('配置缺少type值');
if (newConfig.type === NodeType.ROOT) { if (newConfig.type === NodeType.ROOT) {
this.set('root', newConfig); this.set('root', newConfig as MApp);
return newConfig; return newConfig;
} }
@ -494,19 +522,19 @@ class Editor extends BaseService {
parentNodeItems[index] = newConfig; parentNodeItems[index] = newConfig;
// 将update后的配置更新到nodes中 // 将update后的配置更新到nodes中
const nodes = this.get<MNode[]>('nodes') || []; const nodes = this.get('nodes');
const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`); const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`);
nodes.splice(targetIndex, 1, newConfig); nodes.splice(targetIndex, 1, newConfig);
this.set('nodes', [...nodes]); this.set('nodes', [...nodes]);
this.get<StageCore | null>('stage')?.update({ this.get('stage')?.update({
config: cloneDeep(newConfig), config: cloneDeep(newConfig),
parentId: parent.id, parentId: parent.id,
root: cloneDeep(this.get('root')), root: cloneDeep(root),
}); });
if (newConfig.type === NodeType.PAGE) { if (newConfig.type === NodeType.PAGE) {
this.set('page', newConfig); this.set('page', newConfig as MPage);
} }
this.addModifiedNodeId(newConfig.id); this.addModifiedNodeId(newConfig.id);
@ -538,8 +566,15 @@ class Editor extends BaseService {
* @returns void * @returns void
*/ */
public async sort(id1: Id, id2: Id): Promise<void> { public async sort(id1: Id, id2: Id): Promise<void> {
const node = this.get<MNode>('node'); const root = this.get('root');
const parent = cloneDeep(toRaw(this.get<MContainer>('parent'))); if (!root) throw new Error('root为空');
const node = this.get('node');
if (!node) throw new Error('当前节点为空');
const parent = cloneDeep(toRaw(this.get('parent')));
if (!parent) throw new Error('父节点为空');
const index2 = parent.items.findIndex((node: MNode) => `${node.id}` === `${id2}`); const index2 = parent.items.findIndex((node: MNode) => `${node.id}` === `${id2}`);
// 在 id1 的兄弟组件中若无 id2 则直接 return // 在 id1 的兄弟组件中若无 id2 则直接 return
if (index2 < 0) return; if (index2 < 0) return;
@ -550,10 +585,10 @@ class Editor extends BaseService {
await this.update(parent); await this.update(parent);
await this.select(node); await this.select(node);
this.get<StageCore | null>('stage')?.update({ this.get('stage')?.update({
config: cloneDeep(node), config: cloneDeep(node),
parentId: parent.id, parentId: parent.id,
root: cloneDeep(this.get('root')), root: cloneDeep(root),
}); });
this.addModifiedNodeId(parent.id); this.addModifiedNodeId(parent.id);
@ -581,14 +616,14 @@ class Editor extends BaseService {
if (!Array.isArray(config)) return; if (!Array.isArray(config)) return;
const node = this.get<MNode>('node'); const node = this.get('node');
let parent: MContainer | undefined = undefined; let parent: MContainer | null = null;
// 粘贴的组件为当前选中组件的副本时,则添加到当前选中组件的父组件中 // 粘贴的组件为当前选中组件的副本时,则添加到当前选中组件的父组件中
if (config.length === 1 && config[0].id === node.id) { if (config.length === 1 && config[0].id === node?.id) {
parent = this.get<MContainer>('parent'); parent = this.get('parent');
if (parent.type === NodeType.ROOT) { if (parent?.type === NodeType.ROOT) {
parent = this.get<MPage>('page'); parent = this.get('page');
} }
} }
@ -615,7 +650,7 @@ class Editor extends BaseService {
if (!node.style) return config; if (!node.style) return config;
const stage = this.get<StageCore>('stage'); const stage = this.get('stage');
const doc = stage?.renderer.contentWindow?.document; const doc = stage?.renderer.contentWindow?.document;
if (doc) { if (doc) {
@ -638,7 +673,7 @@ class Editor extends BaseService {
*/ */
public async alignCenter(config: MNode | MNode[]): Promise<MNode | MNode[]> { public async alignCenter(config: MNode | MNode[]): Promise<MNode | MNode[]> {
const nodes = Array.isArray(config) ? config : [config]; const nodes = Array.isArray(config) ? config : [config];
const stage = this.get<StageCore | null>('stage'); const stage = this.get('stage');
const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node))); const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node)));
@ -658,9 +693,16 @@ class Editor extends BaseService {
* @param offset * @param offset
*/ */
public async moveLayer(offset: number | LayerOffset): Promise<void> { public async moveLayer(offset: number | LayerOffset): Promise<void> {
const parent = this.get<MContainer>('parent'); const root = this.get('root');
const node = this.get<MNode>('node'); if (!root) throw new Error('root为空');
const brothers: MNode[] = parent?.items || [];
const parent = this.get('parent');
if (!parent) throw new Error('父节点为空');
const node = this.get('node');
if (!node) throw new Error('当前节点为空');
const brothers: MNode[] = parent.items || [];
const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`); const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`);
// 流式布局与绝对定位布局操作的相反的 // 流式布局与绝对定位布局操作的相反的
@ -678,10 +720,10 @@ class Editor extends BaseService {
} }
const grandparent = this.getParentById(parent.id); const grandparent = this.getParentById(parent.id);
this.get<StageCore | null>('stage')?.update({ this.get('stage')?.update({
config: cloneDeep(toRaw(parent)), config: cloneDeep(toRaw(parent)),
parentId: grandparent?.id, parentId: grandparent?.id,
root: cloneDeep(this.get<MApp>('root')), root: cloneDeep(root),
}); });
this.addModifiedNodeId(parent.id); this.addModifiedNodeId(parent.id);
@ -694,13 +736,12 @@ class Editor extends BaseService {
* @param targetId ID * @param targetId ID
*/ */
public async moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined> { public async moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined> {
const root = cloneDeep(this.get('root'));
const { node, parent } = this.getNodeInfo(config.id, false); const { node, parent } = this.getNodeInfo(config.id, false);
const target = this.getNodeById(targetId, false) as MContainer; const target = this.getNodeById(targetId, false) as MContainer;
const stage = this.get<StageCore | null>('stage'); const stage = this.get('stage');
if (root && node && parent && stage) {
if (node && parent && stage) {
const root = cloneDeep(this.get<MApp>('root'));
const index = getNodeIndex(node, parent); const index = getNodeIndex(node, parent);
parent.items?.splice(index, 1); parent.items?.splice(index, 1);
@ -783,7 +824,7 @@ class Editor extends BaseService {
this.set('stage', null); this.set('stage', null);
this.set('highlightNode', null); this.set('highlightNode', null);
this.set('modifiedNodeIds', new Map()); this.set('modifiedNodeIds', new Map());
this.set('pageLength', new Map()); this.set('pageLength', 0);
} }
public destroy() { public destroy() {
@ -793,7 +834,7 @@ class Editor extends BaseService {
} }
public resetModifiedNodeId() { public resetModifiedNodeId() {
this.get<Map<Id, Id>>('modifiedNodeIds').clear(); this.get('modifiedNodeIds').clear();
} }
/** /**
@ -801,15 +842,13 @@ class Editor extends BaseService {
* @returns {CodeBlockDSL | null} * @returns {CodeBlockDSL | null}
*/ */
public async getCodeDsl(): Promise<CodeBlockDSL | null> { public async getCodeDsl(): Promise<CodeBlockDSL | null> {
const root = this.get<MApp | null>('root'); const root = this.get('root');
if (!root) return null; return root?.codeBlocks || null;
return root.codeBlocks || null;
} }
public getCodeDslSync(): CodeBlockDSL | null { public getCodeDslSync(): CodeBlockDSL | null {
const root = this.get<MApp | null>('root'); const root = this.get('root');
if (!root) return null; return root?.codeBlocks || null;
return root.codeBlocks || null;
} }
/** /**
@ -824,16 +863,17 @@ class Editor extends BaseService {
private addModifiedNodeId(id: Id) { private addModifiedNodeId(id: Id) {
if (!this.isHistoryStateChange) { if (!this.isHistoryStateChange) {
this.get<Map<Id, Id>>('modifiedNodeIds').set(id, id); this.get('modifiedNodeIds').set(id, id);
} }
} }
private pushHistoryState() { private pushHistoryState() {
const curNode = cloneDeep(toRaw(this.get('node'))); const curNode = cloneDeep(toRaw(this.get('node')));
if (!this.isHistoryStateChange) { const page = this.get('page');
if (!this.isHistoryStateChange && curNode && page) {
historyService.push({ historyService.push({
data: cloneDeep(toRaw(this.get('page'))), data: cloneDeep(toRaw(page)),
modifiedNodeIds: this.get<Map<Id, Id>>('modifiedNodeIds'), modifiedNodeIds: this.get('modifiedNodeIds'),
nodeId: curNode.id, nodeId: curNode.id,
}); });
} }
@ -849,7 +889,7 @@ class Editor extends BaseService {
setTimeout(async () => { setTimeout(async () => {
if (!value.nodeId) return; if (!value.nodeId) return;
await this.select(value.nodeId); await this.select(value.nodeId);
this.get<StageCore | null>('stage')?.select(value.nodeId); this.get('stage')?.select(value.nodeId);
}, 0); }, 0);
} }

View File

@ -18,8 +18,6 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import type StageCore from '@tmagic/stage';
import editorService from '../services/editor'; import editorService from '../services/editor';
import type { StageRect, UiState } from '../type'; import type { StageRect, UiState } from '../type';
@ -53,8 +51,8 @@ class Ui extends BaseService {
super(['zoom', 'calcZoom']); super(['zoom', 'calcZoom']);
} }
public set<T = any>(name: keyof UiState, value: T) { public set<K extends keyof UiState, T extends UiState[K]>(name: K, value: T) {
const mask = editorService.get<StageCore>('stage')?.mask; const mask = editorService.get('stage')?.mask;
if (name === 'stageRect') { if (name === 'stageRect') {
this.setStageRect(value as unknown as StageRect); this.setStageRect(value as unknown as StageRect);
@ -69,16 +67,16 @@ class Ui extends BaseService {
mask?.showRule(value as unknown as boolean); mask?.showRule(value as unknown as boolean);
} }
(state as any)[name] = value; state[name] = value;
} }
public get<T>(name: keyof typeof state): T { public get<K extends keyof UiState>(name: K) {
return (state as any)[name]; return state[name];
} }
public async zoom(zoom: number) { public async zoom(zoom: number) {
this.set('zoom', (this.get<number>('zoom') * 100 + zoom * 100) / 100); this.set('zoom', (this.get('zoom') * 100 + zoom * 100) / 100);
if (this.get<number>('zoom') < 0.1) this.set('zoom', 0.1); if (this.get('zoom') < 0.1) this.set('zoom', 0.1);
} }
public async calcZoom() { public async calcZoom() {

View File

@ -82,6 +82,8 @@ export interface StoreState {
pageLength: number; pageLength: number;
} }
export type StoreStateKey = keyof StoreState;
export interface PropsState { export interface PropsState {
propsConfigMap: Record<string, FormConfig>; propsConfigMap: Record<string, FormConfig>;
propsValueMap: Record<string, MNode>; propsValueMap: Record<string, MNode>;
@ -138,9 +140,9 @@ export interface UiState {
} }
export interface EditorNodeInfo { export interface EditorNodeInfo {
node?: MNode; node: MNode | null;
parent?: MContainer; parent: MContainer | null;
page?: MPage; page: MPage | null;
} }
export interface AddMNode { export interface AddMNode {

View File

@ -1,8 +1,7 @@
import { toRaw } from 'vue'; import { toRaw } from 'vue';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { Id, MApp, MContainer, MNode } from '@tmagic/schema'; import { Id, MContainer, MNode } from '@tmagic/schema';
import StageCore from '@tmagic/stage';
import { isPage } from '@tmagic/utils'; import { isPage } from '@tmagic/utils';
import editorService from '../services/editor'; import editorService from '../services/editor';
@ -18,7 +17,7 @@ import { generatePageNameByApp, getInitPositionStyle } from '../utils/editor';
*/ */
export const beforePaste = async (position: PastePosition, config: MNode[]): Promise<MNode[]> => { export const beforePaste = async (position: PastePosition, config: MNode[]): Promise<MNode[]> => {
if (!config[0]?.style) return config; if (!config[0]?.style) return config;
const curNode = editorService.get<MContainer>('node'); const curNode = editorService.get('node');
// 将数组中第一个元素的坐标作为参照点 // 将数组中第一个元素的坐标作为参照点
const { left: referenceLeft, top: referenceTop } = config[0].style; const { left: referenceLeft, top: referenceTop } = config[0].style;
// 坐标校准后的粘贴数据 // 坐标校准后的粘贴数据
@ -28,7 +27,7 @@ export const beforePaste = async (position: PastePosition, config: MNode[]): Pro
const { offsetX = 0, offsetY = 0, ...positionClone } = position; const { offsetX = 0, offsetY = 0, ...positionClone } = position;
let pastePosition = positionClone; let pastePosition = positionClone;
if (!isEmpty(pastePosition) && curNode.items) { if (!isEmpty(pastePosition) && curNode?.items) {
// 如果没有传入粘贴坐标则可能为键盘操作,不再转换 // 如果没有传入粘贴坐标则可能为键盘操作,不再转换
// 如果粘贴时选中了容器,则将元素粘贴到容器内,坐标需要转换为相对于容器的坐标 // 如果粘贴时选中了容器,则将元素粘贴到容器内,坐标需要转换为相对于容器的坐标
pastePosition = getPositionInContainer(pastePosition, curNode.id); pastePosition = getPositionInContainer(pastePosition, curNode.id);
@ -59,8 +58,9 @@ export const beforePaste = async (position: PastePosition, config: MNode[]): Pro
...pastePosition, ...pastePosition,
}; };
} }
if (isPage(pasteConfig)) { const root = editorService.get('root');
pasteConfig.name = generatePageNameByApp(editorService.get('root')); if (isPage(pasteConfig) && root) {
pasteConfig.name = generatePageNameByApp(root);
} }
return pasteConfig as MNode; return pasteConfig as MNode;
}), }),
@ -76,7 +76,7 @@ export const beforePaste = async (position: PastePosition, config: MNode[]): Pro
*/ */
export const getPositionInContainer = (position: PastePosition = {}, id: Id) => { export const getPositionInContainer = (position: PastePosition = {}, id: Id) => {
let { left = 0, top = 0 } = position; let { left = 0, top = 0 } = position;
const parentEl = editorService.get<StageCore>('stage')?.renderer?.contentWindow?.document.getElementById(`${id}`); const parentEl = editorService.get('stage')?.renderer?.contentWindow?.document.getElementById(`${id}`);
const parentElRect = parentEl?.getBoundingClientRect(); const parentElRect = parentEl?.getBoundingClientRect();
left = left - (parentElRect?.left || 0); left = left - (parentElRect?.left || 0);
top = top - (parentElRect?.top || 0); top = top - (parentElRect?.top || 0);
@ -87,14 +87,14 @@ export const getPositionInContainer = (position: PastePosition = {}, id: Id) =>
}; };
export const getAddParent = (node: MNode) => { export const getAddParent = (node: MNode) => {
const curNode = editorService.get<MContainer>('node'); const curNode = editorService.get('node');
let parentNode; let parentNode;
if (isPage(node)) { if (isPage(node)) {
parentNode = editorService.get<MApp>('root'); parentNode = editorService.get('root');
} else if (curNode.items) { } else if (curNode?.items) {
parentNode = curNode; parentNode = curNode as MContainer;
} else { } else if (curNode?.id) {
parentNode = editorService.getParentById(curNode.id, false); parentNode = editorService.getParentById(curNode.id, false);
} }
return parentNode; return parentNode;

View File

@ -1,6 +1,5 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { MApp, MPage } from '@tmagic/schema';
import StageCore, { GuidesType, SortEventData, UpdateEventData } from '@tmagic/stage'; import StageCore, { GuidesType, SortEventData, UpdateEventData } from '@tmagic/stage';
import editorService from '../services/editor'; import editorService from '../services/editor';
@ -9,10 +8,10 @@ import { H_GUIDE_LINE_STORAGE_KEY, StageOptions, V_GUIDE_LINE_STORAGE_KEY } from
import { getGuideLineFromCache } from './editor'; import { getGuideLineFromCache } from './editor';
const root = computed(() => editorService.get<MApp>('root')); const root = computed(() => editorService.get('root'));
const page = computed(() => editorService.get<MPage>('page')); const page = computed(() => editorService.get('page'));
const zoom = computed(() => uiService.get<number>('zoom') || 1); const zoom = computed(() => uiService.get('zoom') || 1);
const uiSelectMode = computed(() => uiService.get<boolean>('uiSelectMode')); const uiSelectMode = computed(() => uiService.get('uiSelectMode'));
const getGuideLineKey = (key: string) => `${key}_${root.value?.id}_${page.value?.id}`; const getGuideLineKey = (key: string) => `${key}_${root.value?.id}_${page.value?.id}`;
@ -76,8 +75,9 @@ export const useStage = (stageOptions: StageOptions) => {
stage.on('select-parent', () => { stage.on('select-parent', () => {
const parent = editorService.get('parent'); const parent = editorService.get('parent');
if (!parent) throw new Error('父节点为空');
editorService.select(parent); editorService.select(parent);
editorService.get<StageCore>('stage').select(parent.id); editorService.get('stage')?.select(parent.id);
}); });
stage.on('change-guides', (e) => { stage.on('change-guides', (e) => {

View File

@ -19,7 +19,7 @@
import { beforeAll, describe, expect, test } from 'vitest'; import { beforeAll, describe, expect, test } from 'vitest';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { MApp, MContainer, MNode } from '@tmagic/schema'; import type { MApp } from '@tmagic/schema';
import { NodeType } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema';
import editorService from '@editor/services/editor'; import editorService from '@editor/services/editor';
@ -74,7 +74,7 @@ enum NodeId {
} }
// mock 页面数据,包含一个页面,两个组件 // mock 页面数据,包含一个页面,两个组件
const root: MNode = { const root: MApp = {
id: NodeId.ROOT_ID, id: NodeId.ROOT_ID,
type: NodeType.ROOT, type: NodeType.ROOT,
items: [ items: [
@ -109,7 +109,7 @@ describe('get', () => {
test('get', () => { test('get', () => {
const root = editorService.get('root'); const root = editorService.get('root');
expect(root.id).toBe(NodeId.ROOT_ID); expect(root?.id).toBe(NodeId.ROOT_ID);
}); });
test('get undefined', () => { test('get undefined', () => {
@ -131,9 +131,9 @@ describe('getNodeInfo', () => {
test('异常', () => { test('异常', () => {
const info = editorService.getNodeInfo(NodeId.ERROR_NODE_ID); const info = editorService.getNodeInfo(NodeId.ERROR_NODE_ID);
expect(info?.node).toBeUndefined(); expect(info?.node).toBeNull();
expect(info?.parent?.id).toBeUndefined(); expect(info?.parent?.id).toBeUndefined();
expect(info?.page).toBeUndefined(); expect(info?.page).toBeNull();
}); });
}); });
@ -147,7 +147,7 @@ describe('getNodeById', () => {
test('异常', () => { test('异常', () => {
const node = editorService.getNodeById(NodeId.ERROR_NODE_ID); const node = editorService.getNodeById(NodeId.ERROR_NODE_ID);
expect(node).toBeUndefined(); expect(node).toBeNull();
}); });
}); });
@ -210,9 +210,9 @@ describe('add', () => {
const node = editorService.get('node'); const node = editorService.get('node');
const parent = editorService.get('parent'); const parent = editorService.get('parent');
if (!Array.isArray(newNode)) { if (!Array.isArray(newNode)) {
expect(node.id).toBe(newNode.id); expect(node?.id).toBe(newNode.id);
} }
expect(parent.items).toHaveLength(3); expect(parent?.items).toHaveLength(3);
}); });
test('正常, 当前不是容器', async () => { test('正常, 当前不是容器', async () => {
@ -226,15 +226,15 @@ describe('add', () => {
const node = editorService.get('node'); const node = editorService.get('node');
const parent = editorService.get('parent'); const parent = editorService.get('parent');
if (!Array.isArray(newNode)) { if (!Array.isArray(newNode)) {
expect(node.id).toBe(newNode.id); expect(node?.id).toBe(newNode.id);
} }
expect(parent.items).toHaveLength(3); expect(parent?.items).toHaveLength(3);
}); });
test('往root下添加page', async () => { test('往root下添加page', async () => {
editorService.set('root', cloneDeep(root)); editorService.set('root', cloneDeep(root));
await editorService.select(NodeId.PAGE_ID); await editorService.select(NodeId.PAGE_ID);
const rootNode = editorService.get<MApp>('root'); const rootNode = editorService.get('root');
const newNode = await editorService.add( const newNode = await editorService.add(
{ {
type: NodeType.PAGE, type: NodeType.PAGE,
@ -243,15 +243,15 @@ describe('add', () => {
); );
const node = editorService.get('node'); const node = editorService.get('node');
if (!Array.isArray(newNode)) { if (!Array.isArray(newNode)) {
expect(node.id).toBe(newNode.id); expect(node?.id).toBe(newNode.id);
} }
expect(rootNode.items.length).toBe(2); expect(rootNode?.items.length).toBe(2);
}); });
test.skip('往root下添加普通节点', () => { test.skip('往root下添加普通节点', () => {
editorService.set('root', cloneDeep(root)); editorService.set('root', cloneDeep(root));
// 根节点下只能加页面 // 根节点下只能加页面
const rootNode = editorService.get<MApp>('root'); const rootNode = editorService.get('root');
expect(() => expect(() =>
editorService.add( editorService.add(
{ {
@ -269,13 +269,13 @@ describe('remove', () => {
test('正常', async () => { test('正常', async () => {
editorService.remove({ id: NodeId.NODE_ID, type: 'text' }); editorService.remove({ id: NodeId.NODE_ID, type: 'text' });
const node = editorService.getNodeById(NodeId.NODE_ID); const node = editorService.getNodeById(NodeId.NODE_ID);
expect(node).toBeUndefined(); expect(node).toBeNull();
}); });
test('remove page', async () => { test('remove page', async () => {
editorService.set('root', cloneDeep(root)); editorService.set('root', cloneDeep(root));
editorService.select(NodeId.PAGE_ID); editorService.select(NodeId.PAGE_ID);
const rootNode = editorService.get<MApp>('root'); const rootNode = editorService.get('root');
// 先加一个页面 // 先加一个页面
const newPage = await editorService.add( const newPage = await editorService.add(
{ {
@ -283,9 +283,9 @@ describe('remove', () => {
}, },
rootNode, rootNode,
); );
expect(rootNode.items.length).toBe(2); expect(rootNode?.items.length).toBe(2);
await editorService.remove(newPage); await editorService.remove(newPage);
expect(rootNode.items.length).toBe(1); expect(rootNode?.items.length).toBe(1);
}); });
test.skip('undefine', async () => { test.skip('undefine', async () => {
@ -347,11 +347,11 @@ describe('sort', () => {
test('正常', async () => { test('正常', async () => {
await editorService.select(NodeId.NODE_ID2); await editorService.select(NodeId.NODE_ID2);
let parent = editorService.get<MContainer>('parent'); let parent = editorService.get('parent');
expect(parent.items[0].id).toBe(NodeId.NODE_ID); expect(parent?.items[0].id).toBe(NodeId.NODE_ID);
await editorService.sort(NodeId.NODE_ID2, NodeId.NODE_ID); await editorService.sort(NodeId.NODE_ID2, NodeId.NODE_ID);
parent = editorService.get<MContainer>('parent'); parent = editorService.get('parent');
expect(parent.items[0].id).toBe(NodeId.NODE_ID2); expect(parent?.items[0].id).toBe(NodeId.NODE_ID2);
}); });
}); });
@ -371,9 +371,9 @@ describe('moveLayer', () => {
test('正常', async () => { test('正常', async () => {
// 设置当前编辑的组件 // 设置当前编辑的组件
await editorService.select(NodeId.NODE_ID); await editorService.select(NodeId.NODE_ID);
const parent = editorService.get<MContainer>('parent'); const parent = editorService.get('parent');
await editorService.moveLayer(1); await editorService.moveLayer(1);
expect(parent.items[0].id).toBe(NodeId.NODE_ID2); expect(parent?.items[0].id).toBe(NodeId.NODE_ID2);
}); });
}); });
@ -385,9 +385,10 @@ describe('undo redo', () => {
// 设置当前编辑的组件 // 设置当前编辑的组件
await editorService.select(NodeId.NODE_ID); await editorService.select(NodeId.NODE_ID);
const node = editorService.get('node'); const node = editorService.get('node');
if (!node) throw new Error('未选中节点');
await editorService.remove(node); await editorService.remove(node);
const removedNode = editorService.getNodeById(NodeId.NODE_ID); const removedNode = editorService.getNodeById(NodeId.NODE_ID);
expect(removedNode).toBeUndefined(); expect(removedNode).toBeNull();
await editorService.undo(); await editorService.undo();
const undoNode = editorService.getNodeById(NodeId.NODE_ID); const undoNode = editorService.getNodeById(NodeId.NODE_ID);
expect(undoNode?.id).toBe(NodeId.NODE_ID); expect(undoNode?.id).toBe(NodeId.NODE_ID);

View File

@ -2,7 +2,7 @@
<router-view></router-view> <router-view></router-view>
</template> </template>
<script> <script lang="ts">
export default { export default {
name: 'App', name: 'App',
}; };

View File

@ -10,7 +10,6 @@
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import { editorService } from '@tmagic/editor'; import { editorService } from '@tmagic/editor';
import type StageCore from '@tmagic/stage';
enum DeviceType { enum DeviceType {
Phone = 'phone', Phone = 'phone',
@ -49,7 +48,7 @@ export default defineComponent({
setup(props, { emit }) { setup(props, { emit }) {
const calcFontsize = (width: number) => { const calcFontsize = (width: number) => {
const { iframe } = editorService.get<StageCore>('stage').renderer; const iframe = editorService.get('stage')?.renderer.iframe;
if (!iframe?.contentWindow) return; if (!iframe?.contentWindow) return;
iframe.contentWindow.appInstance.designWidth = width; iframe.contentWindow.appInstance.designWidth = width;
}; };

View File

@ -39,7 +39,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
import { editorService, MenuBarData, MoveableOptions, TMagicEditor } from '@tmagic/editor'; import { editorService, MenuBarData, MoveableOptions, TMagicEditor } from '@tmagic/editor';
import type { Id, MContainer, MNode } from '@tmagic/schema'; import type { MContainer, MNode } from '@tmagic/schema';
import { NodeType } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema';
import { CustomizeMoveableOptionsCallbackConfig } from '@tmagic/stage'; import { CustomizeMoveableOptionsCallbackConfig } from '@tmagic/stage';
import { asyncLoadJs } from '@tmagic/utils'; import { asyncLoadJs } from '@tmagic/utils';
@ -65,7 +65,7 @@ const stageRect = ref({
}); });
const previewUrl = computed( const previewUrl = computed(
() => `${VITE_RUNTIME_PATH}/page/index.html?localPreview=1&page=${editor.value?.editorService.get('page').id}`, () => `${VITE_RUNTIME_PATH}/page/index.html?localPreview=1&page=${editor.value?.editorService.get('page')?.id}`,
); );
const menu: MenuBarData = { const menu: MenuBarData = {
@ -93,7 +93,7 @@ const menu: MenuBarData = {
text: '预览', text: '预览',
icon: Connection, icon: Connection,
handler: async (services) => { handler: async (services) => {
if (services?.editorService.get<Map<Id, Id>>('modifiedNodeIds').size > 0) { if (services?.editorService.get('modifiedNodeIds').size > 0) {
try { try {
await ElMessageBox.confirm('有修改未保存,是否先保存再预览', '提示', { await ElMessageBox.confirm('有修改未保存,是否先保存再预览', '提示', {
confirmButtonText: '保存并预览', confirmButtonText: '保存并预览',