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 node = nodeInst.node || this.document.rootNode;
const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find((item) => const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find((item) =>
item.contains(targetElement), // 可能是 [null];
item && item.contains(targetElement),
) as HTMLElement; ) as HTMLElement;
if (!rootElement) { if (!rootElement) {
return; return;

View File

@ -33,8 +33,9 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
private async setupAssets() { private async setupAssets() {
const { editor } = this.props; const { editor } = this.props;
try {
const assets = await editor.onceGot('assets'); const assets = await editor.onceGot('assets');
const renderEnv = await editor.onceGot('renderEnv'); const renderEnv = await editor.get('renderEnv');
if (!this._mounted) { if (!this._mounted) {
return; return;
} }
@ -46,6 +47,9 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
renderEnv, renderEnv,
}; };
this.setState(state); this.setState(state);
} catch (e) {
console.log(e);
}
} }
componentWillUnmount() { componentWillUnmount() {

View File

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

View File

@ -1,7 +1,6 @@
import { Component, createElement } from 'rax'; import { Component, createElement } from 'rax';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Debug from 'debug'; import Debug from 'debug';
import View from 'rax-view';
import classnames from 'classnames'; import classnames from 'classnames';
import DataHelper from '../utils/dataHelper'; import DataHelper from '../utils/dataHelper';
import { import {
@ -24,7 +23,7 @@ import {
import VisualDom from '../comp/visualDom'; import VisualDom from '../comp/visualDom';
import AppContext from '../context/appContext'; import AppContext from '../context/appContext';
// import CompWrapper from '../hoc/compWrapper'; import compWrapper from '../hoc/compWrapper';
const debug = Debug('engine:base'); const debug = Debug('engine:base');
const DESIGN_MODE = { const DESIGN_MODE = {
@ -85,8 +84,7 @@ export default class BaseEngine extends Component {
console.warn(e); console.warn(e);
} }
reloadDataSource = () => reloadDataSource = () => new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
debug('reload data source'); debug('reload data source');
if (!this.__dataHelper) { if (!this.__dataHelper) {
this.__showPlaceholder = false; this.__showPlaceholder = false;
@ -125,8 +123,8 @@ export default class BaseEngine extends Component {
__bindCustomMethods = (props = this.props) => { __bindCustomMethods = (props = this.props) => {
const { __schema } = props; const { __schema } = props;
const customMethodsList = Object.keys(__schema.methods || {}) || []; const customMethodsList = Object.keys(__schema.methods || {}) || [];
this.__customMethodsList && this.__customMethodsList
this.__customMethodsList.forEach((item) => { && this.__customMethodsList.forEach((item) => {
if (!customMethodsList.includes(item)) { if (!customMethodsList.includes(item)) {
delete this[item]; delete this[item];
} }
@ -217,8 +215,7 @@ export default class BaseEngine extends Component {
} }
} }
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } = const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } = this.props || {};
this.props || {};
const { engine } = this.context || {}; const { engine } = this.context || {};
if (isJSExpression(schema)) { if (isJSExpression(schema)) {
return parseExpression(schema, self); return parseExpression(schema, self);
@ -229,9 +226,7 @@ export default class BaseEngine extends Component {
} }
if (Array.isArray(schema)) { if (Array.isArray(schema)) {
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo); if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
return schema.map((item, idx) => 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),
);
} }
// //
@ -310,7 +305,9 @@ export default class BaseEngine extends Component {
}, },
}); });
// ref // ref
if (acceptsRef(Comp)) { if (!acceptsRef(Comp)) {
Comp = compWrapper(Comp);
}
otherProps.ref = (ref) => { otherProps.ref = (ref) => {
const refProps = props.ref; const refProps = props.ref;
if (refProps && typeof refProps === 'string') { if (refProps && typeof refProps === 'string') {
@ -318,7 +315,6 @@ export default class BaseEngine extends Component {
} }
engine && engine.props.onCompGetRef(schema, ref); engine && engine.props.onCompGetRef(schema, ref);
}; };
}
// scope // scope
if (scopeKey && this.__compScopes[scopeKey]) { if (scopeKey && this.__compScopes[scopeKey]) {
@ -346,12 +342,12 @@ export default class BaseEngine extends Component {
); );
} }
const renderComp = (props) => engine.createElement( const renderComp = (props) =>
engine.createElement(
Comp, Comp,
props, props,
(!isFileSchema(schema) (!isFileSchema(schema) &&
&& !!schema.children !!schema.children &&
&&
this.__createVirtualDom( this.__createVirtualDom(
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children, isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
self, self,
@ -359,8 +355,8 @@ export default class BaseEngine extends Component {
schema, schema,
Comp, Comp,
}, },
)) || ))
null, || null,
); );
// //
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) { if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {

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() { render() {
const { forwardedRef, ...rest} = this.props; return createElement(Comp, {
console.log('forwardedRef', forwardedRef) ...this.props,
return <Comp { ...rest } ref={forwardedRef}></Comp> });
} }
} }
function forwardedRef(props, ref) { return compWrapper;
return createElement(
WrappedComponent,
{
...props,
forwardedRef: ref
} }
)
}
forwardedRef.displayName = Comp.displayName;
return forwardRef(forwardedRef);
};
export default enhanced;

View File

@ -268,7 +268,7 @@ export function generateI18n(locale = 'zh-CN', messages = {}) {
* @param {*} Comp 需要判断的组件 * @param {*} Comp 需要判断的组件
*/ */
export function acceptsRef(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; z-index: 1;
width: 100%; width: 100%;
white-space: nowrap; 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; z-index: 1;
width: 100%; width: 100%;
white-space: nowrap; white-space: nowrap;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
} }
body.engine-document { body.engine-document {

View File

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

View File

@ -3,3 +3,5 @@
lerna run build --stream 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-general/dist
cp -R ./packages/react-simulator-renderer/dist/* ./packages/editor-preset-vision/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