mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-19 03:55:50 +08:00
feat(core,data-source,utils,react-runtime-help,vue-runtime-help): 新增对页面片节点的管理
This commit is contained in:
parent
aaf8046c63
commit
6a54720068
@ -29,7 +29,7 @@ import Flexible from './Flexible';
|
||||
import FlowState from './FlowState';
|
||||
import Node from './Node';
|
||||
import Page from './Page';
|
||||
import { AppOptionsConfig, ErrorHandler } from './type';
|
||||
import { AppOptionsConfig, ErrorHandler, GetNodeOptions } from './type';
|
||||
import { transformStyle as defaultTransformStyle } from './utils';
|
||||
|
||||
class App extends EventEmitter {
|
||||
@ -45,6 +45,7 @@ class App extends EventEmitter {
|
||||
public codeDsl?: CodeBlockDSL;
|
||||
public dataSourceManager?: DataSourceManager;
|
||||
public page?: Page;
|
||||
public pageFragments: Map<Id, Page> = new Map();
|
||||
public useMock = false;
|
||||
public platform = 'mobile';
|
||||
public jsEngine: JsEngine = 'browser';
|
||||
@ -159,6 +160,10 @@ class App extends EventEmitter {
|
||||
|
||||
super.emit('dsl-change', { dsl: config, curPage: pageId });
|
||||
|
||||
this.pageFragments.forEach((page) => {
|
||||
page.destroy();
|
||||
});
|
||||
this.pageFragments.clear();
|
||||
this.setPage(pageId);
|
||||
|
||||
if (this.dataSourceManager) {
|
||||
@ -192,6 +197,11 @@ class App extends EventEmitter {
|
||||
for (const [, node] of this.page.nodes) {
|
||||
this.eventHelper.bindNodeEvents(node);
|
||||
}
|
||||
for (const [, page] of this.pageFragments) {
|
||||
for (const [, node] of page.nodes) {
|
||||
this.eventHelper.bindNodeEvents(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.emit('page-change', this.page);
|
||||
@ -215,8 +225,8 @@ class App extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public getNode<T extends Node = Node>(id: Id, iteratorContainerId?: Id[], iteratorIndex?: number[]) {
|
||||
return this.page?.getNode<T>(id, iteratorContainerId, iteratorIndex);
|
||||
public getNode<T extends Node = Node>(id: Id, options?: GetNodeOptions) {
|
||||
return this.page?.getNode<T>(id, options);
|
||||
}
|
||||
|
||||
public registerComponent(type: string, Component: any) {
|
||||
@ -299,7 +309,12 @@ class App extends EventEmitter {
|
||||
|
||||
public destroy() {
|
||||
this.removeAllListeners();
|
||||
this.page?.destroy();
|
||||
this.page = undefined;
|
||||
this.pageFragments.forEach((page) => {
|
||||
page.destroy();
|
||||
});
|
||||
this.pageFragments.clear();
|
||||
|
||||
this.flexible?.destroy();
|
||||
this.flexible = undefined;
|
||||
|
@ -258,19 +258,34 @@ export default class EventHelper extends EventEmitter {
|
||||
[to, methodName] = methodName;
|
||||
}
|
||||
|
||||
const toNodes = [];
|
||||
const toNode = this.app.getNode(to);
|
||||
if (!toNode) throw new Error(`ID为${to}的组件不存在`);
|
||||
|
||||
if (toNode.instance) {
|
||||
if (typeof toNode.instance[methodName] === 'function') {
|
||||
await toNode.instance[methodName](fromCpt, ...args);
|
||||
}
|
||||
} else {
|
||||
toNode.addEventToQueue({
|
||||
method: methodName,
|
||||
fromCpt,
|
||||
args,
|
||||
});
|
||||
if (toNode) {
|
||||
toNodes.push(toNode);
|
||||
}
|
||||
|
||||
for (const [, page] of this.app.pageFragments) {
|
||||
const node = page.getNode(to);
|
||||
if (node) {
|
||||
toNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
const instanceMethodPropmise = [];
|
||||
for (const node of toNodes) {
|
||||
if (node.instance) {
|
||||
if (typeof node.instance[methodName] === 'function') {
|
||||
instanceMethodPropmise.push(node.instance[methodName](fromCpt, ...args));
|
||||
}
|
||||
} else {
|
||||
node.addEventToQueue({
|
||||
method: methodName,
|
||||
fromCpt,
|
||||
args,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(instanceMethodPropmise);
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,13 @@ class Node extends EventEmitter {
|
||||
this.events = events || [];
|
||||
this.style = style || {};
|
||||
try {
|
||||
this.instance.config = data;
|
||||
if (
|
||||
this.instance &&
|
||||
!Object.isFrozen(this.instance) &&
|
||||
Object.getOwnPropertyDescriptor(this.instance, 'config')?.writable !== false
|
||||
) {
|
||||
this.instance.config = data;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e: any) {}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import App from './App';
|
||||
import IteratorContainer from './IteratorContainer';
|
||||
import type { default as TMagicNode } from './Node';
|
||||
import Node from './Node';
|
||||
import { GetNodeOptions } from './type';
|
||||
interface ConfigOptions {
|
||||
config: MPage | MPageFragment;
|
||||
app: App;
|
||||
@ -64,8 +65,15 @@ class Page extends Node {
|
||||
|
||||
if (config.type && this.app.pageFragmentContainerType.has(config.type) && config.pageFragmentId) {
|
||||
const pageFragment = this.app.dsl?.items?.find((page) => page.id === config.pageFragmentId);
|
||||
|
||||
if (pageFragment) {
|
||||
config.items = [pageFragment];
|
||||
this.app.pageFragments.set(
|
||||
config.id,
|
||||
new Page({
|
||||
config: pageFragment,
|
||||
app: this.app,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,13 +84,16 @@ class Page extends Node {
|
||||
|
||||
public getNode<T extends TMagicNode = TMagicNode>(
|
||||
id: Id,
|
||||
iteratorContainerId?: Id[],
|
||||
iteratorIndex?: number[],
|
||||
{ iteratorContainerId, iteratorIndex, pageFragmentContainerId }: GetNodeOptions = {},
|
||||
): T | undefined {
|
||||
if (this.nodes.has(id)) {
|
||||
return this.nodes.get(id) as T;
|
||||
}
|
||||
|
||||
if (pageFragmentContainerId) {
|
||||
return this.app.pageFragments.get(pageFragmentContainerId)?.getNode(id, { iteratorContainerId, iteratorIndex });
|
||||
}
|
||||
|
||||
if (Array.isArray(iteratorContainerId) && iteratorContainerId.length && Array.isArray(iteratorIndex)) {
|
||||
let iteratorContainer = this.nodes.get(iteratorContainerId[0]) as IteratorContainer;
|
||||
|
||||
|
@ -43,3 +43,9 @@ export type AfterEventHandler = (args: {
|
||||
source: TMagicNode | DataSource | undefined;
|
||||
args: any[];
|
||||
}) => void;
|
||||
|
||||
export interface GetNodeOptions {
|
||||
iteratorContainerId?: Id[];
|
||||
iteratorIndex?: number[];
|
||||
pageFragmentContainerId?: Id;
|
||||
}
|
||||
|
@ -168,15 +168,21 @@ describe('App', () => {
|
||||
1,
|
||||
);
|
||||
|
||||
expect(app.getNode('text', ['iterator-container_1'], [0])?.data.text).toBe('1');
|
||||
expect(app.getNode('text', ['iterator-container_1'], [1])?.data.text).toBe('2');
|
||||
expect(app.getNode('text_page_fragment', ['iterator-container_1'], [0])?.data.text).toBe('text_page_fragment');
|
||||
expect(app.getNode('text', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [0] })?.data.text).toBe(
|
||||
'1',
|
||||
);
|
||||
expect(app.getNode('text', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [1] })?.data.text).toBe(
|
||||
'2',
|
||||
);
|
||||
expect(
|
||||
app.getNode('text_page_fragment', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [0] })?.data
|
||||
.text,
|
||||
).toBe('text_page_fragment');
|
||||
|
||||
const ic1 = app.getNode(
|
||||
'iterator-container_11',
|
||||
['iterator-container_1'],
|
||||
[0],
|
||||
) as unknown as TMagicIteratorContainer;
|
||||
const ic1 = app.getNode('iterator-container_11', {
|
||||
iteratorContainerId: ['iterator-container_1'],
|
||||
iteratorIndex: [0],
|
||||
}) as unknown as TMagicIteratorContainer;
|
||||
|
||||
ic1?.setNodes(
|
||||
[
|
||||
@ -200,11 +206,10 @@ describe('App', () => {
|
||||
1,
|
||||
);
|
||||
|
||||
const ic2 = app.getNode(
|
||||
'iterator-container_11',
|
||||
['iterator-container_1'],
|
||||
[1],
|
||||
) as unknown as TMagicIteratorContainer;
|
||||
const ic2 = app.getNode('iterator-container_11', {
|
||||
iteratorContainerId: ['iterator-container_1'],
|
||||
iteratorIndex: [1],
|
||||
}) as unknown as TMagicIteratorContainer;
|
||||
|
||||
ic2?.setNodes(
|
||||
[
|
||||
@ -228,10 +233,30 @@ describe('App', () => {
|
||||
1,
|
||||
);
|
||||
|
||||
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [0, 0])?.data.text).toBe('111');
|
||||
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [0, 1])?.data.text).toBe('222');
|
||||
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [1, 0])?.data.text).toBe('11');
|
||||
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [1, 1])?.data.text).toBe('22');
|
||||
expect(
|
||||
app.getNode('text', {
|
||||
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
|
||||
iteratorIndex: [0, 0],
|
||||
})?.data.text,
|
||||
).toBe('111');
|
||||
expect(
|
||||
app.getNode('text', {
|
||||
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
|
||||
iteratorIndex: [0, 1],
|
||||
})?.data.text,
|
||||
).toBe('222');
|
||||
expect(
|
||||
app.getNode('text', {
|
||||
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
|
||||
iteratorIndex: [1, 0],
|
||||
})?.data.text,
|
||||
).toBe('11');
|
||||
expect(
|
||||
app.getNode('text', {
|
||||
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
|
||||
iteratorIndex: [1, 1],
|
||||
})?.data.text,
|
||||
).toBe('22');
|
||||
|
||||
ic.resetNodes();
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { union } from 'lodash-es';
|
||||
|
||||
import type { default as TMagicApp } from '@tmagic/core';
|
||||
import { getDepNodeIds, getNodes, isPage } from '@tmagic/core';
|
||||
import { getDepNodeIds, getNodes, isPage, isPageFragment } from '@tmagic/core';
|
||||
|
||||
import DataSourceManager from './DataSourceManager';
|
||||
import type { ChangeEvent, DataSourceManagerData } from './types';
|
||||
@ -52,18 +52,19 @@ export const createDataSourceManager = (app: TMagicApp, useMock?: boolean, initi
|
||||
|
||||
// ssr环境下,数据应该是提前准备好的(放到initialData中),不应该发生变化,无需监听
|
||||
// 有initialData不一定是在ssr环境下
|
||||
if (app.jsEngine !== 'nodejs') {
|
||||
dataSourceManager.on('change', (sourceId: string, changeEvent: ChangeEvent) => {
|
||||
const dep = dsl.dataSourceDeps?.[sourceId] || {};
|
||||
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
|
||||
if (app.jsEngine === 'nodejs') {
|
||||
return dataSourceManager;
|
||||
}
|
||||
|
||||
const nodeIds = union([...Object.keys(condDep), ...Object.keys(dep)]);
|
||||
dataSourceManager.on('change', (sourceId: string, changeEvent: ChangeEvent) => {
|
||||
const dep = dsl.dataSourceDeps?.[sourceId] || {};
|
||||
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
|
||||
|
||||
const pages = app.page?.data && app.platform !== 'editor' ? [app.page.data] : dsl.items;
|
||||
const nodeIds = union([...Object.keys(condDep), ...Object.keys(dep)]);
|
||||
|
||||
dataSourceManager.emit(
|
||||
'update-data',
|
||||
getNodes(nodeIds, pages).map((node) => {
|
||||
for (const page of dsl.items) {
|
||||
if (app.platform === 'editor' || (isPage(page) && page.id === app.page?.data.id) || isPageFragment(page)) {
|
||||
const newNodes = getNodes(nodeIds, [page]).map((node) => {
|
||||
if (app.platform !== 'editor') {
|
||||
node.condResult = dataSourceManager.compliedConds(node);
|
||||
}
|
||||
@ -73,19 +74,26 @@ export const createDataSourceManager = (app: TMagicApp, useMock?: boolean, initi
|
||||
if (typeof app.page?.setData === 'function') {
|
||||
if (isPage(newNode)) {
|
||||
app.page.setData(newNode);
|
||||
} else if (isPageFragment(newNode)) {
|
||||
for (const [, page] of app.pageFragments) {
|
||||
if (page.data.id === node.id) {
|
||||
page.setData(newNode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const n = app.page.getNode(node.id);
|
||||
n?.setData(newNode);
|
||||
app.getNode(node.id)?.setData(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
return newNode;
|
||||
}),
|
||||
sourceId,
|
||||
changeEvent,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (newNodes.length) {
|
||||
dataSourceManager.emit('update-data', newNodes, sourceId, changeEvent, page.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return dataSourceManager;
|
||||
};
|
||||
|
@ -328,12 +328,11 @@ export const getNodes = (ids: Id[], data: MNode[] = []): MNode[] => {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0, l = data.length; i < l; i++) {
|
||||
const item = data[i];
|
||||
for (const item of data) {
|
||||
const index = ids.findIndex((id: Id) => `${id}` === `${item.id}`);
|
||||
|
||||
if (index > -1) {
|
||||
ids.slice(index, 1);
|
||||
ids.splice(index, 1);
|
||||
nodes.push(item);
|
||||
}
|
||||
|
||||
@ -372,7 +371,7 @@ export const getDepNodeIds = (dataSourceDeps: DataSourceDeps = {}) =>
|
||||
* @param data 需要修改的数据
|
||||
* @param parentId 父节点 id
|
||||
*/
|
||||
export const replaceChildNode = (newNode: MNode, data?: MNode[], parentId?: Id) => {
|
||||
export const replaceChildNode = (newNode: MNode, data?: MNode[], parentId?: Id): void => {
|
||||
const path = getNodePath(newNode.id, data);
|
||||
const node = path.pop();
|
||||
let parent = path.pop();
|
||||
@ -390,6 +389,7 @@ export const replaceChildNode = (newNode: MNode, data?: MNode[], parentId?: Id)
|
||||
|
||||
export const DSL_NODE_KEY_COPY_PREFIX = '__tmagic__';
|
||||
export const IS_DSL_NODE_KEY = '__tmagic__dslNode';
|
||||
export const PAGE_FRAGMENT_CONTAINER_ID_KEY = 'tmagic-page-fragment-container-id';
|
||||
|
||||
export const compiledNode = (
|
||||
compile: (value: any) => any,
|
||||
|
@ -32,17 +32,22 @@ export interface UseAppOptions<T extends MNodeInstance = MNodeInstance> {
|
||||
config: T;
|
||||
iteratorContainerId?: Id[];
|
||||
iteratorIndex?: number[];
|
||||
pageFragmentContainerId?: Id;
|
||||
methods?: {
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
export const useNode = <T extends TMagicNode = TMagicNode>(
|
||||
props: Pick<UseAppOptions, 'config' | 'iteratorContainerId' | 'iteratorIndex'>,
|
||||
props: Pick<UseAppOptions, 'config' | 'iteratorContainerId' | 'iteratorIndex' | 'pageFragmentContainerId'>,
|
||||
app = useContext(AppContent),
|
||||
): T | undefined => {
|
||||
if (isDslNode(props.config) && props.config.id) {
|
||||
app?.getNode(props.config.id, props.iteratorContainerId, props.iteratorIndex);
|
||||
app?.getNode(props.config.id, {
|
||||
iteratorContainerId: props.iteratorContainerId,
|
||||
iteratorIndex: props.iteratorIndex,
|
||||
pageFragmentContainerId: props.pageFragmentContainerId,
|
||||
});
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
|
@ -30,15 +30,20 @@ interface UseAppOptions<T extends MNodeInstance = MNodeInstance> {
|
||||
config: T;
|
||||
iteratorContainerId?: Id[];
|
||||
iteratorIndex?: number[];
|
||||
pageFragmentContainerId?: Id;
|
||||
methods?: Methods;
|
||||
}
|
||||
|
||||
export const useNode = <T extends TMagicNode = TMagicNode>(
|
||||
props: Pick<UseAppOptions, 'config' | 'iteratorContainerId' | 'iteratorIndex'>,
|
||||
props: Pick<UseAppOptions, 'config' | 'iteratorContainerId' | 'iteratorIndex' | 'pageFragmentContainerId'>,
|
||||
app = inject<TMagicApp>('app'),
|
||||
): T | undefined => {
|
||||
if (isDslNode(props.config) && props.config.id) {
|
||||
return app?.getNode(props.config.id, props.iteratorContainerId, props.iteratorIndex);
|
||||
return app?.getNode(props.config.id, {
|
||||
iteratorContainerId: props.iteratorContainerId,
|
||||
iteratorIndex: props.iteratorIndex,
|
||||
pageFragmentContainerId: props.pageFragmentContainerId,
|
||||
});
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
|
@ -1,28 +1,47 @@
|
||||
import { inject, nextTick, onBeforeUnmount, reactive, ref } from 'vue-demi';
|
||||
|
||||
import type TMagicApp from '@tmagic/core';
|
||||
import type { ChangeEvent, MNode } from '@tmagic/core';
|
||||
import { isPage, replaceChildNode } from '@tmagic/core';
|
||||
import type { ChangeEvent, Id, MNode } from '@tmagic/core';
|
||||
import { isPage, isPageFragment, replaceChildNode } from '@tmagic/core';
|
||||
|
||||
export const useDsl = (app = inject<TMagicApp>('app')) => {
|
||||
export const useDsl = (app = inject<TMagicApp>('app'), pageFragmentConstainerId: Id) => {
|
||||
if (!app) {
|
||||
throw new Error('useDsl must be used after MagicApp is created');
|
||||
}
|
||||
|
||||
const pageConfig = ref<MNode | undefined>(app.page?.data);
|
||||
const pageFragment = pageFragmentConstainerId ? app.pageFragments.get(pageFragmentConstainerId) : null;
|
||||
|
||||
app.on('page-change', () => {
|
||||
pageConfig.value = app.page?.data;
|
||||
});
|
||||
const pageConfig = ref<MNode | undefined>(pageFragmentConstainerId ? pageFragment?.data : app.page?.data);
|
||||
|
||||
const updateDataHandler = (nodes: MNode[], sourceId: string, changeEvent: ChangeEvent) => {
|
||||
nodes.forEach((node) => {
|
||||
if (isPage(node)) {
|
||||
if (pageFragmentConstainerId) {
|
||||
app.on('dsl-change', () => {
|
||||
pageConfig.value = pageFragment?.data;
|
||||
});
|
||||
} else {
|
||||
app.on('page-change', () => {
|
||||
pageConfig.value = app.page?.data;
|
||||
});
|
||||
}
|
||||
|
||||
const updateDataHandler = (nodes: MNode[], sourceId: string, changeEvent: ChangeEvent, pageId: Id) => {
|
||||
if (
|
||||
!nodes.length ||
|
||||
(pageFragmentConstainerId && pageFragment?.data.id !== pageId) ||
|
||||
(!pageFragmentConstainerId && app.page?.data.id !== pageId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
if (
|
||||
(isPage(node) && !pageFragmentConstainerId && node.id === pageId) ||
|
||||
(isPageFragment(node) && pageFragmentConstainerId)
|
||||
) {
|
||||
pageConfig.value = node;
|
||||
} else {
|
||||
replaceChildNode(reactive(node), [pageConfig.value as MNode]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
app.emit('replaced-node', { nodes, sourceId, ...changeEvent });
|
||||
|
@ -33,6 +33,7 @@ export interface ComponentProps<T extends Omit<MComponent, 'id'> = MComponent> {
|
||||
iteratorIndex?: number[];
|
||||
iteratorContainerId?: Id[];
|
||||
containerIndex?: number;
|
||||
pageFragmentContainerId?: Id;
|
||||
model?: any;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user