mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-12 08:58:15 +00:00
Revert "Revert "Merge branch 'feat/rax-simulator' into 'release/0.9.0' ""
This reverts commit 5767023d3f7d1406343a14089941e8365d029fa7.
This commit is contained in:
parent
26f5cba13c
commit
d8456fb1cd
@ -2,7 +2,8 @@
|
||||
"entry": {
|
||||
"index": "src/index",
|
||||
"editor-preset-vision": "../editor-preset-vision/src/index.ts",
|
||||
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
|
||||
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts",
|
||||
"rax-simulator-renderer": "../rax-simulator-renderer/src/index.ts"
|
||||
},
|
||||
"vendor": false,
|
||||
"devServer": {
|
||||
@ -15,7 +16,8 @@
|
||||
"prop-types": "var window.PropTypes",
|
||||
"@alifd/next": "var window.Next",
|
||||
"@ali/visualengine": "var window.VisualEngine",
|
||||
"@ali/visualengine-utils": "var window.VisualEngineUtils"
|
||||
"@ali/visualengine-utils": "var window.VisualEngineUtils",
|
||||
"rax": "var window.Rax"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
|
||||
@ -10,12 +10,15 @@ module.exports = ({ onGetWebpackConfig }) => {
|
||||
]);
|
||||
|
||||
config
|
||||
// 定义插件名称
|
||||
.plugin('MonacoWebpackPlugin')
|
||||
// 第一项为具体插件,第二项为插件参数
|
||||
.use(new MonacoWebpackPlugin({
|
||||
languages:["typescript","css","json"]
|
||||
}), []);
|
||||
// 定义插件名称
|
||||
.plugin('MonacoWebpackPlugin')
|
||||
// 第一项为具体插件,第二项为插件参数
|
||||
.use(
|
||||
new MonacoWebpackPlugin({
|
||||
languages: ['typescript', 'css', 'json'],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
config.plugins.delete('hot');
|
||||
config.devServer.hot(false);
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
<script src="https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"></script>
|
||||
<link rel="stylesheet" href="https://alifd.alicdn.com/npm/@alifd/next/1.11.6/next.min.css" />
|
||||
<script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/rax@1.1.3/dist/rax.min.js"></script>
|
||||
<link rel="stylesheet" href="/css/editor-preset-vision.css" />
|
||||
<script>
|
||||
window.pageConfig = {
|
||||
|
||||
20
packages/demo/public/rax.json
Normal file
20
packages/demo/public/rax.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"componentName": "Page",
|
||||
"fileName": "home",
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "View",
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"type": "primary"
|
||||
},
|
||||
"children": ["Welcome to Your Rax App!"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
83
packages/demo/public/raxAssets.json
Normal file
83
packages/demo/public/raxAssets.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"externals": [],
|
||||
"componentDependencies": [{
|
||||
"prototypeConfigsUrl": ["https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-deep-mobile/2.0.1-beta.4/proto.c0b2ef2.js"],
|
||||
"prototypeViewsUrl": null,
|
||||
"alias": "",
|
||||
"library": "AliVcDiv",
|
||||
"urls": [
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-deep-mobile/2.0.1-beta.4/view.c0b2ef2.css",
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-deep-mobile/2.0.1-beta.4/view.c0b2ef2.js"
|
||||
],
|
||||
"components": null,
|
||||
"packageName": "@ali/vc-deep-mobile",
|
||||
"version": "1.0.1"
|
||||
}],
|
||||
"packages": [
|
||||
{
|
||||
"title": "view",
|
||||
"package": "rax-view",
|
||||
"version": "1.19.18",
|
||||
"urls": ["https://cdn.jsdelivr.net/npm/rax-view@1.1.2/build/index.js"],
|
||||
"library": "View"
|
||||
},
|
||||
{
|
||||
"title": "view",
|
||||
"package": "rax-text",
|
||||
"version": "1.19.18",
|
||||
"urls": ["https://cdn.jsdelivr.net/npm/rax-text@1.3.1/build/index.js"],
|
||||
"library": "Text"
|
||||
}
|
||||
],
|
||||
"components": [{
|
||||
"componentName": "Page",
|
||||
"title": "页面",
|
||||
"configure": {
|
||||
"events": {
|
||||
"supportedLifecycles": [{
|
||||
"description": "初始化时",
|
||||
"name": "constructor"
|
||||
}, {
|
||||
"description": "装载后",
|
||||
"name": "componentDidMount"
|
||||
}, {
|
||||
"description": "更新时",
|
||||
"name": "componentDidMount"
|
||||
}, {
|
||||
"description": "卸载时",
|
||||
"name": "componentWillUnmount"
|
||||
}]
|
||||
},
|
||||
"component": {
|
||||
"isContainer": true
|
||||
}
|
||||
}
|
||||
},{
|
||||
"componentName": "View",
|
||||
"npm": {
|
||||
"package": "rax-view",
|
||||
"destructuring": true,
|
||||
"exportName": "View"
|
||||
},
|
||||
"title": "视图",
|
||||
"configure": {
|
||||
"component": {
|
||||
"isContainer": true
|
||||
}
|
||||
}
|
||||
},{
|
||||
"componentName": "Text",
|
||||
"npm": {
|
||||
"package": "rax-text",
|
||||
"destructuring": true,
|
||||
"exportName": "Text"
|
||||
},
|
||||
"title": "文本",
|
||||
"configure": {
|
||||
"component": {
|
||||
"isContainer": true
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
@ -18,4 +18,4 @@ export default {
|
||||
eventBindDialog,
|
||||
variableBindDialog,
|
||||
sourceEditor,
|
||||
}
|
||||
};
|
||||
|
||||
@ -28,7 +28,7 @@ export default {
|
||||
props: {
|
||||
align: 'right',
|
||||
},
|
||||
},*/
|
||||
}, */
|
||||
{
|
||||
pluginKey: 'samplePreview',
|
||||
type: 'Custom',
|
||||
@ -73,10 +73,10 @@ export default {
|
||||
hideTitleBar: true,
|
||||
maxHeight: 800,
|
||||
maxWidth: 1200,
|
||||
title: "动作面板",
|
||||
width: 600
|
||||
title: '动作面板',
|
||||
width: 600,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginKey: 'zhEn',
|
||||
@ -94,7 +94,7 @@ export default {
|
||||
{
|
||||
pluginKey: 'variableBindDialog',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
shortCuts: [],
|
||||
lifeCycles: {
|
||||
|
||||
@ -102,6 +102,7 @@ context.use(HOOKS.VE_SETTING_FIELD_VARIABLE_SETTER, VariableSetter);
|
||||
const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore'];
|
||||
|
||||
async function loadAssets() {
|
||||
// const legaoAssets = await editor.utils.get('./raxAssets.json');
|
||||
const legaoAssets = await editor.utils.get('./legao-assets.json');
|
||||
|
||||
const assets = upgradeAssetsBundle(legaoAssets);
|
||||
@ -144,6 +145,7 @@ async function loadAssets() {
|
||||
}
|
||||
|
||||
async function loadSchema() {
|
||||
// const schema = await editor.utils.get('./rax.json');
|
||||
const schema = await editor.utils.get('./schema.json');
|
||||
editor.set('schema', schema);
|
||||
}
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
// NOTE: 仅用作类型标注,切勿作为实体使用
|
||||
import { BuiltinSimulatorHost } from './host';
|
||||
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, AssetType, assetItem } from '@ali/lowcode-utils';
|
||||
import {
|
||||
AssetLevel,
|
||||
AssetLevels,
|
||||
AssetList,
|
||||
isAssetBundle,
|
||||
isAssetItem,
|
||||
AssetType,
|
||||
assetItem,
|
||||
} from '@ali/lowcode-utils';
|
||||
import { isCSSUrl } from '@ali/lowcode-utils';
|
||||
import { BuiltinSimulatorRenderer } from './renderer';
|
||||
|
||||
@ -16,7 +24,7 @@ export function createSimulator(
|
||||
|
||||
const styles: any = {};
|
||||
const scripts: any = {};
|
||||
AssetLevels.forEach(lv => {
|
||||
AssetLevels.forEach((lv) => {
|
||||
styles[lv] = [];
|
||||
scripts[lv] = [];
|
||||
});
|
||||
@ -56,12 +64,12 @@ export function createSimulator(
|
||||
parseAssetList(vendors);
|
||||
|
||||
const styleFrags = Object.keys(styles)
|
||||
.map(key => {
|
||||
.map((key) => {
|
||||
return styles[key].join('\n') + `<meta level="${key}" />`;
|
||||
})
|
||||
.join('');
|
||||
const scriptFrags = Object.keys(scripts)
|
||||
.map(key => {
|
||||
.map((key) => {
|
||||
return scripts[key].join('\n');
|
||||
})
|
||||
.join('');
|
||||
@ -72,7 +80,7 @@ export function createSimulator(
|
||||
</head><body>${scriptFrags}</body></html>`);
|
||||
doc.close();
|
||||
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
if (win.SimulatorRenderer || host.renderer) {
|
||||
return resolve(win.SimulatorRenderer || host.renderer);
|
||||
}
|
||||
|
||||
@ -4,7 +4,16 @@ 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,
|
||||
} from '@ali/lowcode-utils';
|
||||
import {
|
||||
DragObjectType,
|
||||
isShaken,
|
||||
@ -62,6 +71,20 @@ const defaultSimulatorUrl = (() => {
|
||||
return urls;
|
||||
})();
|
||||
|
||||
const defaultRaxSimulatorUrl = (() => {
|
||||
const publicPath = getPublicPath();
|
||||
let urls;
|
||||
const [_, prefix = '', dev] = /^(.+?)(\/js)?\/?$/.exec(publicPath) || [];
|
||||
if (dev) {
|
||||
urls = [`${prefix}/css/rax-simulator-renderer.css`, `${prefix}/js/rax-simulator-renderer.js`];
|
||||
} else if (process.env.NODE_ENV === 'production') {
|
||||
urls = [`${prefix}/rax-simulator-renderer.css`, `${prefix}/rax-simulator-renderer.js`];
|
||||
} else {
|
||||
urls = [`${prefix}/rax-simulator-renderer.css`, `${prefix}/rax-simulator-renderer.js`];
|
||||
}
|
||||
return urls;
|
||||
})();
|
||||
|
||||
const defaultEnvironment = [
|
||||
// https://g.alicdn.com/mylib/??react/16.11.0/umd/react.production.min.js,react-dom/16.8.6/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.min.js
|
||||
assetItem(AssetType.JSText, 'window.React=parent.React;window.ReactDOM=parent.ReactDOM;', undefined, 'react'),
|
||||
@ -71,6 +94,17 @@ const defaultEnvironment = [
|
||||
),
|
||||
];
|
||||
|
||||
const defaultRaxEnvironment = [
|
||||
assetItem(
|
||||
AssetType.JSText,
|
||||
'window.Rax=parent.Rax;window.React=parent.React;window.ReactDOM=parent.ReactDOM;window.VisualEngineUtils=parent.VisualEngineUtils;window.VisualEngine=parent.VisualEngine',
|
||||
),
|
||||
assetItem(
|
||||
AssetType.JSText,
|
||||
'window.PropTypes=parent.PropTypes;React.PropTypes=parent.PropTypes; window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;',
|
||||
),
|
||||
];
|
||||
|
||||
export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProps> {
|
||||
readonly isSimulator = true;
|
||||
|
||||
@ -78,6 +112,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
readonly designer = this.document.designer;
|
||||
|
||||
@computed get renderEnv(): string {
|
||||
return this.get('renderEnv') || 'default';
|
||||
}
|
||||
|
||||
@computed get device(): string {
|
||||
return this.get('device') || 'default';
|
||||
}
|
||||
@ -186,7 +224,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
const vendors = [
|
||||
// required & use once
|
||||
assetBundle(this.get('environment') || defaultEnvironment, AssetLevel.Environment),
|
||||
assetBundle(
|
||||
this.get('environment') || this.renderEnv === 'rax' ? defaultRaxEnvironment : defaultEnvironment,
|
||||
AssetLevel.Environment,
|
||||
),
|
||||
// required & use once
|
||||
assetBundle(this.get('extraEnvironment'), AssetLevel.Environment),
|
||||
// required & use once
|
||||
@ -194,7 +235,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
// required & TODO: think of update
|
||||
assetBundle(this.theme, AssetLevel.Theme),
|
||||
// required & use once
|
||||
assetBundle(this.get('simulatorUrl') || defaultSimulatorUrl, AssetLevel.Runtime),
|
||||
assetBundle(
|
||||
this.get('simulatorUrl') || this.renderEnv === 'rax' ? defaultRaxSimulatorUrl : defaultSimulatorUrl,
|
||||
AssetLevel.Runtime,
|
||||
),
|
||||
];
|
||||
|
||||
// wait 准备 iframe 内容、依赖库注入
|
||||
@ -326,7 +370,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 +441,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 +456,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -792,10 +842,12 @@ 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 ||
|
||||
// too dirty
|
||||
(typeof dropContainer.container?.componentMeta?.prototype?.options?.canDropIn === 'function' &&
|
||||
!dropContainer.container?.componentMeta?.prototype?.options?.canDropIn(e.dragObject.nodes[0]))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -836,7 +888,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
},
|
||||
source: 'simulator' + this.document.id,
|
||||
event: e,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!children || children.size < 1 || !edge) {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
|
||||
@ -11,6 +11,7 @@ interface DesignerPluginState {
|
||||
componentMetadatas?: any[] | null;
|
||||
library?: any[] | null;
|
||||
extraEnvironment?: any[] | null;
|
||||
renderEnv?: string;
|
||||
}
|
||||
|
||||
export default class DesignerPlugin extends PureComponent<PluginProps, DesignerPluginState> {
|
||||
@ -20,6 +21,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
|
||||
componentMetadatas: null,
|
||||
library: null,
|
||||
extraEnvironment: null,
|
||||
renderEnv: 'default',
|
||||
};
|
||||
|
||||
private _mounted = true;
|
||||
@ -65,7 +67,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { editor } = this.props;
|
||||
const { componentMetadatas, library, extraEnvironment } = this.state;
|
||||
const { componentMetadatas, library, extraEnvironment, renderEnv } = this.state;
|
||||
|
||||
if (!library || !componentMetadatas) {
|
||||
// TODO: use a Loading
|
||||
@ -82,6 +84,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
|
||||
simulatorProps={{
|
||||
library,
|
||||
extraEnvironment,
|
||||
renderEnv,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -26,3 +26,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
### Features
|
||||
|
||||
* init rax-render ([7167767](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7167767))
|
||||
|
||||
&&
|
||||
&&
|
||||
||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@ali/lowcode-engine-rax-renderer",
|
||||
"name": "@ali/lowcode-rax-renderer",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"description": "Rax renderer for Ali lowCode engine",
|
||||
@ -30,7 +30,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "build-scripts start",
|
||||
"prepublish": "npm run build"
|
||||
"prepublish": "npm run build",
|
||||
"build": "build-scripts build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ali/b3-one": "^0.0.17",
|
||||
|
||||
@ -2,6 +2,7 @@ import { Component, createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import View from 'rax-view';
|
||||
import classnames from 'classnames';
|
||||
import DataHelper from '../utils/dataHelper';
|
||||
import {
|
||||
forEach,
|
||||
@ -18,10 +19,12 @@ import {
|
||||
checkPropTypes,
|
||||
generateI18n,
|
||||
acceptsRef,
|
||||
getFileCssName,
|
||||
} from '../utils';
|
||||
import VisualDom from '../comp/visualDom';
|
||||
import AppContext from '../context/appContext';
|
||||
import CompWrapper from '../hoc/compWrapper';
|
||||
|
||||
// import CompWrapper from '../hoc/compWrapper';
|
||||
|
||||
const debug = Debug('engine:base');
|
||||
const DESIGN_MODE = {
|
||||
@ -34,6 +37,7 @@ let scopeIdx = 0;
|
||||
|
||||
export default class BaseEngine extends Component {
|
||||
static dislayName = 'base-engine';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string,
|
||||
messages: PropTypes.object,
|
||||
@ -41,11 +45,13 @@ export default class BaseEngine extends Component {
|
||||
__components: PropTypes.object,
|
||||
__componentsMap: PropTypes.object,
|
||||
__ctx: PropTypes.object,
|
||||
__schema: PropTypes.object
|
||||
__schema: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
__schema: {},
|
||||
};
|
||||
|
||||
static contextType = AppContext;
|
||||
|
||||
constructor(props, context) {
|
||||
@ -79,32 +85,31 @@ export default class BaseEngine extends Component {
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
reloadDataSource = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
debug('reload data source');
|
||||
if (!this.__dataHelper) {
|
||||
reloadDataSource = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
debug('reload data source');
|
||||
if (!this.__dataHelper) {
|
||||
this.__showPlaceholder = false;
|
||||
return resolve();
|
||||
}
|
||||
this.__dataHelper
|
||||
.getInitData()
|
||||
.then((res) => {
|
||||
this.__showPlaceholder = false;
|
||||
return resolve();
|
||||
}
|
||||
this.__dataHelper
|
||||
.getInitData()
|
||||
.then(res => {
|
||||
if (isEmpty(res)) {
|
||||
this.forceUpdate();
|
||||
return resolve();
|
||||
}
|
||||
this.setState(res, resolve);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (this.__showPlaceholder) {
|
||||
this.__showPlaceholder = false;
|
||||
if (isEmpty(res)) {
|
||||
this.forceUpdate();
|
||||
return resolve();
|
||||
}
|
||||
this.setState(res, resolve);
|
||||
})
|
||||
.catch(err => {
|
||||
if (this.__showPlaceholder) {
|
||||
this.__showPlaceholder = false;
|
||||
this.forceUpdate();
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
this.forceUpdate();
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
__setLifeCycleMethods = (method, args) => {
|
||||
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
|
||||
@ -121,7 +126,7 @@ export default class BaseEngine extends Component {
|
||||
const { __schema } = props;
|
||||
const customMethodsList = Object.keys(__schema.methods || {}) || [];
|
||||
this.__customMethodsList &&
|
||||
this.__customMethodsList.forEach(item => {
|
||||
this.__customMethodsList.forEach((item) => {
|
||||
if (!customMethodsList.includes(item)) {
|
||||
delete this[item];
|
||||
}
|
||||
@ -132,12 +137,12 @@ export default class BaseEngine extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
__generateCtx = ctx => {
|
||||
__generateCtx = (ctx) => {
|
||||
const { pageContext, compContext } = this.context;
|
||||
const obj = {
|
||||
page: pageContext,
|
||||
component: compContext,
|
||||
...ctx
|
||||
...ctx,
|
||||
};
|
||||
forEach(obj, (val, key) => {
|
||||
this[key] = val;
|
||||
@ -153,19 +158,22 @@ export default class BaseEngine extends Component {
|
||||
const schema = props.__schema || {};
|
||||
const appHelper = props.__appHelper;
|
||||
const dataSource = (schema && schema.dataSource) || {};
|
||||
this.__dataHelper = new DataHelper(this, dataSource, appHelper, config => this.__parseData(config));
|
||||
this.__dataHelper = new DataHelper(this, dataSource, appHelper, (config) => this.__parseData(config));
|
||||
this.dataSourceMap = this.__dataHelper.dataSourceMap;
|
||||
// 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容
|
||||
this.__showPlaceholder =
|
||||
this.__parseData(schema.props && schema.props.autoLoading) &&
|
||||
(dataSource.list || []).some(item => !!this.__parseData(item.isInit));
|
||||
if (this.__parseData(schema.props && schema.props.autoLoading)) {
|
||||
this.__showPlaceholder = (dataSource.list || []).some((item) => !!this.__parseData(item.isInit));
|
||||
}
|
||||
// this.__showPlaceholder = this.__parseData(schema.props && schema.props.autoLoading) && (dataSource.list || []).some(
|
||||
// (item) => !!this.__parseData(item.isInit),
|
||||
// );
|
||||
};
|
||||
|
||||
__render = () => {
|
||||
const schema = this.props.__schema;
|
||||
this.__setLifeCycleMethods('render');
|
||||
|
||||
const engine = this.context.engine;
|
||||
const { engine } = this.context;
|
||||
if (engine) {
|
||||
engine.props.onCompGetCtx(schema, this);
|
||||
// 画布场景才需要每次渲染bind自定义方法
|
||||
@ -176,7 +184,10 @@ export default class BaseEngine extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
__getRef = ref => {
|
||||
__getRef = (ref) => {
|
||||
const { engine } = this.context;
|
||||
const { __schema } = this.props;
|
||||
engine && engine.props.onCompGetRef(__schema, ref);
|
||||
this.__ref = ref;
|
||||
};
|
||||
|
||||
@ -186,7 +197,7 @@ export default class BaseEngine extends Component {
|
||||
self.__proto__ = __ctx || this;
|
||||
return this.__createVirtualDom(__schema.children, self, {
|
||||
schema: __schema,
|
||||
Comp: __components[__schema.componentName]
|
||||
Comp: __components[__schema.componentName],
|
||||
});
|
||||
};
|
||||
|
||||
@ -200,7 +211,7 @@ export default class BaseEngine extends Component {
|
||||
// rax text prop 兼容处理
|
||||
if (schema.componentName === 'Text') {
|
||||
if (typeof schema.props.text === 'string') {
|
||||
schema = Object.assign({}, schema);
|
||||
schema = { ...schema };
|
||||
schema.children = [schema.props.text];
|
||||
}
|
||||
}
|
||||
@ -218,13 +229,13 @@ export default class BaseEngine extends Component {
|
||||
if (Array.isArray(schema)) {
|
||||
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
|
||||
return schema.map((item, idx) =>
|
||||
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx)
|
||||
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx),
|
||||
);
|
||||
}
|
||||
|
||||
//解析占位组件
|
||||
// 解析占位组件
|
||||
if (schema.componentName === 'Flagment' && schema.children) {
|
||||
let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
|
||||
const tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
|
||||
return this.__createVirtualDom(tarChildren, self, parentInfo);
|
||||
}
|
||||
|
||||
@ -238,11 +249,11 @@ export default class BaseEngine extends Component {
|
||||
return this.__createLoopVirtualDom(
|
||||
{
|
||||
...schema,
|
||||
loop: parseData(schema.loop, self)
|
||||
loop: parseData(schema.loop, self),
|
||||
},
|
||||
self,
|
||||
parentInfo,
|
||||
idx
|
||||
idx,
|
||||
);
|
||||
}
|
||||
const condition = schema.condition === undefined ? true : parseData(schema.condition, self);
|
||||
@ -258,7 +269,7 @@ export default class BaseEngine extends Component {
|
||||
} else if (!schema.__ctx) {
|
||||
// 在生产环境schema没有__ctx上下文,需要手动生成一个lunaKey
|
||||
schema.__ctx = {
|
||||
lunaKey: `luna${++scopeIdx}`
|
||||
lunaKey: `luna${++scopeIdx}`,
|
||||
};
|
||||
scopeKey = schema.__ctx.lunaKey;
|
||||
} else {
|
||||
@ -282,7 +293,7 @@ export default class BaseEngine extends Component {
|
||||
__schema: schema,
|
||||
__appHelper: appHelper,
|
||||
__components: components,
|
||||
__componentsMap: componentsMap
|
||||
// __componentsMap: componentsMap,
|
||||
}
|
||||
: {};
|
||||
if (engine && engine.props.designMode) {
|
||||
@ -294,20 +305,20 @@ export default class BaseEngine extends Component {
|
||||
Comp,
|
||||
componentInfo: {
|
||||
...componentInfo,
|
||||
props: transformArrayToMap(componentInfo.props, 'name')
|
||||
}
|
||||
props: transformArrayToMap(componentInfo.props, 'name'),
|
||||
},
|
||||
});
|
||||
// 对于可以获取到ref的组件做特殊处理
|
||||
if (!acceptsRef(Comp)) {
|
||||
Comp = CompWrapper(Comp);
|
||||
if (acceptsRef(Comp)) {
|
||||
otherProps.ref = (ref) => {
|
||||
const refProps = props.ref;
|
||||
if (refProps && typeof refProps === 'string') {
|
||||
this[refProps] = ref;
|
||||
}
|
||||
engine && engine.props.onCompGetRef(schema, ref);
|
||||
};
|
||||
}
|
||||
otherProps.ref = ref => {
|
||||
const refProps = props.ref;
|
||||
if (refProps && typeof refProps === 'string') {
|
||||
this[refProps] = ref;
|
||||
}
|
||||
engine && engine.props.onCompGetRef(schema, ref);
|
||||
};
|
||||
|
||||
// scope需要传入到组件上
|
||||
if (scopeKey && this.__compScopes[scopeKey]) {
|
||||
props.__scope = this.__compScopes[scopeKey];
|
||||
@ -320,24 +331,39 @@ export default class BaseEngine extends Component {
|
||||
} else if (typeof idx === 'number' && !props.key) {
|
||||
props.key = idx;
|
||||
}
|
||||
const renderComp = props => (
|
||||
<Comp {...props}>
|
||||
{(!isFileSchema(schema) &&
|
||||
!!schema.children &&
|
||||
props.__id = schema.id;
|
||||
|
||||
let Child = null;
|
||||
if (!isFileSchema(schema) && schema.children) {
|
||||
Child = this.__createVirtualDom(
|
||||
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
|
||||
self,
|
||||
{
|
||||
schema,
|
||||
Comp,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const renderComp = (props) => engine.createElement(
|
||||
Comp,
|
||||
props,
|
||||
(!isFileSchema(schema)
|
||||
&& !!schema.children
|
||||
&&
|
||||
this.__createVirtualDom(
|
||||
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
|
||||
self,
|
||||
{
|
||||
schema,
|
||||
Comp
|
||||
}
|
||||
Comp,
|
||||
},
|
||||
)) ||
|
||||
null}
|
||||
</Comp>
|
||||
);
|
||||
//设计模式下的特殊处理
|
||||
null,
|
||||
);
|
||||
// 设计模式下的特殊处理
|
||||
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
|
||||
//对于overlay,dialog等组件为了使其在设计模式下显示,外层需要增加一个div容器
|
||||
// 对于overlay,dialog等组件为了使其在设计模式下显示,外层需要增加一个div容器
|
||||
if (OVERLAY_LIST.includes(schema.componentName)) {
|
||||
const { ref, ...overlayProps } = otherProps;
|
||||
return (
|
||||
@ -373,49 +399,56 @@ export default class BaseEngine extends Component {
|
||||
return schema.loop.map((item, i) => {
|
||||
const loopSelf = {
|
||||
[itemArg]: item,
|
||||
[indexArg]: i
|
||||
[indexArg]: i,
|
||||
};
|
||||
loopSelf.__proto__ = self;
|
||||
return this.__createVirtualDom(
|
||||
{
|
||||
...schema,
|
||||
loop: undefined
|
||||
loop: undefined,
|
||||
},
|
||||
loopSelf,
|
||||
parentInfo,
|
||||
idx ? `${idx}_${i}` : i
|
||||
idx ? `${idx}_${i}` : i,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
__createContextDom = (childCtx, currCtx) => {
|
||||
return (
|
||||
<AppContext.Consumer>
|
||||
{context => {
|
||||
this.context = context;
|
||||
this.__generateCtx(currCtx);
|
||||
this.__render();
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
...childCtx
|
||||
}}
|
||||
>
|
||||
{this.__createDom()}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
);
|
||||
};
|
||||
__createContextDom = (childCtx, currCtx, props) => (
|
||||
<AppContext.Consumer>
|
||||
{(context) => {
|
||||
this.context = context;
|
||||
this.__generateCtx(currCtx);
|
||||
this.__render();
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
...childCtx,
|
||||
}}
|
||||
>
|
||||
{context.engine.createElement(
|
||||
props.__components.Page,
|
||||
{
|
||||
...props,
|
||||
ref: this.__getRef,
|
||||
className: classnames(getFileCssName(props.__schema.fileName), props.className),
|
||||
__id: props.__schema.id,
|
||||
},
|
||||
this.__createDom(),
|
||||
)}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
);
|
||||
|
||||
__parseProps = (props, self, path, info) => {
|
||||
const { schema, Comp, componentInfo = {} } = info;
|
||||
const propInfo = getValue(componentInfo.props, path);
|
||||
const propType = propInfo && propInfo.extra && propInfo.extra.propType;
|
||||
const ignoreParse = schema.__ignoreParse || [];
|
||||
const checkProps = value => {
|
||||
const checkProps = (value) => {
|
||||
if (!propType) return value;
|
||||
return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined;
|
||||
};
|
||||
@ -423,27 +456,26 @@ export default class BaseEngine extends Component {
|
||||
const parseReactNode = (data, params) => {
|
||||
if (isEmpty(params)) {
|
||||
return checkProps(this.__createVirtualDom(data, self, { schema, Comp }));
|
||||
} else {
|
||||
return checkProps(function() {
|
||||
const args = {};
|
||||
if (Array.isArray(params) && params.length) {
|
||||
params.map((item, idx) => {
|
||||
if (typeof item === 'string') {
|
||||
args[item] = arguments[idx];
|
||||
} else if (item && typeof item === 'object') {
|
||||
args[item.name] = arguments[idx];
|
||||
}
|
||||
});
|
||||
}
|
||||
args.__proto__ = self;
|
||||
return self.__createVirtualDom(data, args, { schema, Comp });
|
||||
});
|
||||
}
|
||||
return checkProps(function() {
|
||||
const args = {};
|
||||
if (Array.isArray(params) && params.length) {
|
||||
params.map((item, idx) => {
|
||||
if (typeof item === 'string') {
|
||||
args[item] = arguments[idx];
|
||||
} else if (item && typeof item === 'object') {
|
||||
args[item.name] = arguments[idx];
|
||||
}
|
||||
});
|
||||
}
|
||||
args.__proto__ = self;
|
||||
return self.__createVirtualDom(data, args, { schema, Comp });
|
||||
});
|
||||
};
|
||||
|
||||
// 判断是否需要解析变量
|
||||
if (
|
||||
ignoreParse.some(item => {
|
||||
ignoreParse.some((item) => {
|
||||
if (item instanceof RegExp) {
|
||||
return item.test(path);
|
||||
}
|
||||
@ -469,11 +501,14 @@ export default class BaseEngine extends Component {
|
||||
// 兼容通过componentInfo判断的情况
|
||||
if (isSchema(props)) {
|
||||
return parseReactNode(props);
|
||||
} else if (Array.isArray(props)) {
|
||||
}
|
||||
if (Array.isArray(props)) {
|
||||
return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info)));
|
||||
} else if (typeof props === 'function') {
|
||||
}
|
||||
if (typeof props === 'function') {
|
||||
return checkProps(props.bind(self));
|
||||
} else if (props && typeof props === 'object') {
|
||||
}
|
||||
if (props && typeof props === 'object') {
|
||||
if (props.$$typeof) return checkProps(props);
|
||||
const res = {};
|
||||
forEach(props, (val, key) => {
|
||||
@ -484,7 +519,8 @@ export default class BaseEngine extends Component {
|
||||
res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info);
|
||||
});
|
||||
return checkProps(res);
|
||||
} else if (typeof props === 'string') {
|
||||
}
|
||||
if (typeof props === 'string') {
|
||||
return checkProps(props.trim());
|
||||
}
|
||||
return checkProps(props);
|
||||
@ -493,15 +529,19 @@ export default class BaseEngine extends Component {
|
||||
get utils() {
|
||||
return this.appHelper && this.appHelper.utils;
|
||||
}
|
||||
|
||||
get constants() {
|
||||
return this.appHelper && this.appHelper.constants;
|
||||
}
|
||||
|
||||
get history() {
|
||||
return this.appHelper && this.appHelper.history;
|
||||
}
|
||||
|
||||
get location() {
|
||||
return this.appHelper && this.appHelper.location;
|
||||
}
|
||||
|
||||
get match() {
|
||||
return this.appHelper && this.appHelper.match;
|
||||
}
|
||||
|
||||
@ -9,15 +9,17 @@ const debug = Debug('engine:block');
|
||||
|
||||
export default class BlockEngine extends BaseEngine {
|
||||
static dislayName = 'block-engine';
|
||||
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object
|
||||
__schema: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
__schema: {},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`block.getDerivedStateFromProps`);
|
||||
debug('block.getDerivedStateFromProps');
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
@ -39,18 +41,22 @@ export default class BlockEngine extends BaseEngine {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`block.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`block.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`block.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`block.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidCatch() {
|
||||
await super.componentDidCatch(...arguments);
|
||||
debug(`block.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
@ -69,13 +75,13 @@ export default class BlockEngine extends BaseEngine {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.__getRef}
|
||||
// ref={this.__getRef}
|
||||
className={classnames('luna-block', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||
id={id}
|
||||
style={style}
|
||||
>
|
||||
{this.__createContextDom({
|
||||
blockContext: this
|
||||
blockContext: this,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -9,15 +9,17 @@ const debug = Debug('engine:comp');
|
||||
|
||||
export default class CompEngine extends BaseEngine {
|
||||
static dislayName = 'comp-engine';
|
||||
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object
|
||||
__schema: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
__schema: {},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`comp.getDerivedStateFromProps`);
|
||||
debug('comp.getDerivedStateFromProps');
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
@ -28,7 +30,7 @@ export default class CompEngine extends BaseEngine {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.__generateCtx({
|
||||
component: this
|
||||
component: this,
|
||||
});
|
||||
const schema = props.__schema || {};
|
||||
this.state = this.__parseData(schema.state || {});
|
||||
@ -41,18 +43,22 @@ export default class CompEngine extends BaseEngine {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`comp.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`comp.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`comp.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`comp.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidCatch(e) {
|
||||
super.componentDidCatch(...arguments);
|
||||
debug(`comp.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
@ -67,23 +73,25 @@ export default class CompEngine extends BaseEngine {
|
||||
|
||||
debug(`comp.render - ${__schema.fileName}`);
|
||||
|
||||
const { id, className, style, noContainer } = this.__parseData(__schema.props);
|
||||
const {
|
||||
id, className, style, noContainer
|
||||
} = this.__parseData(__schema.props);
|
||||
|
||||
if (noContainer) {
|
||||
return this.__createContextDom(
|
||||
{
|
||||
compContext: this,
|
||||
blockContext: this
|
||||
blockContext: this,
|
||||
},
|
||||
{
|
||||
component: this
|
||||
}
|
||||
component: this,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.__getRef}
|
||||
// ref={this.__getRef}
|
||||
className={classnames('luna-comp', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||
id={this.props.id || id}
|
||||
style={{ ...style, ...this.props.style }}
|
||||
@ -91,11 +99,11 @@ export default class CompEngine extends BaseEngine {
|
||||
{this.__createContextDom(
|
||||
{
|
||||
compContext: this,
|
||||
blockContext: this
|
||||
blockContext: this,
|
||||
},
|
||||
{
|
||||
component: this
|
||||
}
|
||||
component: this,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,24 +1,43 @@
|
||||
import { Component, createElement } from 'rax';
|
||||
/* eslint-disable */
|
||||
import { Component, createElement as raxCreateElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import * as isEmpty from 'lodash/isEmpty';
|
||||
import findDOMNode from 'rax-find-dom-node';
|
||||
import { isFileSchema, goldlog } from '../utils';
|
||||
import AppContext from '../context/appContext';
|
||||
import Page from './pageEngine';
|
||||
import CustomComp from './compEngine';
|
||||
import Block from './blockEngine';
|
||||
import Temp from './tempEngine';
|
||||
import PageEngine from './pageEngine';
|
||||
import ComponentEngine from './compEngine';
|
||||
import BlockEngine from './blockEngine';
|
||||
import TempEngine from './tempEngine';
|
||||
import BaseEngine from './base';
|
||||
|
||||
const debug = Debug('engine:entry');
|
||||
const ENGINE_COMPS = {
|
||||
Page,
|
||||
Component: CustomComp,
|
||||
Block,
|
||||
Temp,
|
||||
PageEngine,
|
||||
ComponentEngine,
|
||||
BlockEngine,
|
||||
TempEngine,
|
||||
};
|
||||
|
||||
class FaultComponent extends Component {
|
||||
render() {
|
||||
// FIXME: errorlog
|
||||
console.error('render error', this.props);
|
||||
return <Div>RenderError</Div>;
|
||||
}
|
||||
}
|
||||
|
||||
class NotFoundComponent extends Component {
|
||||
render() {
|
||||
console.error('component not found', this.props);
|
||||
return <Div {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default class Engine extends Component {
|
||||
static dislayName = 'engine';
|
||||
|
||||
static propTypes = {
|
||||
appHelper: PropTypes.object,
|
||||
components: PropTypes.object,
|
||||
@ -27,8 +46,12 @@ export default class Engine extends Component {
|
||||
suspended: PropTypes.bool,
|
||||
schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
onCompGetRef: PropTypes.func,
|
||||
onCompGetCtx: PropTypes.func
|
||||
onCompGetCtx: PropTypes.func,
|
||||
customCreateElement: PropTypes.func,
|
||||
notFoundComponent: PropTypes.element,
|
||||
faultComponent: PropTypes.element,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
appHelper: null,
|
||||
components: {},
|
||||
@ -37,7 +60,7 @@ export default class Engine extends Component {
|
||||
suspended: false,
|
||||
schema: {},
|
||||
onCompGetRef: () => {},
|
||||
onCompGetCtx: () => {}
|
||||
onCompGetCtx: () => {},
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
@ -51,9 +74,9 @@ export default class Engine extends Component {
|
||||
'EXP',
|
||||
{
|
||||
action: 'appear',
|
||||
value: !!this.props.designMode
|
||||
value: !!this.props.designMode,
|
||||
},
|
||||
'engine'
|
||||
'engine',
|
||||
);
|
||||
debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`);
|
||||
}
|
||||
@ -74,15 +97,60 @@ export default class Engine extends Component {
|
||||
return !nextProps.suspended;
|
||||
}
|
||||
|
||||
__getRef = ref => {
|
||||
getNotFoundComponent() {
|
||||
const { notFoundComponent } = this.props;
|
||||
return notFoundComponent || NotFoundComponent;
|
||||
}
|
||||
|
||||
getFaultComponent() {
|
||||
const { faultComponent } = this.props;
|
||||
return faultComponent || FaultComponent;
|
||||
}
|
||||
|
||||
__getRef = (ref) => {
|
||||
const { schema, onCompGetRef } = this.props;
|
||||
this.__ref = ref;
|
||||
if (ref) {
|
||||
this.props.onCompGetRef(this.props.schema, ref, true);
|
||||
onCompGetRef(schema, ref, true);
|
||||
}
|
||||
};
|
||||
|
||||
patchDidCatch(Comp) {
|
||||
if (Comp.patchedCatch) {
|
||||
return;
|
||||
}
|
||||
Comp.patchedCatch = true;// eslint-disable-line
|
||||
Comp.getDerivedStateFromError = (error) => ({ engineRenderError: true, error });// eslint-disable-line
|
||||
// const engine = this;
|
||||
const originRender = Comp.prototype.render;
|
||||
Comp.prototype.render = function() {
|
||||
if (this.state && this.state.engineRenderError) {
|
||||
this.state.engineRenderError = false;
|
||||
return engine.createElement(engine.getFaultComponent(), {
|
||||
...this.props,
|
||||
error: this.state.error,
|
||||
});
|
||||
}
|
||||
return originRender.call(this);
|
||||
};
|
||||
const originShouldComponentUpdate = Comp.prototype.shouldComponentUpdate;
|
||||
Comp.prototype.shouldComponentUpdate = (nextProps, nextState) => {// eslint-disable-line
|
||||
if (nextState && nextState.engineRenderError) {
|
||||
return true;
|
||||
}
|
||||
return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true;// eslint-disable-line
|
||||
};
|
||||
}
|
||||
|
||||
createElement(Comp, props, children) {
|
||||
const { customCreateElement } = this.props;
|
||||
// TODO: enable in runtime mode?
|
||||
this.patchDidCatch(Component);
|
||||
return (customCreateElement || raxCreateElement)(Comp, props, children);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { schema, designMode, appHelper, components, componentsMap } = this.props;
|
||||
const { schema, designMode, appHelper, components } = this.props;
|
||||
if (isEmpty(schema)) {
|
||||
return null;
|
||||
}
|
||||
@ -91,15 +159,22 @@ export default class Engine extends Component {
|
||||
}
|
||||
debug('entry.render');
|
||||
const allComponents = { ...ENGINE_COMPS, ...components };
|
||||
const Comp = allComponents[schema.componentName];
|
||||
const { componentName } = schema;
|
||||
// const Comp = allComponents[schema.componentName];
|
||||
let Comp = allComponents[componentName] || ENGINE_COMPS[`${componentName}Engine`];
|
||||
if (Comp && Comp.prototype) {
|
||||
// const proto = Comp.prototype;
|
||||
if (!(Comp.prototype instanceof BaseEngine)) {
|
||||
Comp = ENGINE_COMPS[`${componentName}Engine`];
|
||||
}
|
||||
}
|
||||
if (Comp) {
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
appHelper,
|
||||
components: allComponents,
|
||||
componentsMap,
|
||||
engine: this
|
||||
engine: this,
|
||||
}}
|
||||
>
|
||||
<Comp
|
||||
@ -107,9 +182,9 @@ export default class Engine extends Component {
|
||||
ref={this.__getRef}
|
||||
__appHelper={appHelper}
|
||||
__components={allComponents}
|
||||
__componentsMap={componentsMap}
|
||||
__schema={schema}
|
||||
__designMode={designMode}
|
||||
// __id={schema.id}
|
||||
{...this.props}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
|
||||
@ -2,6 +2,7 @@ import { createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import classnames from 'classnames';
|
||||
import AppContext from '../context/appContext';
|
||||
import { isSchema, getFileCssName } from '../utils';
|
||||
import BaseEngine from './base';
|
||||
|
||||
@ -9,15 +10,19 @@ const debug = Debug('engine:page');
|
||||
|
||||
export default class PageEngine extends BaseEngine {
|
||||
static dislayName = 'page-engine';
|
||||
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object
|
||||
};
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
__schema: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
__schema: {},
|
||||
};
|
||||
|
||||
static contextType = AppContext;
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`page.getDerivedStateFromProps`);
|
||||
debug('page.getDerivedStateFromProps');
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
@ -28,10 +33,11 @@ export default class PageEngine extends BaseEngine {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.__generateCtx({
|
||||
page: this
|
||||
page: this,
|
||||
});
|
||||
const schema = props.__schema || {};
|
||||
this.state = this.__parseData(schema.state || {});
|
||||
|
||||
this.__initDataSource(props);
|
||||
this.__setLifeCycleMethods('constructor', arguments);
|
||||
|
||||
@ -42,31 +48,103 @@ export default class PageEngine extends BaseEngine {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`page.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`page.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`page.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`page.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidCatch() {
|
||||
await super.componentDidCatch(...arguments);
|
||||
debug(`page.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { __schema } = this.props;
|
||||
const { __schema, __components } = this.props;
|
||||
if (!isSchema(__schema, true) || __schema.componentName !== 'Page') {
|
||||
return '页面schema结构异常!';
|
||||
}
|
||||
debug(`page.render - ${__schema.fileName}`);
|
||||
|
||||
const { id, className, style } = this.__parseData(__schema.props);
|
||||
const {
|
||||
id, className, style, autoLoading, defaultHeight = 300, loading
|
||||
} = this.__parseData(__schema.props);
|
||||
|
||||
const { Page } = __components;
|
||||
if (Page) {
|
||||
// const { engine } = this.context || {};
|
||||
return (
|
||||
<AppContext.Consumer>
|
||||
{(context) => {
|
||||
this.context = context;
|
||||
{
|
||||
/* this.__generateCtx(currCtx); */
|
||||
}
|
||||
this.__render();
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
pageContext: this,
|
||||
blockContext: this,
|
||||
}}
|
||||
>
|
||||
{this.context.engine.createElement(
|
||||
Page,
|
||||
{
|
||||
...this.props,
|
||||
ref: this.__getRef,
|
||||
className: classnames(getFileCssName(__schema.fileName), className, this.props.className),
|
||||
__id: __schema.id,
|
||||
},
|
||||
this.__createDom(),
|
||||
)}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
const renderContent = () => (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
pageContext: this,
|
||||
blockContext: this,
|
||||
}}
|
||||
>
|
||||
{this.__createDom()}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
|
||||
// if (autoLoading || loading !== undefined) {
|
||||
// return (
|
||||
// <Loading
|
||||
// size="medium"
|
||||
// visible={!!(this.__showPlaceholder || loading)}
|
||||
// style={{
|
||||
// height: this.__showPlaceholder ? defaultHeight : 'auto',
|
||||
// display: 'block',
|
||||
// ...style,
|
||||
// }}
|
||||
// className={classnames('luna-page', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||
// id={id}
|
||||
// >
|
||||
// {!this.__showPlaceholder && renderContent()}
|
||||
// </Loading>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -75,15 +153,7 @@ export default class PageEngine extends BaseEngine {
|
||||
id={id}
|
||||
style={style}
|
||||
>
|
||||
{this.__createContextDom(
|
||||
{
|
||||
pageContext: this,
|
||||
blockContext: this
|
||||
},
|
||||
{
|
||||
page: this
|
||||
}
|
||||
)}
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,13 +8,15 @@ import BaseEngine from './base';
|
||||
const debug = Debug('engine:temp');
|
||||
export default class TempEngine extends BaseEngine {
|
||||
static dislayName = 'temp-engine';
|
||||
|
||||
static propTypes = {
|
||||
__ctx: PropTypes.object,
|
||||
__schema: PropTypes.object
|
||||
__schema: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
__ctx: {},
|
||||
__schema: {}
|
||||
__schema: {},
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
@ -27,7 +29,7 @@ export default class TempEngine extends BaseEngine {
|
||||
componentDidMount() {
|
||||
const ctx = this.props.__ctx;
|
||||
if (!ctx) return;
|
||||
const setState = ctx.setState;
|
||||
const { setState } = ctx;
|
||||
this.cacheSetState = setState;
|
||||
ctx.setState = (...args) => {
|
||||
setState.call(ctx, ...args);
|
||||
@ -35,9 +37,11 @@ export default class TempEngine extends BaseEngine {
|
||||
};
|
||||
debug(`temp.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
debug(`temp.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const ctx = this.props.__ctx;
|
||||
if (!ctx || !this.cacheSetState) return;
|
||||
@ -45,6 +49,7 @@ export default class TempEngine extends BaseEngine {
|
||||
delete this.cacheSetState;
|
||||
debug(`temp.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
componentDidCatch(e) {
|
||||
console.warn(e);
|
||||
debug(`temp.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
@ -59,7 +64,10 @@ export default class TempEngine extends BaseEngine {
|
||||
debug(`temp.render - ${__schema.fileName}`);
|
||||
|
||||
return (
|
||||
<div ref={this.__getRef} className="luna-temp">
|
||||
<div
|
||||
// ref={this.__getRef}
|
||||
className="luna-temp"
|
||||
>
|
||||
<AppContext.Provider value={{ ...this.context, ...__ctx }}>{this.__createDom()}</AppContext.Provider>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { createElement, Component } from 'rax';
|
||||
|
||||
export default function(Comp) {
|
||||
export default function (Comp) {
|
||||
class CompWrapper extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Comp {...this.props} />;
|
||||
return createElement(Comp, {
|
||||
...this.props,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,9 @@ export const throttle = _throttle;
|
||||
export const debounce = _debounce;
|
||||
export const serialize = _serialize;
|
||||
export const jsonuri = _jsonuri;
|
||||
export { get, post, jsonp, mtop, request } from './request';
|
||||
export {
|
||||
get, post, jsonp, mtop, request
|
||||
} from './request';
|
||||
|
||||
const ReactIs = require('react-is');
|
||||
|
||||
@ -43,18 +45,18 @@ const PropTypes2 = factoryWithTypeCheckers(ReactIs.isElement, true);
|
||||
const EXPRESSION_TYPE = {
|
||||
JSEXPRESSION: 'JSExpression',
|
||||
JSFUNCTION: 'JSFunction',
|
||||
JSSLOT: 'JSSlot'
|
||||
JSSLOT: 'JSSlot',
|
||||
};
|
||||
const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/;
|
||||
const hasSymbol = typeof Symbol === 'function' && Symbol['for'];
|
||||
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol['for']('react.forward_ref') : 0xead0;
|
||||
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
|
||||
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
|
||||
const debug = Debug('utils:index');
|
||||
|
||||
const ENV = {
|
||||
TBE: 'TBE',
|
||||
WEBIDE: 'WEB-IDE',
|
||||
VSCODE: 'VSCODE',
|
||||
WEB: 'WEB'
|
||||
WEB: 'WEB',
|
||||
};
|
||||
|
||||
/**
|
||||
@ -63,7 +65,7 @@ const ENV = {
|
||||
*/
|
||||
export function isSchema(schema, ignoreArr) {
|
||||
if (isEmpty(schema)) return false;
|
||||
if (!ignoreArr && Array.isArray(schema)) return schema.every(item => isSchema(item));
|
||||
if (!ignoreArr && Array.isArray(schema)) return schema.every((item) => isSchema(item));
|
||||
return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props)));
|
||||
}
|
||||
|
||||
@ -83,10 +85,10 @@ export function inSameDomain() {
|
||||
|
||||
export function getFileCssName(fileName) {
|
||||
if (!fileName) return;
|
||||
let name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
return ('luna-' + name)
|
||||
const name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
return `luna-${name}`
|
||||
.split('-')
|
||||
.filter(p => !!p)
|
||||
.filter((p) => !!p)
|
||||
.join('-');
|
||||
}
|
||||
|
||||
@ -97,9 +99,8 @@ export function isJSFunction(obj) {
|
||||
return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSFUNCTION === obj.type;
|
||||
}
|
||||
export function isJSExpression(obj) {
|
||||
//兼容两种写法,有js构造表达式的情况
|
||||
const isJSExpressionObj =
|
||||
obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string';
|
||||
// 兼容两种写法,有js构造表达式的情况
|
||||
const isJSExpressionObj = obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string';
|
||||
const isJSExpressionStr = typeof obj === 'string' && EXPRESSION_REG.test(obj.trim());
|
||||
return isJSExpressionObj || isJSExpressionStr;
|
||||
}
|
||||
@ -109,13 +110,11 @@ export function isJSExpression(obj) {
|
||||
* @description 等待函数
|
||||
*/
|
||||
export function wait(ms) {
|
||||
return new Promise(resolve => setTimeout(() => resolve(true), ms));
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), ms));
|
||||
}
|
||||
|
||||
export function curry(Comp, hocs = []) {
|
||||
return hocs.reverse().reduce((pre, cur) => {
|
||||
return cur(pre);
|
||||
}, Comp);
|
||||
return hocs.reverse().reduce((pre, cur) => cur(pre), Comp);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,16 +129,14 @@ export function getParentWinValue(key) {
|
||||
|
||||
export function getValue(obj, path, defaultValue) {
|
||||
if (isEmpty(obj) || typeof obj !== 'object') return defaultValue;
|
||||
const res = path.split('.').reduce((pre, cur) => {
|
||||
return pre && pre[cur];
|
||||
}, obj);
|
||||
const res = path.split('.').reduce((pre, cur) => pre && pre[cur], obj);
|
||||
if (res === undefined) return defaultValue;
|
||||
return res;
|
||||
}
|
||||
|
||||
export function parseObj(schemaStr) {
|
||||
if (typeof schemaStr !== 'string') return schemaStr;
|
||||
//默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
|
||||
// 默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
|
||||
try {
|
||||
if (inSameDomain() && window.parent.__newFunc) {
|
||||
return window.parent.__newFunc(`"use strict"; return ${schemaStr}`)();
|
||||
@ -157,7 +154,7 @@ export function parseSearch(search) {
|
||||
const str = search.replace(/^\?/, '');
|
||||
const paramStr = str.split('&');
|
||||
const res = {};
|
||||
paramStr.forEach(item => {
|
||||
paramStr.forEach((item) => {
|
||||
const regRes = item.split('=');
|
||||
if (regRes[0] && regRes[1]) {
|
||||
res[regRes[0]] = decodeURIComponent(regRes[1]);
|
||||
@ -172,7 +169,7 @@ export function fastClone(obj) {
|
||||
|
||||
// 更新obj的内容但不改变obj的指针
|
||||
export function fillObj(receiver = {}, ...suppliers) {
|
||||
Object.keys(receiver).forEach(item => {
|
||||
Object.keys(receiver).forEach((item) => {
|
||||
delete receiver[item];
|
||||
});
|
||||
Object.assign(receiver, ...suppliers);
|
||||
@ -181,9 +178,7 @@ export function fillObj(receiver = {}, ...suppliers) {
|
||||
|
||||
// 中划线转驼峰
|
||||
export function toHump(name) {
|
||||
return name.replace(/\-(\w)/g, function(all, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
return name.replace(/\-(\w)/g, (all, letter) => letter.toUpperCase());
|
||||
}
|
||||
// 驼峰转中划线
|
||||
export function toLine(name) {
|
||||
@ -192,7 +187,7 @@ export function toLine(name) {
|
||||
|
||||
// 获取当前环境
|
||||
export function getEnv() {
|
||||
const userAgent = navigator.userAgent;
|
||||
const { userAgent } = navigator;
|
||||
const isVscode = /Electron\//.test(userAgent);
|
||||
if (isVscode) return ENV.VSCODE;
|
||||
const isTheia = window.is_theia === true;
|
||||
@ -206,12 +201,14 @@ export function getEnv() {
|
||||
* @param {*} 用户自定义配置
|
||||
*/
|
||||
export function comboSkeletonConfig(defaultConfig = {}, customConfig) {
|
||||
const { skeleton, theme, addons, hooks, shortCuts, extensions, constants, utils, i18n } = customConfig || {};
|
||||
const {
|
||||
skeleton, theme, addons, hooks, shortCuts, extensions, constants, utils, i18n
|
||||
} = customConfig || {};
|
||||
|
||||
if (skeleton && skeleton.handler && typeof skeleton.handler === 'function') {
|
||||
return skeleton.handler({
|
||||
skeleton,
|
||||
...defaultConfig
|
||||
...defaultConfig,
|
||||
});
|
||||
}
|
||||
|
||||
@ -219,37 +216,37 @@ export function comboSkeletonConfig(defaultConfig = {}, customConfig) {
|
||||
const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard');
|
||||
const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP'];
|
||||
const i18nConfig = {};
|
||||
localeList.forEach(key => {
|
||||
localeList.forEach((key) => {
|
||||
i18nConfig[key] = {
|
||||
...(defaultConfig.i18n && defaultConfig.i18n[key]),
|
||||
...(i18n && i18n[key])
|
||||
...(i18n && i18n[key]),
|
||||
};
|
||||
});
|
||||
return {
|
||||
skeleton,
|
||||
theme: {
|
||||
...defaultConfig.theme,
|
||||
...theme
|
||||
...theme,
|
||||
},
|
||||
addons: {
|
||||
...defaultConfig.addons,
|
||||
...addons
|
||||
...addons,
|
||||
},
|
||||
hooks: [...(defaultConfig.hooks || []), ...(hooks || [])],
|
||||
shortCuts: Object.values({
|
||||
...defaultShortCuts,
|
||||
...customShortCuts
|
||||
...customShortCuts,
|
||||
}),
|
||||
extensions: {
|
||||
...defaultConfig.extensions,
|
||||
...extensions
|
||||
...extensions,
|
||||
},
|
||||
constants: {
|
||||
...defaultConfig.constants,
|
||||
...constants
|
||||
...constants,
|
||||
},
|
||||
utils: [...(defaultConfig.utils || []), ...(utils || [])],
|
||||
i18n: i18nConfig
|
||||
i18n: i18nConfig,
|
||||
};
|
||||
}
|
||||
|
||||
@ -271,9 +268,7 @@ export function generateI18n(locale = 'zh-CN', messages = {}) {
|
||||
* @param {*} Comp 需要判断的组件
|
||||
*/
|
||||
export function acceptsRef(Comp) {
|
||||
return (
|
||||
(Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent)
|
||||
);
|
||||
return !!Comp;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -288,7 +283,7 @@ export function goldlog(gmKey, params = {}, logKey = 'other') {
|
||||
const goKey = serializeParams({
|
||||
sdkVersion: pkg.version,
|
||||
env: getEnv(),
|
||||
...params
|
||||
...params,
|
||||
});
|
||||
if (sendIDEMessage) {
|
||||
sendIDEMessage({
|
||||
@ -296,8 +291,8 @@ export function goldlog(gmKey, params = {}, logKey = 'other') {
|
||||
data: {
|
||||
logKey: `/iceluna.core.${logKey}`,
|
||||
gmKey,
|
||||
goKey
|
||||
}
|
||||
goKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST');
|
||||
@ -307,7 +302,7 @@ export function goldlog(gmKey, params = {}, logKey = 'other') {
|
||||
export function generateUtils(utils, utilsConfig) {
|
||||
if (!Array.isArray(utilsConfig)) return { ...utils };
|
||||
const res = {};
|
||||
utilsConfig.forEach(item => {
|
||||
utilsConfig.forEach((item) => {
|
||||
if (!item.name || !item.type || !item.content) return;
|
||||
if (item.type === 'function' && typeof item.content === 'function') {
|
||||
res[item.name] = item.content;
|
||||
@ -327,7 +322,7 @@ export function setClipboardData(str) {
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
reject('复制失败,请重试!', err);
|
||||
});
|
||||
} else {
|
||||
@ -337,7 +332,7 @@ export function setClipboardData(str) {
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
try {
|
||||
let successful = document.execCommand('copy');
|
||||
const successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
document.body.removeChild(textArea);
|
||||
resolve();
|
||||
@ -357,10 +352,10 @@ export function getClipboardData() {
|
||||
} else if (navigator.clipboard) {
|
||||
return navigator.clipboard
|
||||
.readText()
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
reject('粘贴板获取失败', err);
|
||||
});
|
||||
} else {
|
||||
@ -382,14 +377,15 @@ export function transformToPromise(input) {
|
||||
|
||||
export function moveArrayItem(arr, sourceIdx, distIdx, direction) {
|
||||
if (
|
||||
!Array.isArray(arr) ||
|
||||
sourceIdx === distIdx ||
|
||||
sourceIdx < 0 ||
|
||||
sourceIdx >= arr.length ||
|
||||
distIdx < 0 ||
|
||||
distIdx >= arr.length
|
||||
)
|
||||
!Array.isArray(arr)
|
||||
|| sourceIdx === distIdx
|
||||
|| sourceIdx < 0
|
||||
|| sourceIdx >= arr.length
|
||||
|| distIdx < 0
|
||||
|| distIdx >= arr.length
|
||||
) {
|
||||
return arr;
|
||||
}
|
||||
const item = arr[sourceIdx];
|
||||
if (direction === 'after') {
|
||||
arr.splice(distIdx + 1, 0, item);
|
||||
@ -407,7 +403,7 @@ export function moveArrayItem(arr, sourceIdx, distIdx, direction) {
|
||||
export function transformArrayToMap(arr, key, overwrite = true) {
|
||||
if (isEmpty(arr) || !Array.isArray(arr)) return {};
|
||||
const res = {};
|
||||
arr.forEach(item => {
|
||||
arr.forEach((item) => {
|
||||
const curKey = item[key];
|
||||
if (item[key] === undefined) return;
|
||||
if (res[curKey] && !overwrite) return;
|
||||
@ -426,13 +422,13 @@ export function checkPropTypes(value, name, rule, componentName) {
|
||||
}
|
||||
const err = rule(
|
||||
{
|
||||
[name]: value
|
||||
[name]: value,
|
||||
},
|
||||
name,
|
||||
componentName,
|
||||
'prop',
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
ReactPropTypesSecret,
|
||||
);
|
||||
if (err) {
|
||||
console.warn(err);
|
||||
@ -441,10 +437,11 @@ export function checkPropTypes(value, name, rule, componentName) {
|
||||
}
|
||||
|
||||
export function transformSchemaToPure(obj) {
|
||||
const pureObj = obj => {
|
||||
const pureObj = (obj) => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => pureObj(item));
|
||||
} else if (typeof obj === 'object') {
|
||||
return obj.map((item) => pureObj(item));
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
// 对于undefined及null直接返回
|
||||
if (!obj) return obj;
|
||||
const res = {};
|
||||
@ -460,10 +457,11 @@ export function transformSchemaToPure(obj) {
|
||||
}
|
||||
|
||||
export function transformSchemaToStandard(obj) {
|
||||
const standardObj = obj => {
|
||||
const standardObj = (obj) => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => standardObj(item));
|
||||
} else if (typeof obj === 'object') {
|
||||
return obj.map((item) => standardObj(item));
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
// 对于undefined及null直接返回
|
||||
if (!obj) return obj;
|
||||
const res = {};
|
||||
@ -472,7 +470,7 @@ export function transformSchemaToStandard(obj) {
|
||||
if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') {
|
||||
res[key] = {
|
||||
type: 'JSSlot',
|
||||
value: standardObj(val)
|
||||
value: standardObj(val),
|
||||
};
|
||||
// table特殊处理
|
||||
if (key === 'cell') {
|
||||
@ -483,16 +481,18 @@ export function transformSchemaToStandard(obj) {
|
||||
}
|
||||
});
|
||||
return res;
|
||||
} else if (typeof obj === 'function') {
|
||||
}
|
||||
if (typeof obj === 'function') {
|
||||
return {
|
||||
type: 'JSFunction',
|
||||
value: obj.toString()
|
||||
value: obj.toString(),
|
||||
};
|
||||
} else if (typeof obj === 'string' && EXPRESSION_REG.test(obj.trim())) {
|
||||
}
|
||||
if (typeof obj === 'string' && EXPRESSION_REG.test(obj.trim())) {
|
||||
const regRes = obj.trim().match(EXPRESSION_REG);
|
||||
return {
|
||||
type: 'JSExpression',
|
||||
value: (regRes && regRes[1]) || ''
|
||||
value: (regRes && regRes[1]) || '',
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
@ -504,9 +504,8 @@ export function transformStringToFunction(str) {
|
||||
if (typeof str !== 'string') return str;
|
||||
if (inSameDomain() && window.parent.__newFunc) {
|
||||
return window.parent.__newFunc(`"use strict"; return ${str}`)();
|
||||
} else {
|
||||
return new Function(`"use strict"; return ${str}`)();
|
||||
}
|
||||
return new Function(`"use strict"; return ${str}`)();
|
||||
}
|
||||
|
||||
export function addCssTag(id, content) {
|
||||
@ -524,11 +523,11 @@ export function addCssTag(id, content) {
|
||||
|
||||
// 注册快捷
|
||||
export function registShortCuts(config, appHelper) {
|
||||
const keyboardFilter = (keymaster.filter = event => {
|
||||
const keyboardFilter = (keymaster.filter = (event) => {
|
||||
const eTarget = event.target || event.srcElement;
|
||||
const tagName = eTarget.tagName;
|
||||
const { tagName } = eTarget;
|
||||
const isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
||||
const isContenteditable = target => {
|
||||
const isContenteditable = (target) => {
|
||||
while (target) {
|
||||
if (target.contentEditable === 'true') return true;
|
||||
target = target.parentNode;
|
||||
@ -536,24 +535,23 @@ export function registShortCuts(config, appHelper) {
|
||||
return false;
|
||||
};
|
||||
if (isInput || isContenteditable(eTarget)) {
|
||||
if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); //禁止触发chrome原生的页面保存或查找
|
||||
if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); // 禁止触发chrome原生的页面保存或查找
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const ideMessage = appHelper.utils && appHelper.utils.ideMessage;
|
||||
|
||||
//复制
|
||||
// 复制
|
||||
if (!document.copyListener) {
|
||||
document.copyListener = e => {
|
||||
document.copyListener = (e) => {
|
||||
if (!keyboardFilter(e) || appHelper.isCopying) return;
|
||||
const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey];
|
||||
if (!schema || !isSchema(schema)) return;
|
||||
appHelper.isCopying = true;
|
||||
const schemaStr = serialize(transformSchemaToPure(schema), {
|
||||
unsafe: true
|
||||
unsafe: true,
|
||||
});
|
||||
setClipboardData(schemaStr)
|
||||
.then(() => {
|
||||
@ -561,7 +559,7 @@ export function registShortCuts(config, appHelper) {
|
||||
appHelper.emit('schema.copy', schemaStr, schema);
|
||||
appHelper.isCopying = false;
|
||||
})
|
||||
.catch(errMsg => {
|
||||
.catch((errMsg) => {
|
||||
ideMessage && ideMessage('error', errMsg);
|
||||
appHelper.isCopying = false;
|
||||
});
|
||||
@ -572,16 +570,16 @@ export function registShortCuts(config, appHelper) {
|
||||
}
|
||||
}
|
||||
|
||||
//粘贴
|
||||
// 粘贴
|
||||
if (!document.pasteListener) {
|
||||
const doPaste = (e, text) => {
|
||||
if (!keyboardFilter(e) || appHelper.isPasting) return;
|
||||
const schemaHelper = appHelper.schemaHelper;
|
||||
const { schemaHelper } = appHelper;
|
||||
let targetKey = appHelper.activeKey;
|
||||
let direction = 'after';
|
||||
const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
|
||||
if (!targetKey || topKey === targetKey) {
|
||||
const schemaHelper = appHelper.schemaHelper;
|
||||
const { schemaHelper } = appHelper;
|
||||
const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
|
||||
if (!topKey) return;
|
||||
targetKey = topKey;
|
||||
@ -599,36 +597,36 @@ export function registShortCuts(config, appHelper) {
|
||||
appHelper.emit('material.add', {
|
||||
schema,
|
||||
targetKey,
|
||||
direction
|
||||
direction,
|
||||
});
|
||||
appHelper.isPasting = false;
|
||||
appHelper.emit('schema.paste', schema);
|
||||
};
|
||||
document.pasteListener = e => {
|
||||
document.pasteListener = (e) => {
|
||||
const clipboardData = e.clipboardData || window.clipboardData;
|
||||
const text = clipboardData && clipboardData.getData('text');
|
||||
doPaste(e, text);
|
||||
};
|
||||
document.addEventListener('paste', document.pasteListener);
|
||||
if (getParentWinValue('vscode')) {
|
||||
keymaster('command+v', e => {
|
||||
keymaster('command+v', (e) => {
|
||||
const sendIDEMessage = getParentWinValue('sendIDEMessage');
|
||||
sendIDEMessage &&
|
||||
sendIDEMessage({
|
||||
action: 'readClipboard'
|
||||
sendIDEMessage
|
||||
&& sendIDEMessage({
|
||||
action: 'readClipboard',
|
||||
})
|
||||
.then(text => {
|
||||
.then((text) => {
|
||||
doPaste(e, text);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(config || []).forEach(item => {
|
||||
keymaster(item.keyboard, ev => {
|
||||
(config || []).forEach((item) => {
|
||||
keymaster(item.keyboard, (ev) => {
|
||||
ev.preventDefault();
|
||||
item.handler(ev, appHelper, keymaster);
|
||||
});
|
||||
@ -637,7 +635,7 @@ export function registShortCuts(config, appHelper) {
|
||||
|
||||
// 取消注册快捷
|
||||
export function unRegistShortCuts(config) {
|
||||
(config || []).forEach(item => {
|
||||
(config || []).forEach((item) => {
|
||||
keymaster.unbind(item.keyboard);
|
||||
});
|
||||
if (getParentWinValue('vscode')) {
|
||||
@ -657,13 +655,17 @@ export function unRegistShortCuts(config) {
|
||||
export function parseData(schema, self) {
|
||||
if (isJSExpression(schema)) {
|
||||
return parseExpression(schema, self);
|
||||
} else if (typeof schema === 'string') {
|
||||
}
|
||||
if (typeof schema === 'string') {
|
||||
return schema.trim();
|
||||
} else if (Array.isArray(schema)) {
|
||||
return schema.map(item => parseData(item, self));
|
||||
} else if (typeof schema === 'function') {
|
||||
}
|
||||
if (Array.isArray(schema)) {
|
||||
return schema.map((item) => parseData(item, self));
|
||||
}
|
||||
if (typeof schema === 'function') {
|
||||
return schema.bind(self);
|
||||
} else if (typeof schema === 'object') {
|
||||
}
|
||||
if (typeof schema === 'object') {
|
||||
// 对于undefined及null直接返回
|
||||
if (!schema) return schema;
|
||||
const res = {};
|
||||
@ -676,13 +678,13 @@ export function parseData(schema, self) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/*全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */
|
||||
/* 全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */
|
||||
export function parseExpression(str, self) {
|
||||
try {
|
||||
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
|
||||
contextArr.push('return ');
|
||||
let tarStr;
|
||||
//向前兼容,支持标准协议新格式
|
||||
// 向前兼容,支持标准协议新格式
|
||||
if (typeof str === 'string') {
|
||||
const regRes = str.trim().match(EXPRESSION_REG);
|
||||
tarStr = regRes[1];
|
||||
@ -691,7 +693,7 @@ export function parseExpression(str, self) {
|
||||
}
|
||||
tarStr = tarStr.replace(/this(\W|$)/g, (a, b) => `__self${b}`);
|
||||
tarStr = contextArr.join('\n') + tarStr;
|
||||
//默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
|
||||
// 默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
|
||||
if (inSameDomain() && window.parent.__newFunc) {
|
||||
return window.parent.__newFunc(tarStr)(self);
|
||||
}
|
||||
@ -707,12 +709,13 @@ export function parseExpression(str, self) {
|
||||
* @param {*} componentInfo
|
||||
*/
|
||||
export function hasReactNodeFuncProps(componentInfo) {
|
||||
const isReactNodeFuncProps = config => {
|
||||
const isReactNodeFuncProps = (config) => {
|
||||
if (config.type === 'ReactNode') {
|
||||
return config.props && config.props.type === 'function';
|
||||
} else if (config.type === 'Mixin') {
|
||||
}
|
||||
if (config.type === 'Mixin') {
|
||||
return config.props && config.props.reactNodeProps && config.props.reactNodeProps.type === 'function';
|
||||
}
|
||||
};
|
||||
return componentInfo && (componentInfo.props || []).some(item => isReactNodeFuncProps(item));
|
||||
return componentInfo && (componentInfo.props || []).some((item) => isReactNodeFuncProps(item));
|
||||
}
|
||||
|
||||
21
packages/rax-simulator-renderer/build.json
Normal file
21
packages/rax-simulator-renderer/build.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"build-plugin-rax-component",
|
||||
{
|
||||
"type": "rax",
|
||||
"targets": ["web"]
|
||||
}
|
||||
],
|
||||
[
|
||||
"build-plugin-component",
|
||||
{
|
||||
"filename": "rax-simulator-renderer",
|
||||
"library": "SimulatorRenderer",
|
||||
"libraryExport": "default",
|
||||
"libraryTarget": "umd"
|
||||
}
|
||||
],
|
||||
"./build.plugin.js"
|
||||
]
|
||||
}
|
||||
0
packages/rax-simulator-renderer/build.plugin.js
Normal file
0
packages/rax-simulator-renderer/build.plugin.js
Normal file
53
packages/rax-simulator-renderer/package.json
Normal file
53
packages/rax-simulator-renderer/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@ali/lowcode-rax-simulator-renderer",
|
||||
"version": "0.8.29",
|
||||
"description": "rax simulator renderer for alibaba lowcode designer",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
"license": "MIT",
|
||||
"files": [],
|
||||
"scripts": {
|
||||
"cloud-build": "build-scripts build --skip-demo",
|
||||
"test": "ava",
|
||||
"test:snapshot": "ava --update-snapshots"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ali/lowcode-designer": "^0.9.23",
|
||||
"@ali/lowcode-react-renderer": "^0.8.12",
|
||||
"@ali/lowcode-types": "^0.8.9",
|
||||
"@ali/lowcode-utils": "^0.8.10",
|
||||
"@ali/recore-rax": "^1.2.4",
|
||||
"@ali/vu-css-style": "^1.0.2",
|
||||
"@recore/obx": "^1.0.8",
|
||||
"@recore/obx-react": "^1.0.7",
|
||||
"classnames": "^2.2.6",
|
||||
"driver-universal": "^3.1.3",
|
||||
"lodash": "^4.17.19",
|
||||
"react": "^16",
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alib/build-scripts": "^0.1.18",
|
||||
"@recore/obx": "^1.0.8",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/node": "^13.7.1",
|
||||
"@types/rax": "^1.0.0",
|
||||
"@types/react": "^16",
|
||||
"@types/react-dom": "^16",
|
||||
"build-plugin-component": "^0.2.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rax": "^1.1.0"
|
||||
},
|
||||
"ava": {
|
||||
"compileEnhancements": false,
|
||||
"snapshotDir": "test/fixtures/__snapshots__",
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,226 @@
|
||||
import { Component, createElement } from 'rax';
|
||||
import findDOMNode from 'rax-find-dom-node';
|
||||
import { each, get, omit } from 'lodash';
|
||||
import { getView, setNativeNode, createNodeStyleSheet } from '../renderUtils';
|
||||
|
||||
import { FaultComponent, HiddenComponent, UnknownComponent } from '../UnusualComponent';
|
||||
|
||||
export interface ILeaf {
|
||||
leaf: any;
|
||||
}
|
||||
export default class Leaf extends Component<ILeaf, {}> {
|
||||
static displayName = 'Leaf';
|
||||
|
||||
state = {
|
||||
hasError: false,
|
||||
};
|
||||
|
||||
willDetach: any[];
|
||||
|
||||
styleSheet: any;
|
||||
|
||||
context: any;
|
||||
refs: any;
|
||||
|
||||
componentWillMount() {
|
||||
const { leaf } = this.props;
|
||||
this.willDetach = [
|
||||
leaf.onPropsChange(() => {
|
||||
// 强制刷新
|
||||
this.setState(this.state);
|
||||
}),
|
||||
leaf.onChildrenChange(() => {
|
||||
// 强制刷新
|
||||
this.setState(this.state);
|
||||
}),
|
||||
leaf.onStatusChange((status: { dropping: boolean }, field: string) => {
|
||||
// console.log({...status}, field)
|
||||
if (status.dropping !== false) {
|
||||
// 当 dropping 为 Insertion 对象时,强制渲染会出错,原因待查
|
||||
return;
|
||||
}
|
||||
if (field === 'dragging' || field === 'dropping' || field === 'pseudo' || field === 'visibility') {
|
||||
// 强制刷新
|
||||
this.setState(this.state);
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
/**
|
||||
* while props replaced
|
||||
* bind the new event on it
|
||||
*/
|
||||
leaf.onPropsReplace(() => {
|
||||
this.willDetach[0]();
|
||||
this.willDetach[0] = leaf.onPropsChange(() => {
|
||||
// 强制刷新
|
||||
this.setState(this.state);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.modifyDOM();
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
// forceUpdate 的替代方案
|
||||
return true;
|
||||
// const pageCanRefresh = this.leaf.getPage().canRefresh();
|
||||
// if (pageCanRefresh) {
|
||||
// return pageCanRefresh;
|
||||
// }
|
||||
// const getExtProps = obj => {
|
||||
// const { leaf, ...props } = obj;
|
||||
// return props;
|
||||
// };
|
||||
// return !shallowEqual(getExtProps(this.props), getExtProps(nextProps));
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.modifyDOM();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.willDetach) {
|
||||
this.willDetach.forEach((off) => off());
|
||||
}
|
||||
setNativeNode(this.props.leaf, null);
|
||||
}
|
||||
|
||||
componentDidCatch() {
|
||||
this.setState({ hasError: true }, () => {
|
||||
console.log('error');
|
||||
});
|
||||
}
|
||||
|
||||
modifyDOM() {
|
||||
const shell = findDOMNode(this);
|
||||
const { leaf } = this.props;
|
||||
// 与 React 不同,rax 的 findDOMNode 找不到节点时,
|
||||
// shell 会是 <!-- empty -->,而不是 null,
|
||||
// 所以这里进行是否为注释的判断
|
||||
if (shell && shell.nodeType !== window.Node.COMMENT_NODE) {
|
||||
setNativeNode(leaf, shell);
|
||||
if (leaf.getStatus('dragging')) {
|
||||
get(shell, 'classList').add('engine-dragging');
|
||||
} else {
|
||||
get(shell, 'classList').remove('engine-dragging');
|
||||
}
|
||||
each(get(shell, 'classList'), (cls) => {
|
||||
if (cls.substring(0, 8) === '-pseudo-') {
|
||||
get(shell, 'classList').remove(cls);
|
||||
}
|
||||
});
|
||||
const pseudo = leaf.getStatus('pseudo');
|
||||
if (pseudo) {
|
||||
get(shell, 'classList').add(`-pseudo-${pseudo}`);
|
||||
}
|
||||
} else {
|
||||
setNativeNode(leaf, null);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = omit(this.props, ['leaf']);
|
||||
const { leaf } = this.props;
|
||||
const componentName = leaf.getComponentName();
|
||||
|
||||
const View = getView(componentName);
|
||||
|
||||
const newProps = {
|
||||
_componentName: componentName,
|
||||
};
|
||||
|
||||
if (!View) {
|
||||
return createElement(UnknownComponent, {
|
||||
// _componentName: componentName,
|
||||
...newProps,
|
||||
});
|
||||
}
|
||||
|
||||
let staticProps = {
|
||||
...leaf.getStaticProps(false),
|
||||
...props,
|
||||
_componentName: componentName,
|
||||
_leaf: leaf,
|
||||
componentId: leaf.getId(),
|
||||
};
|
||||
|
||||
if (!leaf.isVisibleInPane()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!leaf.isVisible()) {
|
||||
return createElement(HiddenComponent, {
|
||||
...staticProps,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.hasError) {
|
||||
return createElement(FaultComponent, {
|
||||
// _componentName: componentName,
|
||||
...newProps,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.styleSheet) {
|
||||
this.styleSheet.parentNode.removeChild(this.styleSheet);
|
||||
}
|
||||
|
||||
this.styleSheet = createNodeStyleSheet(staticProps);
|
||||
|
||||
if (leaf.ableToModifyChildren()) {
|
||||
const children = leaf
|
||||
.getChildren()
|
||||
.filter((child: any) => child.getComponentName() !== 'Slot')
|
||||
.map((child: any) =>
|
||||
createElement(Leaf, {
|
||||
key: child.getId(),
|
||||
leaf: child,
|
||||
}),
|
||||
);
|
||||
// const insertion = leaf.getStatus('dropping');
|
||||
// InsertionGhost 都是React节点,用Rax渲染会报错,后面这些节点需要通过Rax组件来实现
|
||||
// if (children.length < 1 && insertion && insertion.getIndex() !== null) {
|
||||
|
||||
// //children = [];
|
||||
// children = [<InsertionGhost key="insertion" />];
|
||||
// } else if (insertion && insertion.isNearEdge()) {
|
||||
// if (insertion.isNearAfter()) {
|
||||
// children.push(<InsertionGhost key="insertion" />);
|
||||
// } else {
|
||||
// children.unshift(<InsertionGhost key="insertion" />);
|
||||
// }
|
||||
// }
|
||||
staticProps = {
|
||||
...staticProps,
|
||||
...this.processSlots(this.props.leaf.getChildren()),
|
||||
};
|
||||
|
||||
return createElement(
|
||||
View,
|
||||
{
|
||||
...staticProps,
|
||||
},
|
||||
children,
|
||||
);
|
||||
}
|
||||
|
||||
return createElement(View, {
|
||||
...staticProps,
|
||||
});
|
||||
}
|
||||
|
||||
processSlots(children: Rax.RaxNodeArray) {
|
||||
const slots: any = {};
|
||||
children &&
|
||||
children.length &&
|
||||
children.forEach((child: any) => {
|
||||
if (child.getComponentName() === 'Slot') {
|
||||
slots[child.getPropValue('slotName')] = <Leaf key={child.getId()} leaf={child} />;
|
||||
}
|
||||
});
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import { Component } from 'rax';
|
||||
import lg from '@ali/vu-logger';
|
||||
|
||||
import './index.less';
|
||||
|
||||
export class UnknownComponent extends Component {
|
||||
props: {
|
||||
_componentName: string;
|
||||
};
|
||||
|
||||
render() {
|
||||
lg.log('ERROR_NO_COMPONENT_VIEW');
|
||||
lg.error('Error component information:', this.props);
|
||||
return <div className="engine-unknow-component">组件 {this.props._componentName} 无视图,请打开控制台排查</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class FaultComponent extends Component {
|
||||
props: {
|
||||
_componentName: string;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div className="engine-fault-component">组件 {this.props._componentName} 渲染错误,请打开控制台排查</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class HiddenComponent extends Component {
|
||||
render() {
|
||||
return <div className="engine-hidden-component">在本页面不显示</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default { FaultComponent, HiddenComponent, UnknownComponent };
|
||||
@ -0,0 +1,82 @@
|
||||
import isObject from 'lodash/isObject';
|
||||
import { toCss } from '@ali/vu-css-style';
|
||||
|
||||
const engine = (window as any).VisualEngine;
|
||||
const { Trunk, Viewport } = engine;
|
||||
|
||||
export const NativeNodeCache: any = {};
|
||||
|
||||
function ucfirst(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
export function shallowEqual(obj: { [key: string]: string }, tObj: { [key: string]: string }) {
|
||||
for (const i in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, i) && obj[i] !== tObj[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function createNodeStyleSheet(props: any) {
|
||||
if (props && props.fieldId) {
|
||||
let styleProp = props.__style__;
|
||||
|
||||
if (isObject(styleProp)) {
|
||||
styleProp = toCss(styleProp);
|
||||
}
|
||||
|
||||
if (typeof styleProp === 'string') {
|
||||
const s = document.createElement('style');
|
||||
const cssId = '_style_pesudo_' + props.fieldId;
|
||||
const cssClass = '_css_pesudo_' + props.fieldId;
|
||||
|
||||
props.className = cssClass;
|
||||
s.setAttribute('type', 'text/css');
|
||||
s.setAttribute('id', cssId);
|
||||
document.getElementsByTagName('head')[0].appendChild(s);
|
||||
|
||||
s.appendChild(
|
||||
document.createTextNode(
|
||||
styleProp
|
||||
.replace(/(\d+)rpx/g, (a, b) => {
|
||||
return `${b / 2}px`;
|
||||
})
|
||||
.replace(/:root/g, '.' + cssClass),
|
||||
),
|
||||
);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setNativeNode(leaf: any, node: Rax.RaxNode) {
|
||||
const id = leaf.getId();
|
||||
if (NativeNodeCache[id] === node) {
|
||||
return;
|
||||
}
|
||||
NativeNodeCache[id] = node;
|
||||
leaf.mountChange();
|
||||
}
|
||||
|
||||
export function getView(componentName: string) {
|
||||
// let view = new Trunk().getPrototypeView(componentName);
|
||||
let view = Trunk.getPrototypeView(componentName);
|
||||
if (!view) {
|
||||
return null;
|
||||
}
|
||||
const viewport = Viewport.getViewport();
|
||||
if (viewport) {
|
||||
const [mode, device] = viewport.split('-', 2).map(ucfirst);
|
||||
if (view.hasOwnProperty(device)) {
|
||||
view = view[device];
|
||||
}
|
||||
|
||||
if (view.hasOwnProperty(mode)) {
|
||||
view = view[mode];
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
4
packages/rax-simulator-renderer/src/host.ts
Normal file
4
packages/rax-simulator-renderer/src/host.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// NOTE: 仅做类型标注,切勿做其它用途
|
||||
import { BuiltinSimulatorHost } from '@ali/lowcode-designer';
|
||||
|
||||
export const host: BuiltinSimulatorHost = (window as any).LCSimulatorHost;
|
||||
1
packages/rax-simulator-renderer/src/image.d.ts
vendored
Normal file
1
packages/rax-simulator-renderer/src/image.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'rax-find-dom-node';
|
||||
7
packages/rax-simulator-renderer/src/index.ts
Normal file
7
packages/rax-simulator-renderer/src/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import renderer from './renderer';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).SimulatorRenderer = renderer;
|
||||
}
|
||||
|
||||
export default renderer;
|
||||
210
packages/rax-simulator-renderer/src/obx-rax/derivation.ts
Normal file
210
packages/rax-simulator-renderer/src/obx-rax/derivation.ts
Normal file
@ -0,0 +1,210 @@
|
||||
import { IObservable, IDepTreeNode, addObserver, removeObserver } from './observable/observable';
|
||||
import { globalState } from './ global-state';
|
||||
|
||||
export enum DerivationState {
|
||||
// before being run or (outside batch and not being observed)
|
||||
// at this point derivation is not holding any data about dependency tree
|
||||
NOT_TRACKING = -1,
|
||||
// no shallow dependency changed since last computation
|
||||
// won't recalculate derivation
|
||||
UP_TO_DATE = 0,
|
||||
// don't have to recompute on every dependency change, but only when it's needed
|
||||
MYBE_DIRTY = 1,
|
||||
// A shallow dependency has changed since last computation and the derivation
|
||||
// will need to recompute when it's needed next.
|
||||
DIRTY = 2,
|
||||
}
|
||||
|
||||
export interface IDerivation extends IDepTreeNode {
|
||||
observing: IObservable[];
|
||||
dependenciesState: DerivationState;
|
||||
newObserving?: null | IObservable[];
|
||||
runId?: number; // Id of the current run of a derivation.
|
||||
unboundDepsCount?: number; // amount of dependencies used by the derivation in this run, which has not been bound yet.
|
||||
onBecomeDirty(): void;
|
||||
}
|
||||
|
||||
export class CaughtException {
|
||||
constructor(public cause: any) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
export function isCaughtException(e: any): e is CaughtException {
|
||||
return e instanceof CaughtException;
|
||||
}
|
||||
|
||||
interface ModifiedValue {
|
||||
ifModified(): void;
|
||||
}
|
||||
|
||||
export function isModifiedValue(v: any): v is ModifiedValue {
|
||||
return v.ifModified ? true : false;
|
||||
}
|
||||
|
||||
export function shouldCompute(derivation: IDerivation): boolean {
|
||||
switch (derivation.dependenciesState) {
|
||||
case DerivationState.UP_TO_DATE:
|
||||
return false;
|
||||
case DerivationState.NOT_TRACKING:
|
||||
case DerivationState.DIRTY:
|
||||
return true;
|
||||
case DerivationState.MYBE_DIRTY: {
|
||||
const prevUntracked = untrackedStart();
|
||||
const obs = derivation.observing;
|
||||
const l = obs.length;
|
||||
for (let i = 0; i < l; i++) {
|
||||
const obj = obs[i];
|
||||
if (isModifiedValue(obj)) {
|
||||
obj.ifModified();
|
||||
}
|
||||
|
||||
if ((derivation.dependenciesState as any) === DerivationState.DIRTY) {
|
||||
untrackedEnd(prevUntracked);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
changeDependenciesStateTo0(derivation);
|
||||
untrackedEnd(prevUntracked);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runDerivedFunction(derivation: IDerivation, f: (...args: any[]) => any, context?: any) {
|
||||
const prevTracking = globalState.trackingDerivation;
|
||||
// pre allocate array allocation + room for variation in deps
|
||||
derivation.newObserving = new Array(derivation.observing.length + 100);
|
||||
derivation.unboundDepsCount = 0;
|
||||
derivation.runId = ++globalState.runId;
|
||||
globalState.trackingDerivation = derivation;
|
||||
let result;
|
||||
try {
|
||||
result = f.call(context);
|
||||
} catch (e) {
|
||||
result = new CaughtException(e);
|
||||
}
|
||||
globalState.trackingDerivation = prevTracking;
|
||||
changeDependenciesStateTo0(derivation);
|
||||
bindDependencies(derivation);
|
||||
return result;
|
||||
}
|
||||
|
||||
function bindDependencies(derivation: IDerivation) {
|
||||
const prevObserving = derivation.observing;
|
||||
const observing = (derivation.observing = derivation.newObserving!);
|
||||
let lowestNewObservingDerivationState = DerivationState.UP_TO_DATE;
|
||||
|
||||
// Go through all new observables and check diffValue: (this list can contain duplicates):
|
||||
// 0: first occurrence, change to 1 and keep it
|
||||
// 1: extra occurrence, drop it
|
||||
let i0 = 0;
|
||||
let l = derivation.unboundDepsCount!;
|
||||
for (let i = 0; i < l; i++) {
|
||||
const dep = observing[i];
|
||||
if (!dep.diffFlag) {
|
||||
dep.diffFlag = true;
|
||||
if (i0 !== i) {
|
||||
observing[i0] = dep;
|
||||
}
|
||||
i0++;
|
||||
}
|
||||
|
||||
// Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,
|
||||
// not hitting the condition
|
||||
if (((dep as any) as IDerivation).dependenciesState > lowestNewObservingDerivationState) {
|
||||
lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState;
|
||||
}
|
||||
}
|
||||
observing.length = i0;
|
||||
|
||||
derivation.newObserving = null;
|
||||
// Go through all old observables and check diffValue: (it is unique after last bindDependencies)
|
||||
// 0: it's not in new observables, unobserve it
|
||||
// 1: it keeps being observed, don't want to notify it. change to 0
|
||||
l = prevObserving.length;
|
||||
while (l--) {
|
||||
const dep = prevObserving[l];
|
||||
if (!dep.diffFlag) {
|
||||
removeObserver(dep, derivation);
|
||||
}
|
||||
dep.diffFlag = false;
|
||||
}
|
||||
|
||||
// Go through all new observables and check diffValue: (now it should be unique)
|
||||
// 0: it was set to 0 in last loop. don't need to do anything.
|
||||
// 1: it wasn't observed, let's observe it. set back to 0
|
||||
while (i0--) {
|
||||
const dep = observing[i0];
|
||||
if (dep.diffFlag) {
|
||||
dep.diffFlag = false;
|
||||
addObserver(dep, derivation);
|
||||
}
|
||||
}
|
||||
|
||||
// Some new observed derivations may become stale during this derivation computation
|
||||
// so they have had no chance to propagate staleness (#916)
|
||||
if (lowestNewObservingDerivationState !== DerivationState.UP_TO_DATE) {
|
||||
derivation.dependenciesState = lowestNewObservingDerivationState;
|
||||
derivation.onBecomeDirty();
|
||||
}
|
||||
}
|
||||
|
||||
export function clearObserving(derivation: IDerivation) {
|
||||
const obs = derivation.observing;
|
||||
derivation.observing = [];
|
||||
let i = obs.length;
|
||||
while (i--) {
|
||||
removeObserver(obs[i], derivation);
|
||||
}
|
||||
|
||||
derivation.dependenciesState = DerivationState.NOT_TRACKING;
|
||||
}
|
||||
|
||||
export function untracked<T>(action: () => T): T {
|
||||
const prev = untrackedStart();
|
||||
const res = action();
|
||||
untrackedEnd(prev);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function untrackedStart(): IDerivation | null {
|
||||
const prev = globalState.trackingDerivation;
|
||||
globalState.trackingDerivation = null;
|
||||
return prev;
|
||||
}
|
||||
|
||||
export function untrackedEnd(prev: IDerivation | null) {
|
||||
globalState.trackingDerivation = prev;
|
||||
}
|
||||
|
||||
export function changeDependenciesStateTo0(derivation: IDerivation) {
|
||||
if (derivation.dependenciesState === DerivationState.UP_TO_DATE) {
|
||||
return;
|
||||
}
|
||||
derivation.dependenciesState = DerivationState.UP_TO_DATE;
|
||||
|
||||
const obs = derivation.observing;
|
||||
let i = obs.length;
|
||||
while (i--) {
|
||||
obs[i].lowestObserverState = DerivationState.UP_TO_DATE;
|
||||
}
|
||||
}
|
||||
|
||||
export function setDerivationDirty(derivation: IDerivation) {
|
||||
if (derivation.dependenciesState === DerivationState.UP_TO_DATE) {
|
||||
derivation.onBecomeDirty();
|
||||
}
|
||||
derivation.dependenciesState = DerivationState.DIRTY;
|
||||
}
|
||||
|
||||
export function setDerivationMybeDirty(derivation: IDerivation) {
|
||||
if (derivation.dependenciesState === DerivationState.UP_TO_DATE) {
|
||||
derivation.onBecomeDirty();
|
||||
}
|
||||
derivation.dependenciesState = DerivationState.MYBE_DIRTY;
|
||||
}
|
||||
|
||||
export function resetDerivationState(derivation: IDerivation) {
|
||||
derivation.dependenciesState = DerivationState.NOT_TRACKING;
|
||||
}
|
||||
68
packages/rax-simulator-renderer/src/obx-rax/global-state.ts
Normal file
68
packages/rax-simulator-renderer/src/obx-rax/global-state.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { IDerivation } from './derivation';
|
||||
import { Reaction } from './reaction';
|
||||
import { IObservable } from './observable/observable';
|
||||
|
||||
export class Globals {
|
||||
/**
|
||||
* Currently running derivation
|
||||
*/
|
||||
trackingDerivation: IDerivation | null = null;
|
||||
|
||||
/**
|
||||
* Are we running a computation currently? (not a reaction)
|
||||
*/
|
||||
computationDepth = 0;
|
||||
|
||||
/**
|
||||
* Each time a derivation is tracked, it is assigned a unique run-id
|
||||
*/
|
||||
runId = 0;
|
||||
|
||||
/**
|
||||
* 'guid' for general purpose. Will be persisted amongst resets.
|
||||
*/
|
||||
guid = 0;
|
||||
|
||||
/**
|
||||
* Are we in a batch block? (and how many of them)
|
||||
*/
|
||||
inBatch = 0;
|
||||
|
||||
/**
|
||||
* Observables that don't have observers anymore
|
||||
*/
|
||||
pendingUnobservations: IObservable[] = [];
|
||||
|
||||
/**
|
||||
* List of scheduled, not yet executed, reactions.
|
||||
*/
|
||||
pendingReactions: Reaction[] = [];
|
||||
|
||||
/**
|
||||
* Are we currently processing reactions?
|
||||
*/
|
||||
isRunningReactions = false;
|
||||
|
||||
/**
|
||||
* disable dynamic observe
|
||||
*/
|
||||
dynamicObserveDisabled = false;
|
||||
|
||||
reset() {
|
||||
this.trackingDerivation = null;
|
||||
this.computationDepth = 0;
|
||||
this.runId = 0;
|
||||
this.guid = 0;
|
||||
this.inBatch = 0;
|
||||
this.pendingUnobservations = [];
|
||||
this.pendingReactions = [];
|
||||
this.isRunningReactions = false;
|
||||
this.dynamicObserveDisabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const globalState: Globals = new Globals();
|
||||
|
||||
export function getGlobalState(): Globals {
|
||||
return globalState;
|
||||
}
|
||||
56
packages/rax-simulator-renderer/src/obx-rax/next-tick.ts
Normal file
56
packages/rax-simulator-renderer/src/obx-rax/next-tick.ts
Normal file
@ -0,0 +1,56 @@
|
||||
const callbacks: Array<() => void> = [];
|
||||
let pending = false;
|
||||
|
||||
function flush() {
|
||||
pending = false;
|
||||
const copies = callbacks.slice(0);
|
||||
callbacks.length = 0;
|
||||
for (const fn of copies) {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
let timerFlush: () => void;
|
||||
if (typeof process === 'object' && process.nextTick) {
|
||||
timerFlush = () => process.nextTick(flush);
|
||||
} else if (typeof Promise === 'function') {
|
||||
// tslint:disable-line
|
||||
const timer = Promise.resolve(); // tslint:disable-line
|
||||
timerFlush = () => {
|
||||
timer.then(flush);
|
||||
// if (isIOS) setTimeout(noop)
|
||||
};
|
||||
} else if (typeof MessageChannel === 'function') {
|
||||
const channel = new MessageChannel();
|
||||
const port = channel.port2;
|
||||
channel.port1.onmessage = flush;
|
||||
timerFlush = () => {
|
||||
port.postMessage(1);
|
||||
};
|
||||
} else {
|
||||
timerFlush = () => {
|
||||
setTimeout(flush, 0);
|
||||
};
|
||||
}
|
||||
|
||||
export function clearTicks() {
|
||||
callbacks.length = 0;
|
||||
}
|
||||
|
||||
export function nextTick(callback?: () => void): Promise<any> {
|
||||
let _resovle: () => void;
|
||||
|
||||
callbacks.push(() => {
|
||||
callback && callback();
|
||||
_resovle();
|
||||
});
|
||||
|
||||
if (!pending) {
|
||||
pending = true;
|
||||
timerFlush();
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
_resovle = resolve;
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
import { getProxiedValue } from './proxy';
|
||||
|
||||
export function is(a: any, b: any) {
|
||||
return getProxiedValue(a) === getProxiedValue(b);
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
import { isObject } from 'lodash/isObject';
|
||||
import { nextId } from '../utils';
|
||||
import { DerivationState, IDerivation, setDerivationDirty } from '../derivation';
|
||||
import { globalState } from '../ global-state';
|
||||
import Obx, { hasObx, getObx, injectObx, ObxFlag } from './obx';
|
||||
|
||||
export interface IDepTreeNode {
|
||||
id: string;
|
||||
name: string;
|
||||
observing: IObservable[];
|
||||
}
|
||||
|
||||
export interface IObservable extends IDepTreeNode {
|
||||
diffFlag?: boolean;
|
||||
|
||||
observers: Set<IDerivation>;
|
||||
|
||||
// Used to avoid redundant propagations
|
||||
lowestObserverState: DerivationState;
|
||||
// Used to push itself to global.pendingUnobservations at most once per batch.
|
||||
isPendingUnobservation?: boolean;
|
||||
|
||||
// Id of the derivation *run* that last accessed this observable.
|
||||
lastAccessedBy?: number;
|
||||
isBeingObserved?: boolean;
|
||||
onBecomeUnobserved(): void;
|
||||
}
|
||||
|
||||
export function addObserver(observable: IObservable, node: IDerivation) {
|
||||
observable.observers.add(node);
|
||||
|
||||
if (observable.lowestObserverState > node.dependenciesState) {
|
||||
observable.lowestObserverState = node.dependenciesState;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeObserver(observable: IObservable, node: IDerivation) {
|
||||
observable.observers.delete(node);
|
||||
if (observable.observers.size === 0) {
|
||||
// deleting last observer
|
||||
queueForUnobservation(observable);
|
||||
}
|
||||
}
|
||||
|
||||
export function queueForUnobservation(observable: IObservable) {
|
||||
if (!observable.isPendingUnobservation) {
|
||||
observable.isPendingUnobservation = true;
|
||||
globalState.pendingUnobservations.push(observable);
|
||||
}
|
||||
}
|
||||
|
||||
export function startBatch() {
|
||||
globalState.inBatch++;
|
||||
}
|
||||
|
||||
export function endBatch() {
|
||||
if (--globalState.inBatch === 0) {
|
||||
// the batch is actually about to finish, all unobserving should happen here.
|
||||
const list = globalState.pendingUnobservations;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const observable = list[i];
|
||||
observable.isPendingUnobservation = false;
|
||||
if (observable.observers.size === 0) {
|
||||
if (observable.isBeingObserved) {
|
||||
// if this observable had reactive observers, trigger the hooks
|
||||
observable.isBeingObserved = false;
|
||||
observable.onBecomeUnobserved();
|
||||
}
|
||||
}
|
||||
}
|
||||
globalState.pendingUnobservations = [];
|
||||
}
|
||||
}
|
||||
|
||||
export function reportObserved(observable: IObservable): void {
|
||||
const derivation = globalState.trackingDerivation;
|
||||
if (!derivation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (derivation.runId !== observable.lastAccessedBy) {
|
||||
observable.lastAccessedBy = derivation.runId;
|
||||
derivation.newObserving![derivation.unboundDepsCount!++] = observable;
|
||||
if (!observable.isBeingObserved) {
|
||||
observable.isBeingObserved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function propagateChanged(observable: IObservable, force = false) {
|
||||
if (observable.lowestObserverState === DerivationState.DIRTY && !force) {
|
||||
return;
|
||||
}
|
||||
observable.lowestObserverState = DerivationState.DIRTY;
|
||||
observable.observers.forEach((d) => setDerivationDirty(d));
|
||||
}
|
||||
|
||||
export function propagateChangeConfirmed(observable: IObservable) {
|
||||
if (observable.lowestObserverState === DerivationState.DIRTY) {
|
||||
return;
|
||||
}
|
||||
observable.lowestObserverState = DerivationState.DIRTY;
|
||||
|
||||
observable.observers.forEach((d) => {
|
||||
if (d.dependenciesState === DerivationState.MYBE_DIRTY) {
|
||||
d.dependenciesState = DerivationState.DIRTY;
|
||||
} else if (d.dependenciesState === DerivationState.UP_TO_DATE) {
|
||||
observable.lowestObserverState = DerivationState.UP_TO_DATE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function propagateMaybeChanged(observable: IObservable) {
|
||||
if (observable.lowestObserverState !== DerivationState.UP_TO_DATE) {
|
||||
return;
|
||||
}
|
||||
observable.lowestObserverState = DerivationState.MYBE_DIRTY;
|
||||
|
||||
observable.observers.forEach((d) => {
|
||||
if (d.dependenciesState === DerivationState.UP_TO_DATE) {
|
||||
d.dependenciesState = DerivationState.MYBE_DIRTY;
|
||||
d.onBecomeDirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function asObservable(thing: any, obxFlag: ObxFlag): Obx | undefined {
|
||||
if (!isObject(thing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasObx(thing)) {
|
||||
return getObx(thing);
|
||||
}
|
||||
|
||||
if (!Object.isExtensible(thing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = (thing.constructor.name || 'ObservableObject') + '@' + nextId();
|
||||
const ObxContructor = (asObservable as any).getObxContructor(thing);
|
||||
const obx = ObxContructor ? new ObxContructor(name, thing, obxFlag) : null;
|
||||
|
||||
if (obx) {
|
||||
injectObx(thing, obx);
|
||||
return obx;
|
||||
}
|
||||
}
|
||||
|
||||
(asObservable as any).getObxContructor = () => Obx;
|
||||
|
||||
export function observeIterable(items: Iterable<any>, obxFlag: ObxFlag): void {
|
||||
for (const n of items) {
|
||||
asObservable(n, obxFlag);
|
||||
}
|
||||
}
|
||||
|
||||
export function reportPropValue(propValue: any, propFlag: ObxFlag): void {
|
||||
if (propValue == null) return;
|
||||
|
||||
const x = propFlag > ObxFlag.REF ? asObservable(propValue, propFlag) : getObx(propValue);
|
||||
|
||||
if (x) {
|
||||
reportObserved(x);
|
||||
}
|
||||
}
|
||||
|
||||
export function reportChildValue(propValue: any, ownerFlag: ObxFlag): void {
|
||||
if (propValue == null) return;
|
||||
|
||||
const x =
|
||||
ownerFlag > ObxFlag.VAL
|
||||
? asObservable(propValue, ownerFlag === ObxFlag.DEEP ? ObxFlag.DEEP : ObxFlag.VAL)
|
||||
: getObx(propValue);
|
||||
|
||||
if (x) {
|
||||
reportObserved(x);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
import { addHiddenProp } from '../utils';
|
||||
import { observeIterable, reportChildValue } from './observable';
|
||||
import { supportProxy, isProxied, createProxy, SYMBOL_PROXY, SYMBOL_RAW, getProxiedValue, getRawValue } from './proxy';
|
||||
import Obx, { getObx, SYMBOL_OBX, ObxFlag } from './obx';
|
||||
import { setPrototypeOf } from '../../utils/set-prototype-of';
|
||||
|
||||
export function childFlag(flag: ObxFlag) {
|
||||
return flag === ObxFlag.DEEP ? ObxFlag.DEEP : ObxFlag.VAL;
|
||||
}
|
||||
|
||||
function isValidArrayIndex(val: any, limit: number = -1): boolean {
|
||||
const n = parseFloat(String(val));
|
||||
return n >= 0 && Math.floor(n) === n && isFinite(val) && (limit < 0 || n < limit);
|
||||
}
|
||||
|
||||
export default class ObxArray extends Obx<any[]> {
|
||||
constructor(name: string, target: any[], obxFlag: ObxFlag = ObxFlag.DEEP) {
|
||||
super(name, target, obxFlag);
|
||||
|
||||
if (supportProxy) {
|
||||
this.target = createProxy(target, arrayProxyTraps);
|
||||
} else if (obxFlag > ObxFlag.VAL) {
|
||||
observeIterable(target, childFlag(obxFlag));
|
||||
}
|
||||
setPrototypeOf(target, arrayMethods);
|
||||
}
|
||||
|
||||
set(key: PropertyKey, val: any) {
|
||||
const target = this.target;
|
||||
if (isValidArrayIndex(key)) {
|
||||
const index = Number(key);
|
||||
target.length = Math.max(target.length, index);
|
||||
target.splice(index, 1, val);
|
||||
return;
|
||||
}
|
||||
super.set(key, val);
|
||||
}
|
||||
|
||||
del(key: PropertyKey) {
|
||||
if (isValidArrayIndex(key, this.target.length)) {
|
||||
this.target.splice(Number(key), 1);
|
||||
return;
|
||||
}
|
||||
super.del(key);
|
||||
}
|
||||
}
|
||||
|
||||
// ======= patches ========
|
||||
const arrayProto = Array.prototype;
|
||||
const arrayMethods = Object.create(arrayProto);
|
||||
|
||||
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
|
||||
const original = (arrayProto as any)[method];
|
||||
addHiddenProp(arrayMethods, method, function mutator(this: any[], ...args: any[]) {
|
||||
const obx = getObx(this) as ObxArray;
|
||||
const proxied = isProxied(this);
|
||||
const length = this.length;
|
||||
// apply to rawTarget avoid to call Proxy.set
|
||||
const result = original.apply(getRawValue(this), args);
|
||||
|
||||
let changed = true;
|
||||
let inserted;
|
||||
switch (method) {
|
||||
case 'push':
|
||||
case 'unshift':
|
||||
inserted = args;
|
||||
changed = inserted.length > 0;
|
||||
break;
|
||||
case 'splice':
|
||||
inserted = args.slice(2);
|
||||
changed = inserted.length > 0 || this.length !== length;
|
||||
break;
|
||||
case 'pop':
|
||||
case 'shift':
|
||||
changed = this.length !== length;
|
||||
break;
|
||||
}
|
||||
if (!proxied && obx.obxFlag > ObxFlag.VAL) {
|
||||
if (inserted && inserted.length > 0) {
|
||||
observeIterable(inserted, childFlag(obx.obxFlag));
|
||||
}
|
||||
}
|
||||
|
||||
if (obx && changed) {
|
||||
obx.reportChange();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
const arrayProxyTraps: ProxyHandler<any[]> = {
|
||||
has(rawTarget, name: PropertyKey) {
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_RAW) {
|
||||
return true;
|
||||
}
|
||||
return name in rawTarget;
|
||||
},
|
||||
get(rawTarget, name: PropertyKey) {
|
||||
if (name === SYMBOL_RAW) {
|
||||
return rawTarget;
|
||||
}
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in arrayMethods) {
|
||||
return (rawTarget as any)[name];
|
||||
}
|
||||
|
||||
if (isValidArrayIndex(name)) {
|
||||
const v = rawTarget[Number(name)];
|
||||
const obx = getObx(rawTarget);
|
||||
if (obx) {
|
||||
reportChildValue(v, obx.obxFlag);
|
||||
}
|
||||
return getProxiedValue(v);
|
||||
}
|
||||
|
||||
return getProxiedValue((rawTarget as any)[name]);
|
||||
},
|
||||
set(rawTarget, name: PropertyKey, value: any) {
|
||||
if (name === 'length') {
|
||||
rawTarget[name] = value;
|
||||
return true;
|
||||
}
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in arrayMethods) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isValidArrayIndex(name)) {
|
||||
const index = Number(name);
|
||||
rawTarget.length = Math.max(rawTarget.length, index);
|
||||
rawTarget.splice(index, 1, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
(rawTarget as any)[name] = value;
|
||||
return true;
|
||||
},
|
||||
deleteProperty(rawTarget, name: PropertyKey) {
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in arrayMethods) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isValidArrayIndex(name)) {
|
||||
rawTarget.splice(Number(name), 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
delete (rawTarget as any)[name];
|
||||
return true;
|
||||
},
|
||||
preventExtensions() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { DecoratorTarget } from '../decorators';
|
||||
import Obx from './obx';
|
||||
|
||||
export default class ObxInstance extends Obx<DecoratorTarget> {
|
||||
set(key: PropertyKey, val: any) {
|
||||
const target = this.target;
|
||||
if (key in target) {
|
||||
(target as any)[key] = val;
|
||||
return;
|
||||
}
|
||||
|
||||
super.set(key, val);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import Obx, { ObxFlag } from './obx';
|
||||
import { patchMutator, patchAccessor } from './obx-set';
|
||||
import { setPrototypeOf } from '../../utils/set-prototype-of';
|
||||
|
||||
type MapType = Map<PropertyKey, any>;
|
||||
|
||||
export default class ObxMap extends Obx<MapType> {
|
||||
constructor(name: string, target: MapType, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
||||
super(name, target, obxFlag);
|
||||
|
||||
setPrototypeOf(target, mapMethods);
|
||||
}
|
||||
|
||||
has(key: PropertyKey) {
|
||||
return this.target.has(key);
|
||||
}
|
||||
|
||||
set(key: PropertyKey, val: any) {
|
||||
this.target.set(key, val);
|
||||
}
|
||||
|
||||
get(key: PropertyKey) {
|
||||
return this.target.get(key);
|
||||
}
|
||||
|
||||
del(key: PropertyKey) {
|
||||
this.target.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// ======= Map ========
|
||||
const mapProto = Map.prototype;
|
||||
const mapMethods = Object.create(mapProto);
|
||||
|
||||
patchMutator(['set', 'clear', 'delete'], mapProto, mapMethods);
|
||||
|
||||
patchAccessor(['values', 'entries', Symbol.iterator, 'forEach', 'get'], mapProto, mapMethods);
|
||||
@ -0,0 +1,93 @@
|
||||
import { walk } from '../utils';
|
||||
import { supportProxy, createProxy, getProxiedValue, SYMBOL_PROXY, SYMBOL_RAW } from './proxy';
|
||||
import Obx, { getObx, SYMBOL_OBX, ObxFlag } from './obx';
|
||||
import { defineObxProperty, ensureObxProperty } from './obx-property';
|
||||
import { hasOwnProperty } from '../../utils/has-own-property';
|
||||
|
||||
function propFlag(flag: ObxFlag) {
|
||||
return flag === ObxFlag.DEEP ? ObxFlag.DEEP : flag - 1;
|
||||
}
|
||||
|
||||
export default class ObxObject extends Obx<object> {
|
||||
constructor(name: string, target: object, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
||||
super(name, target, obxFlag);
|
||||
|
||||
if (supportProxy) {
|
||||
this.target = createProxy(target, objectProxyTraps);
|
||||
} else if (obxFlag > ObxFlag.REF) {
|
||||
walk(target as any, (obj, key, val) => {
|
||||
defineObxProperty(obj, key, val, undefined, propFlag(obxFlag));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
set(key: PropertyKey, val: any) {
|
||||
const target = this.target;
|
||||
if (key in target && !(key in objectProto)) {
|
||||
(target as any)[key] = val;
|
||||
return;
|
||||
}
|
||||
|
||||
super.set(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
const objectProto = Object.prototype;
|
||||
|
||||
const objectProxyTraps: ProxyHandler<any> = {
|
||||
has(rawTarget, name: PropertyKey) {
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_RAW) {
|
||||
return true;
|
||||
}
|
||||
return name in rawTarget;
|
||||
},
|
||||
get(rawTarget, name: PropertyKey) {
|
||||
if (name === SYMBOL_RAW) {
|
||||
return rawTarget;
|
||||
}
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in objectProto) {
|
||||
return rawTarget[name];
|
||||
}
|
||||
|
||||
if (hasOwnProperty(rawTarget, name)) {
|
||||
const obx = getObx(rawTarget);
|
||||
if (obx) {
|
||||
ensureObxProperty(rawTarget, name, propFlag(obx.obxFlag));
|
||||
}
|
||||
}
|
||||
|
||||
return getProxiedValue(rawTarget[name]);
|
||||
},
|
||||
set(rawTarget, name: PropertyKey, value: any) {
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in objectProto) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasOwnProperty(rawTarget, name)) {
|
||||
const obx = getObx(rawTarget);
|
||||
if (obx) {
|
||||
defineObxProperty(rawTarget, name, value, undefined, propFlag(obx.obxFlag));
|
||||
obx.reportChange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
rawTarget[name] = value;
|
||||
return true;
|
||||
},
|
||||
deleteProperty(rawTarget, name: PropertyKey) {
|
||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || !hasOwnProperty(rawTarget, name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete rawTarget[name];
|
||||
const obx = getObx(rawTarget);
|
||||
if (obx) {
|
||||
obx.reportChange();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
preventExtensions() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,212 @@
|
||||
import { globalState } from '../ global-state';
|
||||
import {
|
||||
untrackedStart,
|
||||
untrackedEnd,
|
||||
IDerivation,
|
||||
DerivationState,
|
||||
runDerivedFunction,
|
||||
shouldCompute,
|
||||
isCaughtException,
|
||||
clearObserving,
|
||||
setDerivationDirty,
|
||||
} from '../derivation';
|
||||
import {
|
||||
IObservable,
|
||||
reportObserved,
|
||||
startBatch,
|
||||
endBatch,
|
||||
propagateChangeConfirmed,
|
||||
propagateMaybeChanged,
|
||||
reportPropValue,
|
||||
} from './observable';
|
||||
import { nextId } from '../utils';
|
||||
import { ObxFlag, SYMBOL_OBX, getObx } from './obx';
|
||||
import { getProxiedValue } from './proxy';
|
||||
import { is } from './compare';
|
||||
import { isPrimitive } from '../utils/is-primitive';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import { hasOwnProperty } from '../utils/has-own-property';
|
||||
|
||||
function getVer(obj: any): number {
|
||||
const obx = getObx(obj);
|
||||
return obx ? obx.localVer : 0;
|
||||
}
|
||||
|
||||
export function asNewValue(obj: object) {
|
||||
const obx = getObx(obj);
|
||||
if (obx) {
|
||||
obx.localVer = obx.localVer + 1;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const DIRTY = Symbol('dirty');
|
||||
|
||||
export default class ObxProperty implements IObservable, IDerivation {
|
||||
id = nextId();
|
||||
observing: IObservable[] = [];
|
||||
observers = new Set<IDerivation>();
|
||||
dependenciesState = DerivationState.NOT_TRACKING;
|
||||
lowestObserverState = DerivationState.UP_TO_DATE;
|
||||
|
||||
private isComputing = false;
|
||||
private isRunningSetter = false;
|
||||
private pending = false;
|
||||
private pendingValue: any = null;
|
||||
private objectVer = 0;
|
||||
|
||||
constructor(
|
||||
public name: string,
|
||||
public scope: object | null,
|
||||
private getter?: (...rest: any[]) => any,
|
||||
private setter?: (v: any) => void,
|
||||
private value?: any,
|
||||
private obxFlag: ObxFlag = ObxFlag.DEEP,
|
||||
) {}
|
||||
|
||||
onBecomeDirty() {
|
||||
propagateMaybeChanged(this);
|
||||
}
|
||||
|
||||
onBecomeUnobserved() {
|
||||
clearObserving(this);
|
||||
}
|
||||
|
||||
ifModified() {
|
||||
if (this.getter && shouldCompute(this)) {
|
||||
startBatch();
|
||||
if (this.computeValue()) {
|
||||
propagateChangeConfirmed(this);
|
||||
this.objectVer = getVer(this.value);
|
||||
}
|
||||
endBatch();
|
||||
} else if (this.pending) {
|
||||
this.pending = false;
|
||||
const oldValue = this.value;
|
||||
this.value = this.pendingValue;
|
||||
if (!this.is(oldValue, this.value)) {
|
||||
propagateChangeConfirmed(this);
|
||||
this.objectVer = getVer(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private is(oldValue: any, value: any) {
|
||||
return oldValue !== DIRTY && is(oldValue, value) && (isPrimitive(value) || getVer(value) === this.objectVer);
|
||||
}
|
||||
|
||||
get() {
|
||||
invariant(!this.isComputing, `Cycle detected in computation ${this.name}`, this.getter);
|
||||
|
||||
reportObserved(this);
|
||||
|
||||
this.ifModified();
|
||||
const result = this.value!;
|
||||
|
||||
if (isCaughtException(result)) {
|
||||
throw result.cause;
|
||||
}
|
||||
|
||||
reportPropValue(result, this.obxFlag);
|
||||
|
||||
return getProxiedValue(result);
|
||||
}
|
||||
|
||||
set(value: any) {
|
||||
invariant(!this.isRunningSetter, `The setter of observable value '${this.name}' is trying to update itself.`);
|
||||
|
||||
invariant(Boolean(this.setter || !this.getter), `Cannot assign a new value to readonly value '${this.name}'.`);
|
||||
|
||||
const oldValue = this.pending ? this.pendingValue : this.value;
|
||||
|
||||
if (!isCaughtException(oldValue) && this.is(oldValue, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.setter) {
|
||||
this.pendingValue = value;
|
||||
if (!this.pending) {
|
||||
this.pending = true;
|
||||
propagateMaybeChanged(this);
|
||||
}
|
||||
} else {
|
||||
this.isRunningSetter = true;
|
||||
const prevTracking = untrackedStart();
|
||||
try {
|
||||
this.value = DIRTY;
|
||||
this.setter!.call(this.scope, value);
|
||||
} finally {
|
||||
untrackedEnd(prevTracking);
|
||||
}
|
||||
this.isRunningSetter = false;
|
||||
setDerivationDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
private computeValue(): boolean {
|
||||
const oldValue = this.value;
|
||||
this.isComputing = true;
|
||||
globalState.computationDepth++;
|
||||
this.value = runDerivedFunction(this, this.getter!, this.scope);
|
||||
globalState.computationDepth--;
|
||||
this.isComputing = false;
|
||||
return isCaughtException(oldValue) || isCaughtException(this.value) || !this.is(oldValue, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
function isObxProperty(descriptor?: PropertyDescriptor) {
|
||||
if (!descriptor || !descriptor.get) {
|
||||
return false;
|
||||
}
|
||||
return (descriptor.get as any)[SYMBOL_OBX] ? true : false;
|
||||
}
|
||||
|
||||
export function ensureObxProperty(obj: any, prop: PropertyKey, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
if (!descriptor || isObxProperty(descriptor)) {
|
||||
return;
|
||||
}
|
||||
defineObxProperty(obj, prop, undefined, descriptor, obxFlag);
|
||||
}
|
||||
|
||||
export function defineObxProperty(
|
||||
obj: object,
|
||||
key: PropertyKey,
|
||||
val: any,
|
||||
descriptor?: PropertyDescriptor,
|
||||
obxFlag: ObxFlag = ObxFlag.DEEP,
|
||||
): void {
|
||||
if (!descriptor) {
|
||||
descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||||
}
|
||||
if (descriptor && descriptor.configurable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val == null && descriptor && hasOwnProperty(descriptor, 'value')) {
|
||||
val = descriptor.value;
|
||||
}
|
||||
|
||||
const getter = descriptor && descriptor.get;
|
||||
const setter = descriptor && descriptor.set;
|
||||
const property = new ObxProperty(String(key), obj, getter, setter, val, obxFlag);
|
||||
const get = () => property.get();
|
||||
(get as any)[SYMBOL_OBX] = property;
|
||||
|
||||
Object.defineProperty(obj, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get,
|
||||
set: (newVal) => property.set(newVal),
|
||||
});
|
||||
}
|
||||
|
||||
export function getObxProperty(obj: object, key: PropertyKey) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||||
|
||||
if (!descriptor || !descriptor.get) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (descriptor.get as any)[SYMBOL_OBX] as ObxProperty;
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
import { addHiddenProp } from '../utils';
|
||||
import Obx, { getObx, ObxFlag } from './obx';
|
||||
import { reportChildValue } from './observable';
|
||||
import { getProxiedValue } from './proxy';
|
||||
import { setPrototypeOf } from '../../utils/set-prototype-of';
|
||||
|
||||
type SetType = Set<any> | WeakSet<any>;
|
||||
|
||||
export default class ObxSet extends Obx<SetType> {
|
||||
constructor(name: string, target: SetType, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
||||
super(name, target, obxFlag);
|
||||
|
||||
setPrototypeOf(target, target instanceof Set ? setMethods : weaksetMethods);
|
||||
}
|
||||
|
||||
has(key: PropertyKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set(key: PropertyKey, val: any) {}
|
||||
|
||||
get(key: PropertyKey) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
del(key: PropertyKey) {}
|
||||
}
|
||||
|
||||
function isIterator(v: any): v is Iterator<any> {
|
||||
return v.next ? true : false;
|
||||
}
|
||||
|
||||
export function patchAccessor(keys: Array<string | symbol>, proto: any, methods: object): void {
|
||||
keys.forEach(method => {
|
||||
const original = proto[method];
|
||||
addHiddenProp(methods, method, function accessor(this: any, ...args: any[]) {
|
||||
const obx = getObx(this);
|
||||
const flag = obx ? obx.obxFlag : ObxFlag.REF;
|
||||
if (method === 'forEach') {
|
||||
const fn = args[0];
|
||||
const thisArg = args[0] || this;
|
||||
args[0] = (v: any, a: any, c: any) => {
|
||||
reportChildValue(v, flag);
|
||||
return fn.call(thisArg, getProxiedValue(v), a, c);
|
||||
};
|
||||
return original.apply(this, args);
|
||||
}
|
||||
|
||||
const result = original.apply(this, args);
|
||||
|
||||
if (method === 'get') {
|
||||
reportChildValue(result, flag);
|
||||
return getProxiedValue(result);
|
||||
}
|
||||
|
||||
if (isIterator(result)) {
|
||||
const originNext = result.next;
|
||||
const isMapIter = String(result) === '[object Map Iterator]';
|
||||
const isEntries = method === 'entries';
|
||||
let keys: string[] | null = null;
|
||||
if (isEntries && !isMapIter) {
|
||||
keys = ['0', '1'];
|
||||
} else if (isMapIter && (isEntries || method === Symbol.iterator)) {
|
||||
keys = ['1'];
|
||||
}
|
||||
|
||||
result.next = function next() {
|
||||
let n = originNext.call(this);
|
||||
if (!n.done) {
|
||||
if (keys) {
|
||||
n.value = createResultProxy(n.value, flag, keys);
|
||||
} else {
|
||||
n = createResultProxy(n, flag);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createResultProxy(entries: any, flag: ObxFlag, keys: any[] = ['value']) {
|
||||
return new Proxy(entries, {
|
||||
get(target, key) {
|
||||
const v = target[key];
|
||||
if (v && keys.includes(key)) {
|
||||
reportChildValue(v, flag);
|
||||
}
|
||||
return getProxiedValue(v);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function patchMutator(keys: Array<string | symbol>, proto: any, methods: object): void {
|
||||
keys.forEach(method => {
|
||||
const original = proto[method];
|
||||
addHiddenProp(methods, method, function mutator(this: any, ...args: any[]) {
|
||||
const size = this.size;
|
||||
const result = original.apply(this, args);
|
||||
const obx = getObx(this);
|
||||
let changed = true;
|
||||
switch (method) {
|
||||
case 'add':
|
||||
case 'clear':
|
||||
case 'delete':
|
||||
changed = this.size !== size;
|
||||
break;
|
||||
}
|
||||
// now: "set" not compare values, default changed
|
||||
if (obx && changed) {
|
||||
obx.reportChange();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ======= Set ========
|
||||
const setProto = Set.prototype;
|
||||
const setMethods = Object.create(setProto);
|
||||
|
||||
patchMutator(['add', 'clear', 'delete'], setProto, setMethods);
|
||||
|
||||
patchAccessor(['values', 'keys', 'entries', 'forEach', Symbol.iterator], setProto, setMethods);
|
||||
|
||||
// ======= WeakSet ========
|
||||
const weaksetProto = WeakSet.prototype;
|
||||
const weaksetMethods = Object.create(weaksetProto);
|
||||
|
||||
patchMutator(['add', 'delete', 'clear'], weaksetProto, weaksetMethods);
|
||||
141
packages/rax-simulator-renderer/src/obx-rax/observable/obx.ts
Normal file
141
packages/rax-simulator-renderer/src/obx-rax/observable/obx.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { walk, addHiddenFinalProp, nextId } from '../utils';
|
||||
import { defineObxProperty } from './obx-property';
|
||||
import { IObservable, propagateChanged, startBatch, endBatch } from './observable';
|
||||
import { IDerivation, DerivationState, clearObserving } from '../derivation';
|
||||
import { hasOwnProperty } from '../utils/has-own-property';
|
||||
import { splitPath } from '../utils/split-path';
|
||||
|
||||
export enum ObxFlag {
|
||||
REF = 0,
|
||||
VAL = 1,
|
||||
SHALLOW = 2,
|
||||
DEEP = 3,
|
||||
}
|
||||
|
||||
class Obx<T = any[] | object> implements IObservable, IDerivation {
|
||||
id = nextId();
|
||||
localVer = 0;
|
||||
observing: IObservable[] = [];
|
||||
observers = new Set<IDerivation>();
|
||||
dependenciesState = DerivationState.NOT_TRACKING;
|
||||
lowestObserverState = DerivationState.UP_TO_DATE;
|
||||
|
||||
constructor(public name: string, public target: T, public obxFlag: ObxFlag = ObxFlag.DEEP) {}
|
||||
|
||||
onBecomeDirty() {
|
||||
propagateChanged(this);
|
||||
}
|
||||
|
||||
onBecomeUnobserved() {
|
||||
clearObserving(this);
|
||||
}
|
||||
|
||||
reportChange(force = false) {
|
||||
startBatch();
|
||||
this.localVer++;
|
||||
propagateChanged(this, force);
|
||||
endBatch();
|
||||
}
|
||||
|
||||
// TODO: remove this unused function, move to utils $getAsObx
|
||||
getAsObx(path: PropertyKey): Obx<T | any[] | object> | void {
|
||||
if (path === '') {
|
||||
return this;
|
||||
}
|
||||
|
||||
let entry = path;
|
||||
let nestPath = '';
|
||||
|
||||
if (typeof path === 'string') {
|
||||
const pathArray = splitPath(path);
|
||||
|
||||
if (!pathArray) {
|
||||
return this;
|
||||
}
|
||||
|
||||
entry = pathArray[1];
|
||||
nestPath = pathArray[2];
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
return this.get(nestPath);
|
||||
}
|
||||
|
||||
let ret = this.get(entry);
|
||||
|
||||
if (!hasObx(ret) && nestPath) {
|
||||
ret = this.get(path);
|
||||
}
|
||||
|
||||
const obx = getObx(ret);
|
||||
|
||||
if (obx && nestPath) {
|
||||
return obx.getAsObx(nestPath);
|
||||
}
|
||||
|
||||
return obx;
|
||||
}
|
||||
|
||||
has(key: PropertyKey): boolean {
|
||||
if (key == null || key === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return key in this.target;
|
||||
}
|
||||
|
||||
get(key?: PropertyKey): any {
|
||||
if (key == null || key === '') {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
return (this.target as any)[key];
|
||||
}
|
||||
|
||||
set(key: PropertyKey, val: any): void {
|
||||
if (this.obxFlag > ObxFlag.REF) {
|
||||
defineObxProperty(this.target as any, key, val, undefined, this.obxFlag);
|
||||
} else {
|
||||
(this.target as any)[key] = val;
|
||||
}
|
||||
|
||||
this.reportChange();
|
||||
}
|
||||
|
||||
del(key: PropertyKey) {
|
||||
if (!hasOwnProperty(this.target, key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete (this.target as any)[key];
|
||||
this.reportChange();
|
||||
}
|
||||
|
||||
extend(properties: object) {
|
||||
startBatch();
|
||||
walk(properties, (_, key, val) => this.set(key, val));
|
||||
endBatch();
|
||||
}
|
||||
}
|
||||
|
||||
export default Obx;
|
||||
|
||||
export const SYMBOL_OBX = Symbol.for('obx');
|
||||
export function injectObx(obj: any[] | object, obx: Obx): void {
|
||||
addHiddenFinalProp(obj, SYMBOL_OBX, obx);
|
||||
}
|
||||
|
||||
export function getObx(obj: any): Obx | undefined {
|
||||
return obj ? (obj as any)[SYMBOL_OBX] : undefined;
|
||||
}
|
||||
|
||||
export function hasObx(obj: any[] | object): boolean {
|
||||
return hasOwnProperty(obj, SYMBOL_OBX) && (obj as any)[SYMBOL_OBX] instanceof Obx;
|
||||
}
|
||||
|
||||
export function reportChange(obj: any, force = false) {
|
||||
const obx = getObx(obj);
|
||||
if (obx) {
|
||||
obx.reportChange(force);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { addHiddenFinalProp } from '../utils';
|
||||
|
||||
export const SYMBOL_PROXY = Symbol.for('proxy');
|
||||
export const SYMBOL_RAW = Symbol.for('raw');
|
||||
export interface Proxied<T> {
|
||||
[SYMBOL_PROXY]: T;
|
||||
}
|
||||
|
||||
export function isProxied<T>(target: T): target is T & Proxied<T> {
|
||||
return SYMBOL_PROXY in target;
|
||||
}
|
||||
|
||||
export function getProxy<T>(target: T & Proxied<T>) {
|
||||
return target[SYMBOL_PROXY];
|
||||
}
|
||||
|
||||
export function setProxy(target: object, proxy: object) {
|
||||
addHiddenFinalProp(target, SYMBOL_PROXY, proxy);
|
||||
}
|
||||
|
||||
export function getProxiedValue(target: any) {
|
||||
return (target && getProxy(target)) || target;
|
||||
}
|
||||
|
||||
export function getRawValue(target: any) {
|
||||
return (target && target[SYMBOL_RAW]) || target;
|
||||
}
|
||||
|
||||
export const supportProxy = 'Proxy' in global;
|
||||
|
||||
export function createProxy<T extends object>(target: T, taps: ProxyHandler<T>) {
|
||||
if (isProxied(target)) {
|
||||
return getProxy(target);
|
||||
}
|
||||
|
||||
const proxy = new Proxy(target, taps);
|
||||
setProxy(target, proxy);
|
||||
|
||||
return proxy;
|
||||
}
|
||||
149
packages/rax-simulator-renderer/src/obx-rax/observer.ts
Normal file
149
packages/rax-simulator-renderer/src/obx-rax/observer.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { FunctionComponent, ComponentType } from 'react';
|
||||
import { Component } from 'rax';
|
||||
import { Reaction } from './reaction';
|
||||
import { shallowEqual } from './utils';
|
||||
|
||||
const SYMBOL_REACTION = Symbol('__obxReaction');
|
||||
const SYMBOL_ISUNMOUNTED = Symbol('__obxIsUnmounted');
|
||||
|
||||
/**
|
||||
* ReactiveMixin
|
||||
*/
|
||||
function defaultComponentWillUnmount(this: any) {
|
||||
this.render[SYMBOL_REACTION] && this.render[SYMBOL_REACTION].sleep();
|
||||
this[SYMBOL_ISUNMOUNTED] = true;
|
||||
}
|
||||
|
||||
function defaultShouldComponentUpdate(this: any, nextProps: any, nextState: any) {
|
||||
if (this.state !== nextState) {
|
||||
return true;
|
||||
}
|
||||
return !shallowEqual(this.props, nextProps);
|
||||
}
|
||||
|
||||
// function shouldConstruct(C: any) {
|
||||
// const prototype = C.prototype;
|
||||
// return !!(prototype && prototype.isReactComponent);
|
||||
// }
|
||||
|
||||
function shouldConstruct(C: any) {
|
||||
const prototype = C.prototype;
|
||||
return !!(prototype && prototype.constructor);
|
||||
}
|
||||
|
||||
function isFunctionComponent<T = any>(type: Function): type is FunctionComponent<T> {
|
||||
return !shouldConstruct(type);
|
||||
}
|
||||
|
||||
export function getReaction(target: Component): Reaction | undefined {
|
||||
return (target.render as any)[SYMBOL_REACTION];
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer function / decorator
|
||||
*/
|
||||
export function observer<T extends ComponentType<any>>(target: T): T {
|
||||
if (!target) {
|
||||
throw new Error('Please pass a valid component to "observer"');
|
||||
}
|
||||
if (typeof target !== 'function') {
|
||||
throw new Error('obx observer: needs to be a react class constructor or stateless function components');
|
||||
}
|
||||
|
||||
let componentClass: any = target;
|
||||
|
||||
if (isFunctionComponent(target)) {
|
||||
componentClass = class extends Component {
|
||||
static displayName = componentClass.displayName || componentClass.name;
|
||||
static contextTypes = componentClass.contextTypes;
|
||||
static propTypes = componentClass.propTypes;
|
||||
static defaultProps = componentClass.defaultProps;
|
||||
|
||||
render() {
|
||||
return target.call(this, this.props, this.context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const proto = componentClass.prototype || componentClass;
|
||||
mixinLifecycleEvents(proto);
|
||||
componentClass.isObxReactObserver = true;
|
||||
const baseRender = proto.render;
|
||||
proto.render = function() {
|
||||
return makeComponentReactive.call(this, baseRender);
|
||||
};
|
||||
return componentClass;
|
||||
}
|
||||
|
||||
function makeComponentReactive(this: any, render: any) {
|
||||
function reactiveRender() {
|
||||
isRenderingPending = false;
|
||||
let exception = undefined;
|
||||
let rendering = undefined;
|
||||
reaction.track(() => {
|
||||
try {
|
||||
rendering = baseRender();
|
||||
} catch (e) {
|
||||
exception = e;
|
||||
}
|
||||
});
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
return rendering || baseRender();
|
||||
}
|
||||
|
||||
// Generate friendly name for debugging
|
||||
const initialName =
|
||||
this.displayName ||
|
||||
this.name ||
|
||||
(this.constructor && (this.constructor.displayName || this.constructor.name)) ||
|
||||
'<component>';
|
||||
|
||||
const rootNodeID = (this._reactInternalFiber && this._reactInternalFiber._debugID) || '*';
|
||||
|
||||
// wire up reactive render
|
||||
const baseRender = render.bind(this);
|
||||
let isRenderingPending = false;
|
||||
const reaction = new Reaction(
|
||||
`${initialName}#${rootNodeID}.render()`,
|
||||
() => {
|
||||
if (!isRenderingPending) {
|
||||
isRenderingPending = true;
|
||||
if (typeof this.componentWillReact === 'function') {
|
||||
this.componentWillReact();
|
||||
}
|
||||
if (this[SYMBOL_ISUNMOUNTED] !== true) {
|
||||
let hasError = true;
|
||||
try {
|
||||
Component.prototype.forceUpdate.call(this);
|
||||
hasError = false;
|
||||
} finally {
|
||||
if (hasError) reaction.sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
this.$level || 0,
|
||||
);
|
||||
|
||||
(reactiveRender as any)[SYMBOL_REACTION] = reaction;
|
||||
this.render = reactiveRender;
|
||||
return reactiveRender.call(this);
|
||||
}
|
||||
|
||||
function mixinLifecycleEvents(target: any) {
|
||||
if (!target.componentWillUnmount) {
|
||||
target.componentWillUnmount = defaultComponentWillUnmount;
|
||||
} else {
|
||||
const originFunc = target.componentWillUnmount;
|
||||
target.componentWillUnmount = function(this: any) {
|
||||
originFunc.call(this);
|
||||
defaultComponentWillUnmount.call(this);
|
||||
};
|
||||
}
|
||||
|
||||
if (!target.shouldComponentUpdate) {
|
||||
target.shouldComponentUpdate = defaultShouldComponentUpdate;
|
||||
}
|
||||
}
|
||||
252
packages/rax-simulator-renderer/src/obx-rax/reaction.ts
Normal file
252
packages/rax-simulator-renderer/src/obx-rax/reaction.ts
Normal file
@ -0,0 +1,252 @@
|
||||
import {
|
||||
DerivationState,
|
||||
IDerivation,
|
||||
runDerivedFunction,
|
||||
isCaughtException,
|
||||
shouldCompute,
|
||||
clearObserving,
|
||||
CaughtException,
|
||||
} from './derivation';
|
||||
import { nextTick } from './next-tick';
|
||||
import { IObservable, endBatch, startBatch } from './observable/observable';
|
||||
import { globalState } from './ global-state';
|
||||
import { throttle } from './utils/throttle';
|
||||
|
||||
export function nextId() {
|
||||
return (++globalState.guid).toString(36).toLocaleLowerCase();
|
||||
}
|
||||
|
||||
export class Reaction implements IDerivation {
|
||||
observing: IObservable[] = [];
|
||||
dependenciesState = DerivationState.NOT_TRACKING;
|
||||
id = nextId();
|
||||
scheduled = false;
|
||||
run: () => void;
|
||||
caughtException: any = null;
|
||||
|
||||
private sleeping = false;
|
||||
private running = false;
|
||||
|
||||
constructor(public name: string, private check: () => void, public level: number = 0, throttleWait = 10) {
|
||||
if (throttleWait > 0) {
|
||||
this.run = throttle(this.runReaction.bind(this), throttleWait);
|
||||
} else {
|
||||
this.run = this.runReaction.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
onBecomeDirty() {
|
||||
this.schedule();
|
||||
}
|
||||
|
||||
schedule() {
|
||||
if (this.scheduled || this.sleeping) {
|
||||
return;
|
||||
}
|
||||
this.scheduled = true;
|
||||
scheduleReaction(this);
|
||||
}
|
||||
|
||||
isDirty(): boolean {
|
||||
return shouldCompute(this);
|
||||
}
|
||||
|
||||
runReaction() {
|
||||
if (this.sleeping || this.running) {
|
||||
return;
|
||||
}
|
||||
|
||||
startBatch();
|
||||
if (shouldCompute(this)) {
|
||||
this.caughtException = null;
|
||||
try {
|
||||
this.check();
|
||||
} catch (e) {
|
||||
this.caughtException = new CaughtException(e);
|
||||
}
|
||||
}
|
||||
endBatch();
|
||||
}
|
||||
|
||||
track(fn: () => void) {
|
||||
startBatch();
|
||||
this.running = true;
|
||||
const result = runDerivedFunction(this, fn);
|
||||
if (isCaughtException(result)) {
|
||||
this.caughtException = result;
|
||||
}
|
||||
this.running = false;
|
||||
if (this.sleeping) {
|
||||
clearObserving(this);
|
||||
}
|
||||
endBatch();
|
||||
}
|
||||
|
||||
sleep() {
|
||||
if (this.sleeping) {
|
||||
return;
|
||||
}
|
||||
this.sleeping = true;
|
||||
if (!this.running) {
|
||||
startBatch();
|
||||
clearObserving(this);
|
||||
endBatch();
|
||||
deScheduleReaction(this);
|
||||
}
|
||||
}
|
||||
|
||||
wakeup(sync = false) {
|
||||
if (this.sleeping) {
|
||||
this.sleeping = false;
|
||||
if (sync) {
|
||||
this.runReaction();
|
||||
} else {
|
||||
this.schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let flushIndex = 0;
|
||||
let flushWaiting = false;
|
||||
|
||||
function scheduleReaction(reaction: Reaction) {
|
||||
const { pendingReactions, isRunningReactions } = globalState;
|
||||
if (!isRunningReactions) {
|
||||
pendingReactions.push(reaction);
|
||||
} else {
|
||||
let i = pendingReactions.length - 1;
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
// 0, 0, 1, 1, 1, 2, 2, 2, 3, 3
|
||||
// ^ ^
|
||||
// flushIndex = 2 level = 2
|
||||
// break at: i = 7 or i = 2
|
||||
while (i > flushIndex && pendingReactions[i].level > reaction.level) {
|
||||
i--;
|
||||
}
|
||||
pendingReactions.splice(i + 1, 0, reaction);
|
||||
}
|
||||
|
||||
runReactions();
|
||||
}
|
||||
|
||||
function deScheduleReaction(reaction: Reaction) {
|
||||
const { pendingReactions, isRunningReactions } = globalState;
|
||||
if (!isRunningReactions) {
|
||||
const i = pendingReactions.indexOf(reaction);
|
||||
if (i > -1) {
|
||||
pendingReactions.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runReactions() {
|
||||
// queue the flush
|
||||
if (!flushWaiting) {
|
||||
flushWaiting = true;
|
||||
nextTick(flushReactions);
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_REACTION_ITERATIONS = 100;
|
||||
|
||||
function flushReactions() {
|
||||
globalState.isRunningReactions = true;
|
||||
const allReactions = globalState.pendingReactions;
|
||||
let pendingLength = 0;
|
||||
let iterations = 0;
|
||||
|
||||
// low level run first
|
||||
// sort as:
|
||||
// 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 5
|
||||
allReactions.sort((a, b) => a.level - b.level);
|
||||
|
||||
while (allReactions.length > pendingLength) {
|
||||
pendingLength = allReactions.length;
|
||||
if (++iterations === MAX_REACTION_ITERATIONS) {
|
||||
// tslint:disable-next-line
|
||||
console.error(
|
||||
`Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
|
||||
` Probably there is a cycle in the reactive function: ${allReactions[0]}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
for (; flushIndex < pendingLength; flushIndex++) {
|
||||
allReactions[flushIndex].scheduled = false;
|
||||
allReactions[flushIndex].run();
|
||||
}
|
||||
}
|
||||
|
||||
flushIndex = 0;
|
||||
flushWaiting = false;
|
||||
allReactions.length = 0;
|
||||
globalState.isRunningReactions = false;
|
||||
}
|
||||
|
||||
export function clearReactions() {
|
||||
const { pendingReactions } = globalState;
|
||||
let i = pendingReactions.length;
|
||||
while (i--) {
|
||||
pendingReactions[i].sleep();
|
||||
}
|
||||
}
|
||||
|
||||
export interface Disposer {
|
||||
(): void;
|
||||
name?: string;
|
||||
$obx: Reaction;
|
||||
}
|
||||
export interface AutorunOptions {
|
||||
throttle?: number;
|
||||
sync?: boolean;
|
||||
level?: number;
|
||||
name?: string;
|
||||
runFirstNow?: boolean;
|
||||
}
|
||||
export interface RunContext {
|
||||
dispose: Disposer;
|
||||
firstRun: boolean;
|
||||
}
|
||||
export type Action = (this: RunContext, context: RunContext) => any;
|
||||
|
||||
export function autorun(action: Action, options: number | true | AutorunOptions = {}): Disposer {
|
||||
if (typeof options === 'number') {
|
||||
options = { throttle: options };
|
||||
} else if (options === true) {
|
||||
options = { sync: true };
|
||||
}
|
||||
const name: string = options.name || (action as any).name || 'Autorun@' + nextId();
|
||||
|
||||
const reaction = new Reaction(
|
||||
name,
|
||||
function(this: Reaction) {
|
||||
this.track(reactionRunner);
|
||||
},
|
||||
options.level || 0,
|
||||
options.throttle || 0,
|
||||
);
|
||||
|
||||
const dispose = () => {
|
||||
reaction.sleep();
|
||||
};
|
||||
|
||||
dispose.$obx = reaction;
|
||||
|
||||
let firstRun = true;
|
||||
function reactionRunner() {
|
||||
const ctx: RunContext = {
|
||||
firstRun,
|
||||
dispose,
|
||||
};
|
||||
action.call(ctx, ctx);
|
||||
firstRun = false;
|
||||
}
|
||||
|
||||
if (options.sync || options.runFirstNow) {
|
||||
reaction.runReaction();
|
||||
} else {
|
||||
reaction.schedule();
|
||||
}
|
||||
|
||||
return dispose;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export const prototypeHasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
export function hasOwnProperty(obj: any, key: string | number | symbol): boolean {
|
||||
return obj && prototypeHasOwnProperty.call(obj, key);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
export * from './shallow-equal';
|
||||
export * from './has-own-property';
|
||||
export * from './throttle';
|
||||
export * from './next-id';
|
||||
export * from './is-primitive';
|
||||
export * from './invariant';
|
||||
export * from './splitPath';
|
||||
@ -0,0 +1,5 @@
|
||||
export function invariant(check: any, message: string, thing?: any) {
|
||||
if (!check) {
|
||||
throw new Error('[recore] Invariant failed: ' + message + (thing ? ` in '${thing}'` : ''));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export function isPrimitive(obj: any): boolean {
|
||||
// null | undefined
|
||||
if (obj == null) {
|
||||
return true;
|
||||
}
|
||||
const t = typeof obj;
|
||||
return t === 'boolean' || t === 'number' || t === 'string' || t === 'symbol';
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
import { globalState } from '../ global-state';
|
||||
export function nextId() {
|
||||
return (++globalState.guid).toString(36).toLocaleLowerCase();
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { prototypeHasOwnProperty } from './has-own-property';
|
||||
|
||||
export function shallowEqual(objA: any, objB: any): boolean {
|
||||
if (objA === objB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test for A's keys different from B.
|
||||
const bHasOwnProperty = prototypeHasOwnProperty.bind(objB);
|
||||
for (let i = 0; i < keysA.length; i++) {
|
||||
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
const RE_PATH = /^([^/]*)(?:\/(.*))?$/;
|
||||
const RE_PATH_REVERSE = /^(?:(.*)\/)?([^/]+)$/;
|
||||
export function splitPath(path: string, reverse = false) {
|
||||
return reverse ? RE_PATH_REVERSE.exec(path) : RE_PATH.exec(path);
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
const useRAF = typeof requestAnimationFrame === 'function';
|
||||
|
||||
export function throttle(func: Function, wait: number) {
|
||||
let lastArgs: any;
|
||||
let lastThis: any;
|
||||
let result: any;
|
||||
let timerId: number | undefined;
|
||||
let lastCalled: number | undefined;
|
||||
let lastInvoked = 0;
|
||||
|
||||
function invoke(time: number) {
|
||||
const args = lastArgs;
|
||||
const thisArg = lastThis;
|
||||
|
||||
lastArgs = undefined;
|
||||
lastThis = undefined;
|
||||
lastInvoked = time;
|
||||
result = func.apply(thisArg, args);
|
||||
return result;
|
||||
}
|
||||
|
||||
function startTimer(pendingFunc: any, wait: number): number {
|
||||
if (useRAF) {
|
||||
return requestAnimationFrame(pendingFunc);
|
||||
}
|
||||
return setTimeout(pendingFunc, wait) as any;
|
||||
}
|
||||
|
||||
function leadingEdge(time: number) {
|
||||
lastInvoked = time;
|
||||
timerId = startTimer(timerExpired, wait);
|
||||
return invoke(time);
|
||||
}
|
||||
|
||||
function shouldInvoke(time: number) {
|
||||
const timeSinceLastCalled = time - lastCalled!;
|
||||
const timeSinceLastInvoked = time - lastInvoked;
|
||||
|
||||
return (
|
||||
lastCalled === undefined || timeSinceLastCalled >= wait || timeSinceLastCalled < 0 || timeSinceLastInvoked >= wait
|
||||
);
|
||||
}
|
||||
|
||||
function remainingWait(time: number) {
|
||||
const timeSinceLastCalled = time - lastCalled!;
|
||||
const timeSinceLastInvoked = time - lastInvoked;
|
||||
|
||||
return Math.min(wait - timeSinceLastCalled, wait - timeSinceLastInvoked);
|
||||
}
|
||||
|
||||
function timerExpired() {
|
||||
const time = Date.now();
|
||||
if (shouldInvoke(time)) {
|
||||
return trailingEdge(time);
|
||||
}
|
||||
|
||||
timerId = startTimer(timerExpired, remainingWait(time));
|
||||
}
|
||||
|
||||
function trailingEdge(time: number) {
|
||||
timerId = undefined;
|
||||
|
||||
if (lastArgs) {
|
||||
return invoke(time);
|
||||
}
|
||||
|
||||
lastArgs = undefined;
|
||||
lastThis = undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
function debounced(this: any, ...args: any[]) {
|
||||
const time = Date.now();
|
||||
const isInvoking = shouldInvoke(time);
|
||||
|
||||
lastArgs = args;
|
||||
lastThis = this;
|
||||
lastCalled = time;
|
||||
|
||||
if (isInvoking) {
|
||||
if (timerId === undefined) {
|
||||
return leadingEdge(lastCalled);
|
||||
}
|
||||
|
||||
timerId = startTimer(timerExpired, wait);
|
||||
return invoke(lastCalled);
|
||||
}
|
||||
|
||||
if (timerId === undefined) {
|
||||
timerId = startTimer(timerExpired, wait);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return debounced;
|
||||
}
|
||||
151
packages/rax-simulator-renderer/src/renderer-view.tsx
Normal file
151
packages/rax-simulator-renderer/src/renderer-view.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import { Fragment, Component, createElement } from 'rax';
|
||||
// import { observer } from './obx-rax/observer';
|
||||
import RaxEngine from '../../rax-render/src/index';
|
||||
import { SimulatorRenderer } from './renderer';
|
||||
import { host } from './host';
|
||||
|
||||
import './renderer.less';
|
||||
|
||||
// patch cloneElement avoid lost keyProps
|
||||
const originCloneElement = (window as any).Rax.cloneElement;
|
||||
(window as any).Rax.cloneElement = (child: any, { _leaf, ...props }: any = {}, ...rest: any[]) => {
|
||||
if (child.ref && props.ref) {
|
||||
const dRef = props.ref;
|
||||
const cRef = child.ref;
|
||||
props.ref = (x: any) => {
|
||||
if (cRef) {
|
||||
if (typeof cRef === 'function') {
|
||||
cRef(x);
|
||||
} else {
|
||||
try {
|
||||
cRef.current = x;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
if (dRef) {
|
||||
if (typeof dRef === 'function') {
|
||||
dRef(x);
|
||||
} else {
|
||||
try {
|
||||
dRef.current = x;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return originCloneElement(child, props, ...rest);
|
||||
};
|
||||
|
||||
export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> {
|
||||
render() {
|
||||
const { renderer } = this.props;
|
||||
return (
|
||||
<Layout renderer={renderer}>
|
||||
<Renderer renderer={renderer} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function ucfirst(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.substring(1);
|
||||
}
|
||||
function getDeviceView(view: any, device: string, mode: string) {
|
||||
if (!view || typeof view === 'string') {
|
||||
return view;
|
||||
}
|
||||
|
||||
// compatible vision Mobile | Preview
|
||||
device = ucfirst(device);
|
||||
if (device === 'Mobile' && view.hasOwnProperty(device)) {
|
||||
view = view[device];
|
||||
}
|
||||
mode = ucfirst(mode);
|
||||
if (mode === 'Preview' && view.hasOwnProperty(mode)) {
|
||||
view = view[mode];
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
// @observer
|
||||
class Layout extends Component<{ renderer: SimulatorRenderer }> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
const { renderer, children } = this.props;
|
||||
const layout = renderer.layout;
|
||||
|
||||
if (layout) {
|
||||
const { Component, props } = layout;
|
||||
return <Component props={props}>{children}</Component>;
|
||||
}
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
}
|
||||
|
||||
// @observer
|
||||
class Renderer extends Component<{ renderer: SimulatorRenderer }> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.props.renderer.onReRender(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
const { renderer } = this.props;
|
||||
const { device, designMode } = renderer;
|
||||
console.info(renderer.schema);
|
||||
|
||||
return (
|
||||
<RaxEngine
|
||||
schema={renderer.schema}
|
||||
components={renderer.components}
|
||||
context={renderer.context}
|
||||
designMode={renderer.designMode}
|
||||
suspended={renderer.suspended}
|
||||
self={renderer.scope}
|
||||
onCompGetRef={(schema: any, ref: ReactInstance | null) => {
|
||||
renderer.mountInstance(schema.id, ref);
|
||||
}}
|
||||
customCreateElement={(Component: any, props: any, children: any) => {
|
||||
const { __id, __desingMode, ...viewProps } = props;
|
||||
viewProps.componentId = __id;
|
||||
const leaf = host.document.getNode(__id);
|
||||
viewProps._leaf = leaf;
|
||||
viewProps._componentName = leaf?.componentName;
|
||||
|
||||
// if (viewProps._componentName === 'Menu') {
|
||||
// Object.assign(viewProps, {
|
||||
// _componentName: 'Menu',
|
||||
// className: '_css_pesudo_menu_kbrzyh0f',
|
||||
// context: { VE: (window as any).VisualEngine },
|
||||
// direction: undefined,
|
||||
// events: { ignored: true },
|
||||
// fieldId: 'menu_kbrzyh0f',
|
||||
// footer: '',
|
||||
// header: '',
|
||||
// mode: 'inline',
|
||||
// onItemClick: { ignored: true },
|
||||
// onSelect: { ignored: true },
|
||||
// popupAlign: 'follow',
|
||||
// selectMode: false,
|
||||
// triggerType: 'click',
|
||||
// });
|
||||
// console.info('menuprops', viewProps);
|
||||
// }
|
||||
|
||||
return createElement(
|
||||
getDeviceView(Component, device, designMode),
|
||||
viewProps,
|
||||
leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
111
packages/rax-simulator-renderer/src/renderer.less
Normal file
111
packages/rax-simulator-renderer/src/renderer.less
Normal file
@ -0,0 +1,111 @@
|
||||
body, html {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html.engine-design-mode {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
html.engine-cursor-move, html.engine-cursor-move * {
|
||||
cursor: grabbing !important
|
||||
}
|
||||
|
||||
html.engine-cursor-copy, html.engine-cursor-copy * {
|
||||
cursor: copy !important
|
||||
}
|
||||
|
||||
html.engine-cursor-ew-resize, html.engine-cursor-ew-resize * {
|
||||
cursor: ew-resize !important
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.lc-container {
|
||||
&:empty {
|
||||
background: #f2f3f5;
|
||||
color: #a7b1bd;
|
||||
outline: 1px dashed rgba(31, 56, 88, 0.2);
|
||||
outline-offset: -1px !important;
|
||||
height: 66px;
|
||||
max-height: 100%;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:before {
|
||||
content: '\62D6\62FD\7EC4\4EF6\6216\6A21\677F\5230\8FD9\91CC';
|
||||
font-size: 14px;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.engine-empty {
|
||||
background: #f2f3f5;
|
||||
color: #a7b1bd;
|
||||
outline: 1px dashed rgba(31, 56, 88, 0.2);
|
||||
outline-offset: -1px !important;
|
||||
height: 66px;
|
||||
max-height: 100%;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.engine-empty:before {
|
||||
content: '\62D6\62FD\7EC4\4EF6\6216\6A21\677F\5230\8FD9\91CC';
|
||||
font-size: 14px;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body.engine-document {
|
||||
&:after, &:before {
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
&:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/*
|
||||
.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 {
|
||||
pointer-events: none !important;
|
||||
}*/
|
||||
}
|
||||
|
||||
.engine-live-editing {
|
||||
cursor: text;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0px 2px rgb(102, 188, 92);
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.luna-page{
|
||||
height: 100%;
|
||||
}
|
||||
423
packages/rax-simulator-renderer/src/renderer.ts
Normal file
423
packages/rax-simulator-renderer/src/renderer.ts
Normal file
@ -0,0 +1,423 @@
|
||||
import { BuiltinSimulatorRenderer, NodeInstance, Component } from '@ali/lowcode-designer';
|
||||
import { shared, render, createElement } from 'rax';
|
||||
import DriverUniversal from 'driver-universal';
|
||||
import { computed, obx } from '@recore/obx';
|
||||
import { RootSchema, NpmInfo, ComponentSchema } from '@ali/lowcode-types';
|
||||
import { Asset, isReactComponent, isESModule, setNativeSelection, cursor, isElement } from '@ali/lowcode-utils';
|
||||
|
||||
import SimulatorRendererView from './renderer-view';
|
||||
import RaxEngine from '../../rax-render/src/index';
|
||||
import { getClientRects } from './utils/get-client-rects';
|
||||
import loader from './utils/loader';
|
||||
|
||||
import Leaf from './builtin-components/Leaf';
|
||||
|
||||
import { host } from './host';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const { Instance } = shared;
|
||||
|
||||
export interface LibraryMap {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const SYMBOL_VNID = Symbol('_LCNodeId');
|
||||
|
||||
function accessLibrary(library: string | object) {
|
||||
if (typeof library !== 'string') {
|
||||
return library;
|
||||
}
|
||||
|
||||
return (window as any)[library];
|
||||
}
|
||||
|
||||
export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
||||
readonly isSimulatorRenderer = true;
|
||||
private dispose?: () => void;
|
||||
|
||||
private instancesMap = new Map<string, any[]>();
|
||||
@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;
|
||||
}
|
||||
|
||||
@obx.ref private _designMode = 'design';
|
||||
@computed get designMode(): any {
|
||||
return this._designMode;
|
||||
}
|
||||
|
||||
@obx.ref private _componentsMap = {};
|
||||
@computed get componentsMap(): any {
|
||||
return this._componentsMap;
|
||||
}
|
||||
|
||||
@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 {
|
||||
return false;
|
||||
}
|
||||
@computed get scope(): any {
|
||||
return null;
|
||||
}
|
||||
|
||||
@computed get layout(): any {
|
||||
// TODO: parse layout Component
|
||||
return null;
|
||||
}
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
constructor() {
|
||||
if (!host) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispose = host.connect(this as any, () => {
|
||||
// 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;
|
||||
|
||||
this.emitter.emit('rerender');
|
||||
});
|
||||
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',
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getClosestNodeInstance(from: any, nodeId?: string): NodeInstance<any> | null {
|
||||
const node = getClosestNodeInstance(from, nodeId);
|
||||
if (node) {
|
||||
node.nodeId = (node as any).props?.componentId;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
createComponent(schema: ComponentSchema): Component | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
setNativeSelection(enableFlag: boolean) {
|
||||
setNativeSelection(enableFlag);
|
||||
}
|
||||
setDraggingState(state: boolean) {
|
||||
cursor.setDragging(state);
|
||||
}
|
||||
|
||||
setCopyState(state: boolean) {
|
||||
cursor.setCopy(state);
|
||||
}
|
||||
clearState() {
|
||||
cursor.release();
|
||||
}
|
||||
|
||||
findDOMNodes(instance: any): Array<Element | Text> | null {
|
||||
return [RaxEngine.findDOMNode(instance)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源
|
||||
*/
|
||||
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;
|
||||
instance.componentWillUnmount = newUnmount;
|
||||
}
|
||||
|
||||
(instance as any)[SYMBOL_VNID] = id;
|
||||
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(id, instances);
|
||||
}
|
||||
|
||||
getClientRects(element: Element | Text) {
|
||||
return getClientRects(element);
|
||||
}
|
||||
|
||||
private _running = false;
|
||||
|
||||
run() {
|
||||
if (this._running) {
|
||||
return;
|
||||
}
|
||||
this._running = true;
|
||||
const containerId = 'app';
|
||||
let container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
container.id = containerId;
|
||||
}
|
||||
// ==== compatiable vision
|
||||
document.documentElement.classList.add('engine-page');
|
||||
document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends
|
||||
|
||||
render(createElement(SimulatorRendererView, { renderer: this }), document.getElementById('app'), {
|
||||
driver: DriverUniversal,
|
||||
});
|
||||
host.document.setRendererReady(this);
|
||||
}
|
||||
}
|
||||
|
||||
function getSubComponent(library: any, paths: string[]) {
|
||||
const l = paths.length;
|
||||
if (l < 1 || !library) {
|
||||
return library;
|
||||
}
|
||||
let i = 0;
|
||||
let component: any;
|
||||
while (i < l) {
|
||||
const key = paths[i]!;
|
||||
let ex: any;
|
||||
try {
|
||||
component = library[key];
|
||||
} catch (e) {
|
||||
ex = e;
|
||||
component = null;
|
||||
}
|
||||
if (i === 0 && component == null && key === 'default') {
|
||||
if (ex) {
|
||||
return l === 1 ? library : null;
|
||||
}
|
||||
component = library;
|
||||
} else if (component == null) {
|
||||
return null;
|
||||
}
|
||||
library = component;
|
||||
i++;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmInfo) {
|
||||
if (!npm) {
|
||||
return accessLibrary(componentName);
|
||||
}
|
||||
// libraryName the key access to global
|
||||
// export { exportName } from xxx exportName === global.libraryName.exportName
|
||||
// export exportName from xxx exportName === global.libraryName.default || global.libraryName
|
||||
// export { exportName as componentName } from package
|
||||
// if exportName == null exportName === componentName;
|
||||
// const componentName = exportName.subName, if exportName empty subName donot use
|
||||
const exportName = npm.exportName || npm.componentName || componentName;
|
||||
const libraryName = libraryMap[npm.package] || exportName;
|
||||
const library = accessLibrary(libraryName);
|
||||
const paths = npm.exportName && npm.subName ? npm.subName.split('.') : [];
|
||||
if (npm.destructuring) {
|
||||
paths.unshift(exportName);
|
||||
} else if (isESModule(library)) {
|
||||
paths.unshift('default');
|
||||
}
|
||||
return getSubComponent(library, paths);
|
||||
}
|
||||
|
||||
const builtinComponents = {
|
||||
// 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 matcher(parent: any) {
|
||||
return parent.__isReactiveComponent;
|
||||
}
|
||||
|
||||
function getNodeInstance(dom: HTMLElement): NodeInstance<any> | null {
|
||||
const INTERNAL = '_internal';
|
||||
let instance = Instance.get(dom);
|
||||
let parent;
|
||||
while (instance && instance[INTERNAL]) {
|
||||
if (matcher(instance)) {
|
||||
parent = instance;
|
||||
break;
|
||||
}
|
||||
instance = instance[INTERNAL].__parentInstance;
|
||||
}
|
||||
return parent;
|
||||
// const instance = fiberNode.stateNode;
|
||||
// if (instance) {
|
||||
// console.log(instance);
|
||||
// }
|
||||
// return getNodeInstance(fiberNode.return);
|
||||
// return instance;
|
||||
}
|
||||
|
||||
function checkInstanceMounted(instance: any): boolean {
|
||||
if (isElement(instance)) {
|
||||
return instance.parentElement != null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default new SimulatorRenderer();
|
||||
17
packages/rax-simulator-renderer/src/utils/create-defer.ts
Normal file
17
packages/rax-simulator-renderer/src/utils/create-defer.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export interface Defer<T = any> {
|
||||
resolve(value?: T | PromiseLike<T>): void;
|
||||
reject(reason?: any): void;
|
||||
promise(): Promise<T>;
|
||||
}
|
||||
|
||||
export function createDefer<T = any>(): Defer<T> {
|
||||
const r: any = {};
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
r.resolve = resolve;
|
||||
r.reject = reject;
|
||||
});
|
||||
|
||||
r.promise = () => promise;
|
||||
|
||||
return r;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { isElement } from '@ali/lowcode-utils';
|
||||
|
||||
// a range for test TextNode clientRect
|
||||
const cycleRange = document.createRange();
|
||||
|
||||
export function getClientRects(node: Element | Text) {
|
||||
if (isElement(node)) {
|
||||
return [node.getBoundingClientRect()];
|
||||
}
|
||||
|
||||
cycleRange.selectNode(node);
|
||||
return Array.from(cycleRange.getClientRects());
|
||||
}
|
||||
23
packages/rax-simulator-renderer/src/utils/get-device-view.ts
Normal file
23
packages/rax-simulator-renderer/src/utils/get-device-view.ts
Normal file
@ -0,0 +1,23 @@
|
||||
function ucfirst(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.substring(1);
|
||||
}
|
||||
function getDeviceView(view: any, device: string, mode: string) {
|
||||
if (!view || typeof view === 'string') {
|
||||
return view;
|
||||
}
|
||||
|
||||
// compatible vision Mobile | Preview
|
||||
device = ucfirst(device);
|
||||
if (device === 'Mobile' && view.hasOwnProperty(device)) {
|
||||
view = view[device];
|
||||
}
|
||||
mode = ucfirst(mode);
|
||||
if (mode === 'Preview' && view.hasOwnProperty(mode)) {
|
||||
view = view[mode];
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
export default {
|
||||
getDeviceView,
|
||||
};
|
||||
113
packages/rax-simulator-renderer/src/utils/loader.ts
Normal file
113
packages/rax-simulator-renderer/src/utils/loader.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { load, evaluate } from './script';
|
||||
import StylePoint from './style';
|
||||
import {
|
||||
Asset,
|
||||
AssetLevel,
|
||||
AssetLevels,
|
||||
AssetType,
|
||||
AssetList,
|
||||
isAssetBundle,
|
||||
isAssetItem,
|
||||
assetItem,
|
||||
AssetItem,
|
||||
isCSSUrl,
|
||||
} from '@ali/lowcode-utils';
|
||||
|
||||
function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) {
|
||||
for (const asset of assets) {
|
||||
parseAsset(scripts, styles, asset, level);
|
||||
}
|
||||
}
|
||||
|
||||
function parseAsset(scripts: any, styles: any, asset: Asset | undefined | null, level?: AssetLevel) {
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(asset)) {
|
||||
return parseAssetList(scripts, styles, asset, level);
|
||||
}
|
||||
|
||||
if (isAssetBundle(asset)) {
|
||||
if (asset.assets) {
|
||||
if (Array.isArray(asset.assets)) {
|
||||
parseAssetList(scripts, styles, asset.assets, asset.level || level);
|
||||
} else {
|
||||
parseAsset(scripts, styles, asset.assets, asset.level || level);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAssetItem(asset)) {
|
||||
asset = assetItem(isCSSUrl(asset) ? AssetType.CSSUrl : AssetType.JSUrl, asset, level)!;
|
||||
}
|
||||
|
||||
let lv = asset.level || level;
|
||||
|
||||
if (!lv || AssetLevel[lv] == null) {
|
||||
lv = AssetLevel.App;
|
||||
}
|
||||
|
||||
asset.level = lv;
|
||||
if (asset.type === AssetType.CSSUrl || asset.type == AssetType.CSSText) {
|
||||
styles[lv].push(asset);
|
||||
} else {
|
||||
scripts[lv].push(asset);
|
||||
}
|
||||
}
|
||||
|
||||
export class AssetLoader {
|
||||
async load(asset: Asset) {
|
||||
const styles: any = {};
|
||||
const scripts: any = {};
|
||||
AssetLevels.forEach(lv => {
|
||||
styles[lv] = [];
|
||||
scripts[lv] = [];
|
||||
});
|
||||
parseAsset(scripts, styles, asset);
|
||||
const styleQueue: AssetItem[] = styles[AssetLevel.Environment].concat(
|
||||
styles[AssetLevel.Library],
|
||||
styles[AssetLevel.Theme],
|
||||
styles[AssetLevel.Runtime],
|
||||
styles[AssetLevel.App],
|
||||
);
|
||||
const scriptQueue: AssetItem[] = scripts[AssetLevel.Environment].concat(
|
||||
scripts[AssetLevel.Library],
|
||||
scripts[AssetLevel.Theme],
|
||||
scripts[AssetLevel.Runtime],
|
||||
scripts[AssetLevel.App],
|
||||
);
|
||||
await Promise.all(
|
||||
styleQueue.map(({ content, level, type, id }) => this.loadStyle(content, level!, type === AssetType.CSSUrl, id)),
|
||||
);
|
||||
await Promise.all(scriptQueue.map(({ content, type }) => this.loadScript(content, type === AssetType.JSUrl)));
|
||||
}
|
||||
|
||||
private stylePoints = new Map<string, StylePoint>();
|
||||
private loadStyle(content: string | undefined | null, level: AssetLevel, isUrl?: boolean, id?: string) {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
let point: StylePoint | undefined;
|
||||
if (id) {
|
||||
point = this.stylePoints.get(id);
|
||||
if (!point) {
|
||||
point = new StylePoint(level, id);
|
||||
this.stylePoints.set(id, point);
|
||||
}
|
||||
} else {
|
||||
point = new StylePoint(level);
|
||||
}
|
||||
return isUrl ? point.applyUrl(content) : point.applyText(content);
|
||||
}
|
||||
|
||||
private loadScript(content: string | undefined | null, isUrl?: boolean) {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
return isUrl ? load(content) : evaluate(content);
|
||||
}
|
||||
}
|
||||
|
||||
export default new AssetLoader();
|
||||
54
packages/rax-simulator-renderer/src/utils/script.ts
Normal file
54
packages/rax-simulator-renderer/src/utils/script.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { createDefer } from './create-defer';
|
||||
|
||||
export function evaluate(script: string) {
|
||||
const scriptEl = document.createElement('script');
|
||||
scriptEl.text = script;
|
||||
document.head.appendChild(scriptEl);
|
||||
document.head.removeChild(scriptEl);
|
||||
}
|
||||
|
||||
export function load(url: string) {
|
||||
const node: any = document.createElement('script');
|
||||
|
||||
// node.setAttribute('crossorigin', 'anonymous');
|
||||
|
||||
node.onload = onload;
|
||||
node.onerror = onload;
|
||||
|
||||
const i = createDefer();
|
||||
|
||||
function onload(e: any) {
|
||||
node.onload = null;
|
||||
node.onerror = null;
|
||||
if (e.type === 'load') {
|
||||
i.resolve();
|
||||
} else {
|
||||
i.reject();
|
||||
}
|
||||
// document.head.removeChild(node);
|
||||
// node = null;
|
||||
}
|
||||
|
||||
// node.async = true;
|
||||
node.src = url;
|
||||
|
||||
document.head.appendChild(node);
|
||||
|
||||
return i.promise();
|
||||
}
|
||||
|
||||
export function evaluateExpression(expr: string) {
|
||||
// eslint-disable-next-line no-new-func
|
||||
const fn = new Function(expr);
|
||||
return fn();
|
||||
}
|
||||
|
||||
export function newFunction(args: string, code: string) {
|
||||
try {
|
||||
// eslint-disable-next-line no-new-func
|
||||
return new Function(args, code);
|
||||
} catch (e) {
|
||||
console.warn('Caught error, Cant init func');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
73
packages/rax-simulator-renderer/src/utils/style.ts
Normal file
73
packages/rax-simulator-renderer/src/utils/style.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { createDefer } from './create-defer';
|
||||
|
||||
export default class StylePoint {
|
||||
private lastContent: string | undefined;
|
||||
private lastUrl: string | undefined;
|
||||
private placeholder: Element | Text;
|
||||
|
||||
constructor(readonly level: number, readonly id?: string) {
|
||||
let placeholder: any;
|
||||
if (id) {
|
||||
placeholder = document.head.querySelector(`style[data-id="${id}"]`);
|
||||
}
|
||||
if (!placeholder) {
|
||||
placeholder = document.createTextNode('');
|
||||
const meta = document.head.querySelector(`meta[level="${level}"]`);
|
||||
if (meta) {
|
||||
document.head.insertBefore(placeholder, meta);
|
||||
} else {
|
||||
document.head.appendChild(placeholder);
|
||||
}
|
||||
}
|
||||
this.placeholder = placeholder;
|
||||
}
|
||||
|
||||
applyText(content: string) {
|
||||
if (this.lastContent === content) {
|
||||
return;
|
||||
}
|
||||
this.lastContent = content;
|
||||
this.lastUrl = undefined;
|
||||
const element = document.createElement('style');
|
||||
element.setAttribute('type', 'text/css');
|
||||
if (this.id) {
|
||||
element.setAttribute('data-id', this.id);
|
||||
}
|
||||
element.appendChild(document.createTextNode(content));
|
||||
document.head.insertBefore(element, this.placeholder.parentNode === document.head ? this.placeholder.nextSibling : null);
|
||||
document.head.removeChild(this.placeholder);
|
||||
this.placeholder = element;
|
||||
}
|
||||
|
||||
applyUrl(url: string) {
|
||||
if (this.lastUrl === url) {
|
||||
return;
|
||||
}
|
||||
this.lastContent = undefined;
|
||||
this.lastUrl = url;
|
||||
const element = document.createElement('link');
|
||||
element.onload = onload;
|
||||
element.onerror = onload;
|
||||
|
||||
const i = createDefer();
|
||||
function onload(e: any) {
|
||||
element.onload = null;
|
||||
element.onerror = null;
|
||||
if (e.type === 'load') {
|
||||
i.resolve();
|
||||
} else {
|
||||
i.reject();
|
||||
}
|
||||
}
|
||||
|
||||
element.href = url;
|
||||
element.rel = 'stylesheet';
|
||||
if (this.id) {
|
||||
element.setAttribute('data-id', this.id);
|
||||
}
|
||||
document.head.insertBefore(element, this.placeholder.parentNode === document.head ? this.placeholder.nextSibling : null);
|
||||
document.head.removeChild(this.placeholder);
|
||||
this.placeholder = element;
|
||||
return i.promise();
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,8 @@ import React, { Component, PureComponent, createElement as reactCreateElement }
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import { isEmpty } from '@ali/b3-one/lib/obj';
|
||||
import Div from '@ali/iceluna-comp-div';
|
||||
import AppContext from '../context/appContext';
|
||||
import { isFileSchema, goldlog } from '../utils';
|
||||
import PageEngine from './pageEngine';
|
||||
@ -9,9 +11,7 @@ import ComponentEngine from './compEngine';
|
||||
import BlockEngine from './blockEngine';
|
||||
import AddonEngine from './addonEngine';
|
||||
import TempEngine from './tempEngine';
|
||||
import { isEmpty } from '@ali/b3-one/lib/obj';
|
||||
import BaseEngine from './base';
|
||||
import Div from '@ali/iceluna-comp-div';
|
||||
|
||||
window.React = React;
|
||||
window.ReactDom = ReactDOM;
|
||||
@ -47,6 +47,7 @@ function isReactClass(obj) {
|
||||
|
||||
export default class Engine extends PureComponent {
|
||||
static dislayName = 'engine';
|
||||
|
||||
static propTypes = {
|
||||
appHelper: PropTypes.object,
|
||||
components: PropTypes.object,
|
||||
@ -57,6 +58,7 @@ export default class Engine extends PureComponent {
|
||||
onCompGetCtx: PropTypes.func,
|
||||
customCreateElement: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
appHelper: null,
|
||||
components: {},
|
||||
@ -116,12 +118,10 @@ export default class Engine extends PureComponent {
|
||||
return;
|
||||
}
|
||||
Component.patchedCatch = true;
|
||||
Component.getDerivedStateFromError = (error) => {
|
||||
return { engineRenderError: true, error };
|
||||
};
|
||||
Component.getDerivedStateFromError = (error) => ({ engineRenderError: true, error });
|
||||
const engine = this;
|
||||
const originRender = Component.prototype.render;
|
||||
Component.prototype.render = function () {
|
||||
Component.prototype.render = function() {
|
||||
if (this.state && this.state.engineRenderError) {
|
||||
this.state.engineRenderError = false;
|
||||
return engine.createElement(engine.getFaultComponent(), {
|
||||
@ -132,7 +132,7 @@ export default class Engine extends PureComponent {
|
||||
return originRender.call(this);
|
||||
};
|
||||
const originShouldComponentUpdate = Component.prototype.shouldComponentUpdate;
|
||||
Component.prototype.shouldComponentUpdate = function (nextProps, nextState) {
|
||||
Component.prototype.shouldComponentUpdate = function(nextProps, nextState) {
|
||||
if (nextState && nextState.engineRenderError) {
|
||||
return true;
|
||||
}
|
||||
@ -145,15 +145,19 @@ export default class Engine extends PureComponent {
|
||||
this.patchDidCatch(Component);
|
||||
return (this.props.customCreateElement || reactCreateElement)(Component, props, children);
|
||||
}
|
||||
|
||||
getNotFoundComponent() {
|
||||
return this.props.notFoundComponent || NotFoundComponent;
|
||||
}
|
||||
|
||||
getFaultComponent() {
|
||||
return this.props.faultComponent || FaultComponent;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { schema, designMode, appHelper, components, customCreateElement } = this.props;
|
||||
const {
|
||||
schema, designMode, appHelper, components, customCreateElement
|
||||
} = this.props;
|
||||
if (isEmpty(schema)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -7,19 +7,22 @@ import '@alifd/next/lib/loading/style';
|
||||
import AppContext from '../context/appContext';
|
||||
import BaseEngine from './base';
|
||||
import { isSchema, getFileCssName } from '../utils';
|
||||
|
||||
const debug = Debug('engine:page');
|
||||
|
||||
export default class PageEngine extends BaseEngine {
|
||||
static dislayName = 'page-engine';
|
||||
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
__schema: {},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`page.getDerivedStateFromProps`);
|
||||
debug('page.getDerivedStateFromProps');
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
@ -44,18 +47,22 @@ export default class PageEngine extends BaseEngine {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`page.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`page.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`page.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`page.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
async componentDidCatch() {
|
||||
await super.componentDidCatch(...arguments);
|
||||
debug(`page.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
@ -73,7 +80,9 @@ export default class PageEngine extends BaseEngine {
|
||||
this.__render();
|
||||
|
||||
const props = this.__parseData(__schema.props);
|
||||
const { id, className, style, autoLoading, defaultHeight = 300, loading } = props;
|
||||
const {
|
||||
id, className, style, autoLoading, defaultHeight = 300, loading
|
||||
} = props;
|
||||
|
||||
const { Page } = __components;
|
||||
if (Page) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import LowCodeRenderer from '@ali/lowcode-react-renderer';
|
||||
import { isObject } from 'lodash';
|
||||
// import { isObject } from 'lodash';
|
||||
import { ReactInstance, Fragment, Component, createElement } from 'react';
|
||||
import { observer } from '@recore/obx-react';
|
||||
import { SimulatorRenderer } from './renderer';
|
||||
|
||||
@ -87,11 +87,11 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
||||
@computed get context(): any {
|
||||
return this._appContext;
|
||||
}
|
||||
@obx.ref private _designMode: string = 'design';
|
||||
@obx.ref private _designMode = 'design';
|
||||
@computed get designMode(): any {
|
||||
return this._designMode;
|
||||
}
|
||||
@obx.ref private _device: string = 'default';
|
||||
@obx.ref private _device = 'default';
|
||||
@computed get device() {
|
||||
return this._device;
|
||||
}
|
||||
@ -224,7 +224,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result = {...propsSchema};
|
||||
const result = { ...propsSchema };
|
||||
const reg = /^(?:this\.props|props)\.(\S+)$/;
|
||||
Object.keys(propsSchema).map((key: string) => {
|
||||
if (propsSchema[key].type === 'JSExpression') {
|
||||
@ -250,8 +250,8 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
||||
let props = processPropsSchema(schema.props, propsMap);
|
||||
props = host.document.designer.transformProps(props, node, TransformStage.Init);
|
||||
props = host.document.designer.transformProps(props, node, TransformStage.Render);
|
||||
return createElement(Com, {...props, _leaf}, children);
|
||||
}
|
||||
return createElement(Com, { ...props, _leaf }, children);
|
||||
};
|
||||
|
||||
const renderer = this;
|
||||
class Com extends React.Component {
|
||||
@ -259,7 +259,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
|
||||
const componentsMap = renderer.componentsMap;
|
||||
let children = null;
|
||||
if (_schema.children && Array.isArray(_schema.children)) {
|
||||
children = _schema.children?.map((item:any) => getElement(componentsMap, item, this.props));
|
||||
children = _schema.children?.map((item: any) => getElement(componentsMap, item, this.props));
|
||||
}
|
||||
return createElement(React.Fragment, {}, children);
|
||||
}
|
||||
@ -386,9 +386,12 @@ const builtinComponents = {
|
||||
Leaf,
|
||||
};
|
||||
|
||||
function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> }) {
|
||||
function buildComponents(
|
||||
libraryMap: LibraryMap,
|
||||
componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> },
|
||||
) {
|
||||
const components: any = {
|
||||
...builtinComponents
|
||||
...builtinComponents,
|
||||
};
|
||||
Object.keys(componentsMap).forEach((componentName) => {
|
||||
let component = componentsMap[componentName];
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# FIXME! do not run build
|
||||
lerna exec --scope @ali/lowcode-rax-renderer -- npm run build
|
||||
lerna exec --scope @ali/lowcode-react-renderer -- npm run build
|
||||
lerna exec --scope @ali/lowcode-demo -- npm start
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user