mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 21:12:53 +00:00
feat: added workspace api to support registration of multiple resources
This commit is contained in:
parent
0a2427354b
commit
dae09e3bcb
@ -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)
|
||||
```
|
||||
|
||||
@ -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;
|
||||
```
|
||||
|
||||
155
packages/designer/src/component-actions.ts
Normal file
155
packages/designer/src/component-actions.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)) {
|
||||
return untracked(() => {
|
||||
const shellThis = this.internalToShellPropEntry();
|
||||
return this._setter.call(shellThis, shellThis);
|
||||
});
|
||||
}
|
||||
return this._setter;
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<div id="engine-loading-wrapper">
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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 = () => {
|
||||
export const registerDefaults = (ctx: IPublicModelPluginContext) => {
|
||||
const { material } = ctx;
|
||||
return {
|
||||
init() {
|
||||
// parseFunc
|
||||
registerMetadataTransducer(parseJSFunc, 1, 'parse-func');
|
||||
material.registerMetadataTransducer(parseJSFunc, 1, 'parse-func');
|
||||
|
||||
// parseProps
|
||||
registerMetadataTransducer(parseProps, 5, 'parse-props');
|
||||
material.registerMetadataTransducer(parseProps, 5, 'parse-props');
|
||||
|
||||
// addon/platform custom
|
||||
registerMetadataTransducer(addonCombine, 10, 'combine-props');
|
||||
material.registerMetadataTransducer(addonCombine, 10, 'combine-props');
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
registerDefaults.pluginName = '___register_defaults___';
|
||||
|
||||
@ -50,6 +50,8 @@ export class Skeleton {
|
||||
|
||||
readonly topArea: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
|
||||
|
||||
readonly subTopArea: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
|
||||
|
||||
readonly toolbar: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
|
||||
|
||||
readonly leftFixedArea: Area<PanelConfig, Panel>;
|
||||
@ -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':
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -9,4 +9,13 @@ export interface IPublicModelWindow {
|
||||
|
||||
/** 调用当前窗口视图保存钩子 */
|
||||
save(): Promise<any>;
|
||||
|
||||
/** 窗口 id */
|
||||
id: string;
|
||||
|
||||
/** 窗口标题 */
|
||||
title?: string;
|
||||
|
||||
/** 窗口资源名字 */
|
||||
resourceName?: string;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
export interface IPublicViewFunctions {
|
||||
/** 视图初始化 */
|
||||
/** 视图初始化钩子 */
|
||||
init?: () => Promise<void>;
|
||||
/** 资源保存时调用视图的钩子 */
|
||||
/** 资源保存时,会调用视图的钩子 */
|
||||
save?: () => Promise<void>;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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' |
|
||||
|
||||
@ -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<void>;
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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 <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 (
|
||||
<div className="workspace-engine-main">
|
||||
<div className={`workspace-engine-main ${active ? 'active' : ''}`}>
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`workspace-engine-main ${active ? 'active' : ''}`}>
|
||||
{
|
||||
Array.from(editorViews.values()).map((editorView: any) => {
|
||||
return (
|
||||
<EditorView
|
||||
resource={resource}
|
||||
key={editorView.name}
|
||||
active={editorView.active}
|
||||
editorView={editorView}
|
||||
defaultViewType
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
@ -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<void>,
|
||||
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<string, Resource> = 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';
|
||||
@ -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 (
|
||||
<div className={classNames('lc-left-area', {
|
||||
'lc-area-visible': area.visible,
|
||||
|
||||
67
packages/workspace/src/layouts/sub-top-area.tsx
Normal file
67
packages/workspace/src/layouts/sub-top-area.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { Component, Fragment } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { observer } from '@alilc/lowcode-editor-core';
|
||||
import { Area } from '@alilc/lowcode-editor-skeleton';
|
||||
|
||||
@observer
|
||||
export default class SubTopArea extends Component<{ area: Area; itemClassName?: string }> {
|
||||
render() {
|
||||
const { area, itemClassName } = this.props;
|
||||
|
||||
if (area.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('lc-sub-top-area engine-actionpane', {
|
||||
'lc-area-visible': area.visible,
|
||||
})}
|
||||
>
|
||||
<Contents area={area} itemClassName={itemClassName} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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 = (
|
||||
<div className={itemClassName || ''} key={`top-area-${item.name}`}>
|
||||
{item.content}
|
||||
</div>
|
||||
);
|
||||
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(<div className="lc-sub-top-area-left">{left}</div>);
|
||||
}
|
||||
if (center && center.length) {
|
||||
children.push(<div className="lc-sub-top-area-center">{center}</div>);
|
||||
}
|
||||
if (right && right.length) {
|
||||
children.push(<div className="lc-sub-top-area-right">{right}</div>);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
{children}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
<LeftFloatPane area={skeleton.leftFloatArea} />
|
||||
<LeftFixedPane area={skeleton.leftFixedArea} />
|
||||
<div className="lc-workspace-workbench-center">
|
||||
{/* <Toolbar area={skeleton.toolbar} /> */}
|
||||
<EditorWindowView editorWindow={workspace.window} />
|
||||
<>
|
||||
<SubTopArea area={skeleton.subTopArea} itemClassName={topAreaItemClassName} />
|
||||
<div className="lc-workspace-workbench-window">
|
||||
{
|
||||
workspace.windows.map(d => (
|
||||
<EditorWindowView
|
||||
active={d.id === workspace.window.id}
|
||||
editorWindow={d}
|
||||
key={d.id}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
<MainArea area={skeleton.mainArea} />
|
||||
<BottomArea area={skeleton.bottomArea} />
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
169
packages/workspace/src/workspace.ts
Normal file
169
packages/workspace/src/workspace.ts
Normal file
@ -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<void>,
|
||||
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<string, EditorWindow> = new Map<string, EditorWindow>();
|
||||
|
||||
@obx.ref window: EditorWindow;
|
||||
|
||||
private resources: Map<string, Resource> = 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);
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user