mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-15 05:36:39 +00:00
complete parse props
This commit is contained in:
parent
5f569cc1ca
commit
d8003720a2
@ -31,9 +31,8 @@ import {
|
||||
import { isNodeSchema, NodeSchema } from '../../../designer/schema';
|
||||
import { ComponentMetadata } from '../../../designer/component-meta';
|
||||
import { ReactInstance } from 'react';
|
||||
import { setNativeSelection } from '../../../designer/helper/navtive-selection';
|
||||
import cursor from '../../../designer/helper/cursor';
|
||||
import { isRootNode } from '../../../designer/document/node/root-node';
|
||||
import { parseProps } from '../utils/parse-props';
|
||||
|
||||
export interface SimulatorProps {
|
||||
// 从 documentModel 上获取
|
||||
@ -333,12 +332,26 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
* @see ISimulator
|
||||
*/
|
||||
generateComponentMetadata(componentName: string): ComponentMetadata {
|
||||
// if html tags
|
||||
if (isHTMLTag(componentName)) {
|
||||
return {
|
||||
componentName,
|
||||
// TODO: read builtins html metadata
|
||||
};
|
||||
}
|
||||
|
||||
const component = this.getComponent(componentName);
|
||||
|
||||
if (component) {
|
||||
parseProps(component as any);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// 1. generate builtin div/p/h1/h2
|
||||
// 2. read propTypes
|
||||
return {
|
||||
componentName,
|
||||
props: parseProps(this.getComponent(componentName)),
|
||||
};
|
||||
}
|
||||
|
||||
@ -346,7 +359,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
* @see ISimulator
|
||||
*/
|
||||
getComponent(componentName: string): Component | null {
|
||||
return null;
|
||||
return this.renderer?.getComponent(componentName) || null;
|
||||
}
|
||||
|
||||
@obx.val private instancesMap = new Map<string, ReactInstance[]>();
|
||||
@ -933,6 +946,10 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
// #endregion
|
||||
}
|
||||
|
||||
function isHTMLTag(name: string) {
|
||||
return /^[a-z]\w*$/.test(name);
|
||||
}
|
||||
|
||||
function isPointInRect(point: CanvasPoint, rect: Rect) {
|
||||
return (
|
||||
point.canvasY >= rect.top &&
|
||||
|
||||
@ -7,7 +7,6 @@ import { RootSchema, NpmInfo } from '../../../designer/schema';
|
||||
import { getClientRects } from '../../../utils/get-client-rects';
|
||||
import { Asset } from '../utils/asset';
|
||||
import loader from '../utils/loader';
|
||||
import { ComponentMetadata } from '../../../designer/component-meta';
|
||||
import { reactFindDOMNodes, FIBER_KEY } from '../utils/react-find-dom-nodes';
|
||||
import { isESModule } from '../../../../../utils/is-es-module';
|
||||
import { NodeInstance } from '../../../designer/simulator';
|
||||
@ -68,7 +67,7 @@ export class SimulatorRenderer {
|
||||
private buildComponents() {
|
||||
this._components = buildComponents(this._componentsMap);
|
||||
}
|
||||
@obx.ref private _components = {};
|
||||
@obx.ref private _components: any = {};
|
||||
@computed get components(): object {
|
||||
// 根据 device 选择不同组件,进行响应式
|
||||
// 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl
|
||||
@ -175,6 +174,27 @@ export class SimulatorRenderer {
|
||||
this.ctxMap.set(id, ctx);
|
||||
}
|
||||
|
||||
getComponent(componentName: string) {
|
||||
const paths = componentName.split('.');
|
||||
const subs: string[] = [];
|
||||
|
||||
while (true) {
|
||||
const component = this._components[componentName];
|
||||
if (component) {
|
||||
return getSubComponent(component, subs);
|
||||
}
|
||||
|
||||
const sub = paths.pop();
|
||||
if (!sub) {
|
||||
return null;
|
||||
}
|
||||
subs.unshift(sub);
|
||||
componentName = paths.join('.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getComponentInstances(id: string): ReactInstance[] | null {
|
||||
return this.instancesMap.get(id) || null;
|
||||
}
|
||||
|
||||
200
packages/designer/src/builtins/simulator/utils/parse-props.ts
Normal file
200
packages/designer/src/builtins/simulator/utils/parse-props.ts
Normal file
@ -0,0 +1,200 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { isValidElement } from 'react';
|
||||
import { isElement } from '../../../utils/is-element';
|
||||
import { PropType, PropConfig } from '../../../designer/prop-config';
|
||||
|
||||
export const primitiveTypes = [
|
||||
'string',
|
||||
'number',
|
||||
'array',
|
||||
'bool',
|
||||
'func',
|
||||
'object',
|
||||
'node',
|
||||
'element',
|
||||
'symbol',
|
||||
'any',
|
||||
];
|
||||
|
||||
function makeRequired(propType: any, lowcodeType: string | object) {
|
||||
function lowcodeCheckTypeIsRequired(...rest: any[]) {
|
||||
return propType.isRequired(...rest);
|
||||
}
|
||||
if (typeof lowcodeType === 'string') {
|
||||
lowcodeType = {
|
||||
type: lowcodeType,
|
||||
};
|
||||
}
|
||||
lowcodeCheckTypeIsRequired.lowcodeType = {
|
||||
...lowcodeType,
|
||||
isRequired: true,
|
||||
};
|
||||
return lowcodeCheckTypeIsRequired;
|
||||
}
|
||||
|
||||
function define(propType: any = PropTypes.any, lowcodeType: string | object = {}) {
|
||||
if (!propType._inner && propType.name !== 'lowcodeCheckType') {
|
||||
propType.lowcodeType = lowcodeType;
|
||||
}
|
||||
function lowcodeCheckType(...rest: any[]) {
|
||||
return propType(...rest);
|
||||
}
|
||||
lowcodeCheckType.lowcodeType = lowcodeType;
|
||||
lowcodeCheckType.isRequired = makeRequired(propType, lowcodeType);
|
||||
return lowcodeCheckType;
|
||||
}
|
||||
|
||||
const LowcodeTypes: any = {
|
||||
...PropTypes,
|
||||
define,
|
||||
};
|
||||
|
||||
(window as any).PropTypes = LowcodeTypes;
|
||||
(window as any).React.PropTypes = LowcodeTypes;
|
||||
|
||||
// override primitive type chechers
|
||||
primitiveTypes.forEach(type => {
|
||||
const propType = (PropTypes as any)[type];
|
||||
if (!propType) {
|
||||
return;
|
||||
}
|
||||
propType._inner = true;
|
||||
LowcodeTypes[type] = define(propType, type);
|
||||
});
|
||||
|
||||
// You can ensure that your prop is limited to specific values by treating
|
||||
// it as an enum.
|
||||
LowcodeTypes.oneOf = (list: any[]) => {
|
||||
return define(PropTypes.oneOf(list), {
|
||||
type: 'oneOf',
|
||||
value: list,
|
||||
});
|
||||
};
|
||||
|
||||
// An array of a certain type
|
||||
LowcodeTypes.arrayOf = (type: any) => {
|
||||
return define(PropTypes.arrayOf(type), {
|
||||
type: 'arrayOf',
|
||||
value: type.lowcodeType || 'any',
|
||||
});
|
||||
};
|
||||
|
||||
// An object with property values of a certain type
|
||||
LowcodeTypes.objectOf = (type: any) => {
|
||||
return define(PropTypes.objectOf(type), {
|
||||
type: 'objectOf',
|
||||
value: type.lowcodeType || 'any',
|
||||
});
|
||||
};
|
||||
|
||||
// An object that could be one of many types
|
||||
LowcodeTypes.oneOfType = (types: any[]) => {
|
||||
const itemTypes = types.map(type => type.lowcodeType || 'any');
|
||||
return define(PropTypes.oneOfType(types), {
|
||||
type: 'oneOfType',
|
||||
value: itemTypes,
|
||||
});
|
||||
};
|
||||
|
||||
// An object with warnings on extra properties
|
||||
LowcodeTypes.exact = (typesMap: any) => {
|
||||
const configs = Object.keys(typesMap).map(key => {
|
||||
return {
|
||||
name: key,
|
||||
propType: typesMap[key].lowcodeType || 'any',
|
||||
};
|
||||
});
|
||||
return define(PropTypes.exact(typesMap), {
|
||||
type: 'exact',
|
||||
value: configs,
|
||||
});
|
||||
};
|
||||
|
||||
// An object taking on a particular shape
|
||||
LowcodeTypes.shape = (typesMap: any) => {
|
||||
const configs = Object.keys(typesMap).map(key => {
|
||||
return {
|
||||
name: key,
|
||||
propType: typesMap[key].lowcodeType || 'any',
|
||||
};
|
||||
});
|
||||
return define(PropTypes.shape(typesMap), {
|
||||
type: 'shape',
|
||||
value: configs,
|
||||
});
|
||||
};
|
||||
|
||||
const BasicTypes = ['string', 'number', 'object'];
|
||||
export function parseProps(component: any): PropConfig[] {
|
||||
if (!component) {
|
||||
return [];
|
||||
}
|
||||
const propTypes = component.propTypes || ({} as any);
|
||||
const defaultProps = component.defaultProps || ({} as any);
|
||||
const result: any = {};
|
||||
if (!propTypes) return [];
|
||||
Object.keys(propTypes).forEach(key => {
|
||||
const propTypeItem = propTypes[key];
|
||||
const defaultValue = defaultProps[key];
|
||||
const lowcodeType = propTypeItem.lowcodeType;
|
||||
if (lowcodeType) {
|
||||
result[key] = {
|
||||
name: key,
|
||||
propType: lowcodeType,
|
||||
};
|
||||
if (defaultValue != null) {
|
||||
result[key].defaultValue = defaultValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let i = primitiveTypes.length;
|
||||
while (i-- > 0) {
|
||||
const k = primitiveTypes[i];
|
||||
if ((LowcodeTypes as any)[k] === propTypeItem) {
|
||||
result[key] = {
|
||||
name: key,
|
||||
propType: k,
|
||||
};
|
||||
if (defaultValue != null) {
|
||||
result[key].defaultValue = defaultValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
result[key] = {
|
||||
name: key,
|
||||
propType: 'any',
|
||||
};
|
||||
if (defaultValue != null) {
|
||||
result[key].defaultValue = defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(defaultProps).forEach(key => {
|
||||
if (result[key]) return;
|
||||
const defaultValue = defaultProps[key];
|
||||
let type: string = typeof defaultValue;
|
||||
if (type === 'boolean') {
|
||||
type = 'bool';
|
||||
} else if (type === 'function') {
|
||||
type = 'func';
|
||||
} else if (type === 'object' && Array.isArray(defaultValue)) {
|
||||
type = 'array';
|
||||
} else if (defaultValue && isValidElement(defaultValue)) {
|
||||
type = 'node';
|
||||
} else if (defaultValue && isElement(defaultValue)) {
|
||||
type = 'element';
|
||||
} else if (!BasicTypes.includes(type)) {
|
||||
type = 'any';
|
||||
}
|
||||
|
||||
result[key] = {
|
||||
name: key,
|
||||
propType: type || 'any',
|
||||
defaultValue,
|
||||
};
|
||||
});
|
||||
|
||||
return Object.keys(result).map(key => result[key]);
|
||||
}
|
||||
@ -1,198 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const primitiveTypeMaps = {
|
||||
string: {
|
||||
defaultValue: '',
|
||||
display: 'inline',
|
||||
setter: 'TextSetter',
|
||||
},
|
||||
number: {
|
||||
display: 'inline',
|
||||
setter: 'NumberSetter' // extends TextSetter
|
||||
},
|
||||
array: {
|
||||
defaultValue: [],
|
||||
display: 'inline',
|
||||
// itemType: any
|
||||
setter: 'ArraySetter' // extends ExpressionSetter
|
||||
},
|
||||
bool: {
|
||||
defaultValue: false,
|
||||
display: 'inline',
|
||||
setter: 'BoolSetter'
|
||||
},
|
||||
func: {
|
||||
defaultValue: () => {},
|
||||
display: 'inline',
|
||||
setter: 'FunctionSetter' // extends ExpressionSetter
|
||||
},
|
||||
object: {
|
||||
defaultValue: {},
|
||||
display: 'inline',
|
||||
// itemType: any
|
||||
setter: 'ObjectSetter' // extends ExpressionSetter
|
||||
},
|
||||
// Anything that can be rendered: numbers, strings, elements or an array
|
||||
// (or fragment) containing these types.
|
||||
node: {
|
||||
defaultValue: '',
|
||||
display: 'inline',
|
||||
setter: 'FragmentSetter',
|
||||
},
|
||||
// A React element.
|
||||
element: {
|
||||
display: 'inline',
|
||||
setter: 'JSXSetter', // extends ExpressionSetter
|
||||
},
|
||||
symbol: {
|
||||
display: 'inline',
|
||||
setter: 'ExpressionSetter',
|
||||
},
|
||||
any: {
|
||||
display: 'inline',
|
||||
setter: 'ExpressionSetter',
|
||||
}
|
||||
};
|
||||
|
||||
function makeRequired(propType, visionType) {
|
||||
function visionCheckTypeIsRequired(...rest) {
|
||||
return propType.isRequired(...rest);
|
||||
}
|
||||
visionCheckTypeIsRequired.visionType = {
|
||||
...visionType,
|
||||
required: true,
|
||||
};
|
||||
return visionCheckTypeIsRequired;
|
||||
}
|
||||
|
||||
function define(propType = PropTypes.any, visionType = {}) {
|
||||
if (!propType._inner && propType.name !== 'visionCheckType') {
|
||||
propType.visionType = visionType;
|
||||
}
|
||||
function visionCheckType(...rest) {
|
||||
return propType(...rest);
|
||||
}
|
||||
visionCheckType.visionType = visionType;
|
||||
visionCheckType.isRequired = makeRequired(propType, visionType);
|
||||
return visionCheckType;
|
||||
}
|
||||
|
||||
const VisionTypes = {
|
||||
...PropTypes,
|
||||
define,
|
||||
};
|
||||
|
||||
export default VisionTypes;
|
||||
|
||||
// override primitive type chechers
|
||||
Object.keys(primitiveTypeMaps).forEach((type) => {
|
||||
const propType = PropTypes[type];
|
||||
if (!propType) {
|
||||
return;
|
||||
}
|
||||
propType._inner = true;
|
||||
VisionTypes[type] = define(propType, primitiveTypeMaps[type]);
|
||||
});
|
||||
|
||||
// You can ensure that your prop is limited to specific values by treating
|
||||
// it as an enum.
|
||||
VisionTypes.oneOf = (list) => {
|
||||
return define(PropTypes.oneOf(list), {
|
||||
defaultValue: list && list[0],
|
||||
display: 'inline',
|
||||
setter: {
|
||||
type: 'SelectSetter',
|
||||
options: list,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// An array of a certain type
|
||||
VisionTypes.arrayOf = (type) => {
|
||||
return define(PropTypes.arrayOf(type), {
|
||||
defaultValue: [],
|
||||
display: 'inline',
|
||||
setter: {
|
||||
type: 'ArraySetter', // list
|
||||
itemType: type.visionType || primitiveTypeMaps.any, // addable type
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// An object with property values of a certain type
|
||||
VisionTypes.objectOf = (type) => {
|
||||
return define(PropTypes.objectOf(type), {
|
||||
defaultValue: {},
|
||||
display: 'inline',
|
||||
setter: {
|
||||
type: 'ObjectSetter', // all itemType
|
||||
itemType: type.visionType || primitiveTypeMaps.any, // addable type
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// An object that could be one of many types
|
||||
VisionTypes.oneOfType = (types) => {
|
||||
const itemType = types.map(type => type.visionType || primitiveTypeMaps.any);
|
||||
return define(PropTypes.oneOfType(types), {
|
||||
defaultValue: itemType[0] && itemType[0].defaultValue,
|
||||
display: 'inline',
|
||||
setter: {
|
||||
type: 'OneOfTypeSetter',
|
||||
itemType, // addable type
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// You can also declare that a prop is an instance of a class. This uses
|
||||
// JS's instanceof operator.
|
||||
VisionTypes.instanceOf = (classType) => {
|
||||
return define(PropTypes.instanceOf(classType), {
|
||||
display: 'inline',
|
||||
setter: 'ExpressionSetter',
|
||||
});
|
||||
};
|
||||
|
||||
// An object with warnings on extra properties
|
||||
VisionTypes.exact = (typesMap) => {
|
||||
const exactTypes = {};
|
||||
const defaultValue = {};
|
||||
Object.keys(typesMap).forEach(key => {
|
||||
exactTypes[key] = typesMap[key].visionType || primitiveTypeMaps.any;
|
||||
defaultValue[key] = exactTypes[key].defaultValue;
|
||||
});
|
||||
return define(PropTypes.exact(typesMap), {
|
||||
defaultValue,
|
||||
display: 'inline',
|
||||
setter: {
|
||||
type: 'ObjectSetter', // all itemType
|
||||
exactTypes,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// An object taking on a particular shape
|
||||
VisionTypes.shape = (typesMap) => {
|
||||
const exactTypes = {};
|
||||
const defaultValue = {};
|
||||
Object.keys(typesMap).forEach(key => {
|
||||
exactTypes[key] = typesMap[key].visionType || primitiveTypeMaps.any;
|
||||
defaultValue[key] = exactTypes[key].defaultValue;
|
||||
});
|
||||
return define(PropTypes.shape(typesMap), {
|
||||
defaultValue,
|
||||
display: 'inline',
|
||||
setter: {
|
||||
type: 'ObjectSetter', // all itemType
|
||||
exactTypes,
|
||||
itemType: primitiveTypeMaps.any, // addable type
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// color
|
||||
// time
|
||||
// date
|
||||
// range
|
||||
@ -102,7 +102,7 @@ export default class DocumentModel {
|
||||
let schema: any;
|
||||
if (isDOMText(data) || isJSExpression(data)) {
|
||||
schema = {
|
||||
componentName: '#frag',
|
||||
componentName: 'Leaf',
|
||||
children: data,
|
||||
};
|
||||
} else {
|
||||
@ -281,6 +281,7 @@ export default class DocumentModel {
|
||||
// TODO: emit simulator mounted
|
||||
}
|
||||
|
||||
// FIXME: does needed?
|
||||
getComponent(componentName: string): any {
|
||||
return this.simulator!.getComponent(componentName);
|
||||
}
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
import { obx, computed } from '@recore/obx';
|
||||
import { JSExpression, isJSExpression } from '../../schema';
|
||||
|
||||
export default class NodeContent {
|
||||
@obx.ref private _value: string | JSExpression = '';
|
||||
|
||||
@computed get value(): string | JSExpression {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(val: string | JSExpression) {
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得表达式值
|
||||
*/
|
||||
@computed get code() {
|
||||
if (isJSExpression(this._value)) {
|
||||
return this._value.value;
|
||||
}
|
||||
return JSON.stringify(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表达式值
|
||||
*/
|
||||
set code(code: string) {
|
||||
if (isJSExpression(this._value)) {
|
||||
this._value = {
|
||||
...this._value,
|
||||
value: code,
|
||||
};
|
||||
} else {
|
||||
let useCode: boolean = true;
|
||||
try {
|
||||
const v = JSON.parse(code);
|
||||
const t = typeof v;
|
||||
if (v == null) {
|
||||
this._value = '';
|
||||
useCode = false;
|
||||
} else if (t === 'string' || t === 'number' || t === 'boolean') {
|
||||
this._value = String(v);
|
||||
useCode = false;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
if (useCode) {
|
||||
this._value = {
|
||||
type: 'JSExpression',
|
||||
value: code,
|
||||
mock: this._value,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(value: any) {
|
||||
this.import(value);
|
||||
}
|
||||
|
||||
import(value: any) {
|
||||
const type = typeof value;
|
||||
if (value == null) {
|
||||
this._value = '';
|
||||
} else if (type === 'string' || type === 'number' || type === 'boolean') {
|
||||
this._value = String(value);
|
||||
} else if (isJSExpression(value)) {
|
||||
this._value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否表达式
|
||||
*/
|
||||
@computed isJSExpression(): boolean {
|
||||
return isJSExpression(this._value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否空值
|
||||
*/
|
||||
@computed isEmpty() {
|
||||
if (isJSExpression(this._value)) {
|
||||
return this._value.value === '';
|
||||
}
|
||||
return this._value === '';
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
import { obx, computed, untracked } from '@recore/obx';
|
||||
import { NodeSchema, NodeData, PropsMap, PropsList } from '../../schema';
|
||||
import { obx, computed } from '@recore/obx';
|
||||
import { NodeSchema, NodeData, PropsMap, PropsList, isDOMText, isJSExpression } from '../../schema';
|
||||
import Props, { EXTRA_KEY_PREFIX } from './props/props';
|
||||
import DocumentModel from '../document-model';
|
||||
import NodeChildren from './node-children';
|
||||
import Prop from './props/prop';
|
||||
import NodeContent from './node-content';
|
||||
import { Component } from '../../simulator';
|
||||
import { ComponentMeta } from '../../component-meta';
|
||||
|
||||
/**
|
||||
@ -48,12 +46,12 @@ export default class Node {
|
||||
* * Component 组件/元件
|
||||
*/
|
||||
readonly componentName: string;
|
||||
protected _props?: Props;
|
||||
protected _children: NodeChildren | NodeContent;
|
||||
/**
|
||||
* 属性抽象
|
||||
*/
|
||||
readonly props: Props;
|
||||
protected _children?: NodeChildren;
|
||||
@obx.ref private _parent: NodeParent | null = null;
|
||||
get props(): Props | undefined {
|
||||
return this._props;
|
||||
}
|
||||
/**
|
||||
* 父级节点
|
||||
*/
|
||||
@ -63,8 +61,8 @@ export default class Node {
|
||||
/**
|
||||
* 当前节点子集
|
||||
*/
|
||||
get children(): NodeChildren | NodeContent {
|
||||
return this._children;
|
||||
get children(): NodeChildren | null {
|
||||
return this._children || null;
|
||||
}
|
||||
/**
|
||||
* 当前节点深度
|
||||
@ -99,19 +97,23 @@ export default class Node {
|
||||
this.id = id || `node$${document.nextId()}`;
|
||||
this.componentName = componentName;
|
||||
this._slotFor = slotFor;
|
||||
let _props: Props;
|
||||
if (isNodeParent(this)) {
|
||||
this._props = new Props(this, props, extras);
|
||||
_props = new Props(this, props, extras);
|
||||
this._children = new NodeChildren(this as NodeParent, children || []);
|
||||
} else {
|
||||
this._children = new NodeContent(children);
|
||||
_props = new Props(this, {
|
||||
children: isDOMText(children) || isJSExpression(children) ? children : '',
|
||||
});
|
||||
}
|
||||
this.props = _props;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否一个父亲类节点
|
||||
*/
|
||||
get isNodeParent(): boolean {
|
||||
return this.componentName.charAt(0) !== '#';
|
||||
return this.componentName !== 'Leaf';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,16 +167,6 @@ export default class Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点组件类
|
||||
*/
|
||||
@obx.ref get component(): Component {
|
||||
if (this.isNodeParent) {
|
||||
return this.document.getComponent(this.componentName) || this.componentName;
|
||||
}
|
||||
return this.componentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点组件描述
|
||||
*/
|
||||
@ -186,7 +178,7 @@ export default class Node {
|
||||
if (!this.isNodeParent || this.componentName === 'Fragment') {
|
||||
return null;
|
||||
}
|
||||
return this.props?.export(true).props || null;
|
||||
return this.props.export(true).props || null;
|
||||
}
|
||||
|
||||
private _conditionGroup: string | null = null;
|
||||
@ -231,12 +223,12 @@ export default class Node {
|
||||
//
|
||||
}
|
||||
|
||||
getProp(path: string, useStash = true): Prop | null {
|
||||
return this.props?.query(path, useStash as any) || null;
|
||||
getProp(path: string, stash = true): Prop | null {
|
||||
return this.props.query(path, stash as any) || null;
|
||||
}
|
||||
|
||||
getExtraProp(key: string, useStash = true): Prop | null {
|
||||
return this.props?.get(EXTRA_KEY_PREFIX + key, useStash) || null;
|
||||
getExtraProp(key: string, stash = true): Prop | null {
|
||||
return this.props.get(EXTRA_KEY_PREFIX + key, stash) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,14 +249,14 @@ export default class Node {
|
||||
* 设置多个属性值,和原有值合并
|
||||
*/
|
||||
mergeProps(props: PropsMap) {
|
||||
this.props?.merge(props);
|
||||
this.props.merge(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个属性值,替换原有值
|
||||
*/
|
||||
setProps(props?: PropsMap | PropsList | null) {
|
||||
this.props?.import(props);
|
||||
this.props.import(props);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,10 +312,10 @@ export default class Node {
|
||||
const { componentName, id, children, props, ...extras } = data;
|
||||
|
||||
if (isNodeParent(this)) {
|
||||
this._props!.import(props, extras);
|
||||
this._children.import(children, checkId);
|
||||
this.props.import(props, extras);
|
||||
(this._children as NodeChildren).import(children, checkId);
|
||||
} else {
|
||||
this._children.import(children);
|
||||
this.props.get('children', true)!.setValue(isDOMText(children) || isJSExpression(children) ? children : '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,22 +324,30 @@ export default class Node {
|
||||
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
||||
*/
|
||||
export(serialize = false): NodeSchema {
|
||||
const { props, extras } = this.props?.export(serialize) || {};
|
||||
const schema: any = {
|
||||
const baseSchema: any = {
|
||||
componentName: this.componentName,
|
||||
};
|
||||
|
||||
if (serialize) {
|
||||
baseSchema.id = this.id;
|
||||
}
|
||||
|
||||
if (!isNodeParent(this)) {
|
||||
baseSchema.children = this.props.get('children')?.export(serialize);
|
||||
return baseSchema;
|
||||
}
|
||||
|
||||
const { props, extras } = this.props.export(serialize) || {};
|
||||
const schema: any = {
|
||||
...baseSchema,
|
||||
props,
|
||||
...extras,
|
||||
};
|
||||
if (serialize) {
|
||||
schema.id = this.id;
|
||||
}
|
||||
if (isNodeParent(this)) {
|
||||
if (this.children.size > 0) {
|
||||
schema.children = this.children.export(serialize);
|
||||
}
|
||||
} else {
|
||||
schema.children = (this.children as NodeContent).value;
|
||||
|
||||
if (this.children.size > 0) {
|
||||
schema.children = this.children.export(serialize);
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
@ -400,7 +400,7 @@ export default class Node {
|
||||
if (isNodeParent(this)) {
|
||||
this.children.purge();
|
||||
}
|
||||
this.props?.purge();
|
||||
this.props.purge();
|
||||
this.document.internalRemoveAndPurgeNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,16 +16,18 @@ export interface IPropParent {
|
||||
readonly props: Props;
|
||||
}
|
||||
|
||||
export type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot';
|
||||
|
||||
export default class Prop implements IPropParent {
|
||||
readonly isProp = true;
|
||||
|
||||
readonly id = uniqueId('prop$');
|
||||
|
||||
@obx.ref private _type: 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot' = 'unset';
|
||||
@obx.ref private _type: ValueTypes = 'unset';
|
||||
/**
|
||||
* 属性类型
|
||||
*/
|
||||
get type(): 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot' {
|
||||
get type(): ValueTypes {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
@ -83,6 +85,7 @@ export default class Prop implements IPropParent {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _code: string | null = null;
|
||||
/**
|
||||
* 获得表达式值
|
||||
*/
|
||||
@ -90,13 +93,41 @@ export default class Prop implements IPropParent {
|
||||
if (isJSExpression(this.value)) {
|
||||
return this.value.value;
|
||||
}
|
||||
// todo: JSFunction ...
|
||||
if (this.type === 'slot') {
|
||||
return JSON.stringify(this._slotNode!.export(false));
|
||||
}
|
||||
return JSON.stringify(this.value);
|
||||
return this._code != null ? this._code : JSON.stringify(this.value);
|
||||
}
|
||||
set code(val) {
|
||||
// todo
|
||||
|
||||
/**
|
||||
* 设置表达式值
|
||||
*/
|
||||
set code(code: string) {
|
||||
if (isJSExpression(this._value)) {
|
||||
this.setValue({
|
||||
...this._value,
|
||||
value: code,
|
||||
});
|
||||
this._code = code;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const v = JSON.parse(code);
|
||||
this.setValue(v);
|
||||
this._code = code;
|
||||
return;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
this.setValue({
|
||||
type: 'JSExpression',
|
||||
value: code,
|
||||
mock: this._value,
|
||||
});
|
||||
this._code = code;
|
||||
}
|
||||
|
||||
@computed getAsString(): string {
|
||||
@ -111,6 +142,7 @@ export default class Prop implements IPropParent {
|
||||
*/
|
||||
setValue(val: CompositeValue) {
|
||||
this._value = val;
|
||||
this._code = null;
|
||||
const t = typeof val;
|
||||
if (val == null) {
|
||||
this._value = null;
|
||||
@ -174,7 +206,7 @@ export default class Prop implements IPropParent {
|
||||
this._slotNode?.internalSetParent(null);
|
||||
const owner = this.props.owner;
|
||||
this._slotNode = owner.document.createNode(data, this);
|
||||
this._slotNode.internalSetParent(owner);
|
||||
this._slotNode.internalSetParent(owner as any);
|
||||
}
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { computed, obx } from '@recore/obx';
|
||||
import { uniqueId } from '../../../../../../utils/unique-id';
|
||||
import { CompositeValue, PropsList, PropsMap, CompositeObject } from '../../../schema';
|
||||
import { CompositeValue, PropsList, PropsMap } from '../../../schema';
|
||||
import PropStash from './prop-stash';
|
||||
import Prop, { IPropParent, UNSET } from './prop';
|
||||
import { NodeParent } from '../node';
|
||||
import Node from '../node';
|
||||
|
||||
export const EXTRA_KEY_PREFIX = '__';
|
||||
|
||||
@ -40,7 +40,7 @@ export default class Props implements IPropParent {
|
||||
|
||||
@obx type: 'map' | 'list' = 'map';
|
||||
|
||||
constructor(readonly owner: NodeParent, value?: PropsMap | PropsList | null, extras?: object) {
|
||||
constructor(readonly owner: Node, value?: PropsMap | PropsList | null, extras?: object) {
|
||||
if (Array.isArray(value)) {
|
||||
this.type = 'list';
|
||||
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
||||
|
||||
@ -56,9 +56,6 @@ export default class RootNode extends Node implements NodeParent {
|
||||
get children(): NodeChildren {
|
||||
return this._children as NodeChildren;
|
||||
}
|
||||
get props(): Props {
|
||||
return this._props as any;
|
||||
}
|
||||
internalSetParent(parent: null) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ export interface NodeInstance<T = ComponentInstance> {
|
||||
/**
|
||||
* 组件类定义
|
||||
*/
|
||||
export type Component = ComponentType<any> | object | string;
|
||||
export type Component = ComponentType<any> | object;
|
||||
|
||||
/**
|
||||
* 组件实例定义
|
||||
|
||||
@ -237,7 +237,7 @@ export default class ArraySetter extends Component<{
|
||||
if (setter?.componentName === 'ObjectSetter') {
|
||||
const items: FieldConfig[] = setter.props?.config?.items;
|
||||
if (items && Array.isArray(items)) {
|
||||
columns = items.filter(item => item.isRequired || item.important);
|
||||
columns = items.filter(item => item.isRequired || item.important || (item.setter as any)?.isRequired);
|
||||
if (columns.length === 3) {
|
||||
columns = columns.slice(0, 3);
|
||||
} else if (columns.length > 3) {
|
||||
@ -264,10 +264,7 @@ export default class ArraySetter extends Component<{
|
||||
}
|
||||
this.pipe = (this.context as PopupPipe).create({ width });
|
||||
}
|
||||
this.pipe.send(
|
||||
<TableSetter key={field.id} {...props} columns={columns} />,
|
||||
title,
|
||||
);
|
||||
this.pipe.send(<TableSetter key={field.id} {...props} columns={columns} />, title);
|
||||
return (
|
||||
<Button
|
||||
type={forceInline ? 'normal' : 'primary'}
|
||||
|
||||
@ -32,9 +32,7 @@ export default class ObjectSetter extends Component<{
|
||||
|
||||
interface ObjectSetterConfig {
|
||||
items?: FieldConfig[];
|
||||
extraConfig?: {
|
||||
setter?: SetterType;
|
||||
};
|
||||
extraSetter?: SetterType;
|
||||
}
|
||||
|
||||
interface RowSetterProps {
|
||||
@ -61,7 +59,7 @@ class RowSetter extends Component<RowSetterProps> {
|
||||
const l = Math.min(config.items.length, columns);
|
||||
for (let i = 0; i < l; i++) {
|
||||
const conf = config.items[i];
|
||||
if (conf.isRequired || conf.important) {
|
||||
if (conf.isRequired || conf.important || (conf.setter as any)?.isRequired) {
|
||||
const item = field.createField({
|
||||
...conf,
|
||||
// in column-cell
|
||||
|
||||
@ -160,6 +160,7 @@ export class SettingField implements SettingTarget {
|
||||
readonly isSettingField = true;
|
||||
readonly id = uniqueId('field');
|
||||
readonly type: 'field' | 'virtual-field' | 'group';
|
||||
readonly isRequired: boolean = false;
|
||||
readonly isGroup: boolean;
|
||||
private _name: string | number;
|
||||
get name() {
|
||||
@ -209,6 +210,7 @@ export class SettingField implements SettingTarget {
|
||||
...rest,
|
||||
...extraProps,
|
||||
};
|
||||
this.isRequired = config.isRequired || (setter as any)?.isRequired;
|
||||
this.isGroup = this.type === 'group';
|
||||
|
||||
// copy parent properties
|
||||
|
||||
@ -264,7 +264,7 @@ registerMetadataTransducer((metadata) => {
|
||||
|
||||
registerMetadataTransducer((metadata) => {
|
||||
const { componentName, configure = {} } = metadata;
|
||||
if (componentName === '#frag') {
|
||||
if (componentName === 'Leaf') {
|
||||
return {
|
||||
...metadata,
|
||||
configure: {
|
||||
@ -277,19 +277,23 @@ registerMetadataTransducer((metadata) => {
|
||||
props: {
|
||||
setters: [{
|
||||
componentName: 'StringSetter',
|
||||
props: {
|
||||
// todo:
|
||||
multiline: true,
|
||||
},
|
||||
initialValue: '',
|
||||
}, {
|
||||
componentName: 'ExpressionSetter',
|
||||
initialValue: {
|
||||
type: 'JSExpression',
|
||||
value: ''
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
value: '',
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { props, events, styles } = configure as any;
|
||||
@ -297,8 +301,6 @@ registerMetadataTransducer((metadata) => {
|
||||
let isRoot: boolean = false;
|
||||
if (componentName === 'Page' || componentName === 'Component') {
|
||||
isRoot = true;
|
||||
// todo
|
||||
/*
|
||||
supportEvents = [{
|
||||
description: '初始化时',
|
||||
name: 'constructor'
|
||||
@ -312,7 +314,6 @@ registerMetadataTransducer((metadata) => {
|
||||
description: '卸载时',
|
||||
name: 'componentWillUnmount'
|
||||
}]
|
||||
*/
|
||||
} else {
|
||||
supportEvents = (events?.supportEvents || []).map((event: any) => {
|
||||
return typeof event === 'string' ? {
|
||||
@ -332,9 +333,11 @@ registerMetadataTransducer((metadata) => {
|
||||
}, {
|
||||
name: 'key',
|
||||
title: 'Key',
|
||||
// todo: use Mixin
|
||||
setter: 'StringSetter',
|
||||
}, {
|
||||
name: 'ref',
|
||||
title: 'Ref',
|
||||
setter: 'StringSetter',
|
||||
}, {
|
||||
name: '!more',
|
||||
@ -395,6 +398,11 @@ registerMetadataTransducer((metadata) => {
|
||||
|
||||
if (isRoot) {
|
||||
// todo...
|
||||
combined.push({
|
||||
name: '#advanced',
|
||||
title: '高级',
|
||||
items: []
|
||||
});
|
||||
} else {
|
||||
combined.push({
|
||||
name: '#advanced',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user