diff --git a/packages/editor-core/src/editor.ts b/packages/editor-core/src/editor.ts index 3983b78f4..7cf2f1344 100644 --- a/packages/editor-core/src/editor.ts +++ b/packages/editor-core/src/editor.ts @@ -31,6 +31,15 @@ export interface GetOptions { forceNew?: boolean; sourceCls?: ClassType; } +export type GetReturnType = T extends undefined + ? ClsType extends { + prototype: infer R; + } + ? R + : any + : T; + +const NOT_FOUND = Symbol.for('not_found'); export default class Editor extends EventEmitter { static getInstance = (config: EditorConfig, components: PluginClassSet, utils?: Utils): Editor => { @@ -55,7 +64,9 @@ export default class Editor extends EventEmitter { /** * Ioc Container */ - readonly context = new IocContext(); + private context = new IocContext({ + notFoundHandler: (type: KeyType) => NOT_FOUND, + }); pluginStatus?: PluginStatusSet; @@ -86,8 +97,7 @@ export default class Editor extends EventEmitter { registShortCuts(shortCuts, this); this.emit('editor.afterInit'); return true; - } - catch (err) { + } catch (err) { console.error(err); } } @@ -105,8 +115,12 @@ export default class Editor extends EventEmitter { } } - get(keyOrType: KeyOrType, opt?: GetOptions) { - return this.context.get(keyOrType, opt); + get(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType | undefined { + const x = this.context.get(keyOrType, opt); + if (x === NOT_FOUND) { + return undefined; + } + return x; } has(keyOrType: KeyType): boolean { @@ -114,11 +128,79 @@ export default class Editor extends EventEmitter { } set(key: KeyType, data: any): void { - this.context.register(data, key); + if (this.context.has(key)) { + this.context.replace(key, data, undefined, true); + } else { + this.context.register(data, key); + } + this.notifyGot(key); + } + + private waits = new Map< + KeyType, + Array<{ + once?: boolean; + resolve: (data: any) => void; + }> + >(); + private notifyGot(key: KeyType) { + let waits = this.waits.get(key); + if (!waits) { + return; + } + waits = waits.slice().reverse(); + let i = waits.length; + while (i--) { + waits[i].resolve(this.get(key)); + if (waits[i].once) { + waits.splice(i, 1); + } + } + if (waits.length > 0) { + this.waits.set(key, waits); + } else { + this.waits.delete(key); + } + } + + private setWait(key: KeyType, resolve: (data: any) => void, once?: boolean) { + const waits = this.waits.get(key); + if (waits) { + waits.push({ resolve, once }); + } else { + this.waits.set(key, [{ resolve, once }]); + } + } + + onceGot(keyOrType: KeyOrType): Promise> { + const x = this.context.get(keyOrType); + if (x !== NOT_FOUND) { + return Promise.resolve(x); + } + return new Promise((resolve) => { + this.setWait(keyOrType, resolve, true); + }); + } + + onGot( + keyOrType: KeyOrType, + fn: (data: GetReturnType) => void, + ): () => void { + const x = this.context.get(keyOrType); + if (x !== NOT_FOUND) { + fn(x); + return () => {}; + } else { + this.setWait(keyOrType, fn); + return () => { + + }; + } } register(data: any, key?: KeyType, options?: RegisterOptions): void { this.context.register(data, key, options); + this.notifyGot(key || data); } batchOn(events: string[], lisenter: (...args: any[]) => void): void { diff --git a/packages/setters/src/mixin-setter/index.tsx b/packages/setters/src/mixin-setter/index.tsx index f71c062d9..843c099af 100644 --- a/packages/setters/src/mixin-setter/index.tsx +++ b/packages/setters/src/mixin-setter/index.tsx @@ -144,8 +144,8 @@ export default class Mixin extends PureComponent { ); } } - let TargetNode = this.typeMap[this.state.type]['component'] || 'div'; - let targetProps = this.typeMap[this.state.type]['props'] || {}; + 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'); diff --git a/packages/vision-polyfill/public/index.html b/packages/vision-polyfill/public/index.html index a20e3e78a..4d4a5fcf7 100644 --- a/packages/vision-polyfill/public/index.html +++ b/packages/vision-polyfill/public/index.html @@ -14,11 +14,11 @@ - + - + diff --git a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts index 55221cd7a..8472a38ea 100644 --- a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts +++ b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts @@ -1,3 +1,108 @@ +/** + * 拒绝 + */ +export type REJECTED = 0 | false; +/** + * 限制性的 + */ +export type LIMITED = 2; +/** + * 允许 + */ +export type ALLOWED = true | 4; + +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-Pattern: 'tagName,tagName2,Field-*' + * test-Func: (target, my) => boolean + * Tester: RegExp | Pattern | Func | { exclude: Tester, include: Tester } + * + * model.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 + */ + accept?: AcceptFunc | AT_CHILD; + handleAccept?: (my: ElementNode, locationData: LocationData) => void; +} + +export interface ContentEditable { + propTarget: string; + selector?: string; +} + export enum DISPLAY_TYPE { NONE = 'none', PLAIN = 'plain', diff --git a/packages/vision-polyfill/src/editor.ts b/packages/vision-polyfill/src/editor.ts index e55bb924c..ff5fb3447 100644 --- a/packages/vision-polyfill/src/editor.ts +++ b/packages/vision-polyfill/src/editor.ts @@ -12,12 +12,10 @@ registerSetters(); export const editor = new Editor(); globalContext.register(editor, Editor); -export const skeleton = new Skeleton(); - -console.info(skeleton.editor); +export const skeleton = new Skeleton(editor); +editor.set(Skeleton, skeleton); export const designer = new Designer({ eventPipe: editor }); - editor.set(Designer, designer); skeleton.mainArea.add({ diff --git a/packages/vision-polyfill/src/skeleton/skeleton.ts b/packages/vision-polyfill/src/skeleton/skeleton.ts index 59357c6ec..6672a4f9d 100644 --- a/packages/vision-polyfill/src/skeleton/skeleton.ts +++ b/packages/vision-polyfill/src/skeleton/skeleton.ts @@ -1,5 +1,4 @@ import { Editor } from '@ali/lowcode-editor-core'; -import { inject } from '@ali/lowcode-globals'; import { DockConfig, PanelConfig, @@ -29,8 +28,6 @@ export enum SkeletonEvents { WIDGET_HIDE = 'skeleton.widget.hide', } -console.log(inject); - export class Skeleton { private panels = new Map(); private containers = new Map>(); @@ -44,9 +41,7 @@ export class Skeleton { readonly bottomArea: Area; readonly stages: Area; - @inject() public editor: Editor; - - constructor() { + constructor(readonly editor: Editor) { this.leftArea = new Area( this, 'leftArea',