mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-20 04:18:05 +00:00
feat: 修改rax-render
This commit is contained in:
parent
ac55847b3f
commit
14ad77cd69
@ -2,7 +2,7 @@ import { Fragment, Component, createElement } from 'rax';
|
|||||||
// import { observer } from './obx-rax/observer';
|
// import { observer } from './obx-rax/observer';
|
||||||
import RaxEngine from '@ali/lowcode-rax-renderer/lib/index';
|
import RaxEngine from '@ali/lowcode-rax-renderer/lib/index';
|
||||||
// import RaxEngine from '../../rax-render/lib/index';
|
// import RaxEngine from '../../rax-render/lib/index';
|
||||||
import { SimulatorRenderer } from './renderer';
|
import { SimulatorRendererContainer, DocumentInstance } from './renderer';
|
||||||
import { host } from './host';
|
import { host } from './host';
|
||||||
|
|
||||||
import './renderer.less';
|
import './renderer.less';
|
||||||
@ -37,17 +37,31 @@ const originCloneElement = (window as any).Rax.cloneElement;
|
|||||||
return originCloneElement(child, props, ...rest);
|
return originCloneElement(child, props, ...rest);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> {
|
export default class SimulatorRendererView extends Component<{ rendererContainer: SimulatorRendererContainer }> {
|
||||||
render() {
|
render() {
|
||||||
const { renderer } = this.props;
|
const { rendererContainer } = this.props;
|
||||||
return (
|
return (
|
||||||
<Layout renderer={renderer}>
|
<Layout rendererContainer={rendererContainer}>
|
||||||
<Renderer renderer={renderer} />
|
<Routes rendererContainer={rendererContainer} />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Routes extends Component<{ rendererContainer: SimulatorRendererContainer }> {
|
||||||
|
render() {
|
||||||
|
const { rendererContainer } = this.props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{rendererContainer.documentInstances.map((instance) => {
|
||||||
|
return (
|
||||||
|
<Renderer rendererContainer={rendererContainer} documentInstance={instance} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
function ucfirst(s: string) {
|
function ucfirst(s: string) {
|
||||||
return s.charAt(0).toUpperCase() + s.substring(1);
|
return s.charAt(0).toUpperCase() + s.substring(1);
|
||||||
}
|
}
|
||||||
@ -68,14 +82,13 @@ function getDeviceView(view: any, device: string, mode: string) {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @observer
|
class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }> {
|
||||||
class Layout extends Component<{ renderer: SimulatorRenderer }> {
|
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { renderer, children } = this.props;
|
const { rendererContainer, children } = this.props;
|
||||||
const layout = renderer.layout;
|
const layout = rendererContainer.layout;
|
||||||
|
|
||||||
if (layout) {
|
if (layout) {
|
||||||
const { Component, props } = layout;
|
const { Component, props } = layout;
|
||||||
@ -86,11 +99,10 @@ class Layout extends Component<{ renderer: SimulatorRenderer }> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @observer
|
class Renderer extends Component<{ rendererContainer: SimulatorRendererContainer, documentInstance: DocumentInstance }> {
|
||||||
class Renderer extends Component<{ renderer: SimulatorRenderer }> {
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.props.renderer.onReRender(() => {
|
this.props.rendererContainer.onReRender(() => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -98,24 +110,28 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { renderer } = this.props;
|
const { documentInstance } = this.props;
|
||||||
const { device, designMode } = renderer;
|
const { container } = documentInstance;
|
||||||
|
const { designMode, device } = container;
|
||||||
|
const { rendererContainer: renderer } = this.props;
|
||||||
|
// const { device, designMode } = renderer;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RaxEngine
|
<RaxEngine
|
||||||
schema={renderer.schema}
|
schema={documentInstance.schema}
|
||||||
components={renderer.components}
|
components={renderer.components}
|
||||||
context={renderer.context}
|
context={renderer.context}
|
||||||
designMode={renderer.designMode}
|
designMode={renderer.designMode}
|
||||||
suspended={renderer.suspended}
|
suspended={documentInstance.suspended}
|
||||||
self={renderer.scope}
|
self={documentInstance.scope}
|
||||||
onCompGetRef={(schema: any, ref: any) => {
|
onCompGetRef={(schema: any, ref: any) => {
|
||||||
renderer.mountInstance(schema.id, ref);
|
documentInstance.mountInstance(schema.id, ref);
|
||||||
}}
|
}}
|
||||||
customCreateElement={(Component: any, props: any, children: any) => {
|
customCreateElement={(Component: any, props: any, children: any) => {
|
||||||
const { __id, __desingMode, ...viewProps } = props;
|
const { __id, __desingMode, ...viewProps } = props;
|
||||||
viewProps.componentId = __id;
|
viewProps.componentId = __id;
|
||||||
const leaf = host.document.getNode(__id);
|
const leaf: any = null;
|
||||||
|
// const leaf = host.document.getNode(__id);
|
||||||
viewProps._leaf = leaf;
|
viewProps._leaf = leaf;
|
||||||
viewProps._componentName = leaf?.componentName;
|
viewProps._componentName = leaf?.componentName;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { BuiltinSimulatorRenderer, NodeInstance, Component } from '@ali/lowcode-designer';
|
import { BuiltinSimulatorRenderer, NodeInstance, Component, DocumentModel } from '@ali/lowcode-designer';
|
||||||
import { shared, render as raxRender, createElement } from 'rax';
|
// @ts-ignore
|
||||||
|
import { shared, render as raxRender, createElement, ComponentType } from 'rax';
|
||||||
import DriverUniversal from 'driver-universal';
|
import DriverUniversal from 'driver-universal';
|
||||||
import { computed, obx } from '@recore/obx';
|
import { computed, obx } from '@recore/obx';
|
||||||
import { RootSchema, NpmInfo, ComponentSchema } from '@ali/lowcode-types';
|
import { RootSchema, NpmInfo, ComponentSchema } from '@ali/lowcode-types';
|
||||||
@ -23,6 +24,7 @@ export interface LibraryMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SYMBOL_VNID = Symbol('_LCNodeId');
|
const SYMBOL_VNID = Symbol('_LCNodeId');
|
||||||
|
const SYMBOL_VDID = Symbol('_LCDocId');
|
||||||
|
|
||||||
function accessLibrary(library: string | object) {
|
function accessLibrary(library: string | object) {
|
||||||
if (typeof library !== 'string') {
|
if (typeof library !== 'string') {
|
||||||
@ -32,45 +34,116 @@ function accessLibrary(library: string | object) {
|
|||||||
return (window as any)[library];
|
return (window as any)[library];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
// Slot/Leaf and Fragment|FunctionComponent polyfill(ref)
|
||||||
readonly isSimulatorRenderer = true;
|
|
||||||
private dispose?: () => void;
|
|
||||||
|
|
||||||
|
const builtinComponents = {
|
||||||
|
Slot,
|
||||||
|
Leaf,
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildComponents(libraryMap: LibraryMap,
|
||||||
|
componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> | ComponentSchema },
|
||||||
|
createComponent: (schema: ComponentSchema) => Component | null) {
|
||||||
|
const components: any = {
|
||||||
|
...builtinComponents,
|
||||||
|
};
|
||||||
|
Object.keys(componentsMap).forEach((componentName) => {
|
||||||
|
let component = componentsMap[componentName];
|
||||||
|
if (component && (component as ComponentSchema).componentName === 'Component') {
|
||||||
|
components[componentName] = createComponent(component as ComponentSchema);
|
||||||
|
} else if (isReactComponent(component)) {
|
||||||
|
components[componentName] = component;
|
||||||
|
} else {
|
||||||
|
component = findComponent(libraryMap, componentName, component as NpmInfo);
|
||||||
|
if (component) {
|
||||||
|
components[componentName] = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
let REACT_KEY = '';
|
||||||
|
function cacheReactKey(el: Element): Element {
|
||||||
|
if (REACT_KEY !== '') {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
REACT_KEY = Object.keys(el).find((key) => key.startsWith('__reactInternalInstance$')) || '';
|
||||||
|
if (!REACT_KEY && (el as HTMLElement).parentElement) {
|
||||||
|
return cacheReactKey((el as HTMLElement).parentElement!);
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClosestNodeInstance(from: any, specId?: string): NodeInstance<any> | null {
|
||||||
|
const el: any = from;
|
||||||
|
if (el) {
|
||||||
|
// if (isElement(el)) {
|
||||||
|
// el = cacheReactKey(el);
|
||||||
|
// } else {
|
||||||
|
// return getNodeInstance(el, specId);
|
||||||
|
// }
|
||||||
|
return getNodeInstance(el);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeInstance(dom: HTMLElement): NodeInstance<any> | null {
|
||||||
|
const INTERNAL = '_internal';
|
||||||
|
|
||||||
|
let instance = Instance.get(dom);
|
||||||
|
while (instance && instance[INTERNAL]) {
|
||||||
|
if (isValidDesignModeRaxComponentInstance(instance)) {
|
||||||
|
const docId = (instance as any)[SYMBOL_VDID];
|
||||||
|
return {
|
||||||
|
docId,
|
||||||
|
nodeId: instance.props._leaf.getId(),
|
||||||
|
instance: instance,
|
||||||
|
node: instance.props._leaf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = instance[INTERNAL].__parentInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkInstanceMounted(instance: any): boolean {
|
||||||
|
if (isElement(instance)) {
|
||||||
|
return instance.parentElement != null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isValidDesignModeRaxComponentInstance(
|
||||||
|
raxComponentInst: any,
|
||||||
|
): raxComponentInst is {
|
||||||
|
props: {
|
||||||
|
_leaf: Exclude<NodeInstance<any>['node'], null | undefined>;
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
const leaf = raxComponentInst?.props?._leaf;
|
||||||
|
return leaf && typeof leaf === 'object' && leaf.isNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DocumentInstance {
|
||||||
private instancesMap = new Map<string, any[]>();
|
private instancesMap = new Map<string, any[]>();
|
||||||
|
|
||||||
@obx.ref private _schema?: RootSchema;
|
@obx.ref private _schema?: RootSchema;
|
||||||
@computed get schema(): any {
|
@computed get schema(): any {
|
||||||
return this._schema;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@obx.ref private _designMode = 'design';
|
private dispose?: () => void;
|
||||||
@computed get designMode(): any {
|
|
||||||
return this._designMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@obx.ref private _componentsMap = {};
|
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
|
||||||
@computed get componentsMap(): any {
|
this.dispose = host.autorun(() => {
|
||||||
return this._componentsMap;
|
// sync schema
|
||||||
}
|
this._schema = document.export(1);
|
||||||
|
});
|
||||||
@obx.ref private _device = 'default';
|
|
||||||
@computed get device() {
|
|
||||||
return this._device;
|
|
||||||
}
|
|
||||||
|
|
||||||
// context from: utils、constants、history、location、match
|
|
||||||
@obx.ref private _appContext = {};
|
|
||||||
@computed get context(): any {
|
|
||||||
return this._appContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get suspended(): any {
|
@computed get suspended(): any {
|
||||||
@ -80,23 +153,110 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get layout(): any {
|
get path(): string {
|
||||||
// TODO: parse layout Component
|
return '/' + this.document.fileName;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
private emitter = new EventEmitter();
|
|
||||||
|
|
||||||
constructor() {
|
get id() {
|
||||||
if (!host) {
|
return this.document.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unmountIntance(id: string, instance: any) {
|
||||||
|
const instances = this.instancesMap.get(id);
|
||||||
|
if (instances) {
|
||||||
|
const i = instances.indexOf(instance);
|
||||||
|
if (i > -1) {
|
||||||
|
instances.splice(i, 1);
|
||||||
|
host.setInstance(this.document.id, id, instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mountInstance(id: string, instance: any) {
|
||||||
|
const docId = this.document.id;
|
||||||
|
const instancesMap = this.instancesMap;
|
||||||
|
if (instance == null) {
|
||||||
|
let instances = this.instancesMap.get(id);
|
||||||
|
if (instances) {
|
||||||
|
instances = instances.filter(checkInstanceMounted);
|
||||||
|
if (instances.length > 0) {
|
||||||
|
instancesMap.set(id, instances);
|
||||||
|
host.setInstance(this.document.id, id, instances);
|
||||||
|
} else {
|
||||||
|
instancesMap.delete(id);
|
||||||
|
host.setInstance(this.document.id, id, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const unmountIntance = this.unmountIntance.bind(this);
|
||||||
|
const origId = (instance as any)[SYMBOL_VNID];
|
||||||
|
if (origId && origId !== id) {
|
||||||
|
// 另外一个节点的 instance 在此被复用了,需要从原来地方卸载
|
||||||
|
unmountIntance(origId, instance);
|
||||||
|
}
|
||||||
|
if (isElement(instance)) {
|
||||||
|
cacheReactKey(instance);
|
||||||
|
} else if (origId !== id) {
|
||||||
|
// 涵盖 origId == null || origId !== id 的情况
|
||||||
|
let origUnmount: any = instance.componentWillUnmount;
|
||||||
|
if (origUnmount && origUnmount.origUnmount) {
|
||||||
|
origUnmount = origUnmount.origUnmount;
|
||||||
|
}
|
||||||
|
// hack! delete instance from map
|
||||||
|
const newUnmount = function(this: any) {
|
||||||
|
unmountIntance(id, instance);
|
||||||
|
origUnmount && origUnmount.call(this);
|
||||||
|
};
|
||||||
|
(newUnmount as any).origUnmount = origUnmount;
|
||||||
|
instance.componentWillUnmount = newUnmount;
|
||||||
|
}
|
||||||
|
|
||||||
this.dispose = host.connect(this as any, () => {
|
(instance as any)[SYMBOL_VNID] = id;
|
||||||
|
(instance as any)[SYMBOL_VDID] = docId;
|
||||||
|
let instances = this.instancesMap.get(id);
|
||||||
|
if (instances) {
|
||||||
|
const l = instances.length;
|
||||||
|
instances = instances.filter(checkInstanceMounted);
|
||||||
|
let updated = instances.length !== l;
|
||||||
|
if (!instances.includes(instance)) {
|
||||||
|
instances.push(instance);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (!updated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instances = [instance];
|
||||||
|
}
|
||||||
|
instancesMap.set(id, instances);
|
||||||
|
host.setInstance(this.document.id, id, instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
mountContext(docId: string, id: string, ctx: object) {
|
||||||
|
// this.ctxMap.set(id, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNode(id: string): Node | null {
|
||||||
|
return null;
|
||||||
|
// return this.document.getNode(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
||||||
|
readonly isSimulatorRenderer = true;
|
||||||
|
private dispose?: () => void;
|
||||||
|
// TODO: history
|
||||||
|
readonly history: any;
|
||||||
|
|
||||||
|
@obx.ref private _documentInstances: DocumentInstance[] = [];
|
||||||
|
get documentInstances() {
|
||||||
|
return this._documentInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.dispose = host.connect(this, () => {
|
||||||
// sync layout config
|
// sync layout config
|
||||||
|
|
||||||
// sync schema
|
|
||||||
this._schema = host.document.export(1);
|
|
||||||
|
|
||||||
// todo: split with others, not all should recompute
|
// todo: split with others, not all should recompute
|
||||||
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {
|
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {
|
||||||
this._libraryMap = host.libraryMap || {};
|
this._libraryMap = host.libraryMap || {};
|
||||||
@ -107,14 +267,38 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
// sync designMode
|
// sync designMode
|
||||||
this._designMode = host.designMode;
|
this._designMode = host.designMode;
|
||||||
|
|
||||||
// sync suspended
|
|
||||||
|
|
||||||
// sync scope
|
|
||||||
|
|
||||||
// sync device
|
// sync device
|
||||||
this._device = host.device;
|
this._device = host.device;
|
||||||
|
});
|
||||||
this.emitter.emit('rerender');
|
const documentInstanceMap = new Map<string, DocumentInstance>();
|
||||||
|
let initialEntry = '/';
|
||||||
|
host.autorun(({ firstRun }) => {
|
||||||
|
this._documentInstances = host.project.documents.map((doc) => {
|
||||||
|
let inst = documentInstanceMap.get(doc.id);
|
||||||
|
if (!inst) {
|
||||||
|
inst = new DocumentInstance(this, doc);
|
||||||
|
documentInstanceMap.set(doc.id, inst);
|
||||||
|
}
|
||||||
|
return inst;
|
||||||
|
});
|
||||||
|
const path = host.project.currentDocument
|
||||||
|
? documentInstanceMap.get(host.project.currentDocument.id)!.path
|
||||||
|
: '/';
|
||||||
|
if (firstRun) {
|
||||||
|
initialEntry = path;
|
||||||
|
} else {
|
||||||
|
if (this.history.location.pathname !== path) {
|
||||||
|
this.history.replace(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: [initialEntry],
|
||||||
|
});
|
||||||
|
this.history = history;
|
||||||
|
history.listen((location, action) => {
|
||||||
|
host.project.open(location.pathname.substr(1));
|
||||||
});
|
});
|
||||||
host.componentsConsumer.consume(async (componentsAsset) => {
|
host.componentsConsumer.consume(async (componentsAsset) => {
|
||||||
if (componentsAsset) {
|
if (componentsAsset) {
|
||||||
@ -122,31 +306,69 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
this.buildComponents();
|
this.buildComponents();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this._appContext = {
|
||||||
|
utils: {
|
||||||
|
router: {
|
||||||
|
push(path: string, params?: object) {
|
||||||
|
history.push(withQueryParams(path, params));
|
||||||
|
},
|
||||||
|
replace(path: string, params?: object) {
|
||||||
|
history.replace(withQueryParams(path, params));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legaoBuiltins: {
|
||||||
|
getUrlParams() {
|
||||||
|
const search = history.location.search;
|
||||||
|
return parseQuery(search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
constants: {},
|
||||||
|
};
|
||||||
host.injectionConsumer.consume((data) => {
|
host.injectionConsumer.consume((data) => {
|
||||||
// sync utils, i18n, contants,... config
|
// sync utils, i18n, contants,... config
|
||||||
this._appContext = {
|
|
||||||
utils: {},
|
|
||||||
constants: {
|
|
||||||
name: 'demo',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
getClosestNodeInstance(from: any, nodeId?: string): NodeInstance<any> | null {
|
@computed get layout(): any {
|
||||||
const node = getClosestNodeInstance(from, nodeId);
|
// TODO: parse layout Component
|
||||||
return node;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getComponentInstances(id: string): any[] | null {
|
private _libraryMap: { [key: string]: string } = {};
|
||||||
return this.instancesMap.get(id) || null;
|
private buildComponents() {
|
||||||
|
// TODO: remove this.createComponent
|
||||||
|
this._components = buildComponents(this._libraryMap, this._componentsMap, this.createComponent.bind(this));
|
||||||
}
|
}
|
||||||
|
@obx.ref private _components: any = {};
|
||||||
onReRender(fn: () => void) {
|
@computed get components(): object {
|
||||||
this.emitter.on('rerender', fn);
|
// 根据 device 选择不同组件,进行响应式
|
||||||
return () => {
|
// 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl
|
||||||
this.emitter.removeListener('renderer', fn);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComponent(componentName: string) {
|
getComponent(componentName: string) {
|
||||||
@ -167,20 +389,47 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
componentName = paths.join('.');
|
componentName = paths.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
createComponent(schema: ComponentSchema): Component | null {
|
getClosestNodeInstance(from: any, nodeId?: string): NodeInstance<any> | null {
|
||||||
|
const el: any = from;
|
||||||
|
if (el) {
|
||||||
|
// if (isElement(el)) {
|
||||||
|
// el = cacheReactKey(el);
|
||||||
|
// } else {
|
||||||
|
// return getNodeInstance(el, specId);
|
||||||
|
// }
|
||||||
|
return getNodeInstance(el);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getComponentInstances(id: string): any[] | null {
|
||||||
|
return this.instancesMap.get(id) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReRender(fn: () => void) {
|
||||||
|
this.emitter.on('rerender', fn);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('renderer', fn);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
findDOMNodes(instance: any): Array<Element | Text> | null {
|
||||||
|
return raxFindDOMNodes(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
getClientRects(element: Element | Text) {
|
||||||
|
return getClientRects(element);
|
||||||
|
}
|
||||||
|
|
||||||
setNativeSelection(enableFlag: boolean) {
|
setNativeSelection(enableFlag: boolean) {
|
||||||
setNativeSelection(enableFlag);
|
setNativeSelection(enableFlag);
|
||||||
}
|
}
|
||||||
setDraggingState(state: boolean) {
|
setDraggingState(state: boolean) {
|
||||||
cursor.setDragging(state);
|
cursor.setDragging(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCopyState(state: boolean) {
|
setCopyState(state: boolean) {
|
||||||
cursor.setCopy(state);
|
cursor.setCopy(state);
|
||||||
}
|
}
|
||||||
@ -188,93 +437,100 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
cursor.release();
|
cursor.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
findDOMNodes(instance: any): Array<Element | Text> | null {
|
createComponent(schema: ComponentSchema): Component | null {
|
||||||
return [raxFindDOMNodes(instance)];
|
return null;
|
||||||
}
|
// TODO: use ComponentEngine refactor
|
||||||
|
/*
|
||||||
/**
|
const _schema = {
|
||||||
* 加载资源
|
...schema,
|
||||||
*/
|
|
||||||
load(asset: Asset): Promise<any> {
|
|
||||||
return loader.load(asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// private instancesMap = new Map<string, any[]>();
|
|
||||||
private unmountIntance(id: string, instance: any) {
|
|
||||||
const instances = this.instancesMap.get(id);
|
|
||||||
if (instances) {
|
|
||||||
const i = instances.indexOf(instance);
|
|
||||||
if (i > -1) {
|
|
||||||
instances.splice(i, 1);
|
|
||||||
host.setInstance(id, instances);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mountInstance(id: string, instance: any | null) {
|
|
||||||
const instancesMap = this.instancesMap;
|
|
||||||
if (instance == null) {
|
|
||||||
let instances = this.instancesMap.get(id);
|
|
||||||
if (instances) {
|
|
||||||
instances = instances.filter(checkInstanceMounted);
|
|
||||||
if (instances.length > 0) {
|
|
||||||
instancesMap.set(id, instances);
|
|
||||||
host.setInstance(id, instances);
|
|
||||||
} else {
|
|
||||||
instancesMap.delete(id);
|
|
||||||
host.setInstance(id, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const unmountIntance = this.unmountIntance.bind(this);
|
|
||||||
const origId = (instance as any)[SYMBOL_VNID];
|
|
||||||
if (origId && origId !== id) {
|
|
||||||
// 另外一个节点的 instance 在此被复用了,需要从原来地方卸载
|
|
||||||
unmountIntance(origId, instance);
|
|
||||||
}
|
|
||||||
if (isElement(instance)) {
|
|
||||||
// cacheReactKey(instance);
|
|
||||||
} else if (origId !== id) {
|
|
||||||
// 涵盖 origId == null || origId !== id 的情况
|
|
||||||
let origUnmount: any = instance.componentWillUnmount;
|
|
||||||
if (origUnmount && origUnmount.origUnmount) {
|
|
||||||
origUnmount = origUnmount.origUnmount;
|
|
||||||
}
|
|
||||||
// hack! delete instance from map
|
|
||||||
const newUnmount = function(this: any) {
|
|
||||||
unmountIntance(id, instance);
|
|
||||||
origUnmount && origUnmount.call(this);
|
|
||||||
};
|
};
|
||||||
(newUnmount as any).origUnmount = origUnmount;
|
_schema.methods = {};
|
||||||
instance.componentWillUnmount = newUnmount;
|
_schema.lifeCycles = {};
|
||||||
|
|
||||||
|
const node = host.document.createNode(_schema);
|
||||||
|
_schema = node.export(TransformStage.Render);
|
||||||
|
|
||||||
|
const processPropsSchema = (propsSchema: any, propsMap: any): any => {
|
||||||
|
if (!propsSchema) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
(instance as any)[SYMBOL_VNID] = id;
|
const result = { ...propsSchema };
|
||||||
let instances = this.instancesMap.get(id);
|
const reg = /^(?:this\.props|props)\.(\S+)$/;
|
||||||
if (instances) {
|
Object.keys(result).map((key: string) => {
|
||||||
const l = instances.length;
|
if (result[key]?.type === 'JSExpression') {
|
||||||
instances = instances.filter(checkInstanceMounted);
|
const { value } = result[key];
|
||||||
let updated = instances.length !== l;
|
const matched = reg.exec(value);
|
||||||
if (!instances.includes(instance)) {
|
if (matched) {
|
||||||
instances.push(instance);
|
const propName = matched[1];
|
||||||
updated = true;
|
result[key] = propsMap[propName];
|
||||||
}
|
}
|
||||||
if (!updated) {
|
} else if (result[key]?.type === 'JSSlot') {
|
||||||
|
const schema = result[key].value;
|
||||||
|
result[key] = createElement(Ele, {schema, propsMap: {}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderer = this;
|
||||||
|
const componentsMap = renderer.componentsMap;
|
||||||
|
|
||||||
|
class Ele extends React.Component<{ schema: any, propsMap: any }> {
|
||||||
|
private isModal: boolean;
|
||||||
|
|
||||||
|
constructor(props: any){
|
||||||
|
super(props);
|
||||||
|
const componentMeta = host.document.getComponentMeta(props.schema.componentName);
|
||||||
|
if (componentMeta?.prototype?.isModal()) {
|
||||||
|
this.isModal = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
instances = [instance];
|
|
||||||
}
|
|
||||||
instancesMap.set(id, instances);
|
|
||||||
host.setInstance(id, instances);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getClientRects(element: Element | Text) {
|
render() {
|
||||||
return getClientRects(element);
|
if (this.isModal) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { schema, propsMap } = this.props;
|
||||||
|
const Com = componentsMap[schema.componentName];
|
||||||
|
if (!Com) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let children = null;
|
||||||
|
if (schema.children && schema.children.length > 0) {
|
||||||
|
children = schema.children.map((item: any) => createElement(Ele, {schema: item, propsMap}));
|
||||||
|
}
|
||||||
|
const props = processPropsSchema(schema.props, propsMap);
|
||||||
|
const _leaf = host.document.createNode(schema);
|
||||||
|
|
||||||
|
return createElement(Com, {...props, _leaf}, children);
|
||||||
|
}
|
||||||
|
const _leaf = this.document.designer.currentDocument?.createNode(schema);
|
||||||
|
const node = this.document.createNode(schema);
|
||||||
|
let props = processPropsSchema(schema.props, propsMap);
|
||||||
|
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 container = this;
|
||||||
|
class Com extends React.Component {
|
||||||
|
render() {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Com;
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private _running = false;
|
private _running = false;
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
if (this._running) {
|
if (this._running) {
|
||||||
return;
|
return;
|
||||||
@ -287,6 +543,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
container.id = containerId;
|
container.id = containerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== compatiable vision
|
// ==== compatiable vision
|
||||||
document.documentElement.classList.add('engine-page');
|
document.documentElement.classList.add('engine-page');
|
||||||
document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends
|
document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends
|
||||||
@ -294,7 +551,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
|||||||
raxRender(createElement(SimulatorRendererView, { renderer: this }), container, {
|
raxRender(createElement(SimulatorRendererView, { renderer: this }), container, {
|
||||||
driver: DriverUniversal,
|
driver: DriverUniversal,
|
||||||
});
|
});
|
||||||
host.document.setRendererReady(this);
|
host.project.setRendererReady(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,77 +607,4 @@ function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmI
|
|||||||
return getSubComponent(library, paths);
|
return getSubComponent(library, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
const builtinComponents = {
|
export default new SimulatorRendererContainer();
|
||||||
Slot,
|
|
||||||
Leaf,
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo }) {
|
|
||||||
const components: any = {
|
|
||||||
...builtinComponents,
|
|
||||||
};
|
|
||||||
Object.keys(componentsMap).forEach((componentName) => {
|
|
||||||
let component = componentsMap[componentName];
|
|
||||||
if (isReactComponent(component)) {
|
|
||||||
components[componentName] = component;
|
|
||||||
} else {
|
|
||||||
component = findComponent(libraryMap, componentName, component);
|
|
||||||
if (component) {
|
|
||||||
components[componentName] = component;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getClosestNodeInstance(from: any, specId?: string): NodeInstance<any> | null {
|
|
||||||
const el: any = from;
|
|
||||||
if (el) {
|
|
||||||
// if (isElement(el)) {
|
|
||||||
// el = cacheReactKey(el);
|
|
||||||
// } else {
|
|
||||||
// return getNodeInstance(el, specId);
|
|
||||||
// }
|
|
||||||
return getNodeInstance(el);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidDesignModeRaxComponentInstance(
|
|
||||||
raxComponentInst: any,
|
|
||||||
): raxComponentInst is {
|
|
||||||
props: {
|
|
||||||
_leaf: Exclude<NodeInstance<any>['node'], null | undefined>;
|
|
||||||
};
|
|
||||||
} {
|
|
||||||
const leaf = raxComponentInst?.props?._leaf;
|
|
||||||
return leaf && typeof leaf === 'object' && leaf.isNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNodeInstance(dom: HTMLElement): NodeInstance<any> | null {
|
|
||||||
const INTERNAL = '_internal';
|
|
||||||
|
|
||||||
let instance = Instance.get(dom);
|
|
||||||
while (instance && instance[INTERNAL]) {
|
|
||||||
if (isValidDesignModeRaxComponentInstance(instance)) {
|
|
||||||
return {
|
|
||||||
nodeId: instance.props._leaf.getId(),
|
|
||||||
instance: instance,
|
|
||||||
node: instance.props._leaf,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
instance = instance[INTERNAL].__parentInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkInstanceMounted(instance: any): boolean {
|
|
||||||
if (isElement(instance)) {
|
|
||||||
return instance.parentElement != null;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new SimulatorRenderer();
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user