diff --git a/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx b/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx index c3610d64f..dfb41f797 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx @@ -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 | ActionContentObj return createElement(content, { key, node }); } if (isActionContentObject(content)) { - const { action, description, icon } = content; + const { action, title, icon } = content; return (
| ActionContentObj }} > {icon && createIcon(icon)} - {description} + {title}
); } diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index b47e2b3ec..543031853 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -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 { this.libraryMap[item.package] = item.library; - libraryAsset.push(item.urls); + if (item.urls) { + libraryAsset.push(item.urls); + } }); } diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index e3625c29d..4b2eeed96 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -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); +} diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index 4872db718..28439db23 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -306,24 +306,27 @@ export class Designer { private _lostComponentMetasMap = new Map(); 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 { diff --git a/packages/globals/src/di/setter.ts b/packages/globals/src/di/setter.ts index f94fe0630..23344c15b 100644 --- a/packages/globals/src/di/setter.ts +++ b/packages/globals/src/di/setter.ts @@ -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(); +const settersMap = new Map(); 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') { diff --git a/packages/globals/src/di/transducer.ts b/packages/globals/src/di/transducer.ts index c10639e41..93e079bf7 100644 --- a/packages/globals/src/di/transducer.ts +++ b/packages/globals/src/di/transducer.ts @@ -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[] { diff --git a/packages/globals/src/types/field-config.ts b/packages/globals/src/types/field-config.ts index b0e4380bc..be65dd64b 100644 --- a/packages/globals/src/types/field-config.ts +++ b/packages/globals/src/types/field-config.ts @@ -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' */ diff --git a/packages/globals/src/types/metadata.ts b/packages/globals/src/types/metadata.ts index 484a607e2..c7377753e 100644 --- a/packages/globals/src/types/metadata.ts +++ b/packages/globals/src/types/metadata.ts @@ -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; + 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; +} diff --git a/packages/globals/src/types/npm.ts b/packages/globals/src/types/npm.ts index 0335c3c81..3683e40da 100644 --- a/packages/globals/src/types/npm.ts +++ b/packages/globals/src/types/npm.ts @@ -1,7 +1,7 @@ export interface NpmInfo { componentName?: string; package: string; - version: string; + version?: string; destructuring?: boolean; exportName?: string; subName?: string; diff --git a/packages/globals/src/types/setter-config.ts b/packages/globals/src/types/setter-config.ts index 1df336543..956e5af81 100644 --- a/packages/globals/src/types/setter-config.ts +++ b/packages/globals/src/types/setter-config.ts @@ -1,9 +1,11 @@ import { isReactComponent } from '../utils'; import { ComponentType, ReactElement, isValidElement } from 'react'; +import { TitleContent } from './title'; export type CustomView = ReactElement | ComponentType; 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; +} diff --git a/packages/globals/src/types/title.ts b/packages/globals/src/types/title.ts index a63692933..e9a089b17 100644 --- a/packages/globals/src/types/title.ts +++ b/packages/globals/src/types/title.ts @@ -7,6 +7,7 @@ import { IconType } from './icon'; export interface TitleConfig { label?: I18nData | ReactNode; tip?: TipContent; + docUrl?: string; icon?: IconType; className?: string; } diff --git a/packages/globals/src/types/value-type.ts b/packages/globals/src/types/value-type.ts index 5659a555b..f12f52266 100644 --- a/packages/globals/src/types/value-type.ts +++ b/packages/globals/src/types/value-type.ts @@ -15,7 +15,9 @@ export interface JSExpression { export interface JSSlot { type: 'JSSlot'; - value: NodeSchema; + // 函数的入参 + params?: string[]; + value: NodeSchema[]; } // JSON 基本类型 diff --git a/packages/globals/src/utils/asset.ts b/packages/globals/src/utils/asset.ts index d09abe623..79ab83531 100644 --- a/packages/globals/src/utils/asset.ts +++ b/packages/globals/src/utils/asset.ts @@ -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 , view3 ", +urls: [ + "view.js", + "view.js *", + "view1.js mobile|pc", + "view2.js " +]*/ export function assetItem(type: AssetType, content?: string | null, level?: AssetLevel, id?: string): AssetItem | null { if (!content) { return null; diff --git a/packages/plugin-designer/src/index.tsx b/packages/plugin-designer/src/index.tsx index 396ac61bf..6a45e663f 100644 --- a/packages/plugin-designer/src/index.tsx +++ b/packages/plugin-designer/src/index.tsx @@ -20,50 +20,36 @@ export default class DesignerPlugin extends PureComponent { - 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); }); }; diff --git a/packages/plugin-settings-pane/src/main.ts b/packages/plugin-settings-pane/src/main.ts index 2ce6f55a4..790627a70 100644 --- a/packages/plugin-settings-pane/src/main.ts +++ b/packages/plugin-settings-pane/src/main.ts @@ -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, diff --git a/packages/plugin-settings-pane/src/setters/mixed-setter/index.tsx b/packages/plugin-settings-pane/src/setters/mixed-setter/index.tsx new file mode 100644 index 000000000..a261ca631 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/mixed-setter/index.tsx @@ -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): 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; + 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 = ( + + ); + if (isTwoType) { + moreBtnNode = triggerNode; + } else { + let MenuItems: {} | null | undefined = []; + realTypes.map(type => { + if (this.typeMap[type]) { + MenuItems.push(); + } else { + console.error( + this.i18n('typeError', { + type + }) + ); + } + }); + let MenuNode = ( + + {this.setters.map((setter) => { + return + + </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> + ); + } +} diff --git a/packages/plugin-settings-pane/src/settings-pane.tsx b/packages/plugin-settings-pane/src/settings-pane.tsx index 516129049..2f91c9450 100644 --- a/packages/plugin-settings-pane/src/settings-pane.tsx +++ b/packages/plugin-settings-pane/src/settings-pane.tsx @@ -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) { diff --git a/packages/plugin-settings-pane/src/transducers/register.ts b/packages/plugin-settings-pane/src/transducers/register.ts index a6f1ecfe9..a09e685ed 100644 --- a/packages/plugin-settings-pane/src/transducers/register.ts +++ b/packages/plugin-settings-pane/src/transducers/register.ts @@ -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'); diff --git a/packages/react-renderer/src/engine/base.jsx b/packages/react-renderer/src/engine/base.jsx index bf57c6925..a7990a401 100644 --- a/packages/react-renderer/src/engine/base.jsx +++ b/packages/react-renderer/src/engine/base.jsx @@ -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容器 diff --git a/packages/react-renderer/src/engine/index.jsx b/packages/react-renderer/src/engine/index.jsx index fa88af723..00930d670 100644 --- a/packages/react-renderer/src/engine/index.jsx +++ b/packages/react-renderer/src/engine/index.jsx @@ -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} diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index 91f48670a..48c2be875 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -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); }} diff --git a/packages/setters/src/index.tsx b/packages/setters/src/index.tsx index 73dd1091b..c561256e7 100644 --- a/packages/setters/src/index.tsx +++ b/packages/setters/src/index.tsx @@ -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, }; diff --git a/packages/vision-polyfill/README.md b/packages/vision-polyfill/README.md index e178d197e..688968e8b 100644 --- a/packages/vision-polyfill/README.md +++ b/packages/vision-polyfill/README.md @@ -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 信息都可以丢掉 diff --git a/packages/vision-polyfill/build.json b/packages/vision-polyfill/build.json index c6ed6e7a8..d7df7c427 100644 --- a/packages/vision-polyfill/build.json +++ b/packages/vision-polyfill/build.json @@ -1,6 +1,6 @@ { "entry": { - "index": "src/demo.ts" + "index": "src/demo/index.ts" }, "vendor": false, "devServer": { diff --git a/packages/vision-polyfill/public/index.html b/packages/vision-polyfill/public/index.html index 4d4a5fcf7..1667778be 100644 --- a/packages/vision-polyfill/public/index.html +++ b/packages/vision-polyfill/public/index.html @@ -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> diff --git a/packages/vision-polyfill/src/legao-assets.json b/packages/vision-polyfill/public/legao-assets.json similarity index 82% rename from packages/vision-polyfill/src/legao-assets.json rename to packages/vision-polyfill/public/legao-assets.json index 4d0ce81c0..a7b52f510 100644 --- a/packages/vision-polyfill/src/legao-assets.json +++ b/packages/vision-polyfill/public/legao-assets.json @@ -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": { diff --git a/packages/vision-polyfill/src/bundle/bundle.ts b/packages/vision-polyfill/src/bundle/bundle.ts index df8c0c291..7c4407433 100644 --- a/packages/vision-polyfill/src/bundle/bundle.ts +++ b/packages/vision-polyfill/src/bundle/bundle.ts @@ -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; } } diff --git a/packages/vision-polyfill/src/bundle/prototype.ts b/packages/vision-polyfill/src/bundle/prototype.ts index 768aee46f..40b25b520 100644 --- a/packages/vision-polyfill/src/bundle/prototype.ts +++ b/packages/vision-polyfill/src/bundle/prototype.ts @@ -1,133 +1,25 @@ -import { assign, flow, get, isFunction, isObject } from 'lodash'; -import { Component, ComponentClass } from 'react'; -import { Node } from '../../core/pages/node'; -import Prop from '../../core/pages/prop'; +import { ComponentType, ReactElement } from 'react'; +import { ComponentMetadata, uniqueId, registerMetadataTransducer, FieldConfig } from '@ali/lowcode-globals'; +import { ComponentMeta, addBuiltinComponentAction, isComponentMeta } from '@ali/lowcode-designer'; +import { + OldPropConfig, + OldPrototypeConfig, + upgradeMetadata, + upgradeActions, + upgradePropConfig, + upgradeConfigure, +} from './upgrade-metadata'; +import { designer } from '../editor'; -export enum DISPLAY_TYPE { - NONE = 'none', - PLAIN = 'plain', - INLINE = 'inline', - BLOCK = 'block', - ACCORDION = 'accordion', - TAB = 'tab', - ENTRY = 'entry', -} - -let PID = 0; -const GlobalNodeCanDragConfig: Array<(node: Node | false) => {}> = []; -const GlobalPropsReducers: any[] = []; -const GlobalPropsConfigure: IGlobalPropConfig[] = [ - { - position: 'bottom', - title: '条件渲染', - type: 'group', - name: '', - display: DISPLAY_TYPE.ACCORDION, - collapsed: true, - tip: { - content: '点击查看详细用法', - url: 'https://yuque.antfin-inc.com/legao/help3.0/conditional-render', - }, - items: [ - { - name: '___condition___', - title: '是否渲染', - display: DISPLAY_TYPE.INLINE, - initialValue: true, - supportVariable: true, - setter: 'Switch', - }, - ], - disabled() { - const proto = this.getProps() - .getNode() - .getPrototype(); - if (!proto) { - return true; - } - if (!proto.canUseCondition()) { - return true; - } - return false; - }, - }, - { - position: 'bottom', - title: '循环渲染', - type: 'group', - name: '', - display: DISPLAY_TYPE.ACCORDION, - collapsed: true, - tip: { - content: '点击查看详细用法', - url: 'https://yuque.antfin-inc.com/legao/help3.0/loop-render', - }, - items: [ - { - name: '___loop___', - title: '循环数据', - display: DISPLAY_TYPE.INLINE, - setter: 'Object', - supportVariable: true, - }, - { - name: '___loopArgs___', - title: '循环参数', - display: DISPLAY_TYPE.INLINE, - initialValue: ['item', 'index'], - setter: 'Object', - }, - { - name: 'key', - title: '循环key111', - display: DISPLAY_TYPE.INLINE, - initialValue: '', - setter: 'Input', - ignore() { - if (this.getValue() || this.getVariableValue()) { - return false; - } - return true; - }, - supportVariable: true, - }, - ], - disabled() { - const proto = this.getProps().getNode().getPrototype(); - if (!proto) { - return true; - } - if (!proto.canLoop()) { - return true; - } - return false; - }, - }, -]; -const GlobalExtraActions: any[] = []; +const GlobalPropsConfigure: Array<FieldConfig & { position: string }> = []; const Overrides: any = {}; -function addGlobalPropsReducer(reducer: () => any) { - GlobalPropsReducers.push(reducer); +function addGlobalPropsConfigure(config: OldGlobalPropConfig) { + GlobalPropsConfigure.push({ + position: config.position, + ...upgradePropConfig(config), + }); } - -function addGlobalPropsConfigure(config: IGlobalPropConfig) { - GlobalPropsConfigure.push(config); -} - -function addGlobalExtraActions(actions: () => React.Component[]) { - GlobalExtraActions.push(actions); -} - -function addGlobalNodeCanDragConfig(drag: () => boolean) { - if (!isFunction(drag)) { - /* tslint:disable no-console */ - console.error('ERROR: the global canDrag configure should be a function.'); - return; - } - GlobalNodeCanDragConfig.push(drag); -} - function removeGlobalPropsConfigure(name: string) { let l = GlobalPropsConfigure.length; while (l-- > 0) { @@ -136,221 +28,73 @@ function removeGlobalPropsConfigure(name: string) { } } } - -function overridePropsConfigure(componentName: string, configure: any) { - Overrides[componentName] = configure; +function overridePropsConfigure(componentName: string, config: OldPropConfig | OldPropConfig[]) { + Overrides[componentName] = Array.isArray(config) ? upgradeConfigure(config) : upgradePropConfig(config); } - -export interface ISetterConfig { - title?: string; - setter?: ComponentClass; - componentName?: string; - props?: object; - initialValue?: any; - // use value to decide whether this setter is available - condition?: (value: any) => boolean; -} - -export type SetterGetter = (this: Prop, value: any) => ComponentClass; - -type ReturnBooleanFunction = () => boolean; - -export interface IPropConfig { - /** - * composite share the namespace - * group just be tie up together - */ - type?: 'composite' | 'group'; - /** - * when type is composite or group - */ - items?: IPropConfig[]; - /** - * property name: the field key in props of schema - */ - name: string; - title?: string; - tip?: { - title?: string; - content?: string; - url?: string; - }; - initialValue?: any; - defaultValue?: any; - display?: DISPLAY_TYPE; - fieldStyle?: DISPLAY_TYPE; - 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; - /** - * the prop should be collapsed while display value is accordion - */ - collapse?: boolean; - /** - * alias to collapse - */ - collapsed?: boolean; - fieldCollapsed?: boolean; - /** - * if a prop is declared as disabled, it will not be saved into - * schema - */ - disabled?: boolean | ReturnBooleanFunction; - /** - * will not export data to schema - */ - ignore?: boolean | ReturnBooleanFunction; - /** - * 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; - hidden?: boolean | ReturnBooleanFunction; - /** - * if a prop is a lifeCycle function - */ - lifeCycle?: boolean; - destroy?: () => any; - initial?(this: Prop, value: any, initialValue: any): any; - /** - * when use getValue(), accessor shall be called as initializer - */ - accessor?(this: Prop): any; - /** - * when current prop value mutate, the mutator function shall be called - */ - mutator?( - this: Prop, - value: any, - hotValue: any, - preValue: any, - preHotValue: any, - ): void; - /** - * other values' change will trigger sync function here - */ - sync?(this: Prop, value: any): void; - /** - * 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; - /** - * user click var to change current field to - * variable setting field - */ - useVariableChange?(data: { isUseVariable: boolean }): any; -} - -export interface IGlobalPropConfig extends IPropConfig { - position?: - | 'top' - | 'bottom' - | { [key in 'before' | 'after']: { prop: string; value: any } }; -} - -export interface SettingFieldConfig { - type?: 'field'; - title?: string; - name: string; - setter: ComponentClass | ISetterConfig[] | string | SetterGetter; - extraProps?: { - [key: string]: any; - }; -} - -export interface SettingGroupConfig { - type: 'group'; - title?: string; - items: Array<SettingGroupConfig | SettingFieldConfig>; - extraProps?: { - [key: string]: any; - }; -} - -export interface ComponentDecoratorSpec { - componentName: string; - title?: string; - description?: string; - docUrl?: string; - screenshot?: string; - icon?: string; - category?: string; - tags?: string | string[]; - mode?: 'lowcode' | 'procode'; - uri?: string; - npm?: { - package: string; - exportName?: string; - subName?: string; - destructuring?: boolean; - main?: string; - version: string; - }; - props?: Array<{ - name: string; - description?: string; - propType: string | { type: string; value: any }; - defaultValue?: any; - }>; - configure?: { - props?: Array<SettingGroupConfig | SettingFieldConfig>; - component?: { - isContainer?: boolean; - isModal?: boolean; - descriptor?: string; - nestingRule?: { - childWhitelist?: string[]; - parentWhitelist?: string[]; - }; - }; - callbacks?: object; - }; -} - -export interface ISnippet { - screenshot: string; - label: string; - schema: any; -} - -function toPropConfig( - configure: Array<SettingFieldConfig | SettingGroupConfig>, -): IPropConfig[] { - if (!configure) { - return []; - } - return configure.map((item) => { - if (item.type === 'group') { - return { - type: 'group', - name: '', - title: item.title, - items: toPropConfig(item.items), - ...item.extraProps, - }; +registerMetadataTransducer( + (metadata) => { + const { + configure: { combined, props }, + componentName, + } = metadata; + let top: FieldConfig[]; + let bottom: FieldConfig[]; + if (combined) { + top = combined?.[0].items || combined; + bottom = combined?.[combined.length - 1].items || combined; + } else if (props) { + top = props; + bottom = props; } else { - return { - name: item.name, - title: item.title, - // defaultValue: any, - setter: item.setter, - supportVariable: true, - ...item.extraProps, - }; + metadata.configure.props = top = bottom = []; } - }); + + GlobalPropsConfigure.forEach((config) => { + const position = config.position || 'bottom'; + + if (position === 'top') { + top.unshift(config); + } else if (position === 'bottom') { + bottom.push(config); + } + }); + + const override = Overrides[componentName]; + if (override) { + if (Array.isArray(override)) { + metadata.configure.combined = override; + } else { + let l = top.length; + let item; + while (l-- > 0) { + item = top[l]; + if (item.name in override) { + if (override[item.name]) { + top.splice(l, 1, override[item.name]); + } else { + top.splice(l, 1); + } + } + } + } + } + return metadata; + }, + 100, + 'vision-polyfill', +); + +function addGlobalExtraActions(action: () => ReactElement) { + upgradeActions(action)?.forEach(addBuiltinComponentAction); +} + +const GlobalPropsReducers: any[] = []; +function addGlobalPropsReducer(reducer: () => any) { + GlobalPropsReducers.push(reducer); +} + +export interface OldGlobalPropConfig extends OldPropConfig { + position?: 'top' | 'bottom'; } const packageMaps: any = {}; @@ -364,9 +108,7 @@ function accessLibrary(library: string | object) { return (window as any)[library]; } -export function setPackages( - packages: Array<{ package: string; library: object | string }>, -) { +export function setPackages(packages: Array<{ package: string; library: object | string }>) { packages.forEach((item) => { let lib: any; if (packageMaps.hasOwnProperty(item.package)) { @@ -391,636 +133,130 @@ export function getPackage(name: string): object | null { return null; } -function getSubComponent(component: any, paths: string[] | string) { - if (!Array.isArray(paths)) { - paths = paths.split('.'); - } - const l = paths.length; - if (l < 1) { - return component; - } - if (l === 1 && paths[0] === 'default') { - try { - if ((component as any).default) { - return (component as any).default; - } - return component; - } catch (e) { - return null; - } - } - let i = 0; - while (i < l) { - const key = paths[i]!; - try { - component = (component as any)[key]; - } catch (e) { - return null; - } - if (!component) { - return null; - } - i++; - } - return component; -} - -function getViewByURI(uri: string): any { - const [packName, paths] = uri.split(':'); - const component = getPackage(packName); - if (!paths) { - return component; - } - return getSubComponent(component, paths); -} - -function generateConfigure( - props: Array<{ - name: string; - propType: string | { type: string; value: any }; - defaultValue?: any; - description?: string; - }>, -): any { - return []; -} - -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; -} - -function toOlderSpec( - options: ComponentDecoratorSpec, -): IComponentPrototypeConfigure { - const uri = options.uri || npmToURI(options.npm); - const spec: any = { - packageName: options.npm ? options.npm.package : '', - category: - options.category || - (Array.isArray(options.tags) ? options.tags[0] : options.tags), - componentName: options.componentName, - docUrl: options.docUrl, - defaultProps: {}, - title: options.title, - icon: options.icon, - uri, - view: getViewByURI(uri), - configure: - options.configure && options.configure.props - ? toPropConfig(options.configure.props) - : generateConfigure(options.props), - }; - - if (options.configure && options.configure.component) { - if (options.configure.component.isModal) { - spec.isModal = true; - } - if (options.configure.component.isContainer) { - spec.isContainer = true; - } - if (options.configure.component.descriptor) { - spec.descriptor = options.configure.component.descriptor; - } - if (options.configure.component.nestingRule) { - if (options.configure.component.nestingRule.childWhitelist) { - spec.canDropIn = options.configure.component.nestingRule.childWhitelist; - } - if (options.configure.component.nestingRule.parentWhitelist) { - spec.canDropTo = - options.configure.component.nestingRule.parentWhitelist; - } - } - } - - return spec; -} - -function isNewSpec(options: any): options is ComponentDecoratorSpec { +function isNewSpec(options: any): options is ComponentMetadata { return ( options && - (options.npm || - options.props || - (options.configure && - (options.configure.props || options.configure.component))) + (options.npm || options.props || (options.configure && (options.configure.props || options.configure.component))) ); } -export declare interface IComponentPrototypeConfigure { - packageName: string; - uri?: string; - /** - * category display in the component pane - * component will be hidden while the value is: null - */ - category: string; - componentName: string; - docUrl?: string; - defaultProps?: any; - /** - * extra actions on the outline of current selected node - * by default we have: remove / clone - */ - extraActions?: Component[]; - title?: string; - icon?: Component; - view: Component; - initialChildren?: (props: any) => any[]; - - /** - * Props configurations of node - */ - configure: IPropConfig[]; - snippets?: ISnippet[]; - transducers?: any; - reducers?: any; - /** - * Selector expression rectangle of a node, it is usually a querySelector string - * @example '.classname > div' - */ - rectSelector?: string; - context?: { - [contextInfoName: string]: any; - }; - - isContainer?: boolean; - isInline?: boolean; - isModal?: boolean; - isFloating?: boolean; - descriptor?: string; - - /** - * enable slot-mode - * @see https://yuque.antfin-inc.com/legao/solutions/atgtdl - */ - hasSlot?: boolean; - - // alias to canDragging - canDraging?: boolean; - canDragging?: boolean; - - canOperating?: boolean; - canHovering?: boolean; - canSelecting?: boolean; - canUseCondition?: boolean; - canLoop?: boolean; - canContain?: (dragment: Node) => boolean; - - canDropTo?: ((container: Node) => boolean) | string | string[]; - canDropto?: (container: Node) => boolean; - - canDropIn?: ((dragment: Node) => boolean) | string | string[]; - canDroping?: (dragment: Node) => boolean; - - didDropOut?: (container: any | Prototype, dragment: any) => boolean; - didDropIn?: (container: any | Prototype, dragment: any) => boolean; - - 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; -} - -export interface IComponentPrototypeExtraConfigs { - autoGenerated?: boolean; -} - class Prototype { - public static addGlobalNodeCanDragConfig = addGlobalNodeCanDragConfig; - public static addGlobalPropsReducer = addGlobalPropsReducer; - public static addGlobalPropsConfigure = addGlobalPropsConfigure; - public static addGlobalExtraActions = addGlobalExtraActions; - public static removeGlobalPropsConfigure = removeGlobalPropsConfigure; - public static overridePropsConfigure = overridePropsConfigure; - public static create = function create( - options: IComponentPrototypeConfigure | ComponentDecoratorSpec, - extraConfigs: IComponentPrototypeExtraConfigs = {}, - ) { - return new Prototype(options, extraConfigs); + static addGlobalPropsReducer = addGlobalPropsReducer; + static addGlobalPropsConfigure = addGlobalPropsConfigure; + static addGlobalExtraActions = addGlobalExtraActions; + static removeGlobalPropsConfigure = removeGlobalPropsConfigure; + static overridePropsConfigure = overridePropsConfigure; + static create(config: OldPrototypeConfig | ComponentMetadata | ComponentMeta) { + return new Prototype(config); }; - private options: IComponentPrototypeConfigure; private id: string; - private packageName: string; - private componentName: string; - private category: string; - private view: ComponentClass; + private meta: ComponentMeta; + readonly options: OldPrototypeConfig | ComponentMetadata; - /** - * is this prototype auto-generated by prototypeMocker - * from a normal VIEW file (React Component Class) - */ - private autoGenerated: boolean = false; - - constructor( - options: IComponentPrototypeConfigure | ComponentDecoratorSpec, - extraConfigs: IComponentPrototypeExtraConfigs = {}, - ) { - this.options = isNewSpec(options) ? toOlderSpec(options) : options; - this.id = `prototype-${PID++}`; - if (extraConfigs.autoGenerated) { - this.autoGenerated = extraConfigs.autoGenerated; - } - if (this.options.hasSlot && !this.isContainer()) { - this.options.isContainer = true; - this.options.canDropIn = () => false; + constructor(input: OldPrototypeConfig | ComponentMetadata | ComponentMeta) { + if (isComponentMeta(input)) { + this.meta = input; + this.options = input.getMetadata(); + } else { + this.options = input; + const metadata = isNewSpec(input) ? input : upgradeMetadata(input); + this.meta = designer.createComponentMeta(metadata); } + this.id = uniqueId('prototype'); } - public getId() { + getId() { return this.id; } - public getUri() { - return this.options.uri; - } - - public getConfig(configName?: keyof IComponentPrototypeConfigure) { + getConfig(configName?: keyof (OldPrototypeConfig | ComponentMetadata)) { if (configName) { return this.options[configName]; } return this.options; } - public getPackageName() { - return this.packageName || this.options.packageName; + getPackageName() { + return this.meta.npm?.package; } - public getContextInfo(name?: string): any { - return name ? get(this.options, ['context', name], '') : this.options; + getContextInfo(name: string): any { + return this.meta.getMetadata().experimental?.context?.[name]; } - public getTitle() { - return this.options.title || this.getComponentName(); + getTitle() { + return this.meta.title; } - public getComponentName() { - return this.componentName || this.options.componentName || null; + getComponentName() { + return this.meta.componentName; } - public getDocUrl() { - return this.options.docUrl; + getDocUrl() { + return this.meta.getMetadata().docUrl; } - public getDefaultProps() { - return this.options.defaultProps || {}; - } - - public getPropConfigs() { + getPropConfigs() { return this.options; } - public getExtraActions() { - let extraActions = this.options.extraActions; - if (!extraActions) { - return GlobalExtraActions; - } - if (!Array.isArray(extraActions)) { - extraActions = [extraActions]; - } - return [...GlobalExtraActions, ...extraActions]; - } - - public getCategory() { - if (this.category !== undefined) { + private category?: string; + getCategory() { + if (this.category != null) { return this.category; } - if (this.options.hasOwnProperty('category')) { - return this.options.category; - } - return '*'; + + return this.meta.getMetadata().tags?.[0] || '*'; } - public hasSlot() { - return this.options.hasSlot; - } - - public setCategory(category: string) { + setCategory(category: string) { this.category = category; } - public getIcon() { - return this.options.icon || ''; + getIcon() { + return this.meta.icon; } - public setView(view: ComponentClass) { - this.view = view; + getConfigure() { + return this.meta.configure; } - public getView() { - return this.view || this.options.view || null; + getRectSelector() { + return this.meta.rectSelector; } - public getInitialChildren() { - return this.options.initialChildren || null; + isContainer() { + return this.meta.isContainer; } - public getConfigure() { - let res = (this.options.configure || []).slice(); - GlobalPropsConfigure.forEach((config) => { - const position = config.position || 'bottom'; - - if (position === 'top') { - res.unshift(config); - } else if (position === 'bottom') { - res.push(config); - } else if (isObject(position.before || position.after)) { - const { prop, value } = position.before || position.after; - let index = -1; - if (prop && value) { - index = res.findIndex((e: any) => e[prop] === value); - } - if (index > -1) { - if (position.hasOwnProperty('after')) { - index += 1; - } - res.splice(index, 0, config); - } - } - }); - - const override = Overrides[this.getComponentName()]; - if (override) { - if (Array.isArray(override)) { - res = override; - } else { - let l = res.length; - let item; - while (l-- > 0) { - item = res[l]; - if (item.name in override) { - if (override[item.name]) { - res.splice(l, 1, override[item.name]); - } else { - res.splice(l, 1); - } - } - } - } - } - return res; + isModal() { + return this.meta.isModal; } - public getRectSelector() { - return this.options.rectSelector; - } - - public isInline() { - return this.options.isInline != null ? this.options.isInline : null; - } - - public isContainer() { - if (isFunction(this.options.isContainer)) { - return (this.options.isContainer as any)(this); - } - return this.options.isContainer != null ? this.options.isContainer : false; - } - - public isModal() { - return this.options.isModal || false; - } - - public isFloating() { - return this.options.isFloating || false; - } - - public isAutoGenerated() { - return this.autoGenerated; - } - - public setPackageName(name: string) { - this.packageName = name; - } - - public setComponentName(componentName: string) { - this.componentName = componentName; - } - - /** - * transform value from hot-value to view - */ - public reduce(props: any) { - let reducers = this.options.reducers || []; - - if (!Array.isArray(reducers)) { - reducers = [reducers]; - } - - // prepend global reducers - reducers = GlobalPropsReducers.concat(reducers); - - if (reducers.length < 1) { - return props; - } - - props = reducers.reduce((xprops: any, reducer: any) => { - if (typeof reducer === 'function') { - return reducer(xprops); - } - return xprops; - }, props); - return props; - } - - public transformToActive(props: any) { - let transducers = this.options.transducers; - if (!transducers) { - return props; - } - if (!Array.isArray(transducers)) { - transducers = [transducers]; - } - props = transducers.reduce((xprops: any, transducer: any) => { - if (transducer && typeof transducer.toActive === 'function') { - return transducer.toActive(xprops); - } - return xprops; - }, props); - return props; - } - - public transformToStatic(props: any) { - let transducers = this.options.transducers; - if (!transducers) { - return props; - } - if (!Array.isArray(transducers)) { - transducers = [transducers]; - } - props = transducers.reduce((xprops: any, transducer: any) => { - if (transducer && typeof transducer.toStatic === 'function') { - return transducer.toStatic(xprops); - } - return xprops; - }, props); - return props; - } - - public canDragging(node?: Node) { - if (this.canSelecting()) { - const draggable = - this.options.canDragging === false || this.options.canDraging === false - ? false - : true; - if (GlobalNodeCanDragConfig.length > 0) { - if (!flow(GlobalNodeCanDragConfig)(node)) { - return false; - } - } - if (isFunction(draggable)) { - return draggable.call(node, node); - } - return draggable; - } + isAutoGenerated() { return false; } - public canHovering() { - return this.options.canHovering != null ? this.options.canHovering : true; + setPackageName(name: string) { + this.meta.setNpm({ + package: name, + }); } - public canSelecting() { - return this.options.canSelecting != null ? this.options.canSelecting : true; - } - - public canOperating() { - return this.options.canOperating != null ? this.options.canOperating : true; - } - - public canSetting() { - return this.canSelecting(); - } - - public canUseCondition() { - return this.options.canUseCondition != null - ? this.options.canUseCondition - : true; - } - - public canLoop() { - return this.options.canLoop != null ? this.options.canLoop : true; - } - - public canDropTo(container: Node) { - if (!this.canDragging()) { - return false; - } - - const oCanDropTo = - this.options.canDropTo != null - ? this.options.canDropTo - : this.options.canDropto; - if (oCanDropTo != null) { - return this.xcan(oCanDropTo, container); - } - return true; - } - - public canDropIn(dragment: Node) { - const oCanDropIn = - this.options.canDropIn != null - ? this.options.canDropIn - : this.options.canDroping; - if (oCanDropIn != null) { - return this.xcan(oCanDropIn, dragment); - } - return true; - } - - public didDropIn(dragment: Node, container: any) { - const fn = this.options.didDropIn; - if (typeof fn === 'function') { - fn.call(container || this, dragment); + setView(view: ComponentType<any>) { + const metadata = this.meta.getMetadata(); + if (!metadata.experimental) { + metadata.experimental = { + view, + }; + } else { + metadata.experimental.view = view; } } - public didDropOut(dragment: Node, container: any) { - const fn = this.options.didDropOut; - if (typeof fn === 'function') { - fn.call(container || this, dragment); - } - } - - public canContain(dragment: Node) { - const oCanContain = this.options.canContain; - if (oCanContain != null) { - return this.xcan(oCanContain, dragment); - } - return true; - } - - public clone(options: IComponentPrototypeConfigure) { - return new Prototype(assign({}, this.options, options || {})); - } - - private xcan( - ocan: ((dragment: Node) => boolean) | string | string[], - placement: Node, - ): boolean; - private xcan(ocan: any, placement: Node): boolean { - const t = typeof ocan; - if (t === 'function') { - return ocan(placement); - } else if (Array.isArray(ocan)) { - return ocan.indexOf(placement.getComponentName()) > -1; - } else if (t === 'string') { - return ( - ocan - .trim() - .split(/[ ,;|]+/) - .indexOf(placement.getComponentName()) > -1 - ); - } - return ocan; + getView() { + return this.meta.getMetadata().experimental?.view || designer.currentDocument?.simulator?.getComponent(this.getComponentName()); } } diff --git a/packages/vision-polyfill/src/bundle/trunk.ts b/packages/vision-polyfill/src/bundle/trunk.ts index ae8f669d7..cd676fdf2 100644 --- a/packages/vision-polyfill/src/bundle/trunk.ts +++ b/packages/vision-polyfill/src/bundle/trunk.ts @@ -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'); } } diff --git a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts index 8f97ab236..d0a74b4e5 100644 --- a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts +++ b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts @@ -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,比如 TabsLayout,ColumnsLayout, 大纲树误定位则错误信息透出,拒绝投入 - * accept: false|null 表示不是一个容器,是一个端点,比如input,option - * 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 拖拽时高亮所有 UL,根据Li设置的 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 -} diff --git a/packages/vision-polyfill/src/bus.ts b/packages/vision-polyfill/src/bus.ts index 0d17720b1..278a1b82e 100644 --- a/packages/vision-polyfill/src/bus.ts +++ b/packages/vision-polyfill/src/bus.ts @@ -4,7 +4,7 @@ import { EventEmitter } from 'events'; /** * Bus class as an EventEmitter */ -class Bus { +export class Bus { private emitter = new EventEmitter(); getEmitter() { diff --git a/packages/vision-polyfill/src/demo.ts b/packages/vision-polyfill/src/demo.ts deleted file mode 100644 index a93a5f930..000000000 --- a/packages/vision-polyfill/src/demo.ts +++ /dev/null @@ -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); -} diff --git a/packages/vision-polyfill/src/demo/index.ts b/packages/vision-polyfill/src/demo/index.ts new file mode 100644 index 000000000..bd6ae4f6e --- /dev/null +++ b/packages/vision-polyfill/src/demo/index.ts @@ -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); +} diff --git a/packages/vision-polyfill/src/demo/loader.js b/packages/vision-polyfill/src/demo/loader.js new file mode 100644 index 000000000..0774ae32a --- /dev/null +++ b/packages/vision-polyfill/src/demo/loader.js @@ -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(); +} diff --git a/packages/vision-polyfill/src/editor.ts b/packages/vision-polyfill/src/editor.ts index f3104501e..ec703e532 100644 --- a/packages/vision-polyfill/src/editor.ts +++ b/packages/vision-polyfill/src/editor.ts @@ -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 diff --git a/packages/vision-polyfill/src/vision.ts b/packages/vision-polyfill/src/vision.ts index 9bc6e408b..858696f7d 100644 --- a/packages/vision-polyfill/src/vision.ts +++ b/packages/vision-polyfill/src/vision.ts @@ -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}`,