complete trunk polyfill

This commit is contained in:
kangwei 2020-04-20 00:40:56 +08:00
parent 4531562f79
commit 0430bfd219
36 changed files with 1496 additions and 1914 deletions

View File

@ -100,7 +100,7 @@ class Toolbar extends Component<{ observed: OffsetObserver }> {
// FIXME: need this?
return;
}
if (important && (typeof condition === 'function' ? condition(node) : condition !== false)) {
if (important && (typeof condition === 'function' ? condition(node) !== false : condition !== false)) {
actions.push(createAction(content, name, node));
}
});
@ -120,7 +120,7 @@ function createAction(content: ReactNode | ComponentType<any> | ActionContentObj
return createElement(content, { key, node });
}
if (isActionContentObject(content)) {
const { action, description, icon } = content;
const { action, title, icon } = content;
return (
<div
key={key}
@ -130,7 +130,7 @@ function createAction(content: ReactNode | ComponentType<any> | ActionContentObj
}}
>
{icon && createIcon(icon)}
<EmbedTip>{description}</EmbedTip>
<EmbedTip>{title}</EmbedTip>
</div>
);
}

View File

@ -31,18 +31,18 @@ import clipboard from '../designer/clipboard';
export interface LibraryItem {
package: string;
library: string;
urls: Asset;
urls?: Asset;
}
export interface BuiltinSimulatorProps {
// 从 documentModel 上获取
// suspended?: boolean;
designMode?: 'live' | 'design' | 'mock' | 'extend' | 'border' | 'preview';
designMode?: 'live' | 'design' | 'preview' | 'extend' | 'border';
device?: 'mobile' | 'iphone' | string;
deviceClassName?: string;
simulatorUrl?: Asset;
environment?: Asset;
library?: LibraryItem[];
simulatorUrl?: Asset;
theme?: Asset;
componentsAsset?: Asset;
[key: string]: any;
@ -79,8 +79,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
readonly designer = this.document.designer;
@computed get device(): string | undefined {
// 根据 device 不同来做画布外框样式变化 渲染时可选择不同组件
// renderer 依赖
return this.get('device') || 'default';
}
@ -88,7 +86,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return this.get('deviceClassName');
}
@computed get designMode(): 'live' | 'design' | 'extend' | 'border' | 'preview' {
@computed get designMode(): 'live' | 'design' | 'preview' {
// renderer 依赖
// TODO: 需要根据 design mode 不同切换鼠标响应情况
return this.get('designMode') || 'design';
@ -180,7 +178,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (library) {
library.forEach((item) => {
this.libraryMap[item.package] = item.library;
libraryAsset.push(item.urls);
if (item.urls) {
libraryAsset.push(item.urls);
}
});
}

View File

@ -32,44 +32,8 @@ function ensureAList(list?: string | string[]): string[] | null {
return list;
}
function npmToURI(npm: {
package: string;
exportName?: string;
subName?: string;
destructuring?: boolean;
main?: string;
version: string;
}): string {
const pkg = [];
if (npm.package) {
pkg.push(npm.package);
}
if (npm.main) {
if (npm.main[0] === '/') {
pkg.push(npm.main.slice(1));
} else if (npm.main.slice(0, 2) === './') {
pkg.push(npm.main.slice(2));
} else {
pkg.push(npm.main);
}
}
let uri = pkg.join('/');
uri += `:${npm.destructuring && npm.exportName ? npm.exportName : 'default'}`;
if (npm.subName) {
uri += `.${npm.subName}`;
}
return uri;
}
export class ComponentMeta {
readonly isComponentMeta = true;
private _uri?: string;
get uri(): string {
return this._uri!;
}
private _npm?: NpmInfo;
get npm() {
return this._npm;
@ -125,23 +89,27 @@ export class ComponentMeta {
this.parseMetadata(metadata);
}
setNpm(info: NpmInfo) {
if (!this._npm) {
this._npm = info;
}
}
private parseMetadata(metadta: ComponentMetadata) {
const { componentName, uri, npm } = metadta;
const { componentName, npm } = metadta;
this._npm = npm;
this._uri = uri || (npm ? npmToURI(npm) : componentName);
this._componentName = componentName;
metadta.uri = this._uri;
// 额外转换逻辑
this._transformedMetadata = this.transformMetadata(metadta);
const title = this._transformedMetadata.title;
if (title && typeof title === 'string') {
this._title = {
if (title) {
this._title = typeof title === 'string' ? {
type: 'i18n',
'en-US': this.componentName,
'zh-CN': title,
};
} : title;
}
const { configure = {} } = this._transformedMetadata;
@ -181,12 +149,14 @@ export class ComponentMeta {
@computed get availableActions() {
let { disableBehaviors, actions } = this._transformedMetadata?.configure.component || {};
const disabled = ensureAList(disableBehaviors) || (this.isRootComponent() ? ['copy', 'remove'] : null);
actions = builtinComponentActions.concat(this.designer.getGlobalComponentActions() || [], actions || []);
if (!disableBehaviors && this.isRootComponent()) {
disableBehaviors = ['copy', 'remove'];
}
if (disableBehaviors) {
return actions.filter(action => disableBehaviors!.indexOf(action.name) < 0);
if (disabled) {
if (disabled.includes('*')) {
return actions.filter((action) => action.condition === 'always');
}
return actions.filter((action) => disabled.indexOf(action.name) < 0);
}
return actions;
}
@ -216,6 +186,10 @@ export class ComponentMeta {
}
}
export function isComponentMeta(obj: any): obj is ComponentMeta {
return obj && obj.isComponentMeta;
}
function preprocessMetadata(metadata: ComponentMetadata): TransformedComponentMetadata {
if (metadata.configure) {
if (Array.isArray(metadata.configure)) {
@ -235,7 +209,7 @@ function preprocessMetadata(metadata: ComponentMetadata): TransformedComponentMe
};
}
registerMetadataTransducer(metadata => {
registerMetadataTransducer((metadata) => {
const { configure, componentName } = metadata;
const { component = {} } = configure;
if (!component.nestingRule) {
@ -280,7 +254,7 @@ const builtinComponentActions: ComponentAction[] = [
name: 'remove',
content: {
icon: IconRemove,
description: intl('remove'),
title: intl('remove'),
action(node: Node) {
node.remove();
},
@ -291,7 +265,7 @@ const builtinComponentActions: ComponentAction[] = [
name: 'copy',
content: {
icon: IconClone,
description: intl('copy'),
title: intl('copy'),
action(node: Node) {
// node.remove();
},
@ -299,3 +273,13 @@ const builtinComponentActions: ComponentAction[] = [
important: true,
},
];
export function removeBuiltinComponentAction(name: string) {
const i = builtinComponentActions.findIndex(action => action.name === name);
if (i > -1) {
builtinComponentActions.splice(i, 1);
}
}
export function addBuiltinComponentAction(action: ComponentAction) {
builtinComponentActions.push(action);
}

View File

@ -306,24 +306,27 @@ export class Designer {
private _lostComponentMetasMap = new Map<string, ComponentMeta>();
private buildComponentMetasMap(metas: ComponentMetadata[]) {
metas.forEach((data) => {
const key = data.componentName;
let meta = this._componentMetasMap.get(key);
metas.forEach((data) => this.createComponentMeta(data));
}
createComponentMeta(data: ComponentMetadata): ComponentMeta {
const key = data.componentName;
let meta = this._componentMetasMap.get(key);
if (meta) {
meta.setMetadata(data);
} else {
meta = this._lostComponentMetasMap.get(key);
if (meta) {
meta.setMetadata(data);
this._lostComponentMetasMap.delete(key);
} else {
meta = this._lostComponentMetasMap.get(key);
if (meta) {
meta.setMetadata(data);
this._lostComponentMetasMap.delete(key);
} else {
meta = new ComponentMeta(this, data);
}
this._componentMetasMap.set(key, meta);
meta = new ComponentMeta(this, data);
}
});
this._componentMetasMap.set(key, meta);
}
return meta;
}
getGlobalComponentActions(): ComponentAction[] | null {

View File

@ -7,8 +7,18 @@ export type RegisteredSetter = {
component: CustomView;
defaultProps?: object;
title?: TitleContent;
/**
* for MixedSetter to check this setter if available
*/
condition?: (field: any) => boolean;
/**
* for MixedSetter to manual change to this setter
*/
initialValue?: any | ((field: any) => any);
};
const settersMap = new Map<string, RegisteredSetter>();
const settersMap = new Map<string, RegisteredSetter & {
type: string;
}>();
export function registerSetter(
typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter },
setter?: CustomView | RegisteredSetter,
@ -25,15 +35,19 @@ export function registerSetter(
if (isCustomView(setter)) {
setter = {
component: setter,
// todo: intl
title: (setter as any).displayName || (setter as any).name || 'CustomSetter',
};
}
settersMap.set(typeOrMaps, setter);
settersMap.set(typeOrMaps, { type: typeOrMaps, ...setter });
}
export function getSetter(type: string): RegisteredSetter | null {
return settersMap.get(type) || null;
}
export function getSettersMap() {
return settersMap;
}
export function createSetterContent(setter: any, props: object): ReactNode {
if (typeof setter === 'string') {

View File

@ -1,10 +1,29 @@
import { TransformedComponentMetadata } from '../types';
export type MetadataTransducer = (prev: TransformedComponentMetadata) => TransformedComponentMetadata;
export interface MetadataTransducer {
(prev: TransformedComponentMetadata): TransformedComponentMetadata;
/**
* 0 - 9 system
* 10 - 99 builtin-plugin
* 100 - app & plugin
*/
level?: number;
/**
* use to replace TODO
*/
id?: string;
}
const metadataTransducers: MetadataTransducer[] = [];
export function registerMetadataTransducer(transducer: MetadataTransducer) {
metadataTransducers.push(transducer);
export function registerMetadataTransducer(transducer: MetadataTransducer, level: number = 100, id?: string) {
transducer.level = level;
transducer.id = id;
const i = metadataTransducers.findIndex(item => item.level != null && item.level > level);
if (i < 0) {
metadataTransducers.push(transducer);
} else {
metadataTransducers.splice(i, 0, transducer);
}
}
export function getRegisteredMetadataTransducers(): MetadataTransducer[] {

View File

@ -1,5 +1,5 @@
import { TitleContent } from './title';
import { SetterType } from './setter-config';
import { SetterType, DynamicSetter } from './setter-config';
export interface FieldExtraProps {
@ -18,6 +18,14 @@ export interface FieldExtraProps {
* @default undefined
*/
condition?: (field: any) => boolean;
/**
* autorun when something change
*/
autorun?: (field: any) => void;
/**
* is this field is a virtual field that not save to schema
*/
virtual?: (field: any) => boolean;
/**
* default collapsed when display accordion
*/
@ -46,7 +54,7 @@ export interface FieldConfig extends FieldExtraProps {
/**
* the field body contains when .type = 'field'
*/
setter?: SetterType;
setter?: SetterType | DynamicSetter;
/**
* the setting items which group body contains when .type = 'group'
*/

View File

@ -1,12 +1,13 @@
import { ReactNode } from 'react';
import { ReactNode, ComponentType, ReactElement } from 'react';
import { IconType } from './icon';
import { TipContent } from './tip';
import { TitleContent } from './title';
import { PropConfig } from './prop-config';
import { NpmInfo } from './npm';
import { FieldConfig } from './field-config';
import { NodeSchema } from './schema';
export type NestingFilter = (which: any, my: any) => boolean;
export type NestingFilter = (testNode: any, currentNode: any) => boolean;
export interface NestingRule {
// 子级白名单
childWhitelist?: string[] | string | RegExp | NestingFilter;
@ -26,12 +27,46 @@ export interface ComponentConfigure {
isNullNode?: boolean;
descriptor?: string;
nestingRule?: NestingRule;
rectSelector?: string;
// copy,move,delete
disableBehaviors?: string[];
// copy,move,delete | *
disableBehaviors?: string[] | string;
actions?: ComponentAction[];
}
export interface Snippet {
screenshot: string;
label: string;
schema: NodeSchema;
}
export interface Experimental {
context?: { [contextInfoName: string]: any };
snippets?: Snippet[];
view?: ComponentType<any>;
transducers?: any; // ? should support
callbacks?: Callbacks;
// 样式 及 位置handle上必须有明确的标识以便事件路由判断或者主动设置事件独占模式
// NWSE 是交给引擎计算放置位置ReactElement 必须自己控制初始位置
getResizingHandlers?: (
currentNode: any,
) =>
| Array<{
type: 'N' | 'W' | 'S' | 'E' | 'NW' | 'NE' | 'SE' | 'SW';
content?: ReactElement;
propTarget?: string;
appearOn?: 'mouse-enter' | 'mouse-hover' | 'selected' | 'always';
}>
| ReactElement[];
// hover时 控制柄高亮
// mousedown 时请求独占
// dragstart 请求 通用 resizing 控制
// 请求 hud 显示
// drag 时 计算 并 设置效果
// 更新控制柄位置
}
export interface Configure {
props?: FieldConfig[];
styles?: object;
@ -43,9 +78,9 @@ export interface ActionContentObject {
// 图标
icon?: IconType;
// 描述
description?: TipContent;
title?: TipContent;
// 执行动作
action?: (node: any) => void;
action?: (currentNode: any) => void;
}
export interface ComponentAction {
@ -55,8 +90,8 @@ export interface ComponentAction {
content: string | ReactNode | ActionContentObject;
// 子集
items?: ComponentAction[];
// 显示
condition?: boolean | ((node: any) => boolean);
// 显示与否always: 无法禁用
condition?: boolean | ((currentNode: any) => boolean) | 'always';
// 显示在工具条上
important?: boolean;
}
@ -87,8 +122,52 @@ export interface ComponentMetadata {
npm?: NpmInfo;
props?: PropConfig[];
configure?: FieldConfig[] | Configure;
experimental?: Experimental;
}
export interface TransformedComponentMetadata extends ComponentMetadata {
configure: Configure & { combined?: FieldConfig[] };
}
// handleResizing
// hooks & events
export interface Callbacks {
// hooks
onMouseDownHook?: (e: MouseEvent, currentNode: any) => any;
onDblClickHook?: (e: MouseEvent, currentNode: any) => any;
onClickHook?: (e: MouseEvent, currentNode: any) => any;
onLocateHook?: (e: any, currentNode: any) => any;
onAcceptHook?: (currentNode: any, locationData: any) => any;
onMoveHook?: (currentNode: any) => boolean; // thinkof 限制性拖拽
onChildMoveHook?: (childNode: any, currentNode: any) => boolean;
// events
onNodeRemove?: (removedNode: any, currentNode: any) => void;
onNodeAdd?: (addedNode: any, currentNode: any) => void;
onSubtreeModified?: (currentNode: any, options: any) => void;
onResize?: (
e: MouseEvent & {
trigger: string;
deltaX?: number;
deltaY?: number;
},
currentNode: any,
) => void;
onResizeStart?: (
e: MouseEvent & {
trigger: string;
deltaX?: number;
deltaY?: number;
},
currentNode: any,
) => void;
onResizeEnd?: (
e: MouseEvent & {
trigger: string;
deltaX?: number;
deltaY?: number;
},
currentNode: any,
) => void;
}

View File

@ -1,7 +1,7 @@
export interface NpmInfo {
componentName?: string;
package: string;
version: string;
version?: string;
destructuring?: boolean;
exportName?: string;
subName?: string;

View File

@ -1,9 +1,11 @@
import { isReactComponent } from '../utils';
import { ComponentType, ReactElement, isValidElement } from 'react';
import { TitleContent } from './title';
export type CustomView = ReactElement | ComponentType<any>;
export type DynamicProps = (field: any) => object;
export type DynamicSetter = (field: any) => string | SetterConfig | CustomView;
export interface SetterConfig {
/**
@ -17,12 +19,16 @@ export interface SetterConfig {
children?: any;
isRequired?: boolean;
initialValue?: any | ((field: any) => any);
/* for MixedSetter */
title?: TitleContent;
// for MixedSetter check this is available
condition?: (field: any) => boolean;
}
/**
* if *string* passed must be a registered Setter Name, future support blockSchema
*/
export type SetterType = SetterConfig | string | CustomView;
export type SetterType = SetterConfig | SetterConfig[] | string | CustomView;
export function isSetterConfig(obj: any): obj is SetterConfig {
@ -32,3 +38,7 @@ export function isSetterConfig(obj: any): obj is SetterConfig {
export function isCustomView(obj: any): obj is CustomView {
return obj && (isValidElement(obj) || isReactComponent(obj));
}
export function isDynamicSetter(obj: any): obj is DynamicSetter {
return obj && typeof obj === 'function' && !obj.displayName;
}

View File

@ -7,6 +7,7 @@ import { IconType } from './icon';
export interface TitleConfig {
label?: I18nData | ReactNode;
tip?: TipContent;
docUrl?: string;
icon?: IconType;
className?: string;
}

View File

@ -15,7 +15,9 @@ export interface JSExpression {
export interface JSSlot {
type: 'JSSlot';
value: NodeSchema;
// 函数的入参
params?: string[];
value: NodeSchema[];
}
// JSON 基本类型

View File

@ -1,6 +1,7 @@
export interface AssetItem {
type: AssetType;
content?: string | null;
device?: string;
level?: AssetLevel;
id?: string;
}
@ -68,6 +69,14 @@ export function assetBundle(assets?: Asset | AssetList | null, level?: AssetLeve
};
}
/*
urls: "view.js,view2 <device selector>, view3 <device selector>",
urls: [
"view.js",
"view.js *",
"view1.js mobile|pc",
"view2.js <device selector>"
]*/
export function assetItem(type: AssetType, content?: string | null, level?: AssetLevel, id?: string): AssetItem | null {
if (!content) {
return null;

View File

@ -20,50 +20,36 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
library: null,
};
private _lifeState = 0;
private _mounted = true;
constructor(props: any) {
super(props);
const { editor } = this.props;
const assets = editor.get('assets');
if (assets) {
this.setupAssets(assets);
} else {
editor.once('assets.loaded', this.setupAssets);
}
this._lifeState = 1;
this.setupAssets();
}
setupAssets = (assets: any) => {
if (this._lifeState < 0) {
private async setupAssets() {
const { editor } = this.props;
const assets = await editor.onceGot('assets');
if (!this._mounted) {
return;
}
const { components, packages } = assets;
const state = {
componentMetadatas: components ? Object.values(components) : [],
componentMetadatas: components ? components.filter(item => item.type === 'ComponentMetadata') : [],
library: packages ? Object.values(packages) : [],
};
if (this._lifeState === 0) {
this.state = state;
} else {
this.setState(state);
}
this.setState(state);
};
componentWillUnmount() {
this._lifeState = -1;
this._mounted = false;
}
handleDesignerMount = (designer: Designer): void => {
private handleDesignerMount = (designer: Designer): void => {
const { editor } = this.props;
editor.set(Designer, designer);
editor.emit('designer.ready', designer);
const schema = editor.get('schema');
if (schema) {
designer.project.open(schema);
}
editor.on('schema.loaded', (schema) => {
editor.onGot('schema', (schema) => {
designer.project.open(schema);
});
};

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'events';
import { uniqueId } from '@ali/lowcode-globals';
import { uniqueId, DynamicSetter, isDynamicSetter } from '@ali/lowcode-globals';
import { ComponentMeta, Node, Designer, Selection } from '@ali/lowcode-designer';
import { TitleContent, FieldExtraProps, SetterType, CustomView, FieldConfig, isCustomView } from '@ali/lowcode-globals';
import { getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
@ -73,7 +73,16 @@ export class SettingField implements SettingTarget {
readonly title: TitleContent;
readonly editor: any;
readonly extraProps: FieldExtraProps;
readonly setter?: SetterType;
private _setter?: SetterType | DynamicSetter;
get setter(): SetterType | null {
if (!this._setter) {
return null;
}
if (isDynamicSetter(this._setter)) {
return this._setter(this);
}
return this._setter;
}
readonly isSame: boolean;
readonly isMulti: boolean;
readonly isOne: boolean;
@ -107,7 +116,7 @@ export class SettingField implements SettingTarget {
this._name = name;
// make this reactive
this.title = title || (typeof name === 'number' ? `项目 ${name}` : name);
this.setter = setter;
this._setter = setter;
this.extraProps = {
...rest,
...extraProps,

View File

@ -0,0 +1,241 @@
import React, { PureComponent, Component } from 'react';
import classNames from 'classnames';
import { Dropdown, Button, Menu, Icon } from '@alifd/next';
import { getSetter, getSettersMap, SetterConfig, computed, obx, CustomView, DynamicProps, DynamicSetter, TitleContent, isSetterConfig, Title, createSetterContent } from '@ali/lowcode-globals';
import { SettingField } from 'plugin-settings-pane/src/main';
import './index.scss';
export interface SetterItem {
name: string;
title: TitleContent;
setter: string | DynamicSetter | CustomView;
props?: object | DynamicProps;
condition?: (field: SettingField) => boolean;
initialValue?: (field: SettingField) => any;
}
function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] {
if (!setters) {
const normalized: SetterItem[] = [];
getSettersMap().forEach((setter, name) => {
if (name === 'MixedSetter') {
return;
}
normalized.push({
name,
title: setter.title || name,
setter: name,
condition: setter.condition,
initialValue: setter.initialValue,
});
});
return normalized;
}
const names: string[] = [];
function generateName(n: string) {
let idx = 1;
let got = n;
while (names.indexOf(got) > -1) {
got = `${n}:${idx++}`;
}
names.push(got);
return got;
}
return setters.map(setter => {
const config: any = {
setter,
};
if (isSetterConfig(setter)) {
config.setter = setter.componentName;
config.props = setter.props;
config.condition = setter.condition;
config.initialValue = setter.initialValue;
config.title = setter.title;
}
if (typeof config.setter === 'string') {
config.name = config.setter;
names.push(config.name);
const info = getSetter(config.setter);
if (!config.title) {
config.title = info?.title || config.setter;
}
if (!config.condition) {
config.condition = info?.condition;
}
if (!config.initialValue) {
config.initialValue = info?.initialValue;
}
} else {
config.name = generateName((config.setter as any).displayName || (config.setter as any).name || 'CustomSetter');
if (!config.title) {
config.title = config.name;
}
}
return config;
});
}
export default class MixedSetter extends Component<{
field: SettingField;
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
onSetterChange: (field: SettingField, name: string) => void;
}> {
private setters = nomalizeSetters(this.props.setters);
@obx.ref private used?: string;
@computed private getCurrentSetter() {
const { field } = this.props;
if (this.used != null) {
const selected = this.used;
if (selected.condition) {
if (selected.condition(field)) {
return selected;
}
} else {
return selected;
}
}
return this.setters.find(item => {
if (!item.condition) {
return true;
}
return item.condition(field);
});
}
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.lastElementChild!.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-setter');
} else {
this.shell.classList.remove('lc-block-field');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
private useSetter: (id: string) => {
const { field, onChange } = this.props;
const newValue = setter.initialValue?.(field);
this.used = setter;
onChange && onChange(newValue);
}
render() {
const {
style = {},
className,
types = [],
defaultType,
...restProps
} = this.props;
this.typeMap = {};
let realTypes: any[] = [];
types.forEach( (el: { name: any; props: any; }) => {
const { name, props } = el;
const Setter = getSetter(name);
if (Setter) {
this.typeMap[name] = {
label: name,
component: Setter.component,
props,
}
}
realTypes.push(name);
})
let moreBtnNode = null;
//如果只有2种且有变量表达式则直接展示变量按钮
if (realTypes.length > 1) {
let isTwoType = !!(realTypes.length === 2 && ~realTypes.indexOf('ExpressionSetter'));
let btnProps = {
size: 'small',
text: true,
style: {
position: 'absolute',
left: '100%',
top: 0,
bottom: 0,
margin: 'auto 0 auto 8px',
padding: 0,
width: 16,
height: 16,
lineHeight: '16px',
textAlign: 'center'
}
};
if (isTwoType) {
btnProps.onClick = this.changeType.bind(this, realTypes.indexOf(this.state.type) ? realTypes[0] : realTypes[1]);
}
// 未匹配的 null 值,显示 NullValue 空值
// 未匹配的 其它 值,显示 InvalidValue 非法值
let triggerNode = (
<Button {...btnProps} size={isTwoType ? 'large' : 'small'}>
<Icon type={isTwoType ? 'edit' : 'ellipsis'} />
</Button>
);
if (isTwoType) {
moreBtnNode = triggerNode;
} else {
let MenuItems: {} | null | undefined = [];
realTypes.map(type => {
if (this.typeMap[type]) {
MenuItems.push(<Menu.Item key={type}></Menu.Item>);
} else {
console.error(
this.i18n('typeError', {
type
})
);
}
});
let MenuNode = (
<Menu
selectMode="single"
hasSelectedIcon={false}
selectedKeys={this.used}
onItemClick={this.useSetter}
>
{this.setters.map((setter) => {
return <Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
})}
</Menu>
);
moreBtnNode = (
<Dropdown trigger={triggerNode} triggerType="click">
<Menu
selectMode="single"
hasSelectedIcon={false}
selectedKeys={this.used}
onItemClick={this.useSetter}
>
{this.setters.map((setter) => {
return <Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
})}
</Menu>
</Dropdown>
);
}
}
let TargetNode = this.typeMap[this.state.type]?.component || 'div';
let targetProps = this.typeMap[this.state.type]?.props || {};
let tarStyle = { position: 'relative', ...style };
let classes = classNames(className, 'lowcode-setter-mixin');
return (
<div style={tarStyle} className={classes} >
{createSetterContent()}
{moreBtnNode}
</div>
);
}
}

View File

@ -26,6 +26,10 @@ class SettingFieldView extends Component<{ field: SettingField }> {
const { field } = this.props;
const { setter } = field;
let setterProps: object | DynamicProps = {};
if (Array.isArray(setter)) {
this.setterType = 'MixedSetter';
// setterProps =
}
if (isSetterConfig(setter)) {
this.setterType = setter.componentName;
if (setter.props) {

View File

@ -3,7 +3,7 @@ import parseProps from './parse-props';
import addonCombine from './addon-combine';
// parseProps
registerMetadataTransducer(parseProps);
registerMetadataTransducer(parseProps, 10, 'parse-props');
// addon/platform custom
registerMetadataTransducer(addonCombine);
registerMetadataTransducer(addonCombine, 11, 'combine-props');

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react';
import React, { PureComponent, createElement as reactCreateElement } from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import Div from '@ali/iceluna-comp-div';
@ -41,7 +41,6 @@ export default class BaseEngine extends PureComponent {
messages: PropTypes.object,
__appHelper: PropTypes.object,
__components: PropTypes.object,
__componentsMap: PropTypes.object,
__ctx: PropTypes.object,
__schema: PropTypes.object,
};
@ -199,7 +198,7 @@ export default class BaseEngine extends PureComponent {
// idx Index
__createVirtualDom = (schema, self, parentInfo, idx) => {
if (!schema) return null;
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } =
const { __appHelper: appHelper, __components: components = {} } =
this.props || {};
const { engine } = this.context || {};
if (isJSExpression(schema)) {
@ -277,13 +276,12 @@ export default class BaseEngine extends PureComponent {
__schema: schema,
__appHelper: appHelper,
__components: components,
__componentsMap: componentsMap,
}
: {};
if (engine && engine.props.designMode) {
otherProps.__designMode = engine.props.designMode;
}
const componentInfo = componentsMap[schema.componentName] || {};
const componentInfo = {};
const props = this.__parseProps(schema.props, self, '', {
schema,
Comp,
@ -314,9 +312,12 @@ export default class BaseEngine extends PureComponent {
} else if (typeof idx === 'number' && !props.key) {
props.key = idx;
}
const renderComp = (props) => (
<Comp {...props}>
{(!isFileSchema(schema) &&
const createElement = engine.props.customCreateElement || reactCreateElement;
const renderComp = (props) => {
return createElement(
Comp,
props,
(!isFileSchema(schema) &&
!!schema.children &&
this.__createVirtualDom(
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
@ -326,9 +327,9 @@ export default class BaseEngine extends PureComponent {
Comp,
},
)) ||
null}
</Comp>
);
null,
);
};
//
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
//overlay,dialog使div

View File

@ -27,17 +27,16 @@ export default class Engine extends PureComponent {
static propTypes = {
appHelper: PropTypes.object,
components: PropTypes.object,
componentsMap: PropTypes.object,
designMode: PropTypes.string,
suspended: PropTypes.bool,
schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
onCompGetRef: PropTypes.func,
onCompGetCtx: PropTypes.func,
customCreateElement: PropTypes.func,
};
static defaultProps = {
appHelper: null,
components: {},
componentsMap: {},
designMode: '',
suspended: false,
schema: {},
@ -87,7 +86,7 @@ export default class Engine extends PureComponent {
};
render() {
const { schema, designMode, appHelper, components, componentsMap } = this.props;
const { schema, designMode, appHelper, components } = this.props;
if (isEmpty(schema)) {
return null;
}
@ -103,7 +102,6 @@ export default class Engine extends PureComponent {
value={{
appHelper,
components: allComponents,
componentsMap,
engine: this,
}}
>
@ -112,7 +110,6 @@ export default class Engine extends PureComponent {
ref={this.__getRef}
__appHelper={appHelper}
__components={allComponents}
__componentsMap={componentsMap}
__schema={schema}
__designMode={designMode}
{...this.props}

View File

@ -1,5 +1,5 @@
import LowCodeRenderer from '@ali/lowcode-react-renderer';
import { ReactInstance, Fragment, Component } from 'react';
import { ReactInstance, Fragment, Component, createElement } from 'react';
import { observer } from '@recore/obx-react';
import { SimulatorRenderer } from './renderer';
import './renderer.less';
@ -48,9 +48,11 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
appHelper={renderer.context}
// context={renderer.context}
designMode={renderer.designMode}
componentsMap={renderer.componentsMap}
suspended={renderer.suspended}
self={renderer.scope}
customCreateElement={(Component, props, children) => {
return createElement(Component, props, children);
}}
onCompGetRef={(schema: any, ref: ReactInstance | null) => {
renderer.mountInstance(schema.id, ref);
}}

View File

@ -5,7 +5,7 @@ import MixinSetter from './mixin-setter';
import ColorSetter from './color-setter';
import JsonSetter from './json-setter';
import EventsSetter from './events-setter';
import StyleSetter from './style-setter';
// import StyleSetter from './style-setter';
export const StringSetter = {
component: Input,
@ -29,7 +29,7 @@ export const DateYearSetter = DatePicker.YearPicker;
export const DateMonthSetter = DatePicker.MonthPicker;
export const DateRangeSetter = DatePicker.RangePicker;
export { ExpressionSetter, MixinSetter, EventsSetter, StyleSetter }
export { ExpressionSetter, MixinSetter, EventsSetter }
// todo:
export const ClassNameSetter = () => {
@ -50,7 +50,7 @@ const builtinSetters = {
DateMonthSetter,
DateRangeSetter,
EventsSetter,
StyleSetter,
// StyleSetter,
ColorSetter,
JsonSetter,
};

View File

@ -22,6 +22,8 @@ miniapp view.miniapp.xxx
view.<device>.xxx
通配 view.xxx
universal
规则 2
urls: "view.js,view2 <device selector>, view3 <device selector>",
urls: [
@ -66,3 +68,15 @@ load simulator resources
simulator 中加载资源,根据 componentsMap 构建组件查询字典,
获取 view 相关的样式、脚本
获取 proto 相关的样式
在 simulator 中也加载一次
1. meta 信息构造
2. components 字典构造, proto.getView 或者 通过 npm 信息查询
3.
componentMeta 段描述的信息,如果包含 x-prototype-urls ,那么这个 meta 信息都可以丢掉

View File

@ -1,6 +1,6 @@
{
"entry": {
"index": "src/demo.ts"
"index": "src/demo/index.ts"
},
"vendor": false,
"devServer": {

View File

@ -13,6 +13,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://g.alicdn.com/vision/visualengine-utils/4.3.1/engine-utils.js"></script>
<!-- lowcode engine globals -->
<link rel="stylesheet" href="https://dev.g.alicdn.com/ali-lowcode/ali-lowcode-engine/0.9.0/globals.css" />
</head>

View File

@ -365,197 +365,87 @@
"version": "1.22.0"
}
],
"components": [
"x-prototypes": [
{
"componentName": "AliVcDiv",
"npm": {
"package": "@ali/vc-div",
"library": "AliVcDiv",
"version": "1.0.1",
"destructuring": false
},
"props": [],
"x-prototype-urls": ["https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-div/1.0.1/proto.a264564.js"]
"urls": ["https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-div/1.0.1/proto.a264564.js"]
},
{
"componentName": "AliVcPage",
"npm": {
"package": "@ali/vc-page",
"library": "AliVcPage",
"version": "1.0.5",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-page/1.0.5/proto.899e4b1.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-page/1.0.5/proto.bfe05a5.js"
]
},
{
"componentName": "AliVcDeep",
"npm": {
"package": "@ali/vc-deep",
"library": "AliVcDeep",
"version": "2.0.11",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-deep/2.0.11/proto.15be45f.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-deep/2.0.11/proto.039dc00.js"
]
},
{
"componentName": "AliVcShell",
"npm": {
"package": "@ali/vc-shell",
"library": "AliVcShell",
"version": "1.5.6",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-shell/1.5.6/proto.70bac75.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-shell/1.5.6/proto.8d105cf.js"
]
},
{
"componentName": "AliVcSlot",
"npm": {
"package": "@ali/vc-slot",
"library": "AliVcSlot",
"version": "2.0.1",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-slot/2.0.1/proto.0e43387.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-slot/2.0.1/proto.9e01f34.js"
]
},
{
"componentName": "AliVcText",
"npm": {
"package": "@ali/vc-text",
"library": "AliVcText",
"version": "4.0.1",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-text/4.0.1/proto.595bd91.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-text/4.0.1/proto.90c4998.js"
]
},
{
"componentName": "AliVcLink",
"npm": {
"package": "@ali/vc-link",
"library": "AliVcLink",
"version": "5.1.1",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-link/5.1.1/proto.4828821.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-link/5.1.1/proto.91e063a.js"
]
},
{
"componentName": "AliVcLinkBlock",
"npm": {
"package": "@ali/vc-link-block",
"library": "AliVcLinkBlock",
"version": "5.1.0",
"destructuring": false
},
"props": [],
"x-prototype-urls": ["https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-link-block/5.1.0/proto.4e9a9d2.js"]
"urls": ["https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-link-block/5.1.0/proto.4e9a9d2.js"]
},
{
"componentName": "AliVcChartColumn",
"npm": {
"package": "@ali/vc-chart-column",
"library": "AliVcChartColumn",
"version": "3.0.5",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-column/3.0.5/proto.5973a33.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-column/3.0.5/proto.df847e6.js"
]
},
{
"componentName": "AliVcChartLine",
"npm": {
"package": "@ali/vc-chart-line",
"library": "AliVcChartLine",
"version": "3.0.4",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-line/3.0.4/proto.11e8b2c.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-line/3.0.4/proto.bbc1a73.js"
]
},
{
"componentName": "AliVcChartPie",
"npm": {
"package": "@ali/vc-chart-pie",
"library": "AliVcChartPie",
"version": "3.0.2",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-pie/3.0.2/proto.81a7751.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-pie/3.0.2/proto.ce342e4.js"
]
},
{
"componentName": "AliVcChartRadar",
"npm": {
"package": "@ali/vc-chart-radar",
"library": "AliVcChartRadar",
"version": "3.0.2",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-radar/3.0.2/proto.81a7751.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-chart-radar/3.0.2/proto.638100d.js"
]
},
{
"componentName": "AliVcMarkdown",
"npm": {
"package": "@ali/vc-markdown",
"library": "AliVcMarkdown",
"version": "2.0.0",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-markdown/2.0.0/proto.3f91095.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/vc-markdown/2.0.0/proto.1bed9e5.js"
]
},
{
"componentName": "AliAcAmdpHomeConsumer",
"npm": {
"package": "@ali/ac-amdp-home-consumer",
"library": "AliAcAmdpHomeConsumer",
"version": "1.0.11",
"destructuring": false
},
"props": [],
"x-prototype-urls": [
"urls": [
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/ac-amdp-home-consumer/1.0.11/proto.f3f24d5.css",
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/ac-amdp-home-consumer/1.0.11/proto.f12090f.js"
]
},
}
],
"components": [
{
"componentName": "AliReactJsonView",
"npm": {

View File

@ -1,75 +1,63 @@
import lg from '@ali/vu-logger';
import { camelCase, find, findIndex, upperFirst } from 'lodash';
import { ComponentClass, ReactElement, ComponentType } from 'react';
import { UnknownComponent } from '../../ui/placeholders';
import Trunk, { IComponentBundle } from './trunk';
import { ComponentClass, ComponentType } from 'react';
import Prototype from './prototype';
import { ComponentMeta } from '@ali/lowcode-designer';
import { designer } from '../editor';
function basename(name: string) {
return name ? (/[^\/]+$/.exec(name) || [''])[0] : '';
}
function getCamelName(name: string) {
const words = basename(name).replace(/^((vc)-)?(.+)/, '$3').split('-');
const words = basename(name)
.replace(/^((vc)-)?(.+)/, '$3')
.split('-');
return words.reduce((s, word) => s + word[0].toUpperCase() + word.substring(1), '');
}
export declare interface IComponentProto {
export interface ComponentProtoBundle {
// @ali/vc-xxx
name: string;
module: Prototype;
componentName?: string;
category?: string;
module: Prototype | Prototype[];
}
export interface ComponentViewBundle {
// @ali/vc-xxx
name: string;
// alias to property name
componentName?: string;
category?: string;
module: ComponentType<any>;
}
export default class Bundle {
public static createPrototype = Prototype.create;
public static addGlobalPropsReducer = Prototype.addGlobalPropsReducer;
public static addGlobalPropsConfigure = Prototype.addGlobalPropsConfigure;
public static addGlobalExtraActions = Prototype.addGlobalExtraActions;
public static addGlobalNodeCanDragConfig = Prototype.addGlobalNodeCanDragConfig;
public static removeGlobalPropsConfigure = Prototype.removeGlobalPropsConfigure;
public static overridePropsConfigure = Prototype.overridePropsConfigure;
public static create = function createBundle(
components: Array<IComponentProto[] | IComponentProto>,
views: IComponentBundle[], nameSpace: string) {
return new Bundle(components, views, nameSpace);
};
static createPrototype = Prototype.create;
static addGlobalPropsReducer = Prototype.addGlobalPropsReducer;
static addGlobalPropsConfigure = Prototype.addGlobalPropsConfigure;
static addGlobalExtraActions = Prototype.addGlobalExtraActions;
static removeGlobalPropsConfigure = Prototype.removeGlobalPropsConfigure;
static overridePropsConfigure = Prototype.overridePropsConfigure;
static create(protos: ComponentProtoBundle[], views?: ComponentViewBundle[]) {
return new Bundle(protos, views);
}
/**
* if all components are packed in a single package
* the compositeBundle property shall be true
*/
public compositeBundle: boolean = false;
private viewsMap: { [componentName: string]: ComponentType } = {};
private registry: { [componentName: string]: Prototype } = {};
private prototypeList: Prototype[] = [];
private trunk: Trunk;
private nameSpace: string;
private registry: { [name: string]: Prototype };
private registryById: { [id: string]: Prototype };
private prototypeList: Prototype[];
private viewMap: { [viewName: string]: ComponentClass } = {};
private viewList: ComponentClass[] = [];
constructor(
componentPrototypes: Array<IComponentProto | IComponentProto[]>,
views: IComponentBundle[],
nameSpace: string,
) {
this.nameSpace = nameSpace || '';
this.registry = {};
this.registryById = {};
this.prototypeList = [];
this.trunk = new Trunk();
if (Array.isArray(views)) {
constructor(protos?: ComponentProtoBundle[], views?: ComponentViewBundle[]) {
// 注册 prototypeView 视图
if (views && Array.isArray(views)) {
this.recursivelyRegisterViews(views);
}
componentPrototypes.forEach((item: IComponentProto) => {
protos?.forEach((item) => {
const prototype = item.module;
if (prototype instanceof Prototype) {
this.revisePrototype(item, prototype);
const matchedView = this.viewMap[item.componentName || prototype.getComponentName()] || null;
const componentName = item.componentName || prototype.getComponentName()!;
const matchedView = this.viewsMap[componentName] || null;
if (!prototype.getView() && matchedView) {
prototype.setView(matchedView);
}
@ -78,137 +66,46 @@ export default class Bundle {
this.recursivelyRegisterPrototypes(prototype, item);
}
});
this.prototypeList.forEach((p, idx) => {
if (!p.getView()) {
p.setView(UnknownComponent);
}
});
// invoke prototype mocker while the prototype does not exist
Object.keys(this.viewMap).forEach((viewName) => {
if (!find(this.prototypeList, (proto) => proto.getComponentName() === viewName)) {
const mockedPrototype = this.trunk.mockComponentPrototype(this.viewMap[viewName]);
if (mockedPrototype) {
if (!mockedPrototype.getPackageName()) {
mockedPrototype.setPackageName((this.viewMap[viewName] as any)._packageName_);
}
this.registry[viewName] = mockedPrototype;
this.registryById[mockedPrototype.getId()] = mockedPrototype;
this.prototypeList.push(mockedPrototype);
}
}
});
}
public addComponentBundle(bundles: Array<IComponentProto | IComponentBundle>): void;
public addComponentBundle(bundles: any) {
/**
* Normal Component bundle: [ Prototype, PrototypeView ]
* Component without Prototype.js: [ View ]
*/
if (bundles.length >= 2) {
const prototype = bundles[0];
const prototypeView = bundles[1];
prototype.setView(prototypeView);
this.registerPrototype(prototype);
} else if (bundles.length === 1) {
// Mock a Prototype for DIY Component load from async build
const proto = this.trunk.mockComponentPrototype(bundles[0]);
if (!proto) {
return;
}
if (!proto.getView()) {
proto.setView(bundles[0]);
}
this.registerPrototype(proto);
getFromMeta(componentName: string): Prototype {
if (this.registry[componentName]) {
return this.registry[componentName];
}
const meta = designer.getComponentMeta(componentName);
const prototype = Prototype.create(meta);
this.prototypeList.push(prototype);
this.registry[componentName] = prototype;
return prototype;
}
public removeComponentBundle(componentName: string) {
const cIndex = findIndex(this.prototypeList, (proto) => proto.getComponentName() === componentName);
const id = this.prototypeList[cIndex].getId();
delete this.registryById[id];
removeComponentBundle(componentName: string) {
const cIndex = this.prototypeList.findIndex((proto) => proto.getComponentName() === componentName);
delete this.registry[componentName];
this.prototypeList.splice(cIndex, 1);
}
public getNamespace() {
return this.nameSpace;
}
public getList() {
getList() {
return this.prototypeList;
}
public get(componentName: string) {
get(componentName: string) {
return this.registry[componentName];
}
public getById(id: string) {
return this.registryById[id];
}
public isCompositeBundle() {
return this.isCompositeBundle;
}
public filter(fn: (item: Prototype) => boolean) {
this.prototypeList = this.prototypeList.filter((item) => {
if (fn(item) === false) {
if (this.registry[item.getComponentName()] === item) {
delete this.registry[item.getComponentName()];
}
if (this.registryById[item.getId()] === item) {
delete this.registryById[item.getId()];
}
return false;
}
return true;
});
}
public replacePrototype(componentName: string, cp: Prototype) {
replacePrototype(componentName: string, cp: Prototype) {
const view: any = this.get(componentName).getView();
this.removeComponentBundle(componentName);
this.registry[cp.getComponentName()] = cp;
this.registryById[cp.getId()] = cp;
this.registry[cp.getComponentName()!] = cp;
this.prototypeList.push(cp);
cp.setView(view);
}
private recursivelyRegisterPrototypes(list: any[], cp: IComponentProto) {
const propList: IComponentProto[] = list;
propList.forEach((proto: IComponentProto, index: number) => {
if (Array.isArray(proto)) {
this.recursivelyRegisterPrototypes(proto, cp);
return;
}
if (proto instanceof Prototype) {
if (!proto.getView() && this.viewMap[proto.getComponentName()]) {
proto.setView(this.viewMap[proto.getComponentName()]);
}
if (index === 0 && cp.componentName) {
proto.setComponentName(cp.componentName);
}
if (cp.name && !proto.getPackageName()) {
proto.setPackageName(cp.name);
}
this.registerPrototype(proto);
}
});
}
/**
* register View
* @param list ViewList
* @param viewName
*/
private recursivelyRegisterViews(list: IComponentBundle[], viewName?: string): void;
private recursivelyRegisterViews(list: any[], viewName?: string): void {
list.forEach((item: any) => {
if (Array.isArray(item.module)) {
return this.recursivelyRegisterViews(item.module, item.name);
} else if (Array.isArray(item)) {
this.compositeBundle = true;
return this.recursivelyRegisterViews(item, viewName);
}
let viewDetail: ComponentClass;
@ -220,44 +117,51 @@ export default class Bundle {
if (!viewDetail.displayName) {
lg.log('ERROR_NO_PROTOTYPE_VIEW');
lg.error('WARNING: the package without displayName is', item);
viewDetail.displayName = upperFirst(
// mock componentName from packageName
camelCase((viewName || item.name).split('-').slice(1).join('-')),
);
viewDetail.displayName = getCamelName(viewName || item.name);
}
(viewDetail as any)._packageName_ = viewName || item.name;
this.viewMap[viewDetail.displayName] = viewDetail;
this.viewList.push(viewDetail);
this.viewsMap[viewDetail.displayName] = viewDetail;
});
}
private revisePrototype(item: IComponentProto, prototype: Prototype) {
const name = item.name || item.componentName;
private recursivelyRegisterPrototypes(list: any[], cp: ComponentProtoBundle) {
const propList: ComponentProtoBundle[] = list;
propList.forEach((proto: ComponentProtoBundle, index: number) => {
if (Array.isArray(proto)) {
this.recursivelyRegisterPrototypes(proto, cp);
return;
}
if (proto instanceof Prototype) {
const componentName = proto.getComponentName()!;
if (!proto.getView() && this.viewsMap[componentName]) {
proto.setView(this.viewsMap[componentName]);
}
if (cp.name && !proto.getPackageName()) {
proto.setPackageName(cp.name);
}
this.registerPrototype(proto);
}
});
}
private revisePrototype(item: ComponentProtoBundle, prototype: Prototype) {
if (item.category) {
prototype.setCategory(item.category);
}
if (item.name && !prototype.getPackageName()) {
prototype.setPackageName(item.name);
}
if (item.componentName) {
prototype.setComponentName(item.componentName);
}
if (!prototype.getComponentName()) {
prototype.setComponentName(getCamelName(name));
}
}
private registerPrototype(prototype: Prototype) {
if (this.registry[prototype.getComponentName()]) {
lg.warn('WARN: override prototype', prototype, prototype.getComponentName());
const idx = findIndex(this.prototypeList, (proto) =>
prototype.getComponentName() === proto.getComponentName());
const componentName = prototype.getComponentName()!;
if (this.registry[componentName]) {
lg.warn('WARN: override prototype', prototype, componentName);
const idx = this.prototypeList.findIndex((proto) => componentName === proto.getComponentName());
this.prototypeList[idx] = prototype;
delete this.registryById[prototype.getId()];
} else {
this.prototypeList.push(prototype);
}
this.registry[prototype.getComponentName()] = prototype;
this.registryById[prototype.getId()] = prototype;
this.registry[componentName] = prototype;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,299 +1,47 @@
import lg from '@ali/vu-logger';
import { ReactElement, ComponentType } from 'react';
import { EventEmitter } from 'events';
import { ComponentClass } from 'react';
import { registerSetter } from '@ali/lowcode-globals';
import Bundle from './bundle';
import Prototype, { setPackages } from './prototype';
import Bus from '../bus';
interface IComponentInfo {
image?: string;
description?: string;
componentDetail?: string;
newVersion?: string;
}
interface IComponentLoader {
load: (packageName: string, packageVersion: string, filePath?: string) => Promise<IComponentBundle>;
}
interface IComponentPrototypeMocker {
mockPrototype: (bundle: ComponentClass) => Prototype;
}
interface IComponentBundle {
// @ali/vc-xxx
name: string;
// alias to property name
componentName?: string;
category?: string;
module: ComponentClass;
}
interface IComponentBundleLoadingConfig {
isDIYComponent?: boolean;
// need to emit 'trunk_change' event
isSilence?: boolean;
isNpmComponent?: boolean;
}
interface IComponentBundleConfigListItem {
name: string;
version: string;
path?: string;
// ac component in LeGao
// FIXME: remove this logic out of Trunk in the future
isDIYComponent?: boolean;
// install comp directly from npm
isNpmComponent?: boolean;
}
interface IBeforeLoad extends IComponentBundleLoadingConfig {
name: string;
version: string;
path: string;
}
type beforeLoadFn = (loadingConfig: IBeforeLoad) => IBeforeLoad;
type afterLoadFn = (bundle: IComponentBundle, loadingConfig: IBeforeLoad) => IComponentBundle;
class Trunk {
private trunk: any[];
private list: any[];
private emitter: EventEmitter;
private componentBundleLoader: IComponentLoader;
private componentPrototypeMocker: IComponentPrototypeMocker;
private beforeLoad: beforeLoadFn;
private afterLoad: afterLoadFn;
constructor() {
this.trunk = [];
this.emitter = new EventEmitter();
this.componentBundleLoader = null;
}
export class Trunk {
private trunk: any[] = [];
private emitter: EventEmitter = new EventEmitter();
private metaBundle = new Bundle();
isReady() {
return this.getList().length > 0;
}
addBundle(bundle: Bundle, bundleOptions: {
before?: (bundle: Bundle) => Promise<Bundle>;
after?: (bundle: Bundle) => any;
} = {}) {
// filter exsits
bundle.filter((item) => {
const componentName = item.getComponentName();
if (componentName && this.getPrototype(componentName)) {
return false;
}
return true;
});
if (bundleOptions.before) {
bundleOptions.before.call(this, bundle).then((processedBundle: Bundle) => {
this.trunk.push(processedBundle);
this.emitter.emit('trunkchange');
});
} else {
this.trunk.push(bundle);
this.emitter.emit('trunkchange');
}
if (bundleOptions.after) {
bundleOptions.after.call(this, bundle);
}
addBundle(bundle: Bundle) {
this.trunk.push(bundle);
this.emitter.emit('trunkchange');
}
/**
*
*/
registerComponentBundleLoader(loader: IComponentLoader) {
// warn replacement method
this.componentBundleLoader = loader;
getBundle(): Bundle {
console.warn('Trunk.getBundle is deprecated');
return this.trunk[0];
}
registerComponentPrototypeMocker() {
console.warn('Trunk.registerComponentPrototypeMocker is deprecated');
getList(): any[] {
return this.trunk.reduceRight((prev, cur) => prev.concat(cur.getList()), []);
}
getBundle(nameSpace?: string): Bundle {
console.warn('Trunk.getTrunk is deprecated');
if (!nameSpace) {
return this.trunk[0];
}
return find(this.trunk, (b: any) => b.getNamespace() === nameSpace);
}
public getList(): any[] {
return this.list || this.trunk.reduceRight((prev, cur) => prev.concat(cur.getList()), []);
}
/**
*
*
*/
listByCategory() {
console.warn('Trunk.listByCategory is deprecated');
const categories: any[] = [];
const categoryMap: any = {};
const categoryItems: any[] = [];
const defaultCategory = {
items: categoryItems,
name: '*',
};
categories.push(defaultCategory);
categoryMap['*'] = defaultCategory;
this.getList().forEach((prototype) => {
const cat = prototype.getCategory();
if (!cat) {
return;
}
if (!categoryMap.hasOwnProperty(cat)) {
const categoryMapItems: any[] = [];
categoryMap[cat] = {
items: categoryMapItems,
name: cat,
};
categories.push(categoryMap[cat]);
}
categoryMap[cat].items.push(prototype);
});
return categories;
}
/**
*
*
* @returns {Array}
*/
getTrunk() {
// legao-design 中有用
console.warn('Trunk.getTrunk is deprecated');
return this.trunk;
}
/**
* componentName prototype
*
* @param {string} componentName
* @returns {Prototype}
*/
getPrototype(componentName: string) {
if (!componentName) {
lg.error('ERROR: no component name found while get Prototype');
return null;
}
const name = componentName.split('.');
const namespace = name.length > 1 ? name[0] : '';
let i = this.trunk.length;
let bundle;
let ns;
let prototype;
while (i-- > 0) {
bundle = this.trunk[i];
ns = bundle.getNamespace();
if (ns === '' || namespace === ns) {
prototype = bundle.get(componentName);
if (prototype) {
return prototype;
}
}
}
return null;
}
public getPrototypeById(id: string) {
getPrototype(name: string) {
let i = this.trunk.length;
let bundle;
let prototype;
while (i-- > 0) {
bundle = this.trunk[i];
prototype = bundle.getById(id);
prototype = bundle.get(name);
if (prototype) {
return prototype;
}
}
return null;
return this.metaBundle.getFromMeta(name);
}
public getPrototypeView(componentName: string) {
const prototype = this.getPrototype(componentName);
return prototype ? prototype.getView() : null;
}
public loadComponentBundleList(componentBundleList: IComponentBundleConfigListItem[]) {
Promise.all(componentBundleList.map((componentBundle) => {
const { name, version, path, ...bundleContextInfo } = componentBundle;
return this.loadComponentBundle(name, version, path, {
...bundleContextInfo,
isDIYComponent: componentBundle.isDIYComponent,
isSilence: true,
});
})).then((results) => {
results.forEach((r: any) => this.getBundle().addComponentBundle(r));
this.emitter.emit('trunkchange');
});
}
public loadComponentBundle(
name: string,
version: string,
path?: string,
options?: IComponentBundleLoadingConfig) {
const bundleList: IComponentBundle[] = [];
return new Promise((resolve: any, reject: any) => {
if (options && options.isDIYComponent) {
let result: IBeforeLoad = { name, version, path, ...options };
if (isFunction(this.beforeLoad)) {
result = this.beforeLoad(result);
}
return this.componentBundleLoader.load(result.name, result.version, result.path)
.then((b: IComponentBundle) => {
if (isFunction(this.afterLoad)) {
this.afterLoad(b, { name, path, version, ...options });
}
if (!options.isSilence) {
this.getBundle().addComponentBundle([b]);
this.emitter.emit('trunkchange');
}
resolve([b]);
})
.catch((e: Error) => {
Bus.emit('ve.error.networkError', e);
reject(e);
});
} else {
this.componentBundleLoader.load(name, version, 'build/prototype.js')
.then((b: IComponentBundle) => {
bundleList.push(b);
return this.componentBundleLoader.load(name, version, 'build/prototypeView.js');
})
.then((b: IComponentBundle) => {
bundleList.push(b);
if (!options.isSilence) {
this.getBundle().addComponentBundle(bundleList);
this.emitter.emit('trunkchange');
}
resolve(bundleList);
})
.catch((e: Error) => {
Bus.emit('ve.error.networkError', e);
reject(e);
});
}
});
}
removeComponentBundle(name: string) {
this.getBundle().removeComponentBundle(name);
this.emitter.emit('trunkchange');
}
beforeLoadBundle(fn: beforeLoadFn) {
this.beforeLoad = fn;
}
afterLoadBundle(fn: afterLoadFn) {
this.afterLoad = fn;
getPrototypeView(componentName: string) {
return this.getPrototype(componentName)?.getView();
}
onTrunkChange(func: () => any) {
@ -303,8 +51,25 @@ class Trunk {
};
}
setPackages(packages: Array<{ package: string; library: object | string }>) {
setPackages(packages);
registerSetter(type: string, setter: ReactElement | ComponentType<any>) {
console.warn('Trunk.registerSetter is deprecated');
registerSetter(type, setter);
}
beforeLoadBundle() {
console.warn('Trunk.beforeLoadBundle is deprecated');
}
afterLoadBundle() {
console.warn('Trunk.afterLoadBundle is deprecated');
}
registerComponentPrototypeMocker() {
console.warn('Trunk.registerComponentPrototypeMocker is deprecated');
}
setPackages() {
console.warn('Trunk.setPackages is deprecated');
}
}

View File

@ -1,118 +1,20 @@
/**
*
*/
export type REJECTED = 0 | false;
/**
*
*/
export type LIMITED = 2;
/**
*
*/
export type ALLOWED = true | 4;
import { ComponentType, ReactElement, isValidElement, ComponentClass } from 'react';
import { isI18nData } from '@ali/lowcode-globals';
export type HandleState = REJECTED | ALLOWED | LIMITED;
/*
* model.editing:(dbclick)
* asCode(gotocode) select - option
* asRichText ()
* asPlainText ()
* null|undefined
* false
* handle-function
*
* ## handle
*
* model.shouldRemoveChild: HandleState | (my, child) => HandleState return false
* model.shouldMoveChild: HandleState | (my, child) => HandleState , return false: ; return 0: 不得改变嵌套关系
* model.shouldRemove: HandleState | (my) => HandleState
* model.shouldMove: HandleState | (my) => HandleState return false, return 0;
*
* ## ()
*
* locate
* model.locate: (my, transferData, mouseEvent?) => Location | null, node
*
* test-RegExp: /^tagName./
* test-List: 'tagName,tagName2' | ['tagName', 'tagName2']
* test-Func: (target, my) => boolean
* Tester: RegExp | Pattern | Func
*
* component.accept
* accept: '@CHILD' slot TabsLayoutColumnsLayout,
* accept: false|null inputoption
* accept: true ok div,
* accept: Tester, filter条件 select线
* model.nesting (nextTick异步检查)
* null | undefined | false | true |
* Tester
* model.dropTarget
* Tester // 实时约束
* {
* highlight?: Tester | boolean, // 高亮默认false设为true时根据 parent | ancestor 取值
* parent?: Tester, // 实时约束,主视图限制定位,大纲树定位进去时红线提示
* ancestor?: Tester, // 异步检查,上文检查, 设置此值时parent 可不设置
* }
* '@ROOT'
* null | undefined | boolean |
*
*
*
*
* 1. /UL li
* 2. Form Button, Input
* 3. @千緖
* 4. Li ULLi设置的 dropTargetRules
* 5.
*
*/
export interface BehaviorControl {
handleMove?: HandleState | ((my: ElementNode) => HandleState);
handleRemove?: HandleState | ((my: ElementNode) => HandleState);
handleChildMove?: HandleState | ((my: ElementNode, child: INode) => HandleState);
handleChildRemove?: HandleState | ((my: ElementNode, child: INode) => HandleState);
}
export const AT_CHILD = Symbol.for('@CHILD');
export const AT_ROOT = Symbol.for('@ROOT');
export type AT_ROOT = typeof AT_ROOT;
export type AT_CHILD = typeof AT_CHILD;
export type AcceptFunc = (
my: ElementNode,
e: LocateEvent | KeyboardEvent | MouseEvent,
) => LocationData | INodeParent | AT_CHILD | null;
// should appear couple
export interface AcceptControl {
/**
* MouseEvent: drag a entiy from browser out
* KeyboardEvent: paste a entiy
* LocateEvent: drag a entiy from pane
*/
handleLocate?: AcceptFunc | AT_CHILD;
handleAccept?: (my: ElementNode, locationData: LocationData) => void;
}
export interface ContentEditable {
propTarget: string;
selector?: string;
}
type Field = any;
export enum DISPLAY_TYPE {
NONE = 'none',
NONE = 'none', // => condition'plain'
PLAIN = 'plain',
INLINE = 'inline',
BLOCK = 'block',
ACCORDION = 'accordion',
TAB = 'tab',
TAB = 'tab', // => 'accordion'
ENTRY = 'entry',
}
export interface IPropConfig {
// from vision 5.4
export interface OldPropConfig {
/**
* composite share the namespace
* group just be tie up together
@ -121,32 +23,24 @@ export interface IPropConfig {
/**
* when type is composite or group
*/
items?: IPropConfig[]; // => items
items?: OldPropConfig[]; // => items
/**
* property name: the field key in props of schema
*/
name: string; // =>
title?: string; // =>
tip?: {
// =>
title?: string;
content?: string;
url?: string;
};
initialValue?: any; // => ?
defaultValue?: any; // =>
defaultValue?: any; // => extraProps.defaultValue
initialValue?: any | ((value: any, defaultValue: any) => any); // => extraProps.initialValue
initial?: (value: any, defaultValue: any) => any // => extraProps.initialValue
display?: DISPLAY_TYPE; // => fieldExtraProps
fieldStyle?: DISPLAY_TYPE; // => fieldExtraProps
setter?: ComponentClass | ISetterConfig[] | string | SetterGetter; // =>
/**
* if a prop is dynamicProp, every-time while rendering setting field
* - getValue() will not include value of getHotValue()
* - getValue() will trigger accessor() to calculate a new value
* this prop usually work out when we need to generate prop value
* from node of current page
*/
isDynamicProp?: boolean;
supportVariable?: boolean; // => use MixinSetter
supportVariable?: boolean; // => use MixedSetter
/**
* the prop should be collapsed while display value is accordion
*/
@ -165,75 +59,377 @@ export interface IPropConfig {
* will not export data to schema
*/
ignore?: boolean | ReturnBooleanFunction; // => ?virtualProp ? thinkof global transform
/**
* if a prop is declared as virtual, it will not be saved in
* schema props, instead it will be saved into context field
*/
virtual?: boolean | ReturnBooleanFunction; // =>?virtualProp
hidden?: boolean | ReturnBooleanFunction; // => condition
/**
* if a prop is a lifeCycle function
*/
lifeCycle?: boolean; // =>?
destroy?: () => any; // => x
initial?(this: Prop, value: any, initialValue: any): any;
/**
* when use getValue(), accessor shall be called as initializer
*/
accessor?(this: Prop): any; // => getValue
accessor?(this: Field, value: any): any; // => getValue
/**
* when current prop value mutate, the mutator function shall be called
*/
mutator?( // => setValue
this: Prop,
this: Field,
value: any,
/*
hotValue: any, // => x
preValue: any, // => x
preHotValue: any, // => x
*/
): void;
/**
* other values' change will trigger sync function here
*/
sync?(this: Prop, value: any): void; // => ? autorun
/**
* transform runtime data between view and setter
* @param toHotValue hot value for the setter
* @param toViewValue static value for the view
*/
transformer?( // =>?
toHotValue: (data: any) => any,
toViewValue: (str: string) => any,
): any;
sync?(this: Field, value: any): void; // => autorun
/**
* user click var to change current field to
* variable setting field
*/
useVariableChange?(data: { isUseVariable: boolean }): any; // => ?
useVariableChange?(this: Field, data: { isUseVariable: boolean }): any; // => as MixinSetter param
slotName?: string;
slotTitle?: string;
initialChildren?: any; // schema
allowTextInput: boolean;
}
export interface SettingFieldConfig {
type?: 'field';
title?: string;
name: string;
setter: ComponentClass | ISetterConfig[] | string | SetterGetter;
extraProps?: {
[key: string]: any;
// from vision 5.4
export interface OldPrototypeConfig {
packageName: string; // => npm.package
/**
* category display in the component pane
* component will be hidden while the value is: null
*/
category: string; // => tags
componentName: string; // =>
docUrl?: string; // =>
defaultProps?: any; // => ?
/**
* extra actions on the outline of current selected node
* by default we have: remove / clone
*/
extraActions?: Array<ComponentType<any> | ReactElement> | (() => ReactElement); // => configure.component.actions
title?: string; // =>
icon?: ComponentType<any> | ReactElement; // =>
view: ComponentType; // => ?
initialChildren?: (props: any) => any[]; // => snippets
/**
* Props configurations of node
*/
configure: OldPropConfig[]; // => configure.props
snippets?: any[]; // => snippets
transducers?: any; // => ?
/**
* Selector expression rectangle of a node, it is usually a querySelector string
* @example '.classname > div'
*/
rectSelector?: string; // => configure.component.rectSelector
context?: {
// => ?
[contextInfoName: string]: any;
};
isContainer?: boolean; // => configure.component.isContainer
isModal?: boolean; // => configure.component.isModal
isFloating?: boolean; // => configure.component.isFloating
descriptor?: string; // => configure.component.descriptor
// alias to canDragging
canDraging?: boolean; // => onDrag
canDragging?: boolean; // => ?
canOperating?: boolean; // => disabledActions
canSelecting?: boolean;
canContain?: (dragment: Node) => boolean; // => nestingRule
canDropTo?: ((container: Node) => boolean) | string | string[]; // => nestingRule
canDropto?: (container: Node) => boolean; // => nestingRule
canDropIn?: ((dragment: Node) => boolean) | string | string[]; // => nestingRule
canDroping?: (dragment: Node) => boolean; // => nestingRule
didDropOut?: (dragment: any, container: any) => void; // => hooks
didDropIn?: (dragment: any, container: any) => void; // => hooks
/**
* when sub-node of the current node changed
* including: sub-node insert / remove
*/
subtreeModified?(this: Node): any; // => ? hooks
// => ?
canResizing?: ((dragment: any, triggerDirection: string) => boolean) | boolean;
onResizeStart?: (e: MouseEvent, triggerDirection: string, dragment: Node) => void;
onResize?: (e: MouseEvent, triggerDirection: string, dragment: Node, moveX: number, moveY: number) => void;
onResizeEnd?: (e: MouseEvent, triggerDirection: string, dragment: Node) => void;
}
export interface SettingGroupConfig {
type: 'group';
title?: string;
items: Array<SettingGroupConfig | SettingFieldConfig>;
extraProps?: {
[key: string]: any;
export interface ISetterConfig {
setter?: ComponentClass;
// use value to decide whether this setter is available
condition?: (value: any) => boolean;
}
type SetterGetter = (this: Field, value: any) => ComponentClass;
type ReturnBooleanFunction = (this: Field, value: any) => boolean;
export function upgradePropConfig(config: OldPropConfig) {
const {
type,
name,
title,
tip,
slotName,
slotTitle,
initialChildren,
allowTextInput,
initialValue,
defaultValue,
display,
fieldStyle,
collapse,
collapsed,
fieldCollapsed,
hidden,
disabled,
items,
ignore,
initial,
sync,
accessor,
mutator,
setter,
useVariableChange,
supportVariable,
} = config;
const extraProps: any = {};
const newConfig: any = {
type: type === 'group' ? 'group' : 'field',
name,
title,
extraProps,
};
if (tip) {
if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) {
newConfig.title = {
title,
tip: tip.content,
docUrl: tip.url
};
} else {
newConfig.title = {
...(title as any),
tip: tip.content,
docUrl: tip.url
};
}
}
if (display || fieldStyle) {
extraProps.display = display || fieldStyle;
if (extraProps.display === DISPLAY_TYPE.TAB) {
extraProps.display = DISPLAY_TYPE.ACCORDION;
}
}
if (collapse || collapsed || fieldCollapsed) {
extraProps.defaultCollapsed = true;
}
function isDisabled(field: Field) {
if (typeof disabled === 'function') {
return disabled.call(field, field.getValue()) === true;
}
return disabled === true;
}
function isHidden(field: Field) {
if (typeof hidden === 'function') {
return hidden.call(field, field.getValue()) === true;
}
return hidden === true;
}
if (extraProps.display === DISPLAY_TYPE.NONE) {
extraProps.display = undefined;
extraProps.condition = () => false;
} else if (hidden != null || disabled != null) {
extraProps.condition = (field: Field) => !(isHidden(field) || isDisabled(field));
}
if (ignore != null || disabled != null) {
extraProps.virtual = (field: Field) => {
if (isDisabled(field)) { return true; }
if (typeof ignore === 'function') {
return ignore.call(field, field.getValue()) === true;
}
return ignore === true;
};
}
if (type === 'group') {
newConfig.items = items ? upgradeConfigure(items) : [];
return newConfig;
}
if (slotName) {
newConfig.name = slotName;
if (!newConfig.title && slotTitle) {
newConfig.title = slotTitle;
}
const slotSetter = {
componentName: 'SlotSetter',
initialValue: () => ({
type: 'JSSlot',
// params:
value: initialChildren
}),
}
if (allowTextInput === false) {
newConfig.setter = slotSetter;
} else {
newConfig.setter = [{
componentName: 'StringSetter',
initialValue,
}, slotSetter];
}
return newConfig;
}
if (defaultValue !== undefined) {
extraProps.defaultValue = defaultValue;
} else if (typeof initialValue !== 'function') {
extraProps.defaultValue = initialValue;
}
const initialFn = initial || initialValue;
extraProps.initialValue = (field: Field, defaultValue?: any) => {
if (defaultValue === undefined) {
defaultValue = extraProps.defaultValue;
}
if (typeof initialFn === 'function') {
return initialFn(null, defaultValue);
}
return defaultValue;
};
if (sync) {
extraProps.autorun = (field: Field) => {
const value = sync.call(field, field.getValue());
if (value !== undefined) {
field.setValue(value);
}
}
}
if (accessor) {
extraProps.getValue = (field: Field, fieldValue: any) => {
return accessor.call(field, fieldValue);
};
}
if (mutator) {
extraProps.setValue = (field: Field, value: any) => {
mutator.call(field, value);
};
}
let primarySetter: any;
if (type === 'composite') {
const objItems = items ? upgradeConfigure(items) : [];
primarySetter = {
componentName: 'ObjectSetter',
props: {
config: {
items: objItems,
},
},
initialValue: (field: Field) => {
// FIXME: read from objItems
return extraProps.initialValue(field, {});
},
};
} else if (setter) {
if (Array.isArray(setter)) {
primarySetter = setter.map(({ setter, condition }) => {
return {
componentName: setter,
condition: condition ? (field: Field) => {
return condition.call(field, field.getValue());
} : null,
};
});
} else {
primarySetter = setter;
}
}
if (supportVariable) {
if (primarySetter) {
const setters = Array.isArray(primarySetter) ? primarySetter.concat('ExpressionSetter') : [primarySetter, 'ExpressionSetter'];
primarySetter = {
componentName: 'MixedSetter',
setters,
onSetterChange: (field: Field, name: string) => {
if (useVariableChange) {
useVariableChange.call(field, { isUseVariable: name === 'ExpressionSetter' });
}
}
};
} else {
primarySetter = 'ExpressionSetter';
}
}
newConfig.setter = primarySetter;
return newConfig;
}
export function upgradeConfigure(items: OldPropConfig[]) {
const configure = [];
let ignoreSlotName: any = null;
return items.forEach((config) => {
if (config.slotName) {
ignoreSlotName = config.slotName;
} else if (ignoreSlotName) {
if (config.name === ignoreSlotName) {
ignoreSlotName = null;
return;
}
ignoreSlotName = null;
}
configure.push(upgradePropConfig(config));
});
}
export function upgradeActions(actions?: Array<ComponentType<any> | ReactElement> | (() => ReactElement)) {
if (!actions) {
return null;
}
if (!Array.isArray(actions)) {
actions = [actions];
}
return actions.map((content) => {
const type: any = isValidElement(content) ? content.type : content;
if (typeof content === 'function') {
const fn = content as (() => ReactElement);
content = (({ node }: any) => {
fn.call(node);
}) as any;
}
return {
name: type.displayName || type.name || 'anonymous',
content,
important: true,
};
})
}
/**
*
*/
function upgradeMetadata(oldConfig: OldPrototypeConfig) {
export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
const {
componentName,
docUrl,
@ -242,11 +438,11 @@ function upgradeMetadata(oldConfig: OldPrototypeConfig) {
packageName,
category,
extraActions,
view,
configure,
defaultProps,
initialChildren,
snippets,
view,
configure,
transducers,
isContainer,
rectSelector,
@ -260,16 +456,18 @@ function upgradeMetadata(oldConfig: OldPrototypeConfig) {
canDropto,
canDropIn,
canDroping,
// handles
canDraging, canDragging, // handleDragging
canResizing, // handleResizing
// hooks
canDraging, canDragging, // handleDragging
// events
didDropOut, // onNodeRemove
didDropIn, // onNodeAdd
subtreeModified, // onSubtreeModified
canResizing, // resizing
onResizeStart, // onResizeStart
onResize, // onResize
onResizeEnd, // onResizeEnd
subtreeModified, // onSubtreeModified
} = oldConfig;
@ -279,7 +477,7 @@ function upgradeMetadata(oldConfig: OldPrototypeConfig) {
icon,
docUrl,
devMode: 'procode',
}
};
if (category) {
meta.tags = [category];
@ -303,13 +501,7 @@ function upgradeMetadata(oldConfig: OldPrototypeConfig) {
component.disableBehaviors = '*';
}
if (extraActions) {
component.actions = extraActions.map((content) => {
return {
name: content.displayName || content.name || 'anonymous',
content,
important: true,
};
});
component.actions = upgradeActions(extraActions);
}
const nestingRule: any = {};
if (canContain) {
@ -323,97 +515,98 @@ function upgradeMetadata(oldConfig: OldPrototypeConfig) {
}
component.nestingRule = nestingRule;
if (canDragging || canDraging) {
// hooks|handle
}
// 未考虑清楚的,放在实验性段落
const experimental: any = {};
if (context) {
// for prototype.getContextInfo
experimental.context = context;
}
if (snippets) {
experimental.snippets = snippets;
}
if (defaultProps || initialChildren) {
const snippet = {
screenshot: icon,
label: title,
schema: {
componentName,
props: defaultProps,
children: initialChildren,
},
};
if (experimental.snippets) {
experimental.snippets.push(snippet);
} else {
experimental.snippets = [snippet];
}
}
if (view) {
experimental.view = view;
}
if (transducers) {
// Array<{ toStatic, toNative }>
// ? only twice
experimental.transducers = transducers;
}
if (canResizing) {
// TODO: enhance
experimental.getResizingHandlers = (currentNode: any) => {
const directs = ['n', 'w', 's', 'e'];
if (canResizing === true) {
return directs;
}
return directs.filter((d) => canResizing(currentNode, d));
};
}
const props = {};
const styles = {};
const events = {};
meta.configure = { props, component, styles, events, experimental };
const callbacks: any = {};
if (canDragging != null || canDraging != null) {
let v = true;
if (canDragging === false || canDraging === false) {
v = false;
}
callbacks.onMoveHook = () => v;
}
if (didDropIn) {
callbacks.onNodeAdd = didDropIn;
}
if (didDropOut) {
callbacks.onNodeRemove = didDropOut;
}
if (subtreeModified) {
callbacks.onSubtreeModified = (...args: any[]) => {
// FIXME! args not correct
subtreeModified.apply(args[0], args as any);
};
}
if (onResize) {
callbacks.onResize = (e: any, currentNode: any) => {
// todo: what is trigger?
const { trigger, deltaX, deltaY } = e;
onResize(e, trigger, currentNode, deltaX, deltaY);
}
}
if (onResizeStart) {
callbacks.onResizeStart = (e: any, currentNode: any) => {
// todo: what is trigger?
const { trigger } = e;
onResizeStart(e, trigger, currentNode);
}
}
if (onResizeEnd) {
callbacks.onResizeEnd = (e: any, currentNode: any) => {
// todo: what is trigger?
const { trigger } = e;
onResizeEnd(e, trigger, currentNode);
}
}
experimental.callbacks = callbacks;
const props = upgradeConfigure(configure || []);
meta.configure = { props, component };
meta.experimental = experimental;
return meta;
}
export interface OldPrototypeConfig {
packageName: string; // => npm.package
/**
* category display in the component pane
* component will be hidden while the value is: null
*/
category: string; // => tags
componentName: string; // =>
docUrl?: string; // =>
defaultProps?: any; // => ?
/**
* extra actions on the outline of current selected node
* by default we have: remove / clone
*/
extraActions?: Component[]; // => configure.component.actions
title?: string; // =>
icon?: Component; // =>
view: Component; // => ?
initialChildren?: (props: any) => any[]; // => snippets
/**
* Props configurations of node
*/
configure: IPropConfig[]; // => configure.props
snippets?: ISnippet[]; // => snippets
transducers?: any; // => ?
/**
* Selector expression rectangle of a node, it is usually a querySelector string
* @example '.classname > div'
*/
rectSelector?: string; // => configure.component.rectSelector
context?: {
// => ?
[contextInfoName: string]: any;
};
isContainer?: boolean; // => configure.component.isContainer
isModal?: boolean; // => configure.component.isModal
isFloating?: boolean; // => configure.component.isFloating
descriptor?: string; // => configure.component.descriptor
/**
* enable slot-mode
* @see https://yuque.antfin-inc.com/legao/solutions/atgtdl
*/
hasSlot?: boolean; // => ?
// alias to canDragging
canDraging?: boolean; // => onDrag
canDragging?: boolean; // => ?
canOperating?: boolean; // => disabledActions
canSelecting?: boolean;
canContain?: (dragment: Node) => boolean; // => nestingRule
canDropTo?: ((container: Node) => boolean) | string | string[]; // => nestingRule
canDropto?: (container: Node) => boolean; // => nestingRule
canDropIn?: ((dragment: Node) => boolean) | string | string[]; // => nestingRule
canDroping?: (dragment: Node) => boolean; // => nestingRule
didDropOut?: (container: any | Prototype, dragment: any) => boolean; // => hooks
didDropIn?: (container: any | Prototype, dragment: any) => boolean; // => hooks
// => ?
canResizing?: ((dragment: Node, triggerDirection: string) => boolean) | boolean;
onResizeStart?: (e: MouseEvent, triggerDirection: string, dragment: Node) => void;
onResize?: (e: MouseEvent, triggerDirection: string, dragment: Node, moveX: number, moveY: number) => void;
onResizeEnd?: (e: MouseEvent, triggerDirection: string, dragment: Node) => void;
/**
* when sub-node of the current node changed
* including: sub-node insert / remove
*/
subtreeModified?(this: Node): any; // => ? hooks
}

View File

@ -4,7 +4,7 @@ import { EventEmitter } from 'events';
/**
* Bus class as an EventEmitter
*/
class Bus {
export class Bus {
private emitter = new EventEmitter();
getEmitter() {

View File

@ -1,16 +0,0 @@
import { init } from './vision'; // VisualEngine
import { editor } from './editor';
init();
load();
async function load() {
const assets = await editor.utils.get('./assets.json');
editor.set('assets', assets);
editor.emit('assets.loaded', assets);
const schema = await editor.utils.get('./schema.json');
editor.set('schema', schema);
editor.emit('schema.loaded', schema);
}

View File

@ -0,0 +1,55 @@
import Engine from '../vision'; // VisualEngine
import { editor } from '../editor';
import loadUrls from './loader';
Engine.init();
load();
async function load() {
await loadAssets();
loadSchema();
}
const externals = [
'react',
'react-dom',
'prop-types',
'react-router',
'react-router-dom',
'@ali/recore',
];
async function loadAssets() {
const assets = await editor.utils.get('./legao-assets.json');
// Trunk.setPackages(assets.packages);
if (assets.packages) {
assets.packages.forEach((item: any) => {
if (item.package.indexOf('@ali/vc-') === 0 && item.urls) {
item.urls = item.urls.filter((url: string) => {
return url.indexOf('view.mobile') < 0
});
} else if (item.package && externals.indexOf(item.package) > -1) {
item.urls = null;
}
});
}
if (assets['x-prototypes']) {
const tasks: Array<Promise<any>> = [];
assets['x-prototypes'].forEach((pkg: any) => {
tasks.push(loadUrls(pkg?.urls));
});
await Promise.all(tasks);
// proccess snippets
}
editor.set('assets', assets);
}
async function loadSchema() {
const schema = await editor.utils.get('./schema.json');
editor.set('schema', schema);
}

View File

@ -0,0 +1,171 @@
function getStylePoint(id, level) {
if (stylePointTable[id]) {
return stylePointTable[id];
}
const base = getBasePoint();
if (id === 'base') {
return base;
}
const point = new StylePoint(id, level || 2000);
if (level >= base.level) {
let prev = base;
let next = prev.next;
while (next && level >= next.level) {
prev = next;
next = prev.next;
}
prev.next = point;
point.prev = prev;
if (next) {
point.next = next;
next.prev = point;
}
} else {
let next = base;
let prev = next.prev;
while (prev && level < prev.level) {
next = prev;
prev = next.prev;
}
next.prev = point;
point.next = next;
if (prev) {
point.prev = prev;
prev.next = point;
}
}
point.insert();
stylePointTable[id] = point;
return point;
}
const stylePointTable = {};
function getBasePoint() {
if (!stylePointTable.base) {
stylePointTable.base = new StylePoint('base', 1000);
stylePointTable.base.insert();
}
return stylePointTable.base;
}
class StylePoint {
constructor(id, level, placeholder) {
this.lastContent = null;
this.lastUrl = null;
this.next = null;
this.prev = null;
this.id = id;
this.level = level;
if (placeholder) {
this.placeholder = placeholder;
} else {
this.placeholder = document.createTextNode('');
}
}
insert() {
if (this.next) {
document.head.insertBefore(this.placeholder, this.next.placeholder);
} else if (this.prev) {
document.head.insertBefore(this.placeholder, this.prev.placeholder.nextSibling);
} else {
document.head.appendChild(this.placeholder);
}
}
applyText(content) {
if (this.lastContent === content) {
return;
}
this.lastContent = content;
this.lastUrl = undefined;
const element = document.createElement('style');
element.setAttribute('type', 'text/css');
element.setAttribute('data-for', this.id);
element.appendChild(document.createTextNode(content));
document.head.insertBefore(element, this.placeholder);
document.head.removeChild(this.placeholder);
this.placeholder = element;
}
applyUrl(url) {
if (this.lastUrl === url) {
return;
}
this.lastContent = undefined;
this.lastUrl = url;
const element = document.createElement('link');
element.href = url;
element.rel = 'stylesheet';
element.setAttribute('data-for', this.id);
document.head.insertBefore(element, this.placeholder);
document.head.removeChild(this.placeholder);
this.placeholder = element;
}
}
function loadCSS(url) {
getStylePoint(url).applyUrl(url);
}
function isCSSUrl(url) {
return /\.css$/.test(url);
}
function loadScript(url) {
const node = document.createElement('script');
// node.setAttribute('crossorigin', 'anonymous');
node.onload = onload;
node.onerror = onload;
const i = {};
const promise = new Promise((resolve, reject) => {
i.resolve = resolve;
i.reject = reject;
});
function onload(e) {
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 promise;
}
export default function loadUrls(urls) {
if (!urls || urls.length < 1) {
return Promise.resolve();
}
let promise = null;
urls.forEach((url) => {
if (isCSSUrl(url)) {
loadCSS(url);
} else if (!promise) {
promise = loadScript(url);
} else {
promise = promise.then(() => loadScript(url));
}
});
return promise || Promise.resolve();
}

View File

@ -1,11 +1,11 @@
import { globalContext } from '@ali/lowcode-globals';
import Editor from '@ali/lowcode-editor-core';
import { Designer } from '@ali/lowcode-designer';
import { registerSetters } from '@ali/lowcode-setters';
import OutlinePane from '@ali/lowcode-plugin-outline-pane';
import SettingsPane from '@ali/lowcode-plugin-settings-pane';
import DesignerView from '@ali/lowcode-plugin-designer';
import { registerSetters } from '@ali/lowcode-setters';
import { Skeleton } from './skeleton/skeleton';
import { Designer } from 'designer/src/designer';
import { globalContext } from '@ali/lowcode-globals';
registerSetters();
@ -41,11 +41,3 @@ skeleton.leftArea.add({
area: 'leftFixedArea',
},
});
// editor-core
// 1. di 实现
// 2. general bus: pub/sub
// editor-skeleton/workbench 视图实现
// 1. skeleton 区域划分 panes
// provide fixed left pane
// provide float left pane

View File

@ -6,12 +6,15 @@ import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS } from './const';
import Bus from './bus';
import Symbols from './symbols';
import { editor, skeleton } from './editor';
import { skeleton } from './editor';
import { VisionWorkbench } from './skeleton/workbench';
import Panes from './panes';
import Exchange from './exchange';
import VisualEngineContext from './context';
import VisualManager from './base/visualManager';
import Trunk from './bundle/trunk';
import Prototype from './bundle/prototype';
import Bundle from './bundle/bundle';
function init(container?: Element) {
if (!container) {
@ -40,7 +43,7 @@ const modules = {
const context = new VisualEngineContext();
export {
const VisualEngine = {
/**
* VE.Popup
*/
@ -68,9 +71,14 @@ export {
ui,
Panes,
modules,
Trunk,
Prototype,
Bundle,
};
export default VisualEngine;
(window as any).VisualEngine = VisualEngine;
/*
console.log(
`%cLowcodeEngine %cv${VERSION}`,