diff --git a/docs/docs/api/model/window.md b/docs/docs/api/model/window.md index 177d3438a..6a5413883 100644 --- a/docs/docs/api/model/window.md +++ b/docs/docs/api/model/window.md @@ -11,15 +11,41 @@ sidebar_position: 12 低代码设计器窗口模型 +## 变量 + +### id + +窗口唯一 id + +### title + +窗口标题 + +### resourceName + +窗口资源名字 + ## 方法签名 -### importSchema(schema: IPublicTypeNodeSchema) -当前窗口导入 schema +### importSchema +当前窗口导入 schema, 会调用当前窗口对应资源的 import 钩子 + +```typescript +function importSchema(schema: IPublicTypeNodeSchema): void +``` 相关类型:[IPublicTypeNodeSchema](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/node-schema.ts) -### changeViewType(viewName: string) +### changeViewType 修改当前窗口视图类型 -### async save() -调用当前窗口视图保存钩子 +```typescript +function changeViewType(viewName: string): void +``` + +### save +当前窗口的保存方法,会调用当前窗口对应资源的 save 钩子 + +```typescript +function save(): Promise(void) +``` diff --git a/docs/docs/api/workspace.md b/docs/docs/api/workspace.md index e710bceec..5a9ad2a44 100644 --- a/docs/docs/api/workspace.md +++ b/docs/docs/api/workspace.md @@ -21,6 +21,30 @@ sidebar_position: 12 当前设计器窗口模型 +```typescript +get window(): IPublicModelWindow +``` + +关联模型 [IPublicModelWindow](./model/window) + +### plugins + +应用级别的插件注册 + +```typescript +get plugins(): IPublicApiPlugins +``` + +关联模型 [IPublicApiPlugins](./plugins) + +### windows + +当前设计器的编辑窗口 + +```typescript +get window(): IPublicModelWindow[] +``` + 关联模型 [IPublicModelWindow](./model/window) ## 方法签名 @@ -34,3 +58,19 @@ registerResourceType(resourceName: string, resourceType: 'editor', options: IPub ``` 相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts) + +### onChangeWindows + +窗口新增/删除的事件 + +```typescript +function onChangeWindows(fn: () => void): void; +``` + +### onChangeActiveWindow + +active 窗口变更事件 + +```typescript +function onChangeActiveWindow(fn: () => void): void; +``` diff --git a/packages/designer/src/component-actions.ts b/packages/designer/src/component-actions.ts new file mode 100644 index 000000000..793bbdc20 --- /dev/null +++ b/packages/designer/src/component-actions.ts @@ -0,0 +1,155 @@ +import { IPublicTypeComponentAction, IPublicTypeMetadataTransducer } from '@alilc/lowcode-types'; +import { engineConfig } from '@alilc/lowcode-editor-core'; +import { intlNode } from './locale'; +import { + IconLock, + IconUnlock, + IconRemove, + IconClone, + IconHidden, +} from './icons'; +import { Node } from './document'; +import { componentDefaults, legacyIssues } from './transducers'; + +export class ComponentActions { + actions: IPublicTypeComponentAction[] = [ + { + name: 'remove', + content: { + icon: IconRemove, + title: intlNode('remove'), + /* istanbul ignore next */ + action(node: Node) { + node.remove(); + }, + }, + important: true, + }, + { + name: 'hide', + content: { + icon: IconHidden, + title: intlNode('hide'), + /* istanbul ignore next */ + action(node: Node) { + node.setVisible(false); + }, + }, + /* istanbul ignore next */ + condition: (node: Node) => { + return node.componentMeta.isModal; + }, + important: true, + }, + { + name: 'copy', + content: { + icon: IconClone, + title: intlNode('copy'), + /* istanbul ignore next */ + action(node: Node) { + // node.remove(); + const { document: doc, parent, index } = node; + if (parent) { + const newNode = doc.insertNode(parent, node, index + 1, true); + newNode.select(); + const { isRGL, rglNode } = node.getRGL(); + if (isRGL) { + // 复制 layout 信息 + let layout = rglNode.getPropValue('layout') || []; + let curLayout = layout.filter((item) => item.i === node.getPropValue('fieldId')); + if (curLayout && curLayout[0]) { + layout.push({ + ...curLayout[0], + i: newNode.getPropValue('fieldId'), + }); + rglNode.setPropValue('layout', layout); + // 如果是磁贴块复制,则需要滚动到影响位置 + setTimeout(() => newNode.document.simulator?.scrollToNode(newNode), 10); + } + } + } + }, + }, + important: true, + }, + { + name: 'lock', + content: { + icon: IconLock, // 锁定 icon + title: intlNode('lock'), + /* istanbul ignore next */ + action(node: Node) { + node.lock(); + }, + }, + /* istanbul ignore next */ + condition: (node: Node) => { + return engineConfig.get('enableCanvasLock', false) && node.isContainer() && !node.isLocked; + }, + important: true, + }, + { + name: 'unlock', + content: { + icon: IconUnlock, // 解锁 icon + title: intlNode('unlock'), + /* istanbul ignore next */ + action(node: Node) { + node.lock(false); + }, + }, + /* istanbul ignore next */ + condition: (node: Node) => { + return engineConfig.get('enableCanvasLock', false) && node.isContainer() && node.isLocked; + }, + important: true, + }, + ]; + + constructor() { + this.registerMetadataTransducer(legacyIssues, 2, 'legacy-issues'); // should use a high level priority, eg: 2 + this.registerMetadataTransducer(componentDefaults, 100, 'component-defaults'); + } + + removeBuiltinComponentAction(name: string) { + const i = this.actions.findIndex((action) => action.name === name); + if (i > -1) { + this.actions.splice(i, 1); + } + } + addBuiltinComponentAction(action: IPublicTypeComponentAction) { + this.actions.push(action); + } + + modifyBuiltinComponentAction( + actionName: string, + handle: (action: IPublicTypeComponentAction) => void, + ) { + const builtinAction = this.actions.find((action) => action.name === actionName); + if (builtinAction) { + handle(builtinAction); + } + } + + private metadataTransducers: IPublicTypeMetadataTransducer[] = []; + + registerMetadataTransducer( + transducer: IPublicTypeMetadataTransducer, + level = 100, + id?: string, + ) { + transducer.level = level; + transducer.id = id; + const i = this.metadataTransducers.findIndex((item) => item.level != null && item.level > level); + if (i < 0) { + this.metadataTransducers.push(transducer); + } else { + this.metadataTransducers.splice(i, 0, transducer); + } + } + + getRegisteredMetadataTransducers(): IPublicTypeMetadataTransducer[] { + return this.metadataTransducers; + } +} \ No newline at end of file diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 45f431b6d..70d8630b5 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -4,7 +4,6 @@ import { IPublicTypeNpmInfo, IPublicTypeNodeData, IPublicTypeNodeSchema, - IPublicTypeComponentAction, IPublicTypeTitleContent, IPublicTypeTransformedComponentMetadata, IPublicTypeNestingFilter, @@ -15,20 +14,13 @@ import { IPublicModelComponentMeta, } from '@alilc/lowcode-types'; import { deprecate, isRegExp, isTitleConfig } from '@alilc/lowcode-utils'; -import { computed, engineConfig, createModuleEventBus, IEventBus } from '@alilc/lowcode-editor-core'; -import { componentDefaults, legacyIssues } from './transducers'; +import { computed, createModuleEventBus, IEventBus } from '@alilc/lowcode-editor-core'; import { isNode, Node, INode } from './document'; import { Designer } from './designer'; -import { intlNode } from './locale'; import { - IconLock, - IconUnlock, IconContainer, IconPage, IconComponent, - IconRemove, - IconClone, - IconHidden, } from './icons'; export function ensureAList(list?: string | string[]): string[] | null { @@ -272,7 +264,7 @@ export class ComponentMeta implements IComponentMeta { } private transformMetadata(metadta: IPublicTypeComponentMetadata): IPublicTypeTransformedComponentMetadata { - const result = getRegisteredMetadataTransducers().reduce((prevMetadata, current) => { + const result = this.designer.componentActions.getRegisteredMetadataTransducers().reduce((prevMetadata, current) => { return current(prevMetadata); }, preprocessMetadata(metadta)); @@ -300,7 +292,7 @@ export class ComponentMeta implements IComponentMeta { const disabled = ensureAList(disableBehaviors) || (this.isRootComponent(false) ? ['copy', 'remove', 'lock', 'unlock'] : null); - actions = builtinComponentActions.concat( + actions = this.designer.componentActions.actions.concat( this.designer.getGlobalComponentActions() || [], actions || [], ); @@ -382,142 +374,3 @@ function preprocessMetadata(metadata: IPublicTypeComponentMetadata): IPublicType }; } - -const metadataTransducers: IPublicTypeMetadataTransducer[] = []; - -export function registerMetadataTransducer( - transducer: IPublicTypeMetadataTransducer, - level = 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(): IPublicTypeMetadataTransducer[] { - return metadataTransducers; -} - -const builtinComponentActions: IPublicTypeComponentAction[] = [ - { - name: 'remove', - content: { - icon: IconRemove, - title: intlNode('remove'), - /* istanbul ignore next */ - action(node: Node) { - node.remove(); - }, - }, - important: true, - }, - { - name: 'hide', - content: { - icon: IconHidden, - title: intlNode('hide'), - /* istanbul ignore next */ - action(node: Node) { - node.setVisible(false); - }, - }, - /* istanbul ignore next */ - condition: (node: Node) => { - return node.componentMeta.isModal; - }, - important: true, - }, - { - name: 'copy', - content: { - icon: IconClone, - title: intlNode('copy'), - /* istanbul ignore next */ - action(node: Node) { - // node.remove(); - const { document: doc, parent, index } = node; - if (parent) { - const newNode = doc.insertNode(parent, node, index + 1, true); - newNode.select(); - const { isRGL, rglNode } = node.getRGL(); - if (isRGL) { - // 复制 layout 信息 - let layout = rglNode.getPropValue('layout') || []; - let curLayout = layout.filter((item) => item.i === node.getPropValue('fieldId')); - if (curLayout && curLayout[0]) { - layout.push({ - ...curLayout[0], - i: newNode.getPropValue('fieldId'), - }); - rglNode.setPropValue('layout', layout); - // 如果是磁贴块复制,则需要滚动到影响位置 - setTimeout(() => newNode.document.simulator?.scrollToNode(newNode), 10); - } - } - } - }, - }, - important: true, - }, - { - name: 'lock', - content: { - icon: IconLock, // 锁定 icon - title: intlNode('lock'), - /* istanbul ignore next */ - action(node: Node) { - node.lock(); - }, - }, - /* istanbul ignore next */ - condition: (node: Node) => { - return engineConfig.get('enableCanvasLock', false) && node.isContainer() && !node.isLocked; - }, - important: true, - }, - { - name: 'unlock', - content: { - icon: IconUnlock, // 解锁 icon - title: intlNode('unlock'), - /* istanbul ignore next */ - action(node: Node) { - node.lock(false); - }, - }, - /* istanbul ignore next */ - condition: (node: Node) => { - return engineConfig.get('enableCanvasLock', false) && node.isContainer() && node.isLocked; - }, - 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: IPublicTypeComponentAction) { - builtinComponentActions.push(action); -} - -export function modifyBuiltinComponentAction( - actionName: string, - handle: (action: IPublicTypeComponentAction) => void, -) { - const builtinAction = builtinComponentActions.find((action) => action.name === actionName); - if (builtinAction) { - handle(builtinAction); - } -} - -registerMetadataTransducer(legacyIssues, 2, 'legacy-issues'); // should use a high level priority, eg: 2 -registerMetadataTransducer(componentDefaults, 100, 'component-defaults'); diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index d16d6c267..118d068cf 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -32,6 +32,7 @@ import { OffsetObserver, createOffsetObserver } from './offset-observer'; import { focusing } from './focusing'; import { SettingTopEntry } from './setting'; import { BemToolsManager } from '../builtin-simulator/bem-tools/manager'; +import { ComponentActions } from '../component-actions'; const logger = new Logger({ level: 'warn', bizName: 'designer' }); @@ -60,6 +61,8 @@ export interface DesignerProps { export class Designer implements IDesigner { readonly dragon = new Dragon(this); + readonly componentActions = new ComponentActions(); + readonly activeTracker = new ActiveTracker(); readonly detecting = new Detecting(); diff --git a/packages/designer/src/designer/setting/setting-field.ts b/packages/designer/src/designer/setting/setting-field.ts index a7d0ea9f9..ab5b8eadb 100644 --- a/packages/designer/src/designer/setting/setting-field.ts +++ b/packages/designer/src/designer/setting/setting-field.ts @@ -2,7 +2,7 @@ import { IPublicTypeTitleContent, IPublicTypeSetterType, IPublicTypeDynamicSette import { Transducer } from './utils'; import { SettingPropEntry } from './setting-prop-entry'; import { SettingEntry } from './setting-entry'; -import { computed, obx, makeObservable, action } from '@alilc/lowcode-editor-core'; +import { computed, obx, makeObservable, action, untracked } from '@alilc/lowcode-editor-core'; import { cloneDeep, isCustomView, isDynamicSetter } from '@alilc/lowcode-utils'; function getSettingFieldCollectorKey(parent: SettingEntry, config: IPublicTypeFieldConfig) { @@ -43,8 +43,10 @@ export class SettingField extends SettingPropEntry implements SettingEntry { return null; } if (isDynamicSetter(this._setter)) { - const shellThis = this.internalToShellPropEntry(); - return this._setter.call(shellThis, shellThis); + return untracked(() => { + const shellThis = this.internalToShellPropEntry(); + return this._setter.call(shellThis, shellThis); + }); } return this._setter; } diff --git a/packages/designer/src/project/project-view.tsx b/packages/designer/src/project/project-view.tsx index f6feaa0af..a16d4451a 100644 --- a/packages/designer/src/project/project-view.tsx +++ b/packages/designer/src/project/project-view.tsx @@ -4,7 +4,7 @@ import { Designer } from '../designer'; import { BuiltinSimulatorHostView } from '../builtin-simulator'; import './project.less'; -class BuiltinLoading extends Component { +export class BuiltinLoading extends Component { render() { return (
diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index 08b96d7c4..f140b30d4 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -30,6 +30,8 @@ export class Project implements IProject { private _simulator?: ISimulatorHost; + private isRendererReady: boolean = false; + /** * 模拟器 */ @@ -318,6 +320,7 @@ export class Project implements IProject { } setRendererReady(renderer: any) { + this.isRendererReady = true; this.emitter.emit('lowcode_engine_renderer_ready', renderer); } @@ -328,7 +331,10 @@ export class Project implements IProject { }; } - onRendererReady(fn: (args: any) => void): () => void { + onRendererReady(fn: () => void): () => void { + if (this.isRendererReady) { + fn(); + } this.emitter.on('lowcode_engine_renderer_ready', fn); return () => { this.emitter.removeListener('lowcode_engine_renderer_ready', fn); diff --git a/packages/designer/tests/main/meta/component-meta.test.ts b/packages/designer/tests/main/meta/component-meta.test.ts index a1a113d93..d943f85af 100644 --- a/packages/designer/tests/main/meta/component-meta.test.ts +++ b/packages/designer/tests/main/meta/component-meta.test.ts @@ -1,5 +1,4 @@ import '../../fixtures/window'; -import { Node } from '../../../src/document/node/node'; import { Designer } from '../../../src/designer/designer'; import divMeta from '../../fixtures/component-metadata/div'; import div2Meta from '../../fixtures/component-metadata/div2'; @@ -19,22 +18,18 @@ import page2Meta from '../../fixtures/component-metadata/page2'; import { ComponentMeta, isComponentMeta, - removeBuiltinComponentAction, - addBuiltinComponentAction, - modifyBuiltinComponentAction, ensureAList, buildFilter, - registerMetadataTransducer, - getRegisteredMetadataTransducers, } from '../../../src/component-meta'; -import { componentDefaults } from '../../../src/transducers'; -const mockCreateSettingEntry = jest.fn(); + jest.mock('../../../src/designer/designer', () => { return { Designer: jest.fn().mockImplementation(() => { + const { ComponentActions } = require('../../../src/component-actions'); return { getGlobalComponentActions: () => [], + componentActions: new ComponentActions(), }; }), }; @@ -126,12 +121,12 @@ describe('组件元数据处理', () => { expect(meta.availableActions[1].name).toBe('hide'); expect(meta.availableActions[2].name).toBe('copy'); - removeBuiltinComponentAction('remove'); + designer.componentActions.removeBuiltinComponentAction('remove'); expect(meta.availableActions).toHaveLength(4); expect(meta.availableActions[0].name).toBe('hide'); expect(meta.availableActions[1].name).toBe('copy'); - addBuiltinComponentAction({ + designer.componentActions.addBuiltinComponentAction({ name: 'new', content: { action() {}, @@ -227,17 +222,17 @@ describe('帮助函数', () => { }); it('registerMetadataTransducer', () => { - expect(getRegisteredMetadataTransducers()).toHaveLength(2); + expect(designer.componentActions.getRegisteredMetadataTransducers()).toHaveLength(2); // 插入到 legacy-issues 和 component-defaults 的中间 - registerMetadataTransducer((metadata) => metadata, 3, 'noop'); - expect(getRegisteredMetadataTransducers()).toHaveLength(3); + designer.componentActions.registerMetadataTransducer((metadata) => metadata, 3, 'noop'); + expect(designer.componentActions.getRegisteredMetadataTransducers()).toHaveLength(3); - registerMetadataTransducer((metadata) => metadata); - expect(getRegisteredMetadataTransducers()).toHaveLength(4); + designer.componentActions.registerMetadataTransducer((metadata) => metadata); + expect(designer.componentActions.getRegisteredMetadataTransducers()).toHaveLength(4); }); it('modifyBuiltinComponentAction', () => { - modifyBuiltinComponentAction('copy', (action) => { + designer.componentActions.modifyBuiltinComponentAction('copy', (action) => { expect(action.name).toBe('copy'); }); }); diff --git a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx index 50b18d50f..c12d966be 100644 --- a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx +++ b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx @@ -69,7 +69,7 @@ export class SettingsPrimaryPane extends Component<{ engineEditor: Editor; confi } const workspace = globalContext.get('workspace'); - const editor = workspace.isActive ? workspace.window.editor : globalContext.get('editor'); + const editor = this.props.engineEditor; const designer = editor.get('designer'); const current = designer?.currentSelection?.getNodes()?.[0]; let node: Node | null = settings.first; diff --git a/packages/editor-skeleton/src/layouts/workbench.less b/packages/editor-skeleton/src/layouts/workbench.less index af555c65e..4e96badcf 100644 --- a/packages/editor-skeleton/src/layouts/workbench.less +++ b/packages/editor-skeleton/src/layouts/workbench.less @@ -138,6 +138,16 @@ body { display: flex; flex-direction: column; background-color: #edeff3; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: -1; + + &.active { + z-index: 999; + } .lc-workbench { diff --git a/packages/editor-skeleton/src/register-defaults.ts b/packages/editor-skeleton/src/register-defaults.ts index bb35b277d..573631f78 100644 --- a/packages/editor-skeleton/src/register-defaults.ts +++ b/packages/editor-skeleton/src/register-defaults.ts @@ -1,15 +1,23 @@ -import { registerMetadataTransducer } from '@alilc/lowcode-designer'; import parseJSFunc from './transducers/parse-func'; import parseProps from './transducers/parse-props'; import addonCombine from './transducers/addon-combine'; +import { IPublicModelPluginContext } from '@alilc/lowcode-types'; -export const registerDefaults = () => { - // parseFunc - registerMetadataTransducer(parseJSFunc, 1, 'parse-func'); +export const registerDefaults = (ctx: IPublicModelPluginContext) => { + const { material } = ctx; + return { + init() { + // parseFunc + material.registerMetadataTransducer(parseJSFunc, 1, 'parse-func'); - // parseProps - registerMetadataTransducer(parseProps, 5, 'parse-props'); + // parseProps + material.registerMetadataTransducer(parseProps, 5, 'parse-props'); - // addon/platform custom - registerMetadataTransducer(addonCombine, 10, 'combine-props'); + // addon/platform custom + material.registerMetadataTransducer(addonCombine, 10, 'combine-props'); + }, + }; }; + + +registerDefaults.pluginName = '___register_defaults___'; diff --git a/packages/editor-skeleton/src/skeleton.ts b/packages/editor-skeleton/src/skeleton.ts index 649c45271..90fba2e1b 100644 --- a/packages/editor-skeleton/src/skeleton.ts +++ b/packages/editor-skeleton/src/skeleton.ts @@ -50,6 +50,8 @@ export class Skeleton { readonly topArea: Area; + readonly subTopArea: Area; + readonly toolbar: Area; readonly leftFixedArea: Area; @@ -88,6 +90,17 @@ export class Skeleton { }, false, ); + this.subTopArea = new Area( + this, + 'subTopArea', + (config) => { + if (isWidget(config)) { + return config; + } + return this.createWidget(config); + }, + false, + ); this.toolbar = new Area( this, 'toolbar', @@ -389,6 +402,8 @@ export class Skeleton { case 'topArea': case 'top': return this.topArea.add(parsedConfig as PanelDockConfig); + case 'subTopArea': + return this.subTopArea.add(parsedConfig as PanelDockConfig); case 'toolbar': return this.toolbar.add(parsedConfig as PanelDockConfig); case 'mainArea': diff --git a/packages/engine/src/engine-core.ts b/packages/engine/src/engine-core.ts index 99286c3c3..a580380a1 100644 --- a/packages/engine/src/engine-core.ts +++ b/packages/engine/src/engine-core.ts @@ -59,8 +59,6 @@ export * from './modules/skeleton-types'; export * from './modules/designer-types'; export * from './modules/lowcode-types'; -registerDefaults(); - async function registryInnerPlugin(designer: Designer, editor: Editor, plugins: Plugins) { // 注册一批内置插件 await plugins.register(OutlinePlugin, {}, { autoInit: true }); @@ -68,6 +66,7 @@ async function registryInnerPlugin(designer: Designer, editor: Editor, plugins: await plugins.register(setterRegistry, {}, { autoInit: true }); await plugins.register(defaultPanelRegistry(editor)); await plugins.register(builtinHotkey); + await plugins.register(registerDefaults); } const innerWorkspace = new InnerWorkspace(registryInnerPlugin, shellModelFactory); @@ -82,6 +81,7 @@ editor.set('skeleton' as any, innerSkeleton); const designer = new Designer({ editor, shellModelFactory }); editor.set('designer' as any, designer); + const { project: innerProject } = designer; const innerHotkey = new InnerHotkey(); @@ -195,6 +195,7 @@ export async function init( engineContainer, ); innerWorkspace.setActive(true); + await innerWorkspace.plugins.init(pluginPreference); return; } diff --git a/packages/shell/src/api/material.ts b/packages/shell/src/api/material.ts index e824290a5..08deefdcc 100644 --- a/packages/shell/src/api/material.ts +++ b/packages/shell/src/api/material.ts @@ -1,11 +1,6 @@ import { Editor, globalContext } from '@alilc/lowcode-editor-core'; import { Designer, - registerMetadataTransducer, - getRegisteredMetadataTransducers, - addBuiltinComponentAction, - removeBuiltinComponentAction, - modifyBuiltinComponentAction, isComponentMeta, } from '@alilc/lowcode-designer'; import { IPublicTypeAssetsJson } from '@alilc/lowcode-utils'; @@ -85,20 +80,20 @@ export class Material implements IPublicApiMaterial { * @param level * @param id */ - registerMetadataTransducer( + registerMetadataTransducer = ( transducer: IPublicTypeMetadataTransducer, level?: number, id?: string | undefined, - ) { - registerMetadataTransducer(transducer, level, id); - } + ) => { + this[designerSymbol].componentActions.registerMetadataTransducer(transducer, level, id); + }; /** * 获取所有物料元数据管道函数 * @returns */ getRegisteredMetadataTransducers() { - return getRegisteredMetadataTransducers(); + return this[designerSymbol].componentActions.getRegisteredMetadataTransducers(); } /** @@ -147,7 +142,7 @@ export class Material implements IPublicApiMaterial { * @param action */ addBuiltinComponentAction(action: IPublicTypeComponentAction) { - addBuiltinComponentAction(action); + this[designerSymbol].componentActions.addBuiltinComponentAction(action); } /** @@ -155,7 +150,7 @@ export class Material implements IPublicApiMaterial { * @param name */ removeBuiltinComponentAction(name: string) { - removeBuiltinComponentAction(name); + this[designerSymbol].componentActions.removeBuiltinComponentAction(name); } /** @@ -164,7 +159,7 @@ export class Material implements IPublicApiMaterial { * @param handle */ modifyBuiltinComponentAction(actionName: string, handle: (action: IPublicTypeComponentAction) => void) { - modifyBuiltinComponentAction(actionName, handle); + this[designerSymbol].componentActions.modifyBuiltinComponentAction(actionName, handle); } /** diff --git a/packages/shell/src/api/project.ts b/packages/shell/src/api/project.ts index 9cae7d72e..e97a18334 100644 --- a/packages/shell/src/api/project.ts +++ b/packages/shell/src/api/project.ts @@ -18,14 +18,12 @@ import { import { DocumentModel } from '../model/document-model'; import { SimulatorHost } from './simulator-host'; -import { editorSymbol, projectSymbol, simulatorHostSymbol, simulatorRendererSymbol, documentSymbol } from '../symbols'; +import { editorSymbol, projectSymbol, simulatorHostSymbol, documentSymbol } from '../symbols'; const innerProjectSymbol = Symbol('innerProject'); export class Project implements IPublicApiProject { - private readonly [editorSymbol]: IPublicModelEditor; private readonly [innerProjectSymbol]: InnerProject; private [simulatorHostSymbol]: BuiltinSimulatorHost; - private [simulatorRendererSymbol]: any; get [projectSymbol](): InnerProject { if (this.workspaceMode) { return this[innerProjectSymbol]; @@ -38,9 +36,12 @@ export class Project implements IPublicApiProject { return this[innerProjectSymbol]; } + get [editorSymbol](): IPublicModelEditor { + return this[projectSymbol]?.designer.editor; + } + constructor(project: InnerProject, public workspaceMode: boolean = false) { this[innerProjectSymbol] = project; - this[editorSymbol] = project?.designer.editor; } static create(project: InnerProject) { @@ -201,13 +202,9 @@ export class Project implements IPublicApiProject { * 当前 project 的渲染器 ready 事件 */ onSimulatorRendererReady(fn: () => void): IPublicTypeDisposable { - const offFn = this[projectSymbol].onRendererReady((renderer: any) => { - this[simulatorRendererSymbol] = renderer; + const offFn = this[projectSymbol].onRendererReady(() => { fn(); }); - if (this[simulatorRendererSymbol]) { - fn(); - } return offFn; } diff --git a/packages/shell/src/api/workspace.ts b/packages/shell/src/api/workspace.ts index 28c143030..3440871c8 100644 --- a/packages/shell/src/api/workspace.ts +++ b/packages/shell/src/api/workspace.ts @@ -1,5 +1,6 @@ import { IPublicApiWorkspace } from '@alilc/lowcode-types'; import { Workspace as InnerWorkSpace } from '@alilc/lowcode-workspace'; +import { Plugins } from '@alilc/lowcode-shell'; import { Window } from '../model/window'; import { workspaceSymbol } from '../symbols'; @@ -21,4 +22,36 @@ export class Workspace implements IPublicApiWorkspace { registerResourceType(resourceName: string, resourceType: 'editor', options: any): void { this[workspaceSymbol].registerResourceType(resourceName, resourceType, options); } + + openEditorWindow(resourceName: string, title: string, viewType?: string) { + this[workspaceSymbol].openEditorWindow(resourceName, title, viewType); + } + + openEditorWindowById(id: string) { + this[workspaceSymbol].openEditorWindowById(id); + } + + removeEditorWindow(resourceName: string, title: string) { + this[workspaceSymbol].removeEditorWindow(resourceName, title); + } + + removeEditorWindowById(id: string) { + this[workspaceSymbol].removeEditorWindowById(id); + } + + get plugins() { + return new Plugins(this[workspaceSymbol].plugins, true); + } + + get windows() { + return this[workspaceSymbol].windows.map(d => new Window(d)); + } + + onChangeWindows(fn: () => void) { + return this[workspaceSymbol].onChangeWindows(fn); + } + + onChangeActiveWindow(fn: () => void) { + return this[workspaceSymbol].onChangeActiveWindow(fn); + } } diff --git a/packages/shell/src/model/window.ts b/packages/shell/src/model/window.ts index b471fc864..fee783f53 100644 --- a/packages/shell/src/model/window.ts +++ b/packages/shell/src/model/window.ts @@ -5,6 +5,22 @@ import { EditorWindow } from '@alilc/lowcode-workspace'; export class Window implements IPublicModelWindow { private readonly [windowSymbol]: EditorWindow; + get id() { + return this[windowSymbol].id; + } + + get title() { + return this[windowSymbol].title; + } + + get icon() { + return this[windowSymbol].icon; + } + + get resourceName() { + return this[windowSymbol].resourceName; + } + constructor(editorWindow: EditorWindow) { this[windowSymbol] = editorWindow; } diff --git a/packages/shell/src/symbols.ts b/packages/shell/src/symbols.ts index 6e0924893..b87e1f24b 100644 --- a/packages/shell/src/symbols.ts +++ b/packages/shell/src/symbols.ts @@ -21,7 +21,6 @@ export const dragonSymbol = Symbol('dragon'); export const componentMetaSymbol = Symbol('componentMeta'); export const dropLocationSymbol = Symbol('dropLocation'); export const simulatorHostSymbol = Symbol('simulatorHost'); -export const simulatorRendererSymbol = Symbol('simulatorRenderer'); export const dragObjectSymbol = Symbol('dragObject'); export const locateEventSymbol = Symbol('locateEvent'); export const designerCabinSymbol = Symbol('designerCabin'); diff --git a/packages/types/src/shell/api/workspace.ts b/packages/types/src/shell/api/workspace.ts index 52db05825..b9cd29afa 100644 --- a/packages/types/src/shell/api/workspace.ts +++ b/packages/types/src/shell/api/workspace.ts @@ -1,5 +1,6 @@ import { IPublicModelWindow } from '../model'; import { IPublicResourceOptions } from '../type'; +import { IPublicApiPlugins } from '@alilc/lowcode-types'; export interface IPublicApiWorkspace { /** 是否启用 workspace 模式 */ @@ -10,4 +11,21 @@ export interface IPublicApiWorkspace { /** 注册资源 */ registerResourceType(resourceName: string, resourceType: 'editor', options: IPublicResourceOptions): void; + + /** 打开视图窗口 */ + openEditorWindow(resourceName: string, title: string, viewType?: string): void; + + /** 移除窗口 */ + removeEditorWindow(resourceName: string, title: string): void; + + plugins: IPublicApiPlugins; + + /** 当前设计器的编辑窗口 */ + windows: IPublicModelWindow[]; + + /** 窗口新增/删除的事件 */ + onChangeWindows: (fn: () => void) => void; + + /** active 窗口变更事件 */ + onChangeActiveWindow: (fn: () => void) => void; } \ No newline at end of file diff --git a/packages/types/src/shell/model/window.ts b/packages/types/src/shell/model/window.ts index f0faedbcd..1502f2a3c 100644 --- a/packages/types/src/shell/model/window.ts +++ b/packages/types/src/shell/model/window.ts @@ -9,4 +9,13 @@ export interface IPublicModelWindow { /** 调用当前窗口视图保存钩子 */ save(): Promise; + + /** 窗口 id */ + id: string; + + /** 窗口标题 */ + title?: string; + + /** 窗口资源名字 */ + resourceName?: string; } \ No newline at end of file diff --git a/packages/types/src/shell/type/resource-options.ts b/packages/types/src/shell/type/resource-options.ts index 94547bb57..e82db194c 100644 --- a/packages/types/src/shell/type/resource-options.ts +++ b/packages/types/src/shell/type/resource-options.ts @@ -1,7 +1,7 @@ export interface IPublicViewFunctions { - /** 视图初始化 */ + /** 视图初始化钩子 */ init?: () => Promise; - /** 资源保存时调用视图的钩子 */ + /** 资源保存时,会调用视图的钩子 */ save?: () => Promise; } @@ -20,6 +20,9 @@ export interface IPublicResourceOptions { /** 资源描述 */ description?: string; + /** 资源 icon 标识 */ + icon?: React.ReactElement; + /** 默认视图类型 */ defaultViewType: string; @@ -35,4 +38,7 @@ export interface IPublicResourceOptions { import?: (schema: any) => Promise<{ [viewName: string]: any; }>; + + /** 默认标题 */ + defaultTitle?: string; } \ No newline at end of file diff --git a/packages/types/src/shell/type/widget-config-area.ts b/packages/types/src/shell/type/widget-config-area.ts index 7731ab3b0..41e71baa2 100644 --- a/packages/types/src/shell/type/widget-config-area.ts +++ b/packages/types/src/shell/type/widget-config-area.ts @@ -2,7 +2,7 @@ * 所有可能的停靠位置 */ export type IPublicTypeWidgetConfigArea = 'leftArea' | 'left' | 'rightArea' | - 'right' | 'topArea' | 'top' | + 'right' | 'topArea' | 'subTopArea' | 'top' | 'toolbar' | 'mainArea' | 'main' | 'center' | 'centerArea' | 'bottomArea' | 'bottom' | 'leftFixedArea' | diff --git a/packages/workspace/src/base-context.ts b/packages/workspace/src/base-context.ts index 047e5179e..8ab26e455 100644 --- a/packages/workspace/src/base-context.ts +++ b/packages/workspace/src/base-context.ts @@ -33,7 +33,7 @@ import { IPublicTypePluginMeta, } from '@alilc/lowcode-types'; import { getLogger } from '@alilc/lowcode-utils'; -import { Workspace as InnerWorkspace } from './index'; +import { Workspace as InnerWorkspace } from './workspace'; import { EditorWindow } from './editor-window/context'; export class BasicContext { @@ -51,7 +51,7 @@ export class BasicContext { designer: Designer; registerInnerPlugins: () => Promise; innerSetters: InnerSetters; - innerSkeleton: any; + innerSkeleton: InnerSkeleton; innerHotkey: InnerHotkey; innerPlugins: LowCodePluginManager; canvas: Canvas; @@ -65,7 +65,7 @@ export class BasicContext { const designer: Designer = new Designer({ editor, viewName, - shellModelFactory: innerWorkspace.shellModelFactory, + shellModelFactory: innerWorkspace?.shellModelFactory, }); editor.set('designer' as any, designer); @@ -132,7 +132,7 @@ export class BasicContext { // 注册一批内置插件 this.registerInnerPlugins = async function registerPlugins() { - await innerWorkspace.registryInnerPlugin(designer, editor, plugins); + await innerWorkspace?.registryInnerPlugin(designer, editor, plugins); }; } } \ No newline at end of file diff --git a/packages/workspace/src/editor-view/context.ts b/packages/workspace/src/editor-view/context.ts index 913228674..a845d36c1 100644 --- a/packages/workspace/src/editor-view/context.ts +++ b/packages/workspace/src/editor-view/context.ts @@ -1,7 +1,7 @@ import { makeObservable, obx } from '@alilc/lowcode-editor-core'; import { IPublicEditorView, IPublicViewFunctions } from '@alilc/lowcode-types'; import { flow } from 'mobx'; -import { Workspace as InnerWorkspace } from '../'; +import { Workspace as InnerWorkspace } from '../workspace'; import { BasicContext } from '../base-context'; import { EditorWindow } from '../editor-window/context'; import { getWebviewPlugin } from '../inner-plugins/webview'; diff --git a/packages/workspace/src/editor-view/view.tsx b/packages/workspace/src/editor-view/view.tsx index ca3a08fb2..77f0dffeb 100644 --- a/packages/workspace/src/editor-view/view.tsx +++ b/packages/workspace/src/editor-view/view.tsx @@ -1,14 +1,15 @@ -import { observer } from '@alilc/lowcode-editor-core'; +import { BuiltinLoading } from '@alilc/lowcode-designer'; +import { engineConfig, observer } from '@alilc/lowcode-editor-core'; import { Workbench, } from '@alilc/lowcode-editor-skeleton'; -import { Component } from 'react'; +import { PureComponent } from 'react'; import { Context } from './context'; export * from '../base-context'; @observer -export class EditorView extends Component<{ +export class EditorView extends PureComponent<{ editorView: Context; active: boolean; }, any> { @@ -17,7 +18,8 @@ export class EditorView extends Component<{ const editorView = this.props.editorView; const skeleton = editorView.innerSkeleton; if (!editorView.isInit) { - return null; + const Loading = engineConfig.get('loadingComponent', BuiltinLoading); + return ; } return ( diff --git a/packages/workspace/src/editor-window/context.ts b/packages/workspace/src/editor-window/context.ts index ee680b140..2c1eee719 100644 --- a/packages/workspace/src/editor-window/context.ts +++ b/packages/workspace/src/editor-window/context.ts @@ -1,12 +1,21 @@ +import { uniqueId } from '@alilc/lowcode-utils'; import { makeObservable, obx } from '@alilc/lowcode-editor-core'; import { Context } from '../editor-view/context'; -import { Workspace } from '..'; +import { Workspace } from '../workspace'; import { Resource } from '../resource'; export class EditorWindow { - constructor(readonly resource: Resource, readonly workspace: Workspace) { + id: string = uniqueId('window'); + icon: React.ReactElement | undefined; + + constructor(readonly resource: Resource, readonly workspace: Workspace, public title: string | undefined = '') { makeObservable(this); this.init(); + this.icon = resource.icon; + } + + get resourceName(): string { + return this.resource.options.name; } async importSchema(schema: any) { diff --git a/packages/workspace/src/editor-window/view.tsx b/packages/workspace/src/editor-window/view.tsx index b080c3344..eb049aeed 100644 --- a/packages/workspace/src/editor-window/view.tsx +++ b/packages/workspace/src/editor-window/view.tsx @@ -1,28 +1,35 @@ -import { Component } from 'react'; +import { PureComponent } from 'react'; import { EditorView } from '../editor-view/view'; -import { observer } from '@alilc/lowcode-editor-core'; +import { engineConfig, observer } from '@alilc/lowcode-editor-core'; import { EditorWindow } from './context'; +import { BuiltinLoading } from '@alilc/lowcode-designer'; @observer -export class EditorWindowView extends Component<{ +export class EditorWindowView extends PureComponent<{ editorWindow: EditorWindow; + active: boolean; }, any> { render() { - const { resource, editorView, editorViews } = this.props.editorWindow; + const { active } = this.props; + const { editorView, editorViews } = this.props.editorWindow; if (!editorView) { - return null; + const Loading = engineConfig.get('loadingComponent', BuiltinLoading); + return ( +
+ +
+ ); } + return ( -
+
{ Array.from(editorViews.values()).map((editorView: any) => { return ( ); }) diff --git a/packages/workspace/src/index.ts b/packages/workspace/src/index.ts index 863f8d0b7..00b054eba 100644 --- a/packages/workspace/src/index.ts +++ b/packages/workspace/src/index.ts @@ -1,70 +1 @@ -import { Designer } from '@alilc/lowcode-designer'; -import { Editor } from '@alilc/lowcode-editor-core'; -import { - Skeleton as InnerSkeleton, -} from '@alilc/lowcode-editor-skeleton'; -import { Plugins } from '@alilc/lowcode-shell'; -import { IPublicResourceOptions } from '@alilc/lowcode-types'; -import { EditorWindow } from './editor-window/context'; -import { Resource } from './resource'; - -export { Resource } from './resource'; -export * from './editor-window/context'; -export * from './layouts/workbench'; - -export class Workspace { - readonly editor = new Editor(); - readonly skeleton = new InnerSkeleton(this.editor); - - constructor( - readonly registryInnerPlugin: (designer: Designer, editor: Editor, plugins: Plugins) => Promise, - readonly shellModelFactory: any, - ) { - if (this.defaultResource) { - this.window = new EditorWindow(this.defaultResource, this); - } - } - - private _isActive = false; - - get isActive() { - return this._isActive; - } - - setActive(value: boolean) { - this._isActive = value; - } - - editorWindows: []; - - window: EditorWindow; - - private resources: Map = new Map(); - - registerResourceType(resourceName: string, resourceType: 'editor' | 'webview', options: IPublicResourceOptions): void { - if (resourceType === 'editor') { - const resource = new Resource(options); - this.resources.set(resourceName, resource); - - if (!this.window) { - this.window = new EditorWindow(this.defaultResource, this); - } - } - } - - get defaultResource() { - if (this.resources.size === 1) { - return this.resources.values().next().value; - } - - return null; - } - - removeResourceType(resourceName: string) { - if (this.resources.has(resourceName)) { - this.resources.delete(resourceName); - } - } - - openEditorWindow() {} -} +export { Workspace } from './workspace'; \ No newline at end of file diff --git a/packages/workspace/src/layouts/left-area.tsx b/packages/workspace/src/layouts/left-area.tsx index 6427499df..3057386ed 100644 --- a/packages/workspace/src/layouts/left-area.tsx +++ b/packages/workspace/src/layouts/left-area.tsx @@ -7,6 +7,9 @@ import { Area } from '@alilc/lowcode-editor-skeleton'; export default class LeftArea extends Component<{ area: Area }> { render() { const { area } = this.props; + if (area.isEmpty()) { + return null; + } return (
{ + render() { + const { area, itemClassName } = this.props; + + if (area.isEmpty()) { + return null; + } + + return ( +
+ +
+ ); + } +} + +@observer +class Contents extends Component<{ area: Area; itemClassName?: string }> { + render() { + const { area, itemClassName } = this.props; + const left: any[] = []; + const center: any[] = []; + const right: any[] = []; + area.container.items.slice().sort((a, b) => { + const index1 = a.config?.index || 0; + const index2 = b.config?.index || 0; + return index1 === index2 ? 0 : (index1 > index2 ? 1 : -1); + }).forEach(item => { + const content = ( +
+ {item.content} +
+ ); + if (item.align === 'center') { + center.push(content); + } else if (item.align === 'left') { + left.push(content); + } else { + right.push(content); + } + }); + let children = []; + if (left && left.length) { + children.push(
{left}
); + } + if (center && center.length) { + children.push(
{center}
); + } + if (right && right.length) { + children.push(
{right}
); + } + return ( + + {children} + + ); + } +} diff --git a/packages/workspace/src/layouts/top-area.tsx b/packages/workspace/src/layouts/top-area.tsx index c6301470d..457e928d2 100644 --- a/packages/workspace/src/layouts/top-area.tsx +++ b/packages/workspace/src/layouts/top-area.tsx @@ -8,7 +8,7 @@ export default class TopArea extends Component<{ area: Area; itemClassName?: str render() { const { area, itemClassName } = this.props; - if (!area?.container?.items?.length) { + if (area.isEmpty()) { return null; } diff --git a/packages/workspace/src/layouts/workbench.less b/packages/workspace/src/layouts/workbench.less index 0217b8496..0639a1fa1 100644 --- a/packages/workspace/src/layouts/workbench.less +++ b/packages/workspace/src/layouts/workbench.less @@ -138,7 +138,7 @@ body { display: flex; flex-direction: column; background-color: #edeff3; - .lc-top-area { + .lc-top-area, .lc-sub-top-area { height: var(--top-area-height); background-color: var(--color-pane-background); width: 100%; @@ -150,18 +150,18 @@ body { display: flex; } - .lc-top-area-left { + .lc-top-area-left, .lc-sub-top-area-left { display: flex; align-items: center; } - .lc-top-area-center { + .lc-top-area-center, .lc-sub-top-area-center { flex: 1; display: flex; justify-content: center; margin: 0 8px; } - .lc-top-area-right { + .lc-top-area-right, .lc-sub-top-area-right { display: flex; align-items: center; > * { @@ -335,6 +335,7 @@ body { display: flex; flex-direction: column; z-index: 10; + position: relative; .lc-toolbar { display: flex; height: var(--toolbar-height); @@ -359,6 +360,12 @@ body { } } } + + .lc-workspace-workbench-window { + position: relative; + height: 100%; + } + .lc-right-area { height: 100%; width: var(--right-area-width); diff --git a/packages/workspace/src/layouts/workbench.tsx b/packages/workspace/src/layouts/workbench.tsx index c7b2762e4..66ddb7de0 100644 --- a/packages/workspace/src/layouts/workbench.tsx +++ b/packages/workspace/src/layouts/workbench.tsx @@ -11,10 +11,17 @@ import BottomArea from './bottom-area'; import './workbench.less'; import { SkeletonContext } from '../skeleton-context'; import { EditorConfig, PluginClassSet } from '@alilc/lowcode-types'; -import { Workspace } from '..'; +import { Workspace } from '../workspace'; +import SubTopArea from './sub-top-area'; @observer -export class Workbench extends Component<{ workspace: Workspace; config?: EditorConfig; components?: PluginClassSet; className?: string; topAreaItemClassName?: string }> { +export class Workbench extends Component<{ + workspace: Workspace; + config?: EditorConfig; + components?: PluginClassSet; + className?: string; + topAreaItemClassName?: string; +}> { constructor(props: any) { super(props); const { config, components, workspace } = this.props; @@ -34,8 +41,20 @@ export class Workbench extends Component<{ workspace: Workspace; config?: Editor
- {/* */} - + <> + +
+ { + workspace.windows.map(d => ( + + )) + } +
+
diff --git a/packages/workspace/src/resource.ts b/packages/workspace/src/resource.ts index a1147713e..f881caf0f 100644 --- a/packages/workspace/src/resource.ts +++ b/packages/workspace/src/resource.ts @@ -19,6 +19,10 @@ export class Resource { this.options.init(ctx); } + get icon() { + return this.options.icon; + } + async import(schema: any) { return await this.options.import?.(schema); } @@ -38,4 +42,8 @@ export class Resource { async save(value: any) { return await this.options.save?.(value); } + + get title() { + return this.options.defaultTitle; + } } \ No newline at end of file diff --git a/packages/workspace/src/workspace.ts b/packages/workspace/src/workspace.ts new file mode 100644 index 000000000..8b0fe29c4 --- /dev/null +++ b/packages/workspace/src/workspace.ts @@ -0,0 +1,169 @@ +import { Designer } from '@alilc/lowcode-designer'; +import { createModuleEventBus, Editor, IEventBus, makeObservable, obx } from '@alilc/lowcode-editor-core'; +import { Plugins } from '@alilc/lowcode-shell'; +import { IPublicApiWorkspace, IPublicResourceOptions } from '@alilc/lowcode-types'; +import { BasicContext } from './base-context'; +import { EditorWindow } from './editor-window/context'; +import { Resource } from './resource'; + +export { Resource } from './resource'; +export * from './editor-window/context'; +export * from './layouts/workbench'; + +enum event { + ChangeWindow = 'change_window', + + ChangeActiveWindow = 'change_active_window', +} + +export class Workspace implements IPublicApiWorkspace { + private context: BasicContext; + + private emitter: IEventBus = createModuleEventBus('workspace'); + + get skeleton() { + return this.context.innerSkeleton; + } + + get plugins() { + return this.context.innerPlugins; + } + + constructor( + readonly registryInnerPlugin: (designer: Designer, editor: Editor, plugins: Plugins) => Promise, + readonly shellModelFactory: any, + ) { + this.init(); + makeObservable(this); + } + + init() { + this.initWindow(); + this.context = new BasicContext(this, ''); + } + + initWindow() { + if (!this.defaultResource) { + return; + } + const title = this.defaultResource.title; + this.window = new EditorWindow(this.defaultResource, this, title); + this.editorWindowMap.set(this.window.id, this.window); + this.windows.push(this.window); + this.emitChangeWindow(); + this.emitChangeActiveWindow(); + } + + + private _isActive = false; + + get isActive() { + return this._isActive; + } + + setActive(value: boolean) { + this._isActive = value; + } + + windows: EditorWindow[] = []; + + editorWindowMap: Map = new Map(); + + @obx.ref window: EditorWindow; + + private resources: Map = new Map(); + + async registerResourceType(resourceName: string, resourceType: 'editor' | 'webview', options: IPublicResourceOptions): Promise { + if (resourceType === 'editor') { + const resource = new Resource(options); + this.resources.set(resourceName, resource); + + if (!this.window && this.defaultResource) { + this.initWindow(); + } + } + } + + get defaultResource(): Resource | null { + if (this.resources.size > 1) { + return this.resources.values().next().value; + } + + return null; + } + + removeResourceType(resourceName: string) { + if (this.resources.has(resourceName)) { + this.resources.delete(resourceName); + } + } + + removeEditorWindowById(id: string) { + const index = this.windows.findIndex(d => (d.id === id)); + this.remove(index); + } + + private remove(index: number) { + const window = this.windows[index]; + this.windows = this.windows.splice(index - 1, 1); + if (this.window === window) { + this.window = this.windows[index] || this.windows[index + 1] || this.windows[index - 1]; + this.emitChangeActiveWindow(); + } + this.emitChangeWindow(); + } + + removeEditorWindow(resourceName: string, title: string) { + const index = this.windows.findIndex(d => (d.resourceName === resourceName && d.title)); + this.remove(index); + } + + openEditorWindowById(id: string) { + const window = this.editorWindowMap.get(id); + if (window) { + this.window = window; + this.emitChangeActiveWindow(); + } + } + + openEditorWindow(resourceName: string, title: string, viewType?: string) { + const resource = this.resources.get(resourceName); + if (!resource) { + console.error(`${resourceName} is not available`); + return; + } + const filterWindows = this.windows.filter(d => (d.resourceName === resourceName && d.title == title)); + if (filterWindows && filterWindows.length) { + this.window = filterWindows[0]; + this.emitChangeActiveWindow(); + return; + } + this.window = new EditorWindow(resource, this, title); + this.windows.push(this.window); + this.editorWindowMap.set(this.window.id, this.window); + this.emitChangeWindow(); + this.emitChangeActiveWindow(); + } + + onChangeWindows(fn: () => void) { + this.emitter.on(event.ChangeWindow, fn); + return () => { + this.emitter.removeListener(event.ChangeWindow, fn); + }; + } + + emitChangeWindow() { + this.emitter.emit(event.ChangeWindow); + } + + emitChangeActiveWindow() { + this.emitter.emit(event.ChangeActiveWindow); + } + + onChangeActiveWindow(fn: () => void) { + this.emitter.on(event.ChangeActiveWindow, fn); + return () => { + this.emitter.removeListener(event.ChangeActiveWindow, fn); + }; + } +}