mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-12 08:58:15 +00:00
logic for multi documents one sim
This commit is contained in:
parent
1f8eccb75c
commit
45ffc98277
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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[];
|
||||
|
||||
@ -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 || {};
|
||||
}
|
||||
|
||||
|
||||
@ -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 ) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
class Monitor {
|
||||
export class Monitor {
|
||||
fn = (params: any) => {
|
||||
const { AES } = window as any;
|
||||
if (typeof AES.log === 'function') {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user