diff --git a/packages/designer/src/document/selection.ts b/packages/designer/src/document/selection.ts index caa4e4cef..2e1dc1e96 100644 --- a/packages/designer/src/document/selection.ts +++ b/packages/designer/src/document/selection.ts @@ -157,7 +157,7 @@ export class Selection { return nodes; } - onSelectionChange(fn: () => void): () => void { + onSelectionChange(fn: (ids: string[]) => void): () => void { this.emitter.on('selectionchange', fn); return () => { this.emitter.removeListener('selectionchange', fn); diff --git a/packages/vision-polyfill/src/base/base.ts b/packages/vision-polyfill/src/base/base.ts new file mode 100644 index 000000000..a19c1252a --- /dev/null +++ b/packages/vision-polyfill/src/base/base.ts @@ -0,0 +1,134 @@ +import bus from '../bus'; +import SchemaManager from './schemaManager'; +import VisualDesigner from './visualDesigner'; +import VisualManager from './visualManager'; + +import { findIndex, get, unionBy, uniqueId } from 'lodash'; + +export type removeEventListener = () => void; + +export interface IEventNameMap { + [eventName: string]: string | symbol; +} + +export interface ISchemaController { + getSchemaManager(): SchemaManager; + getSchemaManagerById(id?: string): SchemaManager; + getSchemaManagerByName(name?: string): SchemaManager[]; + getSchemaManagerList(): SchemaManager[]; + connectSchemaManager(manager: SchemaManager): this; + connectSchemaManagerList(managerList: SchemaManager[]): this; + notifyAllSchemaManagers(eventName: string | symbol, eventData: any): boolean; +} + +export interface IManagerController { + getManager(): VisualManager; + getManagerById(id?: string): VisualManager; + getManagerByName(name?: string): VisualManager[]; + getManagerList(name?: string): VisualManager[]; + connectManager(manager: VisualManager): this; + connectManagerList(managerList: VisualManager[]): this; + notifyAllManagers(eventName: string | symbol, eventData: any): boolean; +} + +export interface IDesignerController { + getDesigner(): VisualDesigner; + getDesignerById(id?: string): VisualDesigner; + getDesignerByName(name?: string): VisualDesigner[]; + getDesignerList(): VisualDesigner[]; + connectDesigner(designer: VisualDesigner): this; + connectDesignerList(designerList: VisualDesigner[]): this; + notifyAllDesigners(eventName: string | symbol, eventData: any): boolean; +} + +export interface INameable { + getName(): string; + getId(): string; + setName(name?: string): this; +} + +export interface IObservable { + getEventMap(): IEventNameMap; + on(eventName: string | symbol, callback: () => any): removeEventListener; + emit(eventName: string | symbol, eventData?: any[]): boolean; +} + +export interface IManagerConfigs { + name?: string; + disableEvents?: boolean; + emitter?: IEmitter; +} + +export interface IEmitter { + on(eventName: string | symbol, callback: () => any): removeEventListener; + emit(eventName: string | symbol, eventData?: any): boolean; + removeListener(eventName: string | symbol, callback: () => any): any; +} + +export function connectGeneralManager(manager: any, managerList: any[]) { + const index = findIndex(managerList, (m) => m.getId() === manager.getId()); + if (index > -1) { + managerList.push(manager); + } else { + managerList.splice(index, 1, manager); + } + return managerList; +} + +export function connectGeneralManagerList(managerList: any[], sourceManagerList: any[]): any { + return unionBy(sourceManagerList, managerList, (manager) => manager.getId()); +} + +export class BaseManager implements INameable, IObservable { + static EVENTS: IEventNameMap = {}; + static NAME = 'BaseManager'; + + private name: string; + private id: string; + private emitter: any; + + constructor(managerConfigs: IManagerConfigs = {}) { + this.name = managerConfigs.name || get(this, 'constructor', 'NAME'); + this.id = uniqueId(this.name); + if (!managerConfigs.disableEvents) { + if (managerConfigs.emitter) { + // 使用自定义的满足 EventEmitter 接口要求的自定义事件对象 + this.emitter = managerConfigs.emitter; + } else { + // Bus 为单例模式 + this.emitter = bus; + } + } + } + + getId(): string { + return this.id; + } + + setName(name: string): this { + this.name = name; + return this; + } + + getName(): string { + return this.name; + } + + getEventMap() { + /** + * Hack for get current constructor + * because if we write this.constructor.EVENTS + * ts compiler will show compiled error + */ + return get(this, 'constructor', BaseManager.EVENTS); + } + + on(eventName: string | symbol, callback: () => any): removeEventListener { + this.emitter.on(eventName, callback); + return () => this.emitter.removeListener(eventName, callback); + } + + emit(eventName: string | symbol, ...eventData: any[]): boolean { + return this.emitter.emit.call(this.emitter, eventName, ...eventData); + } +} diff --git a/packages/vision-polyfill/src/base/const.ts b/packages/vision-polyfill/src/base/const.ts new file mode 100644 index 000000000..b0eb8cd12 --- /dev/null +++ b/packages/vision-polyfill/src/base/const.ts @@ -0,0 +1,44 @@ +/** + * Storage the const variables + */ + +/** + * Global + */ +export const VERSION = '5.3.0'; + +/** + * schema version defined in alibaba + */ +export const ALI_SCHEMA_VERSION = '1.0.0'; + +export const VE_EVENTS = { + /** + * node props to be dynamically replaced + * @event props the new props object been replaced + */ + VE_NODE_CREATED: 've.node.created', + VE_NODE_DESTROY: 've.node.destroyed', + VE_NODE_PROPS_REPLACE: 've.node.props.replaced', + // copy / clone node + VE_OVERLAY_ACTION_CLONE_NODE: 've.overlay.cloneElement', + // remove / delete node + VE_OVERLAY_ACTION_REMOVE_NODE: 've.overlay.removeElement', + // one page successfully mount on the DOM + VE_PAGE_PAGE_READY: 've.page.pageReady', +}; + +export const VE_HOOKS = { + // a decorator function + VE_NODE_PROPS_DECORATOR: 've.leaf.props.decorator', + // a remove callback function + VE_NODE_REMOVE_HELPER: 've.outline.actions.removeHelper', + /** + * provide customization field + */ + VE_SETTING_FIELD_PROVIDER: 've.settingField.provider', + /** + * VariableSetter for variable mode of a specified prop + */ + VE_SETTING_FIELD_VARIABLE_SETTER: 've.settingField.variableSetter', +}; diff --git a/packages/vision-polyfill/src/base/schemaManager.ts b/packages/vision-polyfill/src/base/schemaManager.ts new file mode 100644 index 000000000..00ef16561 --- /dev/null +++ b/packages/vision-polyfill/src/base/schemaManager.ts @@ -0,0 +1,102 @@ +import { cloneDeep, find } from 'lodash'; + +import { + BaseManager, + connectGeneralManager, + connectGeneralManagerList, + IManagerController, + ISchemaController, +} from './base'; +import VisualManager from './visualManager'; + +export default class SchemaManager extends BaseManager implements IManagerController, ISchemaController { + private schemaData: object = {}; + private visualManagerList: VisualManager[] = []; + private schemaManagerList: SchemaManager[] = []; + + getManager(): VisualManager { + return this.visualManagerList[0]; + } + + getManagerByName(name?: string): VisualManager[] { + return this.visualManagerList.filter((m) => m.getName() === name); + } + + getManagerById(id?: string): VisualManager { + return find(this.visualManagerList, (m) => m.getId() === id) as VisualManager; + } + + getManagerList(): VisualManager[] { + return this.visualManagerList; + } + + getSchemaManager(): SchemaManager { + return this.schemaManagerList[0]; + } + + getSchemaManagerById(id?: string): SchemaManager { + return find(this.schemaManagerList, (m) => m.getId() === id) as SchemaManager; + } + + getSchemaManagerByName(name?: string): SchemaManager[] { + return this.schemaManagerList.filter((m) => m.getName() === name); + } + + getSchemaManagerList() { + return this.schemaManagerList; + } + + connectManager(manager: any) { + connectGeneralManager.call(this, manager, this.visualManagerList as any); + return this; + } + + connectSchemaManager(manager: SchemaManager): this { + connectGeneralManager.call(this, manager, this.schemaManagerList); + return this; + } + + connectManagerList(managerList: VisualManager[]): this { + this.visualManagerList = connectGeneralManagerList.call(this, managerList as any, this.visualManagerList as any); + return this; + } + + connectSchemaManagerList(managerList: SchemaManager[]): this { + this.schemaManagerList = connectGeneralManagerList.call(this, managerList, this.schemaManagerList); + return this; + } + + notifyAllManagers(eventName: string | symbol, ...eventData: any[]): boolean { + return this.visualManagerList.map((m) => m.emit(eventName, eventData)).every((r) => r); + } + + notifyAllSchemaManagers(eventName: string | symbol, ...eventData: any[]): boolean { + return this.schemaManagerList.map((m) => m.emit(eventName, eventData)).every((r) => r); + } + + exportSchema(): string { + try { + return JSON.stringify(this.schemaData); + } catch (e) { + throw new Error(e.message); + } + } + + exportSchemaObject(): object { + return cloneDeep(this.schemaData); + } + + importSchema(schemaString: string): this { + try { + this.schemaData = JSON.parse(schemaString); + return this; + } catch (e) { + throw new Error(e.message); + } + } + + importSchemaObject(schema: object): this { + this.schemaData = schema; + return this; + } +} diff --git a/packages/vision-polyfill/src/base/visualDesigner.ts b/packages/vision-polyfill/src/base/visualDesigner.ts new file mode 100644 index 000000000..a69018aba --- /dev/null +++ b/packages/vision-polyfill/src/base/visualDesigner.ts @@ -0,0 +1,110 @@ +import { assign, find, get } from 'lodash'; +import { Component } from 'react'; + +import bus from '../bus'; +import { + BaseManager, + connectGeneralManager, + connectGeneralManagerList, + IEmitter, + IEventNameMap, + IManagerController, + INameable, + IObservable, +} from './base'; +import VisualManager from './visualManager'; + +interface IDesignerProps { + name?: string; + visualManagers?: VisualManager[]; + emitter?: IEmitter; +} + +export default class VisualDesigner extends Component implements IManagerController, IObservable, INameable { + static NAME = 'VisualDesigner'; + static EVENTS: IEventNameMap = {}; + props: IDesignerProps = {}; + defaultProps: IDesignerProps = { + name: 'defaultDesigner', + visualManagers: [], + }; + + private visualManagerList: VisualManager[] = []; + private name = ''; + private id = ''; + private emitter: IEmitter; + + constructor(props: IDesignerProps) { + super(props); + this.setName(props.name || get(this, 'constructor', 'NAME')); + this.connectManagerList(this.props.visualManagers as any); + + if (props.emitter) { + // 使用自定义的满足 EventEmitter 接口要求的自定义事件对象 + this.emitter = props.emitter; + } else { + this.emitter = bus; + } + } + + getId(): string { + return this.id; + } + + setName(name: string): this { + this.name = name; + return this; + } + + getName() { + return this.name; + } + + getManager(): VisualManager { + return this.visualManagerList[0]; + } + + getManagerByName(name?: string): VisualManager[] { + return this.visualManagerList.filter((m) => m.getName() === name); + } + + getManagerById(id: string): VisualManager { + return find(this.visualManagerList, (m) => m.getId() === id) as VisualManager; + } + + getManagerList(): VisualManager[] { + return this.visualManagerList; + } + + connectManager(manager: VisualManager) { + connectGeneralManager.call(this, manager, this.visualManagerList); + return this; + } + + connectManagerList(managerList: VisualManager[]): this { + this.visualManagerList = connectGeneralManagerList.call(this, managerList, this.visualManagerList); + return this; + } + + getEventMap() { + /** + * Hack for get current constructor + * because if we write this.constructor.EVENTS + * ts compiler will show compiled error + */ + return get(this, 'constructor', BaseManager.EVENTS); + } + + notifyAllManagers(eventName: string | symbol, ...eventData: any[]): boolean { + return this.visualManagerList.map((m) => m.emit(eventName, eventData)).every((r) => r); + } + + on(eventName: string | symbol, callback: () => any) { + this.emitter.on(eventName, callback); + return () => this.emitter.removeListener(eventName, callback); + } + + emit(eventName: string | symbol, ...eventData: any[]): boolean { + return this.emitter.emit.call(this.emitter, eventName, ...eventData); + } +} diff --git a/packages/vision-polyfill/src/base/visualManager.ts b/packages/vision-polyfill/src/base/visualManager.ts new file mode 100644 index 000000000..e28b6b519 --- /dev/null +++ b/packages/vision-polyfill/src/base/visualManager.ts @@ -0,0 +1,79 @@ +import { find } from 'lodash'; + +import { + BaseManager, + connectGeneralManager, + connectGeneralManagerList, + IDesignerController, + IManagerController, +} from './base'; +import VisualDesigner from './visualDesigner'; + +export default class VisualManager extends BaseManager implements IManagerController, IDesignerController { + private visualManagerList: VisualManager[] = []; + private visualDesignerList: VisualDesigner[] = []; + + getManager(): VisualManager { + return this.visualManagerList[0]; + } + + getManagerByName(name?: string): VisualManager[] { + return this.visualManagerList.filter((m) => m.getName() === name); + } + + getManagerById(id?: string): VisualManager { + return find(this.visualManagerList, (m) => m.getId() === id) as VisualManager; + } + + getManagerList(): VisualManager[] { + return this.visualManagerList; + } + + getDesigner(): VisualDesigner { + return this.visualDesignerList[0]; + } + + getDesignerByName(name?: string): VisualDesigner[] { + return this.visualDesignerList.filter((m) => m.getName() === name); + } + + getDesignerById(id?: string): VisualDesigner { + return find(this.visualDesignerList, (m) => m.getId() === id) as VisualDesigner; + } + + getDesignerList() { + return this.visualDesignerList; + } + + connectManager(manager: VisualManager) { + connectGeneralManager.call(this, manager, this.visualManagerList); + return this; + } + + connectDesigner(manager: VisualDesigner): this { + connectGeneralManager.call(this, manager, this.visualDesignerList); + return this; + } + + connectManagerList(managerList: VisualManager[]): this { + this.visualManagerList = connectGeneralManagerList.call(this, managerList, this.visualManagerList); + return this; + } + + connectDesignerList(managerList: VisualDesigner[]): this { + this.visualDesignerList = connectGeneralManagerList.call(this, managerList, this.visualDesignerList); + return this; + } + + notifyAllManagers(eventName: string | symbol, ...eventData: any[]): boolean { + return this.getManagerList() + .map((m) => m.emit(eventName, eventData)) + .every((r) => r); + } + + notifyAllDesigners(eventName: string | symbol, ...eventData: any[]): boolean { + return this.getDesignerList() + .map((m) => m.emit(eventName, eventData)) + .every((r) => r); + } +} diff --git a/packages/vision-polyfill/src/base/visualRender.ts b/packages/vision-polyfill/src/base/visualRender.ts new file mode 100644 index 000000000..513b978f0 --- /dev/null +++ b/packages/vision-polyfill/src/base/visualRender.ts @@ -0,0 +1,48 @@ +import { find } from 'lodash'; + +import { BaseManager, connectGeneralManager, connectGeneralManagerList, IManagerController } from './base'; +import VisualManager from './visualManager'; + +export default class VisualRender extends BaseManager implements IManagerController { + private visualManagerList: VisualManager[] = []; + + getManager(): VisualManager { + return this.visualManagerList[0]; + } + + getManagerByName(name?: string): VisualManager[] { + return this.visualManagerList.filter((m) => m.getName() === name); + } + + getManagerById(id?: string): VisualManager { + return find(this.visualManagerList, (m) => m.getId() === id) as VisualManager; + } + + getManagerList(): VisualManager[] { + return this.visualManagerList; + } + + connectManager(manager: VisualManager) { + connectGeneralManager.call(this, manager, this.visualManagerList); + return this; + } + + connectManagerList(managerList: VisualManager[]): this { + this.visualManagerList = connectGeneralManagerList.call(this, managerList, this.visualManagerList); + return this; + } + + notifyAllManagers(eventName: string | symbol, ...eventData: any[]): boolean { + return this.visualManagerList.map((m) => m.emit(eventName, eventData)).every((r) => r); + } + + /** + * Render function + * @override + * + * @memberof VisualRender + */ + render(): any { + return ''; + } +} diff --git a/packages/vision-polyfill/src/context.ts b/packages/vision-polyfill/src/context.ts new file mode 100644 index 000000000..f60258b74 --- /dev/null +++ b/packages/vision-polyfill/src/context.ts @@ -0,0 +1,104 @@ +import { assign } from 'lodash'; + +import { Component, ReactElement } from 'react'; +import VisualManager from './base/visualManager'; +import Prototype from './bundle/prototype'; + +// TODO: Env 本地引入后需要兼容方法 getDesignerLocale +// import Env from './env'; + +let contextInstance: VisualEngineContext; + +// prop is Prop object in Designer +export type SetterProvider = (prop: any, componentPrototype: Prototype) => Component | ReactElement; + +export default class VisualEngineContext { + private managerMap: { [name: string]: VisualManager } = {}; + private moduleMap: { [name: string]: any } = {}; + private pluginsMap: { [name: string]: any } = {}; + + constructor() { + if (!contextInstance) { + contextInstance = this; + } else { + return contextInstance; + } + } + + use(pluginName: string, plugin: any) { + this.pluginsMap[pluginName || 'unknown'] = plugin; + } + + getPlugin(name: string) { + if (!name) { + name = 'default'; + } + if (this.pluginsMap[name]) { + return this.pluginsMap[name]; + } else if (this.moduleMap[name]) { + return this.moduleMap[name]; + } + return this.getManager(name); + } + + registerManager(managerMap?: { [name: string]: VisualManager }): this; + registerManager(name: string, manager: VisualManager): this; + registerManager(name?: any, manager?: VisualManager): this { + if (name && typeof name === 'object') { + this.managerMap = assign(this.managerMap, name); + } else { + this.managerMap[name] = manager as VisualManager; + } + return this; + } + + registerModule(moduleMap: { [name: string]: any }): this; + registerModule(name: string, module: any): this; + registerModule(name?: any, module?: any): this { + if (typeof name === 'object') { + this.moduleMap = Object.assign({}, this.moduleMap, name); + } else { + this.moduleMap[name] = module; + } + return this; + } + + getManager(name: string): VisualManager { + return this.managerMap[name]; + } + + getModule(name: string): any { + return this.moduleMap[name]; + } + + // getDesignerLocale(): string { + // return Env.getLocale(); + // } + + /** + * Builtin APIs + */ + + /** + * support dynamic setter replacement + */ + registerDynamicSetterProvider(setterProvider: SetterProvider) { + if (!setterProvider) { + console.error('ERROR: ', 'please set provider function.'); + return; + } + this.use('ve.plugin.setterProvider', setterProvider); + } + + /** + * support add treePane on the setting pane + * @param treePane see @ali/ve-tree-pane + * @param treeCore see @ali/ve-tree-pane + */ + registerTreePane(TreePane: Component, TreeCore: Component) { + if (TreePane && TreeCore) { + this.registerModule('TreePane', TreePane); + this.registerModule('TreeCore', TreeCore); + } + } +} diff --git a/packages/vision-polyfill/src/exchange.ts b/packages/vision-polyfill/src/exchange.ts new file mode 100644 index 000000000..812bf38a6 --- /dev/null +++ b/packages/vision-polyfill/src/exchange.ts @@ -0,0 +1,51 @@ +import { Selection, DocumentModel, Node } from '@ali/lowcode-designer'; +import editor from './editor'; + +let currentSelection: Selection; +// let currentDocument: DocumentModel; + +// get selection async +editor.once('designer.ready', () => { + const getSelection = () => { + if (editor.designer.currentSelection) { + currentSelection = editor.designer.currentSelection; + // currentDocument = editor.designer.currentDocument; + + currentSelection.onSelectionChange((ids: string[]) => { + // console.log(ids); + // const nodes = ids.map((id: string) => currentDocument.getNode(id)); + // console.log(nodes); + }); + } else { + console.log('waiting ...'); + requestAnimationFrame(getSelection); + } + }; + getSelection(); +}); + +export default { + select: (node: Node) => { + if (!node) { + return currentSelection.clear(); + } + currentSelection.select(node.id); + }, + getSelected: () => { + const nodes = currentSelection.getNodes(); + return nodes; + }, + // 以下废弃 + // hover: (node: Node) => { + // hovering.hover(node); + // }, + // getDropping: () => { + // return null; + // }, + // onIntoView: (func: (node: any, insertion: Insertion) => any) => { + // currentSelection.onSelectionChange((ids) => { + // console.log(ids); + // }); + // return null; + // }, +} diff --git a/packages/vision-polyfill/src/vision.ts b/packages/vision-polyfill/src/vision.ts index a6a186835..2eeea6cb0 100644 --- a/packages/vision-polyfill/src/vision.ts +++ b/packages/vision-polyfill/src/vision.ts @@ -9,6 +9,9 @@ import Symbols from './symbols'; import { editor, 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'; function init(container?: Element) { if (!container) { @@ -31,6 +34,12 @@ const ui = { Popup, }; +const modules = { + VisualManager, +}; + +const context = new VisualEngineContext(); + export { /** * VE.Popup @@ -48,6 +57,8 @@ export { HOOKS, /* Symbol 管理类 */ Symbols, + Exchange, + context, /** * VE.init * @@ -56,4 +67,5 @@ export { init, ui, Panes, + modules, };