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 }) {
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;
if (nodeId) {
node = editorService.getNodeById(nodeId);

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, Z
import { NodeType } from '@tmagic/schema';
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(
defineProps<{
@ -29,12 +29,12 @@ const props = withDefaults(
const services = inject<Services>('services');
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 showGuides = computed((): boolean => uiService?.get<boolean>('showGuides') ?? true);
const showRule = computed((): boolean => uiService?.get<boolean>('showRule') ?? true);
const zoom = computed((): number => uiService?.get<number>('zoom') ?? 1);
const showGuides = computed((): boolean => uiService?.get('showGuides') ?? true);
const showRule = computed((): boolean => uiService?.get('showRule') ?? true);
const zoom = computed((): number => uiService?.get('zoom') ?? 1);
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
const ctrl = isMac ? 'Command' : 'Ctrl';
@ -67,7 +67,10 @@ const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => {
icon: markRaw(Delete),
tooltip: `刪除(Delete)`,
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;
case 'undo':

View File

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ import { computed, inject } from 'vue';
import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue';
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 type { Services } from '../../type';
@ -61,7 +61,7 @@ import PageBarScrollContainer from './PageBarScrollContainer.vue';
const services = inject<Services>('services');
const editorService = services?.editorService;
const root = computed(() => editorService?.get<MApp>('root'));
const root = computed(() => editorService?.get('root'));
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)`;
};
const pageLength = computed(() => editorService?.get<number>('pageLength'));
const pageLength = computed(() => editorService?.get('pageLength'));
watch(pageLength, (length = 0, preLength = 0) => {
setTimeout(() => {
@ -115,9 +115,11 @@ watch(pageLength, (length = 0, preLength = 0) => {
const addPage = () => {
if (!editorService) return;
const root = toRaw(editorService.get('root'));
if (!root) throw new Error('root 不能为空');
const pageConfig = {
type: NodeType.PAGE,
name: generatePageNameByApp(toRaw(editorService.get('root'))),
name: generatePageNameByApp(root),
};
editorService.add(pageConfig);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import serialize from 'serialize-javascript';
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 { CustomizeMoveableOptionsCallbackConfig } from '@tmagic/stage';
import { asyncLoadJs } from '@tmagic/utils';
@ -65,7 +65,7 @@ const stageRect = ref({
});
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 = {
@ -93,7 +93,7 @@ const menu: MenuBarData = {
text: '预览',
icon: Connection,
handler: async (services) => {
if (services?.editorService.get<Map<Id, Id>>('modifiedNodeIds').size > 0) {
if (services?.editorService.get('modifiedNodeIds').size > 0) {
try {
await ElMessageBox.confirm('有修改未保存,是否先保存再预览', '提示', {
confirmButtonText: '保存并预览',