feat: added apis support resource tree in workspace mode

This commit is contained in:
liujuping 2023-01-09 17:18:30 +08:00 committed by 林熠
parent a37a647ef6
commit ea08173af0
25 changed files with 419 additions and 101 deletions

View File

@ -44,3 +44,7 @@ sidebar_position: 0
2. 事件events的命名格式为on[Will|Did]VerbNoun?,参考 [https://code.visualstudio.com/api/references/vscode-api#events](https://code.visualstudio.com/api/references/vscode-api#events)
3. 基于 Disposable 模式,对于事件的绑定、快捷键的绑定函数,返回值则是解绑函数
4. 对于属性的导出,统一用 .xxx 的 getter 模式,(尽量)不使用 .getXxx()
## experimental
说明此模块处于公测阶段, API 可能会发生改变.

View File

@ -1,5 +1,5 @@
---
title: plugin-instance
title: PluginInstance
sidebar_position: 12
---

View File

@ -0,0 +1,26 @@
---
title: ResourceType
sidebar_position: 12
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicModelWindow](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/resource-type.ts)<br/>
> **@since** v1.1.0
## 变量
### description
资源描述
`@type {string}`
### icon
资源 icon
`@type {ReactElement}`
### name
`@type {string}`

View File

@ -0,0 +1,36 @@
---
title: Resource
sidebar_position: 12
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicModelWindow](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/resource.ts)<br/>
> **@since** v1.1.0
## 变量
### title
资源标题
`@type {string}`
### icon
资源 icon
`@type {ReactElement}`
### options
资源配置信息
`@type {Object}`
### resourceType
资源所属的资源类型
`@type {IPublicModelResourceType}`
关联模型 [IPublicModelResourceType](./resource-type)

View File

@ -3,6 +3,7 @@ title: Window
sidebar_position: 12
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicModelWindow](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/window.ts)<br/>
> **@since** v1.1.0
@ -17,13 +18,25 @@ sidebar_position: 12
窗口唯一 id
`@type {string}`
### title
窗口标题
### resourceName
`@type {string}`
窗口资源名字
### icon
`@type {ReactElement}`
### resourceType
窗口资源类型
`@type {IPublicModelResourceType}`
关联模型 [IPublicModelResourceType](./resource-type)
## 方法签名

View File

@ -222,7 +222,7 @@ your-plugin/package.json
}
```
转换后的结构:
```json
```typescript
const debug = (ctx: IPublicModelPluginContext, options: any) => {
return {};
}

View File

@ -3,10 +3,10 @@ title: workspace - 应用级 API
sidebar_position: 12
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicApiWorkspace](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/workspace.ts)<br/>
> **@since** v1.1.0
## 模块简介
通过该模块可以开发应用级低代码设计器。
@ -47,6 +47,16 @@ get window(): IPublicModelWindow[]
关联模型 [IPublicModelWindow](./model/window)
### resourceList
当前设计器的资源列表数据
```
get resourceList(): IPublicModelResource;
```
关联模型 [IPublicModelResource](./model/resource)
## 方法签名
### registerResourceType
@ -54,7 +64,7 @@ get window(): IPublicModelWindow[]
```typescript
/** 注册资源 */
registerResourceType(resourceName: string, resourceType: 'editor', options: IPublicResourceOptions): void;
registerResourceType(name: string, type: 'editor', options: IPublicResourceOptions): void;
```
相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts)
@ -74,3 +84,55 @@ active 窗口变更事件
```typescript
function onChangeActiveWindow(fn: () => void): void;
```
### setResourceList
设置设计器资源列表数据
```typescript
setResourceList(resourceList: IPublicResourceList) {}
```
相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts)
### onResourceListChange
设计器资源列表数据变更事件
```typescript
onResourceListChange(fn: (resourceList: IPublicResourceList): void): (): void;
```
相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts)
### openEditorWindow
打开视图窗口
```typescript
openEditorWindow(resourceName: string, title: string, options: Object, viewName?: string): void;
```
### openEditorWindowById
通过视图 id 打开窗口
```typescript
openEditorWindowById(id: string): void;
```
### removeEditorWindow
移除视图窗口
```typescript
removeEditorWindow(resourceName: string, title: string): void;
```
### removeEditorWindowById
通过视图 id 移除窗口
```typescript
removeEditorWindowById(id: string): void;
```

View File

@ -1,8 +1,9 @@
import { IPublicApiWorkspace } from '@alilc/lowcode-types';
import { IPublicApiWorkspace, IPublicResourceList, IPublicResourceOptions } 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';
import { Resource } from '../model';
export class Workspace implements IPublicApiWorkspace {
readonly [workspaceSymbol]: InnerWorkSpace;
@ -11,6 +12,18 @@ export class Workspace implements IPublicApiWorkspace {
this[workspaceSymbol] = innerWorkspace;
}
get resourceList() {
return this[workspaceSymbol].getResourceList().map(d => new Resource(d));
}
setResourceList(resourceList: IPublicResourceList) {
this[workspaceSymbol].setResourceList(resourceList);
}
onResourceListChange(fn: (resourceList: IPublicResourceList) => void): () => void {
return this[workspaceSymbol].onResourceListChange(fn);
}
get isActive() {
return this[workspaceSymbol].isActive;
}
@ -19,12 +32,12 @@ export class Workspace implements IPublicApiWorkspace {
return new Window(this[workspaceSymbol].window);
}
registerResourceType(resourceName: string, resourceType: 'editor', options: any): void {
this[workspaceSymbol].registerResourceType(resourceName, resourceType, options);
registerResourceType(name: string, type: 'editor', options: IPublicResourceOptions): void {
this[workspaceSymbol].registerResourceType(name, type, options);
}
openEditorWindow(resourceName: string, title: string, viewType?: string) {
this[workspaceSymbol].openEditorWindow(resourceName, title, viewType);
openEditorWindow(resourceName: string, title: string, extra: Object, viewName?: string) {
this[workspaceSymbol].openEditorWindow(resourceName, title, extra, viewName);
}
openEditorWindowById(id: string) {

View File

@ -13,4 +13,6 @@ export * from './prop';
export * from './props';
export * from './selection';
export * from './setting-prop-entry';
export * from './setting-top-entry';
export * from './setting-top-entry';
export * from './resource';
export * from './resource-type';

View File

@ -0,0 +1,22 @@
import { IPublicModelResourceType } from '@alilc/lowcode-types';
import { ResourceType as InnerResourceType } from '@alilc/lowcode-workspace';
import { resourceTypeSymbol } from '../symbols';
export class ResourceType implements IPublicModelResourceType {
readonly [resourceTypeSymbol]: InnerResourceType;
constructor(resourceType: InnerResourceType) {
this[resourceTypeSymbol] = resourceType;
}
get name() {
return this[resourceTypeSymbol].name;
}
get description() {
return this[resourceTypeSymbol].options.description;
}
get icon() {
return this[resourceTypeSymbol].options.icon;
}
}

View File

@ -0,0 +1,28 @@
import { IPublicModelResource } from '@alilc/lowcode-types';
import { Resource as InnerResource } from '@alilc/lowcode-workspace';
import { resourceSymbol } from '../symbols';
import { ResourceType } from './resource-type';
export class Resource implements IPublicModelResource {
readonly [resourceSymbol]: InnerResource;
constructor(resource: InnerResource) {
this[resourceSymbol] = resource;
}
get title() {
return this[resourceSymbol].title;
}
get icon() {
return this[resourceSymbol].icon;
}
get options() {
return this[resourceSymbol].options;
}
get resourceType() {
return new ResourceType(this[resourceSymbol].resourceType);
}
}

View File

@ -1,6 +1,7 @@
import { windowSymbol } from '../symbols';
import { IPublicModelWindow } from '@alilc/lowcode-types';
import { EditorWindow } from '@alilc/lowcode-workspace';
import { ResourceType } from './resource-type';
export class Window implements IPublicModelWindow {
private readonly [windowSymbol]: EditorWindow;
@ -17,8 +18,8 @@ export class Window implements IPublicModelWindow {
return this[windowSymbol].icon;
}
get resourceName() {
return this[windowSymbol].resourceName;
get resourceType(): ResourceType {
return new ResourceType(this[windowSymbol].resourceType);
}
constructor(editorWindow: EditorWindow) {

View File

@ -28,4 +28,6 @@ export const hotkeySymbol = Symbol('hotkey');
export const pluginsSymbol = Symbol('plugins');
export const workspaceSymbol = Symbol('workspace');
export const windowSymbol = Symbol('window');
export const pluginInstanceSymbol = Symbol('plugin-instance');
export const pluginInstanceSymbol = Symbol('plugin-instance');
export const resourceTypeSymbol = Symbol('resourceType');
export const resourceSymbol = Symbol('resource');

View File

@ -1,23 +1,39 @@
import { IPublicModelWindow } from '../model';
import { IPublicResourceOptions } from '../type';
import { IPublicApiPlugins } from '@alilc/lowcode-types';
import { IPublicApiPlugins, IPublicModelResource, IPublicResourceList } from '@alilc/lowcode-types';
export interface IPublicApiWorkspace {
/** 是否启用 workspace 模式 */
isActive: boolean;
/** 当前设计器窗口 */
window: IPublicModelWindow;
/** 获取资源树列表 */
get resourceList(): IPublicModelResource[];
/** 设置资源树列表 */
setResourceList(resourceList: IPublicResourceList): void;
/** 资源树列表更新事件 */
onResourceListChange(fn: (resourceList: IPublicResourceList) => void): () => void;
/** 注册资源 */
registerResourceType(resourceName: string, resourceType: 'editor', options: IPublicResourceOptions): void;
registerResourceType(resourceName: string, type: 'editor', options: IPublicResourceOptions): void;
/** 打开视图窗口 */
openEditorWindow(resourceName: string, title: string, viewType?: string): void;
openEditorWindow(resourceName: string, title: string, extra: Object, viewName?: string): void;
/** 移除窗口 */
/** 通过视图 id 打开窗口 */
openEditorWindowById(id: string): void;
/** 移除视图窗口 */
removeEditorWindow(resourceName: string, title: string): void;
/** 通过视图 id 移除窗口 */
removeEditorWindowById(id: string): void;
plugins: IPublicApiPlugins;
/** 当前设计器的编辑窗口 */

View File

@ -27,4 +27,6 @@ export * from './engine-config';
export * from './editor';
export * from './preference';
export * from './plugin-instance';
export * from './sensor';
export * from './sensor';
export * from './resource-type';
export * from './resource';

View File

@ -0,0 +1,7 @@
import { ReactElement } from 'react';
export interface IPublicModelResourceType {
get description(): string | undefined;
get icon(): ReactElement | undefined;
}

View File

@ -0,0 +1,12 @@
import { ReactElement } from 'react';
import { IPublicModelResourceType } from './resource-type';
export interface IPublicModelResource {
get title(): string;
get icon(): ReactElement | undefined;
get options(): Object;
get resourceType(): IPublicModelResourceType | undefined;
}

View File

@ -1,6 +1,9 @@
import { ReactElement } from 'react';
import { IPublicTypeNodeSchema } from '../type';
import { IPublicModelResourceType } from './resource-type';
export interface IPublicModelWindow {
/** 当前窗口导入 schema */
importSchema(schema: IPublicTypeNodeSchema): void;
@ -16,6 +19,9 @@ export interface IPublicModelWindow {
/** 窗口标题 */
title?: string;
/** 窗口资源名字 */
resourceName?: string;
/** 窗口 icon */
icon?: ReactElement;
/** 窗口资源类型 */
resourceType?: IPublicModelResourceType;
}

View File

@ -1,21 +1,23 @@
export interface IPublicViewFunctions {
/** 视图初始化钩子 */
init?: () => Promise<void>;
/** 资源保存时,会调用视图的钩子 */
save?: () => Promise<void>;
}
export interface IPublicEditorView {
/** 资源名字 */
viewName: string;
/** 资源类型 */
viewType?: 'editor' | 'webview';
(ctx: any): IPublicViewFunctions;
(ctx: any, options: any): IPublicViewFunctions;
}
export interface IPublicResourceOptions {
/** 资源名字 */
name: string;
/** 资源描述 */
description?: string;
@ -41,4 +43,14 @@ export interface IPublicResourceOptions {
/** 默认标题 */
defaultTitle?: string;
}
}
export interface IPublicResourceData {
resourceName: string;
title: string;
options: {
[key: string]: any;
};
}
export type IPublicResourceList = IPublicResourceData[];

View File

@ -13,13 +13,13 @@ export class Context extends BasicContext {
viewType: 'editor' | 'webview';
constructor(public workspace: InnerWorkspace, public editorWindow: EditorWindow, public editorView: IPublicEditorView) {
constructor(public workspace: InnerWorkspace, public editorWindow: EditorWindow, public editorView: IPublicEditorView, options: Object) {
super(workspace, editorView.viewName, editorWindow);
this.viewType = editorView.viewType || 'editor';
this.viewName = editorView.viewName;
this.instance = editorView(this.innerPlugins._getLowCodePluginContext({
pluginName: 'any',
}));
}), options);
makeObservable(this);
}

View File

@ -2,24 +2,20 @@ import { uniqueId } from '@alilc/lowcode-utils';
import { makeObservable, obx } from '@alilc/lowcode-editor-core';
import { Context } from '../editor-view/context';
import { Workspace } from '../workspace';
import { Resource } from '../resource';
import { ResourceType } from '../resource-type';
export class EditorWindow {
id: string = uniqueId('window');
icon: React.ReactElement | undefined;
constructor(readonly resource: Resource, readonly workspace: Workspace, public title: string | undefined = '') {
constructor(readonly resourceType: ResourceType, readonly workspace: Workspace, public title: string | undefined = '', private options: Object = {}) {
makeObservable(this);
this.init();
this.icon = resource.icon;
}
get resourceName(): string {
return this.resource.options.name;
this.icon = resourceType.icon;
}
async importSchema(schema: any) {
const newSchema = await this.resource.import(schema);
const newSchema = await this.resourceType.import(schema);
if (!newSchema) {
return;
@ -33,13 +29,13 @@ export class EditorWindow {
async save() {
const value: any = {};
const editorViews = this.resource.editorViews;
const editorViews = this.resourceType.editorViews;
for (let i = 0; i < editorViews.length; i++) {
const name = editorViews[i].viewName;
const saveResult = await this.editorViews.get(name)?.save();
value[name] = saveResult;
}
return await this.resource.save(value);
return await this.resourceType.save(value);
}
async init() {
@ -49,7 +45,7 @@ export class EditorWindow {
}
initViewTypes = async () => {
const editorViews = this.resource.editorViews;
const editorViews = this.resourceType.editorViews;
for (let i = 0; i < editorViews.length; i++) {
const name = editorViews[i].viewName;
await this.initViewType(name);
@ -60,7 +56,7 @@ export class EditorWindow {
};
execViewTypesInit = async () => {
const editorViews = this.resource.editorViews;
const editorViews = this.resourceType.editorViews;
for (let i = 0; i < editorViews.length; i++) {
const name = editorViews[i].viewName;
this.changeViewType(name);
@ -69,7 +65,7 @@ export class EditorWindow {
};
setDefaultViewType = () => {
this.changeViewType(this.resource.defaultViewType);
this.changeViewType(this.resourceType.defaultViewType);
};
@obx.ref editorView: Context;
@ -77,11 +73,11 @@ export class EditorWindow {
@obx editorViews: Map<string, Context> = new Map<string, Context>();
initViewType = async (name: string) => {
const viewInfo = this.resource.getEditorView(name);
const viewInfo = this.resourceType.getEditorView(name);
if (this.editorViews.get(name)) {
return;
}
const editorView = new Context(this.workspace, this, viewInfo as any);
const editorView = new Context(this.workspace, this, viewInfo as any, this.options);
this.editorViews.set(name, editorView);
};

View File

@ -1,4 +1,5 @@
export { Workspace } from './workspace';
export { Resource } from './resource';
export { ResourceType } from './resource-type';
export * from './editor-window/context';
export * from './layouts/workbench';
export { Resource } from './resource';

View File

@ -0,0 +1,53 @@
import { IPublicEditorView, IPublicModelResourceType, IPublicResourceOptions } from '@alilc/lowcode-types';
export class ResourceType implements IPublicModelResourceType {
constructor(readonly name: string, readonly type: 'editor' | 'webview', options: IPublicResourceOptions) {
if (options.editorViews) {
options.editorViews.forEach((d: any) => {
this.editorViewMap.set(d.viewName, d);
});
}
this.options = options;
}
get description() {
return this.options.description;
}
options: IPublicResourceOptions;
editorViewMap: Map<string, IPublicEditorView> = new Map<string, IPublicEditorView>();
init(ctx: any) {
this.options.init(ctx);
}
get icon() {
return this.options.icon;
}
async import(schema: any) {
return await this.options.import?.(schema);
}
getEditorView(name: string) {
return this.editorViewMap.get(name);
}
get defaultViewType() {
return this.options.defaultViewType || this.editorViewMap.keys().next().value;
}
get editorViews() {
return Array.from(this.editorViewMap.values());
}
async save(value: any) {
return await this.options.save?.(value);
}
get title() {
return this.options.defaultTitle;
}
}

View File

@ -1,49 +1,29 @@
import { IPublicEditorView, IPublicResourceOptions } from '@alilc/lowcode-types';
import { IPublicModelResource, IPublicResourceData } from '@alilc/lowcode-types';
import { Logger } from '@alilc/lowcode-utils';
import { ResourceType } from './resource-type';
export class Resource {
constructor(options: IPublicResourceOptions) {
if (options.editorViews) {
options.editorViews.forEach((d: any) => {
this.editorViewMap.set(d.viewName, d);
});
const logger = new Logger({ level: 'warn', bizName: 'workspace:resource' });
export class Resource implements IPublicModelResource {
constructor(readonly resourceData: IPublicResourceData, readonly resourceType: ResourceType) {
if (!resourceType) {
logger.error(`resourceType[${resourceType}] is unValid.`);
}
this.options = options;
}
options: IPublicResourceOptions;
editorViewMap: Map<string, IPublicEditorView> = new Map<string, IPublicEditorView>();
init(ctx: any) {
this.options.init(ctx);
}
get icon() {
return this.options.icon;
return this.resourceType?.icon;
}
async import(schema: any) {
return await this.options.import?.(schema);
}
getEditorView(name: string) {
return this.editorViewMap.get(name);
}
get defaultViewType() {
return this.options.defaultViewType || this.editorViewMap.keys().next().value;
}
get editorViews() {
return Array.from(this.editorViewMap.values());
}
async save(value: any) {
return await this.options.save?.(value);
get type() {
return this.resourceData.resourceName;
}
get title() {
return this.options.defaultTitle;
return this.resourceData.title;
}
get options() {
return this.resourceData.options;
}
}

View File

@ -1,10 +1,11 @@
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 { IPublicApiWorkspace, IPublicResourceList, IPublicResourceOptions } from '@alilc/lowcode-types';
import { BasicContext } from './base-context';
import { EditorWindow } from './editor-window/context';
import { Resource } from './resource';
import { ResourceType } from './resource-type';
enum event {
ChangeWindow = 'change_window',
@ -12,6 +13,8 @@ enum event {
ChangeActiveWindow = 'change_active_window',
}
const CHANGE_EVENT = 'resource.list.change';
export class Workspace implements IPublicApiWorkspace {
private context: BasicContext;
@ -42,7 +45,7 @@ export class Workspace implements IPublicApiWorkspace {
if (!this.defaultResource) {
return;
}
const title = this.defaultResource.title;
const title = this.defaultResource.description;
this.window = new EditorWindow(this.defaultResource, this, title);
this.editorWindowMap.set(this.window.id, this.window);
this.windows.push(this.window);
@ -50,7 +53,6 @@ export class Workspace implements IPublicApiWorkspace {
this.emitChangeActiveWindow();
}
private _isActive = false;
get isActive() {
@ -67,12 +69,14 @@ export class Workspace implements IPublicApiWorkspace {
@obx.ref window: EditorWindow;
private resources: Map<string, Resource> = new Map();
private resourceTypeMap: Map<string, ResourceType> = new Map();
async registerResourceType(resourceName: string, resourceType: 'editor' | 'webview', options: IPublicResourceOptions): Promise<void> {
if (resourceType === 'editor') {
const resource = new Resource(options);
this.resources.set(resourceName, resource);
private resourceList: Resource[] = [];
async registerResourceType(resourceName: string, type: 'editor' | 'webview', options: IPublicResourceOptions): Promise<void> {
if (type === 'editor') {
const resourceType = new ResourceType(resourceName, type, options);
this.resourceTypeMap.set(resourceName, resourceType);
if (!this.window && this.defaultResource) {
this.initWindow();
@ -80,17 +84,37 @@ export class Workspace implements IPublicApiWorkspace {
}
}
get defaultResource(): Resource | null {
if (this.resources.size > 1) {
return this.resources.values().next().value;
getResourceList() {
return this.resourceList;
}
setResourceList(resourceList: IPublicResourceList) {
this.resourceList = resourceList.map(d => new Resource(d, this.getResourceType(d.resourceName)));
this.emitter.emit(CHANGE_EVENT, resourceList);
}
onResourceListChange(fn: (resourceList: IPublicResourceList) => void): () => void {
this.emitter.on(CHANGE_EVENT, fn);
return () => {
this.emitter.off(CHANGE_EVENT, fn);
};
}
getResourceType(resourceName: string): ResourceType {
return this.resourceTypeMap.get(resourceName)!;
}
get defaultResource(): ResourceType | null {
if (this.resourceTypeMap.size > 1) {
return this.resourceTypeMap.values().next().value;
}
return null;
}
removeResourceType(resourceName: string) {
if (this.resources.has(resourceName)) {
this.resources.delete(resourceName);
if (this.resourceTypeMap.has(resourceName)) {
this.resourceTypeMap.delete(resourceName);
}
}
@ -110,7 +134,7 @@ export class Workspace implements IPublicApiWorkspace {
}
removeEditorWindow(resourceName: string, title: string) {
const index = this.windows.findIndex(d => (d.resourceName === resourceName && d.title));
const index = this.windows.findIndex(d => (d.resourceType.name === resourceName && d.title));
this.remove(index);
}
@ -122,19 +146,19 @@ export class Workspace implements IPublicApiWorkspace {
}
}
openEditorWindow(resourceName: string, title: string, viewType?: string) {
const resource = this.resources.get(resourceName);
if (!resource) {
console.error(`${resourceName} is not available`);
openEditorWindow(name: string, title: string, options: Object, viewType?: string) {
const resourceType = this.resourceTypeMap.get(name);
if (!resourceType) {
console.error(`${name} is not available`);
return;
}
const filterWindows = this.windows.filter(d => (d.resourceName === resourceName && d.title == title));
const filterWindows = this.windows.filter(d => (d.resourceType.name === name && d.title == title));
if (filterWindows && filterWindows.length) {
this.window = filterWindows[0];
this.emitChangeActiveWindow();
return;
}
this.window = new EditorWindow(resource, this, title);
this.window = new EditorWindow(resourceType, this, title, options);
this.windows.push(this.window);
this.editorWindowMap.set(this.window.id, this.window);
this.emitChangeWindow();