logic for multi documents one sim

This commit is contained in:
kangwei 2020-08-07 18:48:09 +08:00
parent 1f8eccb75c
commit 45ffc98277
14 changed files with 286 additions and 263 deletions

View File

@ -1,9 +1,8 @@
import { Component } from 'react';
import { observer } from '@ali/lowcode-editor-core';
import { BuiltinSimulatorHost, BuiltinSimulatorProps } from './host';
import { DocumentModel } from '../document';
import { SimulatorContext } from './context';
import { BemTools } from './bem-tools';
import { Project } from '../project';
import './host.less';
/*
@ -11,12 +10,11 @@ import './host.less';
Canvas(DeviceShell) CanvasViewport
CanvasViewport Canvas
Content(Shell) CanvasViewport margin
ContentFrame Content
Auxiliary Content 0,0 Canvas, Content
BemTools Content 0,0 Canvas, Content
*/
type SimulatorHostProps = BuiltinSimulatorProps & {
documentContext: DocumentModel;
project: Project;
onMount?: (host: BuiltinSimulatorHost) => void;
};
@ -24,8 +22,8 @@ export class BuiltinSimulatorHostView extends Component<SimulatorHostProps> {
readonly host: BuiltinSimulatorHost;
constructor(props: any) {
super(props);
const { documentContext } = this.props;
this.host = (documentContext.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(documentContext);
const { project } = this.props;
this.host = (project.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(project);
this.host.setProps(this.props);
}
shouldComponentUpdate(nextProps: BuiltinSimulatorProps) {

View File

@ -4,7 +4,17 @@ import Viewport from './viewport';
import { createSimulator } from './create-simulator';
import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document';
import ResourceConsumer from './resource-consumer';
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, isElement, isFormEvent } from '@ali/lowcode-utils';
import {
AssetLevel,
Asset,
AssetList,
assetBundle,
assetItem,
AssetType,
isElement,
isFormEvent,
hasOwnProperty,
} from '@ali/lowcode-utils';
import {
DragObjectType,
isShaken,
@ -26,6 +36,7 @@ import { ComponentMetadata, ComponentSchema } from '@ali/lowcode-types';
import { BuiltinSimulatorRenderer } from './renderer';
import clipboard from '../designer/clipboard';
import { LiveEditing } from './live-editing/live-editing';
import { Project } from '../project';
export interface LibraryItem {
package: string;
@ -74,9 +85,9 @@ const defaultEnvironment = [
export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProps> {
readonly isSimulator = true;
constructor(readonly document: DocumentModel) {}
constructor(readonly project: Project) {}
readonly designer = this.document.designer;
readonly designer = this.project.designer;
@computed get device(): string {
return this.get('device') || 'default';
@ -326,7 +337,12 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
x.initEvent('click', true);
this._iframe?.dispatchEvent(x);
const target = e.target as HTMLElement;
if (isFormEvent(e) || target?.closest('.next-input-group,.next-checkbox-group,.next-date-picker,.next-input,.next-month-picker,.next-number-picker,.next-radio-group,.next-range,.next-range-picker,.next-rating,.next-select,.next-switch,.next-time-picker,.next-upload,.next-year-picker,.next-breadcrumb-item,.next-calendar-header,.next-calendar-table')) {
if (
isFormEvent(e) ||
target?.closest(
'.next-input-group,.next-checkbox-group,.next-date-picker,.next-input,.next-month-picker,.next-number-picker,.next-radio-group,.next-range,.next-range-picker,.next-rating,.next-select,.next-switch,.next-time-picker,.next-upload,.next-year-picker,.next-breadcrumb-item,.next-calendar-header,.next-calendar-table',
)
) {
e.preventDefault();
e.stopPropagation();
}
@ -392,7 +408,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
}
const node = nodeInst.node || this.document.rootNode;
const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find(item => item.contains(targetElement)) as HTMLElement;
const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find((item) =>
item.contains(targetElement),
) as HTMLElement;
if (!rootElement) {
return;
}
@ -405,7 +423,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
},
true,
);
}
/**
@ -490,12 +507,17 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return this.renderer?.createComponent(schema) || null;
}
@obx.val private instancesMap = new Map<string, ComponentInstance[]>();
setInstance(id: string, instances: ComponentInstance[] | null) {
@obx private instancesMap: {
[docId: string]: Map<string, ComponentInstance[]>;
} = {};
setInstance(docId: string, id: string, instances: ComponentInstance[] | null) {
if (hasOwnProperty(this.instancesMap, docId)) {
this.instancesMap[docId] = new Map();
}
if (instances == null) {
this.instancesMap.delete(id);
this.instancesMap[docId].delete(id);
} else {
this.instancesMap.set(id, instances.slice());
this.instancesMap[docId].set(id, instances.slice());
}
}
@ -503,7 +525,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
* @see ISimulator
*/
getComponentInstances(node: Node): ComponentInstance[] | null {
return this.instancesMap.get(node.id) || null;
const docId = node.document.id;
return this.instancesMap[docId]?.get(node.id) || null;
}
/**
@ -792,10 +816,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
this.sensing = true;
this.scroller.scrolling(e);
const dropContainer = this.getDropContainer(e);
if (!dropContainer ||
// too dirty
(typeof dropContainer.container?.componentMeta?.prototype?.options?.canDropIn === 'function' &&
!dropContainer.container?.componentMeta?.prototype?.options?.canDropIn(e.dragObject.nodes[0]))) {
if (!dropContainer) {
return null;
}
@ -826,7 +847,12 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
event: e,
};
if (e.dragObject && e.dragObject.nodes && e.dragObject.nodes.length && e.dragObject.nodes[0].getPrototype()?.isModal()) {
if (
e.dragObject &&
e.dragObject.nodes &&
e.dragObject.nodes.length &&
e.dragObject.nodes[0].getPrototype()?.isModal()
) {
return this.designer.createLocation({
target: this.document.rootNode,
detail,

View File

@ -3,9 +3,7 @@ import { ComponentSchema } from '@ali/lowcode-types';
export interface BuiltinSimulatorRenderer {
readonly isSimulatorRenderer: true;
createComponent(schema: ComponentSchema): Component | null;
getComponent(componentName: string): Component;
getComponentInstances(id: string): ComponentInstance[] | null;
getClosestNodeInstance(from: ComponentInstance, nodeId?: string): NodeInstance<ComponentInstance> | null;
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null;
getClientRects(element: Element | Text): DOMRect[];

View File

@ -348,7 +348,7 @@ export class Designer {
@obx.ref private _simulatorProps?: object | ((document: DocumentModel) => object);
@computed get simulatorProps(): object | ((document: DocumentModel) => object) {
@computed get simulatorProps(): object | ((project: Project) => object) {
return this._simulatorProps || {};
}

View File

@ -41,7 +41,6 @@ export class DocumentModel {
private nodesMap = new Map<string, Node>();
@obx.val private nodes = new Set<Node>();
private seqId = 0;
private _simulator?: ISimulatorHost;
private emitter: EventEmitter;
private rootNodeVisitorMap: { [visitorName: string]: any } = {};
private modalNodesManager: ModalNodesManager;
@ -50,7 +49,7 @@ export class DocumentModel {
*
*/
get simulator(): ISimulatorHost | null {
return this._simulator || null;
return this.project.simulator;
}
get fileName(): string {
@ -320,27 +319,6 @@ export class DocumentModel {
return !this.history.isSavePoint();
}
/**
*
*/
@computed get simulatorProps(): object {
let simulatorProps = this.designer.simulatorProps;
if (typeof simulatorProps === 'function') {
simulatorProps = simulatorProps(this);
}
return {
...simulatorProps,
documentContext: this,
onMount: this.mountSimulator.bind(this),
};
}
private mountSimulator(simulator: ISimulatorHost) {
// TODO: 多设备 simulator 支持
this._simulator = simulator;
// TODO: emit simulator mounted
}
// FIXME: does needed?
getComponent(componentName: string): any {
return this.simulator!.getComponent(componentName);
@ -498,17 +476,6 @@ export class DocumentModel {
return this.rootNode;
}
onRendererReady(fn: (args: any) => void): () => void {
this.emitter.on('lowcode_engine_renderer_ready', fn);
return () => {
this.emitter.removeListener('lowcode_engine_renderer_ready', fn);
};
}
setRendererReady(renderer: any) {
this.emitter.emit('lowcode_engine_renderer_ready', renderer);
}
acceptRootNodeVisitor(
visitorName: string = 'default',
visitorFn: (node: RootNode) => any ) {

View File

@ -1,36 +0,0 @@
import { Component } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-editor-core';
import { DocumentModel } from './document-model';
import { BuiltinSimulatorHostView } from '../builtin-simulator';
@observer
export class DocumentView extends Component<{ document: DocumentModel }> {
shouldComponentUpdate() {
return false;
}
render() {
const { document } = this.props;
const simulatorProps = document.simulatorProps;
const Simulator = document.designer.simulatorComponent || BuiltinSimulatorHostView;
return (
<div
className={classNames('lc-document', {
'lc-document-hidden': document.suspensed,
})}
>
{/* 这一层将来做缩放用途 */}
<div className="lc-simulator-shell">
<Simulator {...simulatorProps} />
</div>
<DocumentInfoView document={document} />
</div>
);
}
}
class DocumentInfoView extends Component<{ document: DocumentModel }> {
render() {
return null;
}
}

View File

@ -533,6 +533,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
if (stage !== TransformStage.Clone) {
baseSchema.id = this.id;
}
if (stage === TransformStage.Render) {
baseSchema.docId = this.document.id;
}
if (this.isLeaf()) {
baseSchema.children = this.props.get('children')?.export(stage);

View File

@ -1,23 +1,22 @@
import { Component } from 'react';
import { observer } from '@ali/lowcode-editor-core';
import { Designer } from '../designer';
import { DocumentView } from '../document';
import { intl } from '../locale';
import { BuiltinSimulatorHostView } from '../builtin-simulator';
import './project.less';
@observer
export class ProjectView extends Component<{ designer: Designer }> {
render() {
const { designer } = this.props;
// TODO: support splitview
const opens = designer.project.documents.filter((doc) => doc.opened);
const project = designer.project;
const simulatorProps = project.simulatorProps;
const Simulator = designer.simulatorComponent || BuiltinSimulatorHostView;
return (
<div className="lc-project">
{opens.length > 0 ? (
opens.map((doc) => <DocumentView key={doc.id} document={doc} />)
) : (
<div className="lc-project-empty">{intl('No opened document')}</div>
)}
<div className="lc-simulator-shell">
<Simulator {...simulatorProps} />
</div>
</div>
);
}

View File

@ -3,6 +3,7 @@ import { obx, computed } from '@ali/lowcode-editor-core';
import { Designer } from '../designer';
import { DocumentModel, isDocumentModel } from '../document';
import { ProjectSchema, RootSchema } from '@ali/lowcode-types';
import { ISimulatorHost } from '../simulator';
export class Project {
private emitter = new EventEmitter();
@ -12,6 +13,15 @@ export class Project {
@obx.ref canvasDisplayMode: 'exclusive' | 'overview' = 'exclusive';
private _simulator?: ISimulatorHost;
/**
*
*/
get simulator(): ISimulatorHost | null {
return this._simulator || null;
}
// TODO: 考虑项目级别 History
constructor(readonly designer: Designer, schema?: ProjectSchema) {
@ -102,7 +112,7 @@ export class Project {
| string,
): any {}
open(doc?: string | DocumentModel | RootSchema): void {
open(doc?: string | DocumentModel | RootSchema): DocumentModel | null {
if (!doc) {
const got = this.documents.find((item) => item.isBlank());
if (got) {
@ -125,7 +135,7 @@ export class Project {
return doc.open();
}
return;
return null;
}
if (isDocumentModel(doc)) {
@ -154,6 +164,37 @@ export class Project {
});
}
/**
*
*/
@computed get simulatorProps(): object {
let simulatorProps = this.designer.simulatorProps;
if (typeof simulatorProps === 'function') {
simulatorProps = simulatorProps(this);
}
return {
...simulatorProps,
project: this,
onMount: this.mountSimulator.bind(this),
};
}
private mountSimulator(simulator: ISimulatorHost) {
// TODO: 多设备 simulator 支持
this._simulator = simulator;
}
setRendererReady(renderer: any) {
this.emitter.emit('lowcode_engine_renderer_ready', renderer);
}
onRendererReady(fn: (args: any) => void): () => void {
this.emitter.on('lowcode_engine_renderer_ready', fn);
return () => {
this.emitter.removeListener('lowcode_engine_renderer_ready', fn);
};
}
onCurrentDocumentChange(fn: (doc: DocumentModel) => void): () => void {
this.emitter.on('current-document.change', fn);
return () => {

View File

@ -142,7 +142,7 @@ export interface ISimulatorHost<P = object> extends ISensor {
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null;
findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | null;
/**
*
*/
@ -154,6 +154,7 @@ export function isSimulatorHost(obj: any): obj is ISimulatorHost {
}
export interface NodeInstance<T = ComponentInstance> {
docId: string;
nodeId: string;
instance: T;
node?: Node | null;

View File

@ -1,4 +1,4 @@
class Monitor {
export class Monitor {
fn = (params: any) => {
const { AES } = window as any;
if (typeof AES.log === 'function') {

View File

@ -25,10 +25,8 @@ export const designer = new Designer({ editor: editor });
editor.set(Designer, designer);
editor.set('designer', designer);
designer.project.onCurrentDocumentChange((doc) => {
doc.onRendererReady(() => {
bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
});
designer.project.onRendererReady(() => {
bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
});
interface Variable {

View File

@ -1,9 +1,7 @@
import LowCodeRenderer from '@ali/lowcode-react-renderer';
import { isObject } from 'lodash';
import { ReactInstance, Fragment, Component, createElement } from 'react';
import { observer } from '@recore/obx-react';
import { SimulatorRenderer } from './renderer';
import { host } from './host';
import { SimulatorRendererContainer, DocumentInstance } from './renderer';
import './renderer.less';
// patch cloneElement avoid lost keyProps
@ -36,12 +34,20 @@ const originCloneElement = window.React.cloneElement;
return originCloneElement(child, props, ...rest);
};
export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> {
export default class SimulatorRendererView extends Component<{ rendererContainer: SimulatorRendererContainer }> {
render() {
const { renderer } = this.props;
const { rendererContainer } = this.props;
return (
<Layout renderer={renderer}>
<Renderer renderer={renderer} />
<Layout rendererContainer={rendererContainer}>
<Router onChange={(currentPath: string) => {
rendererContainer.redirect(currentPath);
}}>
{rendererContainer.getDocumentInstances().map((instance) => {
return <Route path={instance.document.get('fileName')}>
<Renderer documentInstance={instance} />
</Route>
})}
</Router>
</Layout>
);
}
@ -68,13 +74,13 @@ function getDeviceView(view: any, device: string, mode: string) {
}
@observer
class Layout extends Component<{ renderer: SimulatorRenderer }> {
class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }> {
shouldComponentUpdate() {
return false;
}
render() {
const { renderer, children } = this.props;
const layout = renderer.layout;
const { rendererContainer, children } = this.props;
const layout = rendererContainer.layout;
if (layout) {
const { Component, props } = layout;
@ -86,26 +92,28 @@ class Layout extends Component<{ renderer: SimulatorRenderer }> {
}
@observer
class Renderer extends Component<{ renderer: SimulatorRenderer }> {
class Renderer extends Component<{ documentInstance: DocumentInstance }> {
shouldComponentUpdate() {
return false;
}
render() {
const { renderer } = this.props;
const { device, designMode } = renderer;
const { documentInstance } = this.props;
const { container } = documentInstance;
const { designMode, device } = container;
return (
<LowCodeRenderer
schema={renderer.schema}
components={renderer.components}
appHelper={renderer.context}
schema={documentInstance.schema}
components={container.components}
appHelper={container.context}
// context={renderer.context}
designMode={designMode}
suspended={renderer.suspended}
self={renderer.scope}
suspended={documentInstance.suspended}
self={documentInstance.scope}
customCreateElement={(Component: any, props: any, children: any) => {
const { __id, __desingMode, ...viewProps } = props;
viewProps.componentId = __id;
const leaf = host.document.getNode(__id);
const leaf = documentInstance.getNode(__id);
viewProps._leaf = leaf;
viewProps._componentName = leaf?.componentName;
let _children = leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : children;
@ -158,7 +166,7 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
);
}}
onCompGetRef={(schema: any, ref: ReactInstance | null) => {
renderer.mountInstance(schema.id, ref);
documentInstance.mountInstance(schema.id, ref);
}}
//onCompGetCtx={(schema: any, ctx: object) => {
// renderer.mountContext(schema.id, ctx);

View File

@ -10,120 +10,47 @@ import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes';
import { isESModule, isElement, cursor, setNativeSelection } from '@ali/lowcode-utils';
import { RootSchema, NpmInfo, ComponentSchema, TransformStage } from '@ali/lowcode-types';
// just use types
import { BuiltinSimulatorRenderer, NodeInstance, Component } from '@ali/lowcode-designer';
import { BuiltinSimulatorRenderer, NodeInstance, Component, DocumentModel, Node } from '@ali/lowcode-designer';
import Slot from './builtin-components/slot';
import Leaf from './builtin-components/leaf';
export class SimulatorRenderer implements BuiltinSimulatorRenderer {
readonly isSimulatorRenderer = true;
private dispose?: () => void;
constructor() {
if (!host) {
return;
}
this.dispose = host.connect(this, () => {
// sync layout config
// sync schema
this._schema = host.document.export(1);
// todo: split with others, not all should recompute
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {
this._libraryMap = host.libraryMap || {};
this._componentsMap = host.designer.componentsMap;
this.buildComponents();
}
// sync designMode
this._designMode = host.designMode;
// sync suspended
// sync scope
// sync device
this._device = host.device;
});
host.componentsConsumer.consume(async (componentsAsset) => {
if (componentsAsset) {
await this.load(componentsAsset);
this.buildComponents();
}
});
host.injectionConsumer.consume((data) => {
// sync utils, i18n, contants,... config
this._appContext = {
utils: {},
constants: {
name: 'demo',
},
};
});
}
@computed get layout(): any {
// TODO: parse layout Component
return null;
}
export class DocumentInstance {
private instancesMap = new Map<string, ReactInstance[]>();
@obx.ref private _schema?: RootSchema;
@computed get schema(): any {
return this._schema;
}
private _libraryMap: { [key: string]: string } = {};
private buildComponents() {
this._components = buildComponents(this._libraryMap, this._componentsMap);
}
@obx.ref private _components: any = {};
@computed get components(): object {
// 根据 device 选择不同组件,进行响应式
// 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl
return this._components;
}
// context from: utils、constants、history、location、match
@obx.ref private _appContext = {};
@computed get context(): any {
return this._appContext;
}
@obx.ref private _designMode: string = 'design';
@computed get designMode(): any {
return this._designMode;
}
@obx.ref private _device: string = 'default';
@computed get device() {
return this._device;
}
@obx.ref private _componentsMap = {};
@computed get componentsMap(): any {
return this._componentsMap;
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
this.dispose = host.connect(this, () => {
// sync layout config
// sync schema
this._schema = host.document.export(1);
})
}
@computed get suspended(): any {
return false;
}
@computed get scope(): any {
return null;
}
/**
*
*/
load(asset: Asset): Promise<any> {
return loader.load(asset);
}
private instancesMap = new Map<string, ReactInstance[]>();
private unmountIntance(id: string, instance: ReactInstance) {
const instances = this.instancesMap.get(id);
if (instances) {
const i = instances.indexOf(instance);
if (i > -1) {
instances.splice(i, 1);
host.setInstance(id, instances);
host.setInstance(this.document.id, id, instances);
}
}
}
mountInstance(id: string, instance: ReactInstance | null) {
const docId = this.document.id;
const instancesMap = this.instancesMap;
if (instance == null) {
let instances = this.instancesMap.get(id);
@ -131,10 +58,10 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
instances = instances.filter(checkInstanceMounted);
if (instances.length > 0) {
instancesMap.set(id, instances);
host.setInstance(id, instances);
host.setInstance(this.document.id, id, instances);
} else {
instancesMap.delete(id);
host.setInstance(id, null);
host.setInstance(this.document.id, id, null);
}
}
return;
@ -163,6 +90,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
}
(instance as any)[SYMBOL_VNID] = id;
(instance as any)[SYMBOL_VDID] = docId;
let instances = this.instancesMap.get(id);
if (instances) {
const l = instances.length;
@ -179,37 +107,11 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
instances = [instance];
}
instancesMap.set(id, instances);
host.setInstance(id, instances);
host.setInstance(this.document.id, id, instances);
}
private ctxMap = new Map<string, object>();
mountContext(id: string, ctx: object) {
this.ctxMap.set(id, ctx);
}
getComponent(componentName: string) {
const paths = componentName.split('.');
const subs: string[] = [];
while (true) {
const component = this._components[componentName];
if (component) {
return getSubComponent(component, subs);
}
const sub = paths.pop();
if (!sub) {
return null;
}
subs.unshift(sub);
componentName = paths.join('.');
}
return null;
}
getComponentInstances(id: string): ReactInstance[] | null {
return this.instancesMap.get(id) || null;
mountContext(docId: string, id: string, ctx: object) {
// this.ctxMap.set(id, ctx);
}
createComponent(schema: ComponentSchema): Component | null {
@ -245,18 +147,18 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
if (schema.children && schema.children.length > 0) {
children = schema.children.map((item: any) => getElement(componentsMap, item, propsMap));
}
const _leaf = host.document.designer.currentDocument?.createNode(schema);
const node = host.document.createNode(schema);
const _leaf = this.document.designer.currentDocument?.createNode(schema);
const node = this.document.createNode(schema);
let props = processPropsSchema(schema.props, propsMap);
props = host.document.designer.transformProps(props, node, TransformStage.Init);
props = host.document.designer.transformProps(props, node, TransformStage.Render);
props = this.document.designer.transformProps(props, node, TransformStage.Init);
props = this.document.designer.transformProps(props, node, TransformStage.Render);
return createElement(Com, {...props, _leaf}, children);
}
const renderer = this;
const container = this.container;
class Com extends React.Component {
render() {
const componentsMap = renderer.componentsMap;
const componentsMap = container.componentsMap;
let children = null;
if (_schema.children && Array.isArray(_schema.children)) {
children = _schema.children?.map((item:any) => getElement(componentsMap, item, this.props));
@ -268,6 +170,119 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
return Com;
}
getNode(id: string): Node | null {
return this.document.getNode(id);
}
}
export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
readonly isSimulatorRenderer = true;
private dispose?: () => void;
constructor() {
if (!host) {
return;
}
this.dispose = host.connect(this, () => {
// sync layout config
// todo: split with others, not all should recompute
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {
this._libraryMap = host.libraryMap || {};
this._componentsMap = host.designer.componentsMap;
this.buildComponents();
}
// sync designMode
this._designMode = host.designMode;
// sync device
this._device = host.device;
});
host.componentsConsumer.consume(async (componentsAsset) => {
if (componentsAsset) {
await this.load(componentsAsset);
this.buildComponents();
}
});
host.injectionConsumer.consume((data) => {
// sync utils, i18n, contants,... config
this._appContext = {
utils: {},
constants: {
name: 'demo',
},
};
});
}
@computed get layout(): any {
// TODO: parse layout Component
return null;
}
private _libraryMap: { [key: string]: string } = {};
private buildComponents() {
this._components = buildComponents(this._libraryMap, this._componentsMap);
}
@obx.ref private _components: any = {};
@computed get components(): object {
// 根据 device 选择不同组件,进行响应式
// 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl
return this._components;
}
// context from: utils、constants、history、location、match
@obx.ref private _appContext = {};
@computed get context(): any {
return this._appContext;
}
@obx.ref private _designMode: string = 'design';
@computed get designMode(): any {
return this._designMode;
}
@obx.ref private _device: string = 'default';
@computed get device() {
return this._device;
}
@obx.ref private _componentsMap = {};
@computed get componentsMap(): any {
return this._componentsMap;
}
/**
*
*/
load(asset: Asset): Promise<any> {
return loader.load(asset);
}
private documentInstanceMap = new Map<string, DocumentInstance>();
redirect(path: string) {
}
getComponent(componentName: string) {
const paths = componentName.split('.');
const subs: string[] = [];
while (true) {
const component = this._components[componentName];
if (component) {
return getSubComponent(component, subs);
}
const sub = paths.pop();
if (!sub) {
return null;
}
subs.unshift(sub);
componentName = paths.join('.');
}
return null;
}
getClosestNodeInstance(from: ReactInstance, nodeId?: string): NodeInstance<ReactInstance> | null {
return getClosestNodeInstance(from, nodeId);
}
@ -310,8 +325,8 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
document.documentElement.classList.add('engine-page');
document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends
reactRender(createElement(SimulatorRendererView, { renderer: this }), container);
host.document.setRendererReady(this);
reactRender(createElement(SimulatorRendererView, { rendererContainer: this }), container);
host.project.setRendererReady(this);
}
}
@ -417,6 +432,7 @@ function cacheReactKey(el: Element): Element {
}
const SYMBOL_VNID = Symbol('_LCNodeId');
const SYMBOL_VDID = Symbol('_LCDocId');
function getClosestNodeInstance(from: ReactInstance, specId?: string): NodeInstance<ReactInstance> | null {
let el: any = from;
@ -430,9 +446,11 @@ function getClosestNodeInstance(from: ReactInstance, specId?: string): NodeInsta
while (el) {
if (SYMBOL_VNID in el) {
const nodeId = el[SYMBOL_VNID];
const docId = el[SYMBOL_VDID];
if (!specId || specId === nodeId) {
return {
nodeId: nodeId,
docId,
nodeId,
instance: el,
};
}
@ -450,9 +468,11 @@ function getNodeInstance(fiberNode: any, specId?: string): NodeInstance<ReactIns
const instance = fiberNode.stateNode;
if (instance && SYMBOL_VNID in instance) {
const nodeId = instance[SYMBOL_VNID];
const docId = instance[SYMBOL_VDID];
if (!specId || specId === nodeId) {
return {
nodeId: nodeId,
docId,
nodeId,
instance: instance,
};
}
@ -467,4 +487,4 @@ function checkInstanceMounted(instance: any): boolean {
return true;
}
export default new SimulatorRenderer();
export default new SimulatorRendererContainer();