feat: rax simulator

This commit is contained in:
响指 2020-07-21 18:26:52 +08:00
commit 05b262dc19
12 changed files with 403 additions and 327 deletions

View File

@ -442,7 +442,8 @@ 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),
// 可能是 [null];
item && item.contains(targetElement),
) as HTMLElement;
if (!rootElement) {
return;

View File

@ -33,19 +33,23 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
private async setupAssets() {
const { editor } = this.props;
const assets = await editor.onceGot('assets');
const renderEnv = await editor.onceGot('renderEnv');
if (!this._mounted) {
return;
try {
const assets = await editor.onceGot('assets');
const renderEnv = await editor.get('renderEnv');
if (!this._mounted) {
return;
}
const { components, packages, extraEnvironment } = assets;
const state = {
componentMetadatas: components || [],
library: packages || [],
extraEnvironment,
renderEnv,
};
this.setState(state);
} catch (e) {
console.log(e);
}
const { components, packages, extraEnvironment } = assets;
const state = {
componentMetadatas: components || [],
library: packages || [],
extraEnvironment,
renderEnv,
};
this.setState(state);
}
componentWillUnmount() {

View File

@ -1,6 +1,4 @@
import { Component } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import './index.css';
export default class VisualDom extends Component {
@ -12,12 +10,12 @@ export default class VisualDom extends Component {
const { children, title, label, text, __componentName } = this.props;
return (
<View className="visual-dom">
<View className="panel-container">
<Text className="title">{title || label || text || __componentName}</Text>
<View className="content">{children}</View>
</View>
</View>
<div className="visual-dom">
<div className="panel-container">
<span className="title">{title || label || text || __componentName}</span>
<div className="content">{children}</div>
</div>
</div>
);
}
}

View File

@ -1,7 +1,6 @@
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 {
@ -24,7 +23,7 @@ import {
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 = {
@ -85,31 +84,30 @@ export default class BaseEngine extends Component {
console.warn(e);
}
reloadDataSource = () =>
new Promise((resolve, reject) => {
debug('reload data source');
if (!this.__dataHelper) {
this.__showPlaceholder = false;
return resolve();
}
this.__dataHelper
.getInitData()
.then((res) => {
reloadDataSource = () => new Promise((resolve, reject) => {
debug('reload data source');
if (!this.__dataHelper) {
this.__showPlaceholder = false;
if (isEmpty(res)) {
this.forceUpdate();
return resolve();
}
this.setState(res, resolve);
})
.catch((err) => {
if (this.__showPlaceholder) {
return resolve();
}
this.__dataHelper
.getInitData()
.then((res) => {
this.__showPlaceholder = false;
this.forceUpdate();
}
reject(err);
});
});
if (isEmpty(res)) {
this.forceUpdate();
return resolve();
}
this.setState(res, resolve);
})
.catch((err) => {
if (this.__showPlaceholder) {
this.__showPlaceholder = false;
this.forceUpdate();
}
reject(err);
});
});
__setLifeCycleMethods = (method, args) => {
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
@ -125,8 +123,8 @@ export default class BaseEngine extends Component {
__bindCustomMethods = (props = this.props) => {
const { __schema } = props;
const customMethodsList = Object.keys(__schema.methods || {}) || [];
this.__customMethodsList &&
this.__customMethodsList.forEach((item) => {
this.__customMethodsList
&& this.__customMethodsList.forEach((item) => {
if (!customMethodsList.includes(item)) {
delete this[item];
}
@ -217,8 +215,7 @@ export default class BaseEngine extends Component {
}
}
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } =
this.props || {};
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } = this.props || {};
const { engine } = this.context || {};
if (isJSExpression(schema)) {
return parseExpression(schema, self);
@ -229,9 +226,7 @@ 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),
);
return schema.map((item, idx) => this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx),);
}
//
@ -291,11 +286,11 @@ export default class BaseEngine extends Component {
// propscontext
const otherProps = isFileSchema(schema)
? {
__schema: schema,
__appHelper: appHelper,
__components: components,
// __componentsMap: componentsMap,
}
__schema: schema,
__appHelper: appHelper,
__components: components,
// __componentsMap: componentsMap,
}
: {};
if (engine && engine.props.designMode) {
otherProps.__designMode = engine.props.designMode;
@ -310,15 +305,16 @@ export default class BaseEngine extends Component {
},
});
// ref
if (acceptsRef(Comp)) {
otherProps.ref = (ref) => {
const refProps = props.ref;
if (refProps && typeof refProps === 'string') {
this[refProps] = ref;
}
engine && engine.props.onCompGetRef(schema, ref);
};
if (!acceptsRef(Comp)) {
Comp = compWrapper(Comp);
}
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]) {
@ -346,12 +342,12 @@ export default class BaseEngine extends Component {
);
}
const renderComp = (props) => engine.createElement(
Comp,
const renderComp = (props) =>
engine.createElement(
Comp,
props,
(!isFileSchema(schema)
&& !!schema.children
&&
(!isFileSchema(schema) &&
!!schema.children &&
this.__createVirtualDom(
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
self,
@ -359,8 +355,8 @@ export default class BaseEngine extends Component {
schema,
Comp,
},
)) ||
null,
))
|| null,
);
//
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
@ -458,7 +454,7 @@ export default class BaseEngine extends Component {
if (isEmpty(params)) {
return checkProps(this.__createVirtualDom(data, self, { schema, Comp }));
}
return checkProps(function() {
return checkProps(function () {
const args = {};
if (Array.isArray(params) && params.length) {
params.map((item, idx) => {

View File

@ -1,28 +1,17 @@
import { Component, createElement, forwardRef } from 'rax';
import { createElement, Component } from 'rax';
export default function (Comp) {
class compWrapper extends Component {
constructor(props, context) {
super(props, context);
}
function enhanced(Comp) {
class WrappedComponent extends Component {
render() {
const { forwardedRef, ...rest} = this.props;
console.log('forwardedRef', forwardedRef)
return <Comp { ...rest } ref={forwardedRef}></Comp>
return createElement(Comp, {
...this.props,
});
}
}
function forwardedRef(props, ref) {
return createElement(
WrappedComponent,
{
...props,
forwardedRef: ref
}
)
}
forwardedRef.displayName = Comp.displayName;
return forwardRef(forwardedRef);
};
export default enhanced;
return compWrapper;
}

View File

@ -268,7 +268,7 @@ export function generateI18n(locale = 'zh-CN', messages = {}) {
* @param {*} Comp 需要判断的组件
*/
export function acceptsRef(Comp) {
return true;
return Comp && Comp.prototype && Comp.prototype.setState;
}
/**

View File

@ -1,226 +0,0 @@
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;
}
}

View File

@ -0,0 +1,250 @@
import { Component } from 'rax';
class Leaf extends Component {
static displayName = 'Leaf';
static componentMetadata = {
componentName: 'Leaf',
configure: {
props: [{
name: 'children',
setter: 'StringSetter',
}],
// events/className/style/general/directives
supports: false,
}
};
render() {
const { children } = this.props;
return children;
}
}
export default Leaf;
// 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;
// }
// }

View File

@ -0,0 +1,53 @@
import { Component } from 'rax';
class Slot extends Component {
static displayName = 'Slot';
static componentMetadata = {
componentName: 'Slot',
configure: {
props: [{
name: '___title',
title: {
type: 'i18n',
'en-US': 'Slot Title',
'zh-CN': '插槽标题'
},
setter: 'StringSetter',
defaultValue: '插槽容器'
}, {
name: '___params',
title: {
type: 'i18n',
'en-US': 'Slot Params',
'zh-CN': '插槽入参'
},
setter: {
componentName: 'ArraySetter',
props: {
itemSetter: {
componentName: 'StringSetter',
props: {
placeholder: {
type: 'i18n',
'zh-CN': '参数名称',
'en-US': 'Argument Name'
}
}
}
}
}
}],
// events/className/style/general/directives
supports: false,
}
};
render() {
const { children } = this.props;
return (
<div className="lc-container">{children}</div>
);
}
}
export default Slot;

View File

@ -49,6 +49,10 @@ html.engine-cursor-ew-resize, html.engine-cursor-ew-resize * {
z-index: 1;
width: 100%;
white-space: nowrap;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
@ -73,6 +77,10 @@ html.engine-cursor-ew-resize, html.engine-cursor-ew-resize * {
z-index: 1;
width: 100%;
white-space: nowrap;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
body.engine-document {

View File

@ -10,7 +10,8 @@ import { raxFindDOMNodes } from './utils/find-dom-nodes';
import { getClientRects } from './utils/get-client-rects';
import loader from './utils/loader';
import Leaf from './builtin-components/Leaf';
import Leaf from './builtin-components/leaf';
import Slot from './builtin-components/slot';
import { host } from './host';
import { EventEmitter } from 'events';
@ -354,7 +355,7 @@ function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmI
}
const builtinComponents = {
// Slot,
Slot,
Leaf,
};
@ -390,7 +391,7 @@ function getClosestNodeInstance(from: any, specId?: string): NodeInstance<any> |
}
function matcher(parent: any) {
return parent.__isReactiveComponent;
return parent.__isReactiveComponent && parent.props.componentId;
}
function getNodeInstance(dom: HTMLElement): NodeInstance<any> | null {

View File

@ -3,3 +3,5 @@
lerna run build --stream
cp -R ./packages/react-simulator-renderer/dist/* ./packages/editor-preset-general/dist
cp -R ./packages/react-simulator-renderer/dist/* ./packages/editor-preset-vision/dist
cp -R ./packages/react-simulator-renderer/dist/* ./packages/editor-preset-general/dist
cp -R ./packages/react-simulator-renderer/dist/* ./packages/editor-preset-vision/dist