feat: support workspace

This commit is contained in:
JackLian 2022-12-13 18:29:01 +08:00
parent d9e1203eeb
commit f0e60bb9f8
98 changed files with 3278 additions and 626 deletions

View File

@ -34,7 +34,7 @@
"@alife/build-plugin-lowcode": "^1.0.7", "@alife/build-plugin-lowcode": "^1.0.7",
"@alib/build-scripts": "^0.1.3", "@alib/build-scripts": "^0.1.3",
"@alifd/adaptor-generate": "^0.1.3", "@alifd/adaptor-generate": "^0.1.3",
"build-plugin-component": "^0.2.0", "build-plugin-component": "^1.0.0",
"build-plugin-fusion": "^0.1.0", "build-plugin-fusion": "^0.1.0",
"build-plugin-fusion-cool": "^0.1.0", "build-plugin-fusion-cool": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0", "build-plugin-moment-locales": "^0.1.0",

View File

@ -172,7 +172,8 @@ export class BoxResizingInstance extends Component<{
metadata.configure.advanced.callbacks.onResizeEnd(e, cbNode); metadata.configure.advanced.callbacks.onResizeEnd(e, cbNode);
} }
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
const selected = const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || [npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||

View File

@ -131,7 +131,8 @@ function createAction(content: ReactNode | ComponentType<any> | ActionContentObj
className="lc-borders-action" className="lc-borders-action"
onClick={() => { onClick={() => {
action && action(node); action && action(node);
const editor = globalContext.get('editor'); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
const selected = const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || [npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||

View File

@ -22,6 +22,7 @@ export function createSimulator(
const doc = iframe.contentDocument!; const doc = iframe.contentDocument!;
win.LCSimulatorHost = host; win.LCSimulatorHost = host;
win._ = window._;
const styles: any = {}; const styles: any = {};
const scripts: any = {}; const scripts: any = {};

View File

@ -23,8 +23,8 @@ export class BuiltinSimulatorHostView extends Component<SimulatorHostProps> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
const { project, onMount } = this.props; const { project, onMount, designer } = this.props;
this.host = (project.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(project); this.host = (project.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(project, designer);
this.host.setProps(this.props); this.host.setProps(this.props);
onMount?.(this.host); onMount?.(this.host);
} }
@ -76,7 +76,8 @@ class Content extends Component<{ host: BuiltinSimulatorHost }> {
private dispose?: () => void; private dispose?: () => void;
componentDidMount() { componentDidMount() {
const editor = globalContext.get('editor'); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const onEnableEvents = (type: boolean) => { const onEnableEvents = (type: boolean) => {
this.setState({ this.setState({
disabledEvents: type, disabledEvents: type,
@ -97,7 +98,7 @@ class Content extends Component<{ host: BuiltinSimulatorHost }> {
render() { render() {
const sim = this.props.host; const sim = this.props.host;
const { disabledEvents } = this.state; const { disabledEvents } = this.state;
const { viewport } = sim; const { viewport, designer } = sim;
const frameStyle: any = { const frameStyle: any = {
transform: `scale(${viewport.scale})`, transform: `scale(${viewport.scale})`,
height: viewport.contentHeight, height: viewport.contentHeight,
@ -107,10 +108,12 @@ class Content extends Component<{ host: BuiltinSimulatorHost }> {
frameStyle.pointerEvents = 'none'; frameStyle.pointerEvents = 'none';
} }
const name = designer.name;
return ( return (
<div className="lc-simulator-content"> <div className="lc-simulator-content">
<iframe <iframe
name="SimulatorRenderer" name={`${name}-SimulatorRenderer`}
className="lc-simulator-content-frame" className="lc-simulator-content-frame"
style={frameStyle} style={frameStyle}
ref={(frame) => sim.mountContentFrame(frame)} ref={(frame) => sim.mountContentFrame(frame)}

View File

@ -64,7 +64,7 @@ import {
DragNodeObject, DragNodeObject,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import { BuiltinSimulatorRenderer } from './renderer'; import { BuiltinSimulatorRenderer } from './renderer';
import clipboard from '../designer/clipboard'; import { clipboard } from '../designer/clipboard';
import { LiveEditing } from './live-editing/live-editing'; import { LiveEditing } from './live-editing/live-editing';
import { Project } from '../project'; import { Project } from '../project';
import { Scroller } from '../designer/scroller'; import { Scroller } from '../designer/scroller';
@ -192,10 +192,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
this.renderer?.enableAutoRepaintNode(); this.renderer?.enableAutoRepaintNode();
} }
constructor(project: Project) { constructor(project: Project, designer: Designer) {
makeObservable(this); makeObservable(this);
this.project = project; this.project = project;
this.designer = project?.designer; this.designer = designer;
this.scroller = this.designer.createScroller(this.viewport); this.scroller = this.designer.createScroller(this.viewport);
this.autoRender = !engineConfig.get('disableAutoRender', false); this.autoRender = !engineConfig.get('disableAutoRender', false);
this.componentsConsumer = new ResourceConsumer<Asset | undefined>(() => this.componentsAsset); this.componentsConsumer = new ResourceConsumer<Asset | undefined>(() => this.componentsAsset);
@ -418,7 +418,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
this.renderer?.rerender?.(); this.renderer?.rerender?.();
} }
async mountContentFrame(iframe: HTMLIFrameElement | null) { async mountContentFrame(iframe: HTMLIFrameElement | null): Promise<void> {
if (!iframe || this._iframe === iframe) { if (!iframe || this._iframe === iframe) {
return; return;
} }
@ -478,6 +478,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
this.setupEvents(); this.setupEvents();
// bind hotkey & clipboard // bind hotkey & clipboard
const hotkey = this.designer.editor.get('innerHotkey');
hotkey.mount(this._contentWindow); hotkey.mount(this._contentWindow);
focusTracker.mount(this._contentWindow); focusTracker.mount(this._contentWindow);
clipboard.injectCopyPaster(this._contentDocument); clipboard.injectCopyPaster(this._contentDocument);

View File

@ -52,7 +52,8 @@ export class LiveEditing {
const targetElement = event.target as HTMLElement; const targetElement = event.target as HTMLElement;
const { liveTextEditing } = node.componentMeta; const { liveTextEditing } = node.componentMeta;
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
const selected = const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || ''; [npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || '';

View File

@ -62,7 +62,8 @@ export default class InstanceNodeSelector extends React.Component<IProps, IState
if (canClick && typeof node.select === 'function') { if (canClick && typeof node.select === 'function') {
node.select(); node.select();
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
const selected = const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') || [npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||

View File

@ -1,354 +0,0 @@
import { hotkey, Editor, globalContext } from '@alilc/lowcode-editor-core';
import { isFormEvent } from '@alilc/lowcode-utils';
import { focusing } from './focusing';
import { insertChildren, TransformStage } from '../document';
import clipboard from './clipboard';
export function isInLiveEditing() {
if (globalContext.has(Editor)) {
return Boolean(
globalContext.get(Editor).get('designer')?.project?.simulator?.liveEditing?.editing,
);
}
}
/* istanbul ignore next */
function getNextForSelect(next: any, head?: any, parent?: any): any {
if (next) {
if (!head) {
return next;
}
let ret;
if (next.isContainer()) {
const children = next.getChildren() || [];
if (children && !children.isEmpty()) {
ret = getNextForSelect(children.get(0));
if (ret) {
return ret;
}
}
}
ret = getNextForSelect(next.nextSibling);
if (ret) {
return ret;
}
}
if (parent) {
return getNextForSelect(parent.nextSibling, false, parent.getParent());
}
return null;
}
/* istanbul ignore next */
function getPrevForSelect(prev: any, head?: any, parent?: any): any {
if (prev) {
let ret;
if (!head && prev.isContainer()) {
const children = prev.getChildren() || [];
const lastChild = children && !children.isEmpty() ? children.get(children.size - 1) : null;
ret = getPrevForSelect(lastChild);
if (ret) {
return ret;
}
}
if (!head) {
return prev;
}
ret = getPrevForSelect(prev.prevSibling);
if (ret) {
return ret;
}
}
if (parent) {
return parent;
}
return null;
}
// hotkey binding
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
if (isInLiveEditing()) return;
// TODO: use focus-tracker
const doc = focusing.focusDesigner?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const sel = doc.selection;
const topItems = sel.getTopNodes();
// TODO: check can remove
topItems.forEach((node) => {
if (node.canPerformAction('remove')) {
doc.removeNode(node);
}
});
sel.clear();
});
hotkey.bind('escape', (e: KeyboardEvent) => {
// const currentFocus = focusing.current;
if (isInLiveEditing()) return;
const sel = focusing.focusDesigner?.currentDocument?.selection;
if (isFormEvent(e) || !sel) {
return;
}
e.preventDefault();
sel.clear();
// currentFocus.esc();
});
// command + c copy command + x cut
hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
if (isInLiveEditing()) return;
const doc = focusing.focusDesigner?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
let selected = doc.selection.getTopNodes(true);
selected = selected.filter((node) => {
return node.canPerformAction('copy');
});
if (!selected || selected.length < 1) {
return;
}
const componentsMap = {};
const componentsTree = selected.map((item) => item.export(TransformStage.Clone));
// FIXME: clear node.id
const data = { type: 'nodeSchema', componentsMap, componentsTree };
clipboard.setData(data);
const cutMode = action && action.indexOf('x') > 0;
if (cutMode) {
selected.forEach((node) => {
const parentNode = node.getParent();
parentNode?.select();
node.remove();
});
}
});
// command + v paste
hotkey.bind(['command+v', 'ctrl+v'], (e) => {
if (isInLiveEditing()) return;
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !designer || !doc) {
return;
}
/* istanbul ignore next */
clipboard.waitPasteData(e, ({ componentsTree }) => {
if (componentsTree) {
const { target, index } = designer.getSuitableInsertion(componentsTree) || {};
if (!target) {
return;
}
let canAddComponentsTree = componentsTree.filter((i) => {
return doc.checkNestingUp(target, i);
});
if (canAddComponentsTree.length === 0) return;
const nodes = insertChildren(target, canAddComponentsTree, index);
if (nodes) {
doc.selection.selectAll(nodes.map((o) => o.id));
setTimeout(() => designer.activeTracker.track(nodes[0]), 10);
}
}
});
});
// command + z undo
hotkey.bind(['command+z', 'ctrl+z'], (e) => {
if (isInLiveEditing()) return;
const his = focusing.focusDesigner?.currentHistory;
if (isFormEvent(e) || !his) {
return;
}
e.preventDefault();
const selection = focusing.focusDesigner?.currentSelection;
const curSelected = Array.from(selection?.selected);
his.back();
selection?.selectAll(curSelected);
});
// command + shift + z redo
hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e) => {
if (isInLiveEditing()) return;
const his = focusing.focusDesigner?.currentHistory;
if (isFormEvent(e) || !his) {
return;
}
e.preventDefault();
const selection = focusing.focusDesigner?.currentSelection;
const curSelected = Array.from(selection?.selected);
his.forward();
selection?.selectAll(curSelected);
});
// sibling selection
hotkey.bind(['left', 'right'], (e, action) => {
if (isInLiveEditing()) return;
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
const firstNode = selected[0];
const silbing = action === 'left' ? firstNode?.prevSibling : firstNode?.nextSibling;
silbing?.select();
});
hotkey.bind(['up', 'down'], (e, action) => {
if (isInLiveEditing()) return;
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
const firstNode = selected[0];
if (action === 'down') {
const next = getNextForSelect(firstNode, true, firstNode.getParent());
next?.select();
} else if (action === 'up') {
const prev = getPrevForSelect(firstNode, true, firstNode.getParent());
prev?.select();
}
});
hotkey.bind(['option+left', 'option+right'], (e, action) => {
if (isInLiveEditing()) return;
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode.getParent();
if (!parent) return;
const isPrev = action && /(left)$/.test(action);
const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
if (silbing) {
if (isPrev) {
parent.insertBefore(firstNode, silbing);
} else {
parent.insertAfter(firstNode, silbing);
}
firstNode?.select();
}
});
hotkey.bind(['option+up'], (e) => {
if (isInLiveEditing()) return;
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode.getParent();
if (!parent) {
return;
}
const silbing = firstNode.prevSibling;
if (silbing) {
if (silbing.isContainer()) {
const place = silbing.getSuitablePlace(firstNode, null);
place.container.insertAfter(firstNode, place.ref);
} else {
parent.insertBefore(firstNode, silbing);
}
firstNode?.select();
} else {
const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) {
place.container.insertBefore(firstNode, place.ref);
firstNode?.select();
}
}
});
hotkey.bind(['option+down'], (e) => {
if (isInLiveEditing()) return;
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode.getParent();
if (!parent) {
return;
}
const silbing = firstNode.nextSibling;
if (silbing) {
if (silbing.isContainer()) {
// const place = silbing.getSuitablePlace(firstNode, null);
silbing.insertBefore(firstNode, undefined);
// place.container.insertBefore(firstNode, place.ref);
} else {
parent.insertAfter(firstNode, silbing);
}
firstNode?.select();
} else {
const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) {
place.container.insertAfter(firstNode, place.ref);
firstNode?.select();
}
}
});

View File

@ -109,4 +109,4 @@ class Clipboard {
} }
} }
export default new Clipboard(); export const clipboard = new Clipboard();

View File

@ -4,16 +4,18 @@ import BuiltinDragGhostComponent from './drag-ghost';
import { Designer, DesignerProps } from './designer'; import { Designer, DesignerProps } from './designer';
import { ProjectView } from '../project'; import { ProjectView } from '../project';
import './designer.less'; import './designer.less';
import clipboard from './clipboard'; import { clipboard } from './clipboard';
export class DesignerView extends Component<DesignerProps & { export class DesignerView extends Component<DesignerProps & {
designer?: Designer; designer?: Designer;
}> { }> {
readonly designer: Designer; readonly designer: Designer;
readonly name: string;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
const { designer, ...designerProps } = props; const { designer, ...designerProps } = props;
this.name = designer.name;
if (designer) { if (designer) {
this.designer = designer; this.designer = designer;
designer.setProps(designerProps); designer.setProps(designerProps);

View File

@ -74,14 +74,17 @@ export class Designer {
return this.currentDocument?.selection; return this.currentDocument?.selection;
} }
name: string;
constructor(props: DesignerProps) { constructor(props: DesignerProps) {
makeObservable(this); makeObservable(this);
const { editor, shellModelFactory } = props; const { editor, name, shellModelFactory } = props;
this.editor = editor; this.editor = editor;
this.name = name;
this.shellModelFactory = shellModelFactory; this.shellModelFactory = shellModelFactory;
this.setProps(props); this.setProps(props);
this.project = new Project(this, props.defaultSchema); this.project = new Project(this, props.defaultSchema, name);
this.dragon.onDragstart((e) => { this.dragon.onDragstart((e) => {
this.detecting.enable = false; this.detecting.enable = false;
@ -364,12 +367,12 @@ export class Designer {
const { components, packages } = incrementalAssets; const { components, packages } = incrementalAssets;
components && this.buildComponentMetasMap(components); components && this.buildComponentMetasMap(components);
if (packages) { if (packages) {
await this.project.simulator!.setupComponents(packages); await this.project.simulator?.setupComponents(packages);
} }
if (components) { if (components) {
// 合并assets // 合并 assets
let assets = this.editor.get('assets'); let assets = this.editor.get('assets') || {};
let newAssets = megreAssets(assets, incrementalAssets); let newAssets = megreAssets(assets, incrementalAssets);
// 对于 assets 存在需要二次网络下载的过程,必须 await 等待结束之后,再进行事件触发 // 对于 assets 存在需要二次网络下载的过程,必须 await 等待结束之后,再进行事件触发
await this.editor.set('assets', newAssets); await this.editor.set('assets', newAssets);
@ -403,6 +406,21 @@ export class Designer {
return this._simulatorProps || {}; return this._simulatorProps || {};
} }
/**
*
*/
@computed get projectSimulatorProps(): any {
return {
...this.simulatorProps,
project: this.project,
designer: this,
onMount: (simulator: any) => {
this.project.mountSimulator(simulator);
this.editor.set('simulator', simulator);
},
};
}
@obx.ref private _suspensed = false; @obx.ref private _suspensed = false;
get suspensed(): boolean { get suspensed(): boolean {

View File

@ -145,6 +145,8 @@ function isDragEvent(e: any): e is DragEvent {
export class Dragon { export class Dragon {
private sensors: ISensor[] = []; private sensors: ISensor[] = [];
key = Math.random();
/** /**
* current active sensor, * current active sensor,
*/ */
@ -162,10 +164,13 @@ export class Dragon {
return this._dragging; return this._dragging;
} }
name: string;
private emitter = new EventEmitter(); private emitter = new EventEmitter();
constructor(readonly designer: Designer) { constructor(readonly designer: Designer) {
makeObservable(this); makeObservable(this);
this.name = designer.name;
} }
/** /**

View File

@ -1,5 +1,3 @@
import './builtin-hotkey';
export * from './designer'; export * from './designer';
export * from './designer-view'; export * from './designer-view';
export * from './dragon'; export * from './dragon';
@ -9,3 +7,6 @@ export * from './offset-observer';
export * from './scroller'; export * from './scroller';
export * from './setting'; export * from './setting';
export * from './active-tracker'; export * from './active-tracker';
export * from './focusing';
export * from '../document';
export * from './clipboard';

View File

@ -1,4 +1,4 @@
import { obx, computed, makeObservable, runInAction } from '@alilc/lowcode-editor-core'; import { obx, computed, makeObservable, runInAction, Setters } from '@alilc/lowcode-editor-core';
import { GlobalEvent, IEditor, ISetValueOptions } from '@alilc/lowcode-types'; import { GlobalEvent, IEditor, ISetValueOptions } from '@alilc/lowcode-types';
import { uniqueId, isJSExpression } from '@alilc/lowcode-utils'; import { uniqueId, isJSExpression } from '@alilc/lowcode-utils';
import { SettingEntry } from './setting-entry'; import { SettingEntry } from './setting-entry';
@ -18,6 +18,8 @@ export class SettingPropEntry implements SettingEntry {
readonly isSingle: boolean; readonly isSingle: boolean;
readonly setters: Setters;
readonly nodes: Node[]; readonly nodes: Node[];
readonly componentMeta: ComponentMeta | null; readonly componentMeta: ComponentMeta | null;
@ -70,6 +72,7 @@ export class SettingPropEntry implements SettingEntry {
// copy parent static properties // copy parent static properties
this.editor = parent.editor; this.editor = parent.editor;
this.nodes = parent.nodes; this.nodes = parent.nodes;
this.setters = parent.setters;
this.componentMeta = parent.componentMeta; this.componentMeta = parent.componentMeta;
this.isSameComponent = parent.isSameComponent; this.isSameComponent = parent.isSameComponent;
this.isMultiple = parent.isMultiple; this.isMultiple = parent.isMultiple;

View File

@ -72,6 +72,8 @@ export class SettingTopEntry implements SettingEntry {
readonly designer: Designer; readonly designer: Designer;
readonly setters: any;
disposeFunctions: any[] = []; disposeFunctions: any[] = [];
constructor(readonly editor: IEditor, readonly nodes: Node[]) { constructor(readonly editor: IEditor, readonly nodes: Node[]) {
@ -81,6 +83,7 @@ export class SettingTopEntry implements SettingEntry {
this.id = generateSessionId(nodes); this.id = generateSessionId(nodes);
this.first = nodes[0]; this.first = nodes[0];
this.designer = this.first.document.designer; this.designer = this.first.document.designer;
this.setters = editor.get('setters');
// setups // setups
this.setupComponentMeta(); this.setupComponentMeta();

View File

@ -2,7 +2,6 @@
import { isValidElement } from 'react'; import { isValidElement } from 'react';
import { FieldConfig, SetterConfig } from '@alilc/lowcode-types'; import { FieldConfig, SetterConfig } from '@alilc/lowcode-types';
import { isSetterConfig, isDynamicSetter } from '@alilc/lowcode-utils'; import { isSetterConfig, isDynamicSetter } from '@alilc/lowcode-utils';
import { getSetter } from '@alilc/lowcode-editor-core';
import { SettingField } from './setting-field'; import { SettingField } from './setting-field';
function getHotterFromSetter(setter) { function getHotterFromSetter(setter) {
@ -64,7 +63,7 @@ export class Transducer {
isDynamic = dynamicFlag !== false; isDynamic = dynamicFlag !== false;
} }
if (typeof setter === 'string') { if (typeof setter === 'string') {
const { component, isDynamic: dynamicFlag } = getSetter(setter) || {}; const { component, isDynamic: dynamicFlag } = context.setters.getSetter(setter) || {};
setter = component; setter = component;
// 如果在物料配置中声明了,在 registerSetter 没有声明,取物料配置中的声明 // 如果在物料配置中声明了,在 registerSetter 没有声明,取物料配置中的声明
isDynamic = dynamicFlag === undefined ? isDynamic : dynamicFlag !== false; isDynamic = dynamicFlag === undefined ? isDynamic : dynamicFlag !== false;

View File

@ -118,7 +118,8 @@ export class History<T = NodeSchema> {
} }
const cursor = this.session.cursor - 1; const cursor = this.session.cursor - 1;
this.go(cursor); this.go(cursor);
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
if (!editor) { if (!editor) {
return; return;
} }
@ -131,7 +132,8 @@ export class History<T = NodeSchema> {
} }
const cursor = this.session.cursor + 1; const cursor = this.session.cursor + 1;
this.go(cursor); this.go(cursor);
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
if (!editor) { if (!editor) {
return; return;
} }

View File

@ -161,7 +161,9 @@ export class NodeChildren {
const { document } = node; const { document } = node;
/* istanbul ignore next */ /* istanbul ignore next */
if (globalContext.has('editor')) { if (globalContext.has('editor')) {
globalContext.get('editor').emit('node.remove', { node, index: i }); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
editor.emit('node.remove', { node, index: i });
} }
document.unlinkNode(node); document.unlinkNode(node);
document.selection.remove(node.id); document.selection.remove(node.id);
@ -196,12 +198,14 @@ export class NodeChildren {
const i = children.indexOf(node); const i = children.indexOf(node);
if (node.parent) { if (node.parent) {
/* istanbul ignore next */ if (globalContext.has('editor')) {
globalContext.has('editor') && const workSpace = globalContext.get('workSpace');
globalContext.get('editor').emit('node.remove.topLevel', { const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
editor.emit('node.remove.topLevel', {
node, node,
index: node.index, index: node.index,
}); });
}
} }
if (i < 0) { if (i < 0) {
@ -231,7 +235,9 @@ export class NodeChildren {
this.emitter.emit('insert', node); this.emitter.emit('insert', node);
/* istanbul ignore next */ /* istanbul ignore next */
if (globalContext.has('editor')) { if (globalContext.has('editor')) {
globalContext.get('editor').emit('node.add', { node }); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
editor.emit('node.add', { node });
} }
if (useMutator) { if (useMutator) {
this.reportModified(node, this.owner, { type: 'insert' }); this.reportModified(node, this.owner, { type: 'insert' });

View File

@ -1018,7 +1018,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
} }
/** /**
* @deprecated * TODO: replace non standard metas with standard ones.
*/ */
getSuitablePlace(node: Node, ref: any): any { getSuitablePlace(node: Node, ref: any): any {
const focusNode = this.document?.focusNode; const focusNode = this.document?.focusNode;

View File

@ -42,7 +42,6 @@ export default class PluginContext implements ILowCodePluginContext, ILowCodePlu
contextApiAssembler: ILowCodePluginContextApiAssembler, contextApiAssembler: ILowCodePluginContextApiAssembler,
) { ) {
contextApiAssembler.assembleApis(this); contextApiAssembler.assembleApis(this);
this.plugins = plugins;
const { pluginName = 'anonymous' } = options; const { pluginName = 'anonymous' } = options;
this.logger = getLogger({ level: 'warn', bizName: `designer:plugin:${pluginName}` }); this.logger = getLogger({ level: 'warn', bizName: `designer:plugin:${pluginName}` });

View File

@ -27,19 +27,19 @@ const logger = getLogger({ level: 'warn', bizName: 'designer:pluginManager' });
export class LowCodePluginManager implements ILowCodePluginManager { export class LowCodePluginManager implements ILowCodePluginManager {
private plugins: ILowCodePlugin[] = []; private plugins: ILowCodePlugin[] = [];
private pluginsMap: Map<string, ILowCodePlugin> = new Map(); pluginsMap: Map<string, ILowCodePlugin> = new Map();
private pluginPreference?: PluginPreference = new Map(); private pluginPreference?: PluginPreference = new Map();
contextApiAssembler: ILowCodePluginContextApiAssembler; contextApiAssembler: ILowCodePluginContextApiAssembler;
constructor(contextApiAssembler: ILowCodePluginContextApiAssembler) { constructor(contextApiAssembler: ILowCodePluginContextApiAssembler, readonly name = 'unknown') {
this.contextApiAssembler = contextApiAssembler; this.contextApiAssembler = contextApiAssembler;
} }
private _getLowCodePluginContext(options: IPluginContextOptions) { _getLowCodePluginContext = (options: IPluginContextOptions) => {
return new LowCodePluginContext(this, options, this.contextApiAssembler); return new LowCodePluginContext(this, options, this.contextApiAssembler);
} };
isEngineVersionMatched(versionExp: string): boolean { isEngineVersionMatched(versionExp: string): boolean {
const engineVersion = engineConfig.get('ENGINE_VERSION'); const engineVersion = engineConfig.get('ENGINE_VERSION');
@ -107,6 +107,7 @@ export class LowCodePluginManager implements ILowCodePluginManager {
const plugin = new LowCodePlugin(pluginName, this, config, meta); const plugin = new LowCodePlugin(pluginName, this, config, meta);
// support initialization of those plugins which registered after normal initialization by plugin-manager // support initialization of those plugins which registered after normal initialization by plugin-manager
if (registerOptions?.autoInit) { if (registerOptions?.autoInit) {
// debugger
await plugin.init(); await plugin.init();
} }
this.plugins.push(plugin); this.plugins.push(plugin);

View File

@ -10,6 +10,7 @@ import {
CompositeObject, CompositeObject,
ComponentAction, ComponentAction,
MetadataTransducer, MetadataTransducer,
IPublicApiPlugins,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import { EngineConfig } from '@alilc/lowcode-editor-core'; import { EngineConfig } from '@alilc/lowcode-editor-core';
import { Setters } from '../types'; import { Setters } from '../types';
@ -126,6 +127,7 @@ export interface ILowCodePluginContextPrivate {
set event(event: IPublicApiEvent); set event(event: IPublicApiEvent);
set config(config: EngineConfig); set config(config: EngineConfig);
set common(common: IPublicApiCommon); set common(common: IPublicApiCommon);
set plugins(plugins: IPublicApiPlugins);
} }
export interface ILowCodePluginContextApiAssembler { export interface ILowCodePluginContextApiAssembler {
assembleApis: (context: ILowCodePluginContextPrivate) => void; assembleApis: (context: ILowCodePluginContextPrivate) => void;

View File

@ -26,8 +26,7 @@ export class ProjectView extends Component<{ designer: Designer }> {
} }
render() { render() {
const { designer } = this.props; const { designer } = this.props;
const { project } = designer; const { project, projectSimulatorProps: simulatorProps } = designer;
const { simulatorProps } = project;
const Simulator = designer.simulatorComponent || BuiltinSimulatorHostView; const Simulator = designer.simulatorComponent || BuiltinSimulatorHostView;
const Loading = engineConfig.get('loadingComponent', BuiltinLoading); const Loading = engineConfig.get('loadingComponent', BuiltinLoading);

View File

@ -13,6 +13,10 @@
padding-top: 50%; padding-top: 50%;
} }
.lc-simulator {
background: rgb(237, 239, 243);
}
.lc-simulator-shell { .lc-simulator-shell {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { obx, computed, makeObservable, action } from '@alilc/lowcode-editor-core'; import { obx, computed, makeObservable, action, engineConfig } from '@alilc/lowcode-editor-core';
import { Designer } from '../designer'; import { Designer } from '../designer';
import { DocumentModel, isDocumentModel } from '../document'; import { DocumentModel, isDocumentModel } from '../document';
import { import {
@ -32,9 +32,11 @@ export class Project {
return this._simulator || null; return this._simulator || null;
} }
key = Math.random();
// TODO: 考虑项目级别 History // TODO: 考虑项目级别 History
constructor(readonly designer: Designer, schema?: ProjectSchema) { constructor(readonly designer: Designer, schema?: ProjectSchema, public name = 'unknown') {
makeObservable(this); makeObservable(this);
this.load(schema); this.load(schema);
} }
@ -301,25 +303,9 @@ export class Project {
}); });
} }
/** mountSimulator(simulator: ISimulatorHost) {
*
*/
@computed get simulatorProps(): object {
let { simulatorProps } = this.designer;
if (typeof simulatorProps === 'function') {
simulatorProps = simulatorProps(this);
}
return {
...simulatorProps,
project: this,
onMount: this.mountSimulator.bind(this),
};
}
private mountSimulator(simulator: ISimulatorHost) {
// TODO: 多设备 simulator 支持 // TODO: 多设备 simulator 支持
this._simulator = simulator; this._simulator = simulator;
this.designer.editor.set('simulator', simulator);
this.emitter.emit('lowcode_engine_simulator_ready', simulator); this.emitter.emit('lowcode_engine_simulator_ready', simulator);
} }

View File

@ -44,32 +44,72 @@ function getInitialFromSetter(setter: any) {
) || null; // eslint-disable-line ) || null; // eslint-disable-line
} }
export function getSetter(type: string): RegisteredSetter | null { export class Setters {
return settersMap.get(type) || null; constructor(public name: string = 'unknown') {}
}
export function getSettersMap() {
return settersMap;
}
export function createSetterContent(setter: any, props: Record<string, any>): ReactNode { settersMap = new Map<string, RegisteredSetter & {
if (typeof setter === 'string') { type: string;
setter = getSetter(setter); }>();
if (!setter) {
return null; getSetter = (type: string): RegisteredSetter | null => {
return this.settersMap.get(type) || null;
};
registerSetter = (
typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter },
setter?: CustomView | RegisteredSetter,
) => {
if (typeof typeOrMaps === 'object') {
Object.keys(typeOrMaps).forEach(type => {
this.registerSetter(type, typeOrMaps[type]);
});
return;
} }
if (setter.defaultProps) { if (!setter) {
props = { return;
...setter.defaultProps, }
...props, if (isCustomView(setter)) {
setter = {
component: setter,
// todo: intl
title: (setter as any).displayName || (setter as any).name || 'CustomSetter',
}; };
} }
setter = setter.component; if (!setter.initialValue) {
} const initial = getInitialFromSetter(setter.component);
if (initial) {
setter.initialValue = (field: any) => {
return initial.call(field, field.getValue());
};
}
}
this.settersMap.set(typeOrMaps, { type: typeOrMaps, ...setter });
};
// Fusion的表单组件都是通过 'value' in props 来判断是否使用 defaultValue getSettersMap = () => {
if ('value' in props && typeof props.value === 'undefined') { return this.settersMap;
delete props.value; };
}
return createContent(setter, props); createSetterContent = (setter: any, props: Record<string, any>): ReactNode => {
} if (typeof setter === 'string') {
setter = this.getSetter(setter);
if (!setter) {
return null;
}
if (setter.defaultProps) {
props = {
...setter.defaultProps,
...props,
};
}
setter = setter.component;
}
// Fusion的表单组件都是通过 'value' in props 来判断是否使用 defaultValue
if ('value' in props && typeof props.value === 'undefined') {
delete props.value;
}
return createContent(setter, props);
};
}

View File

@ -51,6 +51,10 @@ export declare interface Editor extends StrictEventEmitter<EventEmitter, GlobalE
// eslint-disable-next-line no-redeclare // eslint-disable-next-line no-redeclare
export class Editor extends (EventEmitter as any) implements IEditor { export class Editor extends (EventEmitter as any) implements IEditor {
constructor(public name: string = 'unknown', public workspaceMode: boolean = false) {
// eslint-disable-next-line constructor-super
super();
}
/** /**
* Ioc Container * Ioc Container
*/ */
@ -114,11 +118,47 @@ export class Editor extends (EventEmitter as any) implements IEditor {
if (remoteComponentDescriptions && remoteComponentDescriptions.length) { if (remoteComponentDescriptions && remoteComponentDescriptions.length) {
await Promise.all( await Promise.all(
remoteComponentDescriptions.map(async (component: any) => { remoteComponentDescriptions.map(async (component: any) => {
const { exportName, url } = component; const { exportName, url, npm } = component;
await (new AssetLoader()).load(url); await (new AssetLoader()).load(url);
function setAssetsComponent(component: any, extraNpmInfo: any = {}) {
const components = component.components;
if (Array.isArray(components)) {
components.forEach(d => {
assets.components = assets.components.concat({
npm: {
...npm,
...extraNpmInfo,
},
...d,
} || []);
});
return;
}
assets.components = assets.components.concat({
npm: {
...npm,
...extraNpmInfo,
},
...component.components,
} || []);
// assets.componentList = assets.componentList.concat(component.componentList || []);
}
function setArrayAssets(value: any[], preExportName: string = '', preSubName: string = '') {
value.forEach((d: any, i: number) => {
const exportName = [preExportName, i.toString()].filter(d => !!d).join('.');
const subName = [preSubName, i.toString()].filter(d => !!d).join('.');
Array.isArray(d) ? setArrayAssets(d, exportName, subName) : setAssetsComponent(d, {
exportName,
subName,
});
});
}
if (window[exportName]) { if (window[exportName]) {
assets.components = assets.components.concat((window[exportName] as any).components || []); if (Array.isArray(window[exportName])) {
assets.componentList = assets.componentList?.concat((window[exportName] as any).componentList || []); setArrayAssets(window[exportName] as any);
} else {
setAssetsComponent(window[exportName] as any);
}
} }
return window[exportName]; return window[exportName];
}), }),

View File

@ -330,7 +330,8 @@ function getKeyInfo(combination: string, action?: string): KeyInfo {
*/ */
function fireCallback(callback: HotkeyCallback, e: KeyboardEvent, combo?: string, sequence?: string): void { function fireCallback(callback: HotkeyCallback, e: KeyboardEvent, combo?: string, sequence?: string): void {
try { try {
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const designer = editor.get('designer'); const designer = editor.get('designer');
const node = designer?.currentSelection?.getNodes()?.[0]; const node = designer?.currentSelection?.getNodes()?.[0];
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
@ -353,7 +354,7 @@ function fireCallback(callback: HotkeyCallback, e: KeyboardEvent, combo?: string
} }
export class Hotkey { export class Hotkey {
private callBacks: HotkeyCallbacks = {}; callBacks: HotkeyCallbacks = {};
private directMap: HotkeyDirectMap = {}; private directMap: HotkeyDirectMap = {};
@ -367,6 +368,16 @@ export class Hotkey {
private nextExpectedAction: boolean | string = false; private nextExpectedAction: boolean | string = false;
private isActivate = true;
constructor(readonly name: string = 'unknown') {
this.mount(window);
}
activate(activate: boolean): void {
this.isActivate = activate;
}
mount(window: Window) { mount(window: Window) {
const { document } = window; const { document } = window;
const handleKeyEvent = this.handleKeyEvent.bind(this); const handleKeyEvent = this.handleKeyEvent.bind(this);
@ -541,6 +552,9 @@ export class Hotkey {
} }
private handleKeyEvent(e: KeyboardEvent): void { private handleKeyEvent(e: KeyboardEvent): void {
if (!this.isActivate) {
return;
}
const character = characterFromEvent(e); const character = characterFromEvent(e);
// no character found then stop // no character found then stop
@ -644,5 +658,5 @@ export class Hotkey {
} }
} }
export const hotkey = new Hotkey(); // export const hotkey = new Hotkey();
hotkey.mount(window); // hotkey.mount(window);

View File

@ -1,5 +1,5 @@
import { Component, MouseEvent, Fragment } from 'react'; import { Component, MouseEvent, Fragment } from 'react';
import { shallowIntl, createSetterContent, observer, obx, engineConfig, runInAction, globalContext } from '@alilc/lowcode-editor-core'; import { shallowIntl, observer, obx, engineConfig, runInAction, globalContext } from '@alilc/lowcode-editor-core';
import { createContent, isJSSlot, isSetterConfig } from '@alilc/lowcode-utils'; import { createContent, isJSSlot, isSetterConfig } from '@alilc/lowcode-utils';
import { Skeleton } from '@alilc/lowcode-editor-skeleton'; import { Skeleton } from '@alilc/lowcode-editor-skeleton';
import { CustomView } from '@alilc/lowcode-types'; import { CustomView } from '@alilc/lowcode-types';
@ -8,6 +8,7 @@ import { createField } from '../field';
import PopupService, { PopupPipe } from '../popup'; import PopupService, { PopupPipe } from '../popup';
import { SkeletonContext } from '../../context'; import { SkeletonContext } from '../../context';
import { intl } from '../../locale'; import { intl } from '../../locale';
import { Setters } from '@alilc/lowcode-shell';
function isStandardComponent(componentMeta: ComponentMeta | null) { function isStandardComponent(componentMeta: ComponentMeta | null) {
if (!componentMeta) return false; if (!componentMeta) return false;
@ -38,6 +39,8 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
stageName: string | undefined; stageName: string | undefined;
setters: Setters;
constructor(props: SettingFieldViewProps) { constructor(props: SettingFieldViewProps) {
super(props); super(props);
@ -45,8 +48,10 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
const { extraProps } = field; const { extraProps } = field;
const { display } = extraProps; const { display } = extraProps;
const editor = globalContext.get('editor'); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const { stages } = editor.get('skeleton') as Skeleton; const { stages } = editor.get('skeleton') as Skeleton;
this.setters = editor.get('setters');
let stageName; let stageName;
if (display === 'entry') { if (display === 'entry') {
runInAction(() => { runInAction(() => {
@ -192,7 +197,6 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
setterProps = {}, setterProps = {},
setterType, setterType,
initialValue = null, initialValue = null,
} = this.setterInfo; } = this.setterInfo;
const value = this.value; const value = this.value;
@ -215,7 +219,7 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
...extraProps, ...extraProps,
}, },
!stageName && !stageName &&
createSetterContent(setterType, { this.setters.createSetterContent(setterType, {
...shallowIntl(setterProps), ...shallowIntl(setterProps),
forceInline: extraProps.forceInline, forceInline: extraProps.forceInline,
key: field.id, key: field.id,
@ -268,7 +272,8 @@ class SettingGroupView extends Component<SettingGroupViewProps> {
const { field } = this.props; const { field } = this.props;
const { extraProps } = field; const { extraProps } = field;
const { display } = extraProps; const { display } = extraProps;
const editor = globalContext.get('editor'); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const { stages } = editor.get('skeleton') as Skeleton; const { stages } = editor.get('skeleton') as Skeleton;
// const items = field.items; // const items = field.items;

View File

@ -14,19 +14,25 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any
state = { state = {
shouldIgnoreRoot: false, shouldIgnoreRoot: false,
}; };
private main = new SettingsMain(globalContext.get('editor')); private main;
@obx.ref private _activeKey?: any; @obx.ref private _activeKey?: any;
constructor(props) { constructor(props) {
super(props); super(props);
makeObservable(this); makeObservable(this);
const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
this.main = new SettingsMain(editor);
} }
componentDidMount() { componentDidMount() {
this.setShouldIgnoreRoot(); this.setShouldIgnoreRoot();
globalContext.get('editor').on('designer.selection.change', () => { const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
editor.on('designer.selection.change', () => {
if (!engineConfig.get('stayOnTheSameSettingTab', false)) { if (!engineConfig.get('stayOnTheSameSettingTab', false)) {
this._activeKey = null; this._activeKey = null;
} }
@ -65,7 +71,8 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any
); );
} }
const editor = globalContext.get('editor'); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const designer = editor.get('designer'); const designer = editor.get('designer');
const current = designer?.currentSelection?.getNodes()?.[0]; const current = designer?.currentSelection?.getNodes()?.[0];
let node: Node | null = settings.first; let node: Node | null = settings.first;
@ -128,7 +135,8 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any
render() { render() {
const { settings } = this.main; const { settings } = this.main;
const editor = globalContext.get('editor'); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
if (!settings) { if (!settings) {
// 未选中节点,提示选中 或者 显示根节点设置 // 未选中节点,提示选中 或者 显示根节点设置
return ( return (

View File

@ -116,12 +116,14 @@ export class DraggableLineView extends Component<{ panel: Panel }> {
} }
// 抛出事件,对于有些需要 panel 插件随着 度变化进行再次渲染的由panel插件内部监听事件实现 // 抛出事件,对于有些需要 panel 插件随着 度变化进行再次渲染的由panel插件内部监听事件实现
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
editor?.emit('dockpane.drag', width); editor?.emit('dockpane.drag', width);
} }
onDragChange(type: 'start' | 'end') { onDragChange(type: 'start' | 'end') {
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
editor?.emit('dockpane.dragchange', type); editor?.emit('dockpane.dragchange', type);
// builtinSimulator 屏蔽掉 鼠标事件 // builtinSimulator 屏蔽掉 鼠标事件
editor?.emit('designer.builtinSimulator.disabledEvents', type === 'start'); editor?.emit('designer.builtinSimulator.disabledEvents', type === 'start');
@ -185,7 +187,8 @@ export class TitledPanelView extends Component<{ panel: Panel; area?: string }>
if (!panel.inited) { if (!panel.inited) {
return null; return null;
} }
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const panelName = area ? `${area}-${panel.name}` : panel.name; const panelName = area ? `${area}-${panel.name}` : panel.name;
editor?.emit('skeleton.panel.toggle', { editor?.emit('skeleton.panel.toggle', {
name: panelName || '', name: panelName || '',
@ -247,7 +250,8 @@ export class PanelView extends Component<{
if (!panel.inited) { if (!panel.inited) {
return null; return null;
} }
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const panelName = area ? `${area}-${panel.name}` : panel.name; const panelName = area ? `${area}-${panel.name}` : panel.name;
editor?.emit('skeleton.panel.toggle', { editor?.emit('skeleton.panel.toggle', {
name: panelName || '', name: panelName || '',

View File

@ -65,7 +65,6 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
this.props.area.setVisible(false); this.props.area.setVisible(false);
}, },
onBlur: () => { onBlur: () => {
// debugger
this.props.area.setVisible(false); this.props.area.setVisible(false);
}, },
}); });

View File

@ -133,11 +133,38 @@ body {
} }
} }
.lc-workbench { .workspace-engine-main {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: #edeff3; background-color: #edeff3;
.lc-workbench {
}
.engine-editor-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
&.active {
z-index: 999;
}
}
}
.lc-workbench {
&.engine-main {
height: 100%;
display: flex;
flex-direction: column;
background-color: #edeff3;
}
.lc-top-area { .lc-top-area {
height: var(--top-area-height); height: var(--top-area-height);
background-color: var(--color-pane-background); background-color: var(--color-pane-background);
@ -349,6 +376,7 @@ body {
} }
.lc-main-area { .lc-main-area {
flex: 1; flex: 1;
background-color: #edeff3;
} }
.lc-bottom-area { .lc-bottom-area {
height: var(--bottom-area-height); height: var(--bottom-area-height);

View File

@ -1,4 +1,4 @@
import { Editor, action, makeObservable } from '@alilc/lowcode-editor-core'; import { Editor, action, makeObservable, obx } from '@alilc/lowcode-editor-core';
import { import {
DockConfig, DockConfig,
PanelConfig, PanelConfig,
@ -56,13 +56,13 @@ export class Skeleton {
readonly rightArea: Area<PanelConfig, Panel>; readonly rightArea: Area<PanelConfig, Panel>;
readonly mainArea: Area<WidgetConfig | PanelConfig, Widget | Panel>; @obx readonly mainArea: Area<WidgetConfig | PanelConfig, Widget | Panel>;
readonly bottomArea: Area<PanelConfig, Panel>; readonly bottomArea: Area<PanelConfig, Panel>;
readonly stages: Area<StageConfig, Stage>; readonly stages: Area<StageConfig, Stage>;
constructor(readonly editor: Editor) { constructor(readonly editor: Editor, readonly name: string = 'unknown') {
makeObservable(this); makeObservable(this);
this.leftArea = new Area( this.leftArea = new Area(
this, this,
@ -366,6 +366,12 @@ export class Skeleton {
...this.parseConfig(config), ...this.parseConfig(config),
...extraConfig, ...extraConfig,
}; };
parsedConfig.contentProps = {
context: this.editor.get('innerPlugins')?._getLowCodePluginContext({
pluginName: 'any',
}),
...(parsedConfig.contentProps || {}),
};
let { area } = parsedConfig; let { area } = parsedConfig;
if (!area) { if (!area) {
if (parsedConfig.type === 'Panel') { if (parsedConfig.type === 'Panel') {

View File

@ -1,23 +1,32 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { createElement } from 'react'; import { createElement } from 'react';
import { render, unmountComponentAtNode } from 'react-dom'; import { render, unmountComponentAtNode } from 'react-dom';
import { globalContext, Editor, engineConfig, EngineOptions } from '@alilc/lowcode-editor-core'; import {
globalContext,
Editor,
engineConfig,
Setters as InnerSetters,
Hotkey as InnerHotkey,
} from '@alilc/lowcode-editor-core';
import { EngineOptions,
IPublicModelDocumentModel,
} from '@alilc/lowcode-types';
import { import {
Designer, Designer,
LowCodePluginManager, LowCodePluginManager,
ILowCodePluginContext,
ILowCodePluginContextPrivate, ILowCodePluginContextPrivate,
ILowCodePluginContextApiAssembler, ILowCodePluginContextApiAssembler,
PluginPreference, PluginPreference,
} from '@alilc/lowcode-designer'; } from '@alilc/lowcode-designer';
import { import {
Skeleton as InnerSkeleton, Skeleton as InnerSkeleton,
SettingsPrimaryPane,
registerDefaults, registerDefaults,
} from '@alilc/lowcode-editor-skeleton'; } from '@alilc/lowcode-editor-skeleton';
import {
WorkSpace,
Workbench as WorkSpaceWorkbench,
} from '@alilc/lowcode-workspace';
import Outline, { OutlineBackupPane, getTreeMaster } from '@alilc/lowcode-plugin-outline-pane';
import DesignerPlugin from '@alilc/lowcode-plugin-designer';
import { import {
Hotkey, Hotkey,
Project, Project,
@ -25,6 +34,7 @@ import {
Setters, Setters,
Material, Material,
Event, Event,
Plugins,
DocumentModel, DocumentModel,
Common, Common,
} from '@alilc/lowcode-shell'; } from '@alilc/lowcode-shell';
@ -32,7 +42,11 @@ import { getLogger, isPlainObject } from '@alilc/lowcode-utils';
import './modules/live-editing'; import './modules/live-editing';
import classes from './modules/classes'; import classes from './modules/classes';
import symbols from './modules/symbols'; import symbols from './modules/symbols';
import { componentMetaParser } from './inner-plugins/component-meta-parser';
import { setterRegistry } from './inner-plugins/setter-registry';
import { defaultPanelRegistry } from './inner-plugins/default-panel-registry';
import { shellModelFactory } from './modules/shell-model-factory'; import { shellModelFactory } from './modules/shell-model-factory';
import { builtinHotkey } from './inner-plugins/builtin-hotkey';
export * from './modules/skeleton-types'; export * from './modules/skeleton-types';
export * from './modules/designer-types'; export * from './modules/designer-types';
@ -40,9 +54,11 @@ export * from './modules/lowcode-types';
registerDefaults(); registerDefaults();
const workSpace = new WorkSpace();
const editor = new Editor(); const editor = new Editor();
globalContext.register(editor, Editor); globalContext.register(editor, Editor);
globalContext.register(editor, 'editor'); globalContext.register(editor, 'editor');
globalContext.register(workSpace, 'workSpace');
const innerSkeleton = new InnerSkeleton(editor); const innerSkeleton = new InnerSkeleton(editor);
editor.set('skeleton' as any, innerSkeleton); editor.set('skeleton' as any, innerSkeleton);
@ -51,15 +67,23 @@ const designer = new Designer({ editor, shellModelFactory });
editor.set('designer' as any, designer); editor.set('designer' as any, designer);
const { project: innerProject } = designer; const { project: innerProject } = designer;
const hotkey = new Hotkey(); const innerHotkey = new InnerHotkey();
const hotkey = new Hotkey(innerHotkey);
const project = new Project(innerProject); const project = new Project(innerProject);
const skeleton = new Skeleton(innerSkeleton); const skeleton = new Skeleton(innerSkeleton);
const setters = new Setters(); const innerSetters = new InnerSetters();
const setters = new Setters(innerSetters);
const material = new Material(editor); const material = new Material(editor);
editor.set('project', project);
editor.set('setters' as any, setters);
editor.set('material', material);
editor.set('innerHotkey', innerHotkey);
const config = engineConfig; const config = engineConfig;
const event = new Event(editor, { prefix: 'common' }); const event = new Event(editor, { prefix: 'common' });
const logger = getLogger({ level: 'warn', bizName: 'common' }); const logger = getLogger({ level: 'warn', bizName: 'common' });
const common = new Common(editor, innerSkeleton); const common = new Common(editor, innerSkeleton);
let plugins: Plugins;
const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = { const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
assembleApis: (context: ILowCodePluginContextPrivate) => { assembleApis: (context: ILowCodePluginContextPrivate) => {
@ -71,9 +95,13 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
context.event = event; context.event = event;
context.config = config; context.config = config;
context.common = common; context.common = common;
context.plugins = plugins;
}, },
}; };
const plugins = new LowCodePluginManager(pluginContextApiAssembler).toProxy();
const innerPlugins = new LowCodePluginManager(pluginContextApiAssembler);
plugins = new Plugins(innerPlugins).toProxy();
editor.set('innerPlugins' as any, innerPlugins);
editor.set('plugins' as any, plugins); editor.set('plugins' as any, plugins);
export { export {
@ -89,6 +117,7 @@ export {
common, common,
// 兼容原 editor 的事件功能 // 兼容原 editor 的事件功能
event as editor, event as editor,
workSpace,
}; };
// declare this is open-source version // declare this is open-source version
export const isOpenSource = true; export const isOpenSource = true;
@ -96,100 +125,7 @@ export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
symbols, symbols,
classes, classes,
}; };
engineConfig.set('isOpenSource', isOpenSource); config.set('isOpenSource', isOpenSource);
// 注册一批内置插件
(async function registerPlugins() {
// 处理 editor.set('assets'),将组件元数据创建好
const componentMetaParser = (ctx: ILowCodePluginContext) => {
return {
init() {
editor.onGot('assets', (assets: any) => {
const { components = [] } = assets;
designer.buildComponentMetasMap(components);
});
},
};
};
componentMetaParser.pluginName = '___component_meta_parser___';
await plugins.register(componentMetaParser);
// 注册默认的 setters
const setterRegistry = (ctx: ILowCodePluginContext) => {
return {
init() {
if (engineConfig.get('disableDefaultSetters')) return;
const builtinSetters = require('@alilc/lowcode-engine-ext')?.setters;
if (builtinSetters) {
ctx.setters.registerSetter(builtinSetters);
}
},
};
};
setterRegistry.pluginName = '___setter_registry___';
await plugins.register(setterRegistry);
// 注册默认的面板
const defaultPanelRegistry = (ctx: ILowCodePluginContext) => {
return {
init() {
skeleton.add({
area: 'mainArea',
name: 'designer',
type: 'Widget',
content: DesignerPlugin,
});
if (!engineConfig.get('disableDefaultSettingPanel')) {
skeleton.add({
area: 'rightArea',
name: 'settingsPane',
type: 'Panel',
content: SettingsPrimaryPane,
props: {
ignoreRoot: true,
},
});
}
// by default in float area;
let isInFloatArea = true;
const hasPreferenceForOutline = editor
?.getPreference()
?.contains('outline-pane-pinned-status-isFloat', 'skeleton');
if (hasPreferenceForOutline) {
isInFloatArea = editor
?.getPreference()
?.get('outline-pane-pinned-status-isFloat', 'skeleton');
}
skeleton.add({
area: 'leftArea',
name: 'outlinePane',
type: 'PanelDock',
content: Outline,
panelProps: {
area: isInFloatArea ? 'leftFloatArea' : 'leftFixedArea',
keepVisibleWhileDragging: true,
...engineConfig.get('defaultOutlinePaneProps'),
},
});
skeleton.add({
area: 'rightArea',
name: 'backupOutline',
type: 'Panel',
props: {
condition: () => {
return designer.dragon.dragging && !getTreeMaster(designer).hasVisibleTreeBoard();
},
},
content: OutlineBackupPane,
});
},
};
};
defaultPanelRegistry.pluginName = '___default_panel___';
await plugins.register(defaultPanelRegistry);
})();
// container which will host LowCodeEngine DOM // container which will host LowCodeEngine DOM
let engineContainer: HTMLElement; let engineContainer: HTMLElement;
@ -202,6 +138,7 @@ export async function init(
options?: EngineOptions, options?: EngineOptions,
pluginPreference?: PluginPreference, pluginPreference?: PluginPreference,
) { ) {
await destroy(); await destroy();
let engineOptions = null; let engineOptions = null;
if (isPlainObject(container)) { if (isPlainObject(container)) {
@ -220,9 +157,42 @@ export async function init(
} }
engineConfig.setEngineOptions(engineOptions as any); engineConfig.setEngineOptions(engineOptions as any);
if (options && options.enableWorkspaceMode) {
render(
createElement(WorkSpaceWorkbench, {
workSpace,
className: 'engine-main',
topAreaItemClassName: 'engine-actionitem',
}),
engineContainer,
);
workSpace.setActive(true);
return;
}
// 注册一批内置插件
await plugins.register(componentMetaParser(designer));
await plugins.register(setterRegistry);
await plugins.register(defaultPanelRegistry(editor, designer));
await plugins.register(builtinHotkey);
await plugins.init(pluginPreference as any); await plugins.init(pluginPreference as any);
const { Workbench } = common.skeletonCabin; const { Workbench } = common.skeletonCabin;
if (options && options.enableWorkspaceMode) {
render(
createElement(WorkSpaceWorkbench, {
workSpace,
// skeleton: workSpace.skeleton,
className: 'engine-main',
topAreaItemClassName: 'engine-actionitem',
}),
engineContainer,
);
return;
}
render( render(
createElement(Workbench, { createElement(Workbench, {
skeleton: innerSkeleton, skeleton: innerSkeleton,
@ -237,7 +207,7 @@ export async function destroy() {
// remove all documents // remove all documents
const { documents } = project; const { documents } = project;
if (Array.isArray(documents) && documents.length > 0) { if (Array.isArray(documents) && documents.length > 0) {
documents.forEach(((doc: DocumentModel) => project.removeDocument(doc))); documents.forEach(((doc: IPublicModelDocumentModel) => project.removeDocument(doc)));
} }
// TODO: delete plugins except for core plugins // TODO: delete plugins except for core plugins

View File

@ -0,0 +1,407 @@
import { IPublicModelNode } from './../../../types/src/shell/model/node';
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
import { isFormEvent } from '@alilc/lowcode-utils';
import {
focusing,
insertChildren,
TransformStage,
clipboard,
ILowCodePluginContext,
} from '@alilc/lowcode-designer';
export function isInLiveEditing() {
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return Boolean(
workSpace.window.editor.get('designer')?.project?.simulator?.liveEditing?.editing,
);
}
if (globalContext.has(Editor)) {
return Boolean(
globalContext.get(Editor).get('designer')?.project?.simulator?.liveEditing?.editing,
);
}
}
/* istanbul ignore next */
function getNextForSelect(next: IPublicModelNode | null, head?: any, parent?: IPublicModelNode | null): any {
if (next) {
if (!head) {
return next;
}
let ret;
if (next.isContainer) {
const children = next.children;
if (children && !children.isEmpty) {
ret = getNextForSelect(children.get(0));
if (ret) {
return ret;
}
}
}
ret = getNextForSelect(next.nextSibling);
if (ret) {
return ret;
}
}
if (parent) {
return getNextForSelect(parent.nextSibling, false, parent?.parent);
}
return null;
}
/* istanbul ignore next */
function getPrevForSelect(prev: IPublicModelNode | null, head?: any, parent?: IPublicModelNode | null): any {
if (prev) {
let ret;
if (!head && prev.isContainer) {
const children = prev.children;
const lastChild = children && !children.isEmpty ? children.get(children.size - 1) : null;
ret = getPrevForSelect(lastChild);
if (ret) {
return ret;
}
}
if (!head) {
return prev;
}
ret = getPrevForSelect(prev.prevSibling);
if (ret) {
return ret;
}
}
if (parent) {
return parent;
}
return null;
}
// 注册默认的 setters
export const builtinHotkey = (ctx: ILowCodePluginContext) => {
return {
init() {
const { hotkey, project, logger } = ctx;
// hotkey binding
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
// TODO: use focus-tracker
const doc = project.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const sel = doc.selection;
const topItems = sel.getTopNodes();
// TODO: check can remove
topItems.forEach((node) => {
if (node?.canPerformAction('remove')) {
node && doc.removeNode(node);
}
});
sel.clear();
});
hotkey.bind('escape', (e: KeyboardEvent, action) => {
logger.info(`action ${action} is triggered`);
// const currentFocus = focusing.current;
if (isInLiveEditing()) {
return;
}
const sel = focusing.focusDesigner?.currentDocument?.selection;
if (isFormEvent(e) || !sel) {
return;
}
e.preventDefault();
sel.clear();
// currentFocus.esc();
});
// command + c copy command + x cut
hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const doc = project.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
let selected = doc.selection.getTopNodes(true);
selected = selected.filter((node) => {
return node?.canPerformAction('copy');
});
if (!selected || selected.length < 1) {
return;
}
const componentsMap = {};
const componentsTree = selected.map((item) => item?.exportSchema(TransformStage.Clone));
// FIXME: clear node.id
const data = { type: 'nodeSchema', componentsMap, componentsTree };
clipboard.setData(data);
const cutMode = action && action.indexOf('x') > 0;
if (cutMode) {
selected.forEach((node) => {
const parentNode = node?.parent;
parentNode?.select();
node?.remove();
});
}
});
// command + v paste
hotkey.bind(['command+v', 'ctrl+v'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
if (isInLiveEditing()) return;
// TODO
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !designer || !doc) {
return;
}
/* istanbul ignore next */
clipboard.waitPasteData(e, ({ componentsTree }) => {
if (componentsTree) {
const { target, index } = designer.getSuitableInsertion(componentsTree) || {};
if (!target) {
return;
}
let canAddComponentsTree = componentsTree.filter((i) => {
return doc.checkNestingUp(target, i);
});
if (canAddComponentsTree.length === 0) return;
const nodes = insertChildren(target, canAddComponentsTree, index);
if (nodes) {
doc.selection.selectAll(nodes.map((o) => o.id));
setTimeout(() => designer.activeTracker.track(nodes[0]), 10);
}
}
});
});
// command + z undo
hotkey.bind(['command+z', 'ctrl+z'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const history = project.currentDocument?.history;
if (isFormEvent(e) || !history) {
return;
}
e.preventDefault();
const selection = project.currentDocument?.selection;
const curSelected = selection?.selected && Array.from(selection?.selected);
history.back();
selection?.selectAll(curSelected);
});
// command + shift + z redo
hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const history = project.currentDocument?.history;
if (isFormEvent(e) || !history) {
return;
}
e.preventDefault();
const selection = project.currentDocument?.selection;
const curSelected = selection?.selected && Array.from(selection?.selected);
history.forward();
selection?.selectAll(curSelected);
});
// sibling selection
hotkey.bind(['left', 'right'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const doc = project.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
const firstNode = selected[0];
const silbing = action === 'left' ? firstNode?.prevSibling : firstNode?.nextSibling;
silbing?.select();
});
hotkey.bind(['up', 'down'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const doc = project.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
const firstNode = selected[0];
if (action === 'down') {
const next = getNextForSelect(firstNode, true, firstNode?.parent);
next?.select();
} else if (action === 'up') {
const prev = getPrevForSelect(firstNode, true, firstNode?.parent);
prev?.select();
}
});
hotkey.bind(['option+left', 'option+right'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const doc = project.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode?.parent;
if (!parent) return;
const isPrev = action && /(left)$/.test(action);
const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
if (silbing) {
if (isPrev) {
parent.insertBefore(firstNode, silbing);
} else {
parent.insertAfter(firstNode, silbing);
}
firstNode?.select();
}
});
hotkey.bind(['option+up'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const doc = project.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode?.parent;
if (!parent) {
return;
}
const silbing = firstNode.prevSibling;
if (silbing) {
if (silbing.isContainer) {
const place = silbing.getSuitablePlace(firstNode, null);
silbing.insertAfter(place, place.ref);
} else {
parent.insertBefore(firstNode, silbing);
}
firstNode?.select();
} else {
const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) {
place.container.insertBefore(firstNode, place.ref);
firstNode?.select();
}
}
});
hotkey.bind(['option+down'], (e, action) => {
logger.info(`action ${action} is triggered`);
if (isInLiveEditing()) {
return;
}
const doc = project.getCurrentDocument();
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动,原 ve 里是用 node.canOperating() 来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode?.parent;
if (!parent) {
return;
}
const silbing = firstNode.nextSibling;
if (silbing) {
if (silbing.isContainer) {
// const place = silbing.getSuitablePlace(firstNode, null);
silbing.insertBefore(firstNode, undefined);
// place.container.insertBefore(firstNode, place.ref);
} else {
parent.insertAfter(firstNode, silbing);
}
firstNode?.select();
} else {
const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) {
place.container.insertAfter(firstNode, place.ref);
firstNode?.select();
}
}
});
},
};
};
builtinHotkey.pluginName = '___builtin_hotkey___';

View File

@ -0,0 +1,20 @@
import { ILowCodePluginContext } from '@alilc/lowcode-designer';
export const componentMetaParser = (designer: any) => {
const fun = (ctx: ILowCodePluginContext) => {
return {
init() {
const { material } = ctx;
material.onChangeAssets(() => {
const assets = material.getAssets();
const { components = [] } = assets;
designer.buildComponentMetasMap(components);
});
},
};
};
fun.pluginName = '___component_meta_parser___';
return fun;
};

View File

@ -0,0 +1,93 @@
import { ILowCodePluginContext } from '@alilc/lowcode-designer';
import { SettingsPrimaryPane } from '@alilc/lowcode-editor-skeleton';
import DesignerPlugin from '@alilc/lowcode-plugin-designer';
import Outline, { getTreeMaster, OutlineBackupPane } from '@alilc/lowcode-plugin-outline-pane';
// 注册默认的面板
export const defaultPanelRegistry = (editor: any, designer: any) => {
const fun = (ctx: ILowCodePluginContext) => {
return {
init() {
const { skeleton, config } = ctx;
skeleton.add({
area: 'mainArea',
name: 'designer',
type: 'Widget',
content: <DesignerPlugin
engineConfig={config}
engineEditor={editor}
/>,
});
if (!config.get('disableDefaultSettingPanel')) {
skeleton.add({
area: 'rightArea',
name: 'settingsPane',
type: 'Panel',
content: SettingsPrimaryPane,
props: {
ignoreRoot: true,
},
});
}
// by default in float area;
let isInFloatArea = true;
const hasPreferenceForOutline = editor
?.getPreference()
?.contains('outline-pane-pinned-status-isFloat', 'skeleton');
if (hasPreferenceForOutline) {
isInFloatArea = editor
?.getPreference()
?.get('outline-pane-pinned-status-isFloat', 'skeleton');
}
skeleton.add({
area: 'leftArea',
name: 'outlinePane',
type: 'PanelDock',
content: {
...Outline,
content: (props: any) => {
const Content = Outline.content;
return (
<Content
engineConfig={config}
engineEditor={editor}
{...props}
/>
);
},
},
panelProps: {
area: isInFloatArea ? 'leftFloatArea' : 'leftFixedArea',
keepVisibleWhileDragging: true,
...config.get('defaultOutlinePaneProps'),
},
});
skeleton.add({
area: 'rightArea',
name: 'backupOutline',
type: 'Panel',
props: {
condition: () => {
return designer.dragon.dragging && !getTreeMaster(designer).hasVisibleTreeBoard();
},
},
content: () => (
<OutlineBackupPane
engineConfig={config}
engineEditor={editor}
/>
),
});
},
};
};
fun.pluginName = '___default_panel___';
return fun;
};
export default defaultPanelRegistry;

View File

@ -0,0 +1,17 @@
import { ILowCodePluginContext } from '@alilc/lowcode-designer';
// 注册默认的 setters
export const setterRegistry = (ctx: ILowCodePluginContext) => {
return {
init() {
const { config } = ctx;
if (config.get('disableDefaultSetters')) return;
const builtinSetters = require('@alilc/lowcode-engine-ext')?.setters;
if (builtinSetters) {
ctx.setters.registerSetter(builtinSetters);
}
},
};
};
setterRegistry.pluginName = '___setter_registry___';

View File

@ -5,7 +5,9 @@ import { Asset } from '@alilc/lowcode-utils';
import './index.scss'; import './index.scss';
export interface PluginProps { export interface PluginProps {
editor: Editor; // editor?: Editor;
// engineConfig?: any;
engineEditor?: any;
} }
interface DesignerPluginState { interface DesignerPluginState {
@ -46,7 +48,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
} }
private async setupAssets() { private async setupAssets() {
const editor = globalContext.get('editor'); const editor = this.props.engineEditor;
try { try {
const assets = await editor.onceGot('assets'); const assets = await editor.onceGot('assets');
const renderEnv = engineConfig.get('renderEnv') || editor.get('renderEnv'); const renderEnv = engineConfig.get('renderEnv') || editor.get('renderEnv');
@ -85,7 +87,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
} }
private handleDesignerMount = (designer: Designer): void => { private handleDesignerMount = (designer: Designer): void => {
const editor = globalContext.get('editor'); const editor = this.props.engineEditor;
editor.set('designer', designer); editor.set('designer', designer);
editor.emit('designer.ready', designer); editor.emit('designer.ready', designer);
editor.onGot('schema', (schema) => { editor.onGot('schema', (schema) => {
@ -94,7 +96,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
}; };
render(): React.ReactNode { render(): React.ReactNode {
const editor = globalContext.get('editor'); const editor = this.props.engineEditor;
const { const {
componentMetadatas, componentMetadatas,
utilsMetadata, utilsMetadata,
@ -119,6 +121,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
onMount={this.handleDesignerMount} onMount={this.handleDesignerMount}
className="lowcode-plugin-designer" className="lowcode-plugin-designer"
editor={editor} editor={editor}
name={editor.name}
designer={editor.get('designer')} designer={editor.get('designer')}
componentMetadatas={componentMetadatas} componentMetadatas={componentMetadatas}
simulatorProps={{ simulatorProps={{

View File

@ -5,11 +5,12 @@ import { OutlinePane } from './pane';
export const Backup = Symbol.for('backup-outline'); export const Backup = Symbol.for('backup-outline');
export class OutlineBackupPane extends PureComponent<PluginProps> { export class OutlineBackupPane extends PureComponent<any> {
render() { render() {
return ( return (
<OutlinePane <OutlinePane
editor={globalContext.get('editor')} editor={this.props.engineEditor}
engineEditor={this.props.engineEditor}
config={{ config={{
name: Backup, name: Backup,
}} }}

View File

@ -7,9 +7,15 @@ import './style.less';
import { IEditor } from '@alilc/lowcode-types'; import { IEditor } from '@alilc/lowcode-types';
import Filter from './filter'; import Filter from './filter';
interface Props { config: any; editor: IEditor }
@observer @observer
export class OutlinePane extends Component<{ config: any; editor: IEditor }> { export class OutlinePane extends Component<any> {
private main = new OutlineMain(globalContext.get('editor'), this.props.config.name || this.props.config.pluginKey); private main;
constructor(props: Props) {
super(props);
this.main = new OutlineMain(this.props.engineEditor, this.props.config.name || this.props.config.pluginKey);
}
componentWillUnmount() { componentWillUnmount() {
this.main.purge(); this.main.purge();

View File

@ -16,7 +16,8 @@ import { IconLock, IconUnlock } from '../icons';
function emitOutlineEvent(type: string, treeNode: TreeNode, rest?: Record<string, unknown>) { function emitOutlineEvent(type: string, treeNode: TreeNode, rest?: Record<string, unknown>) {
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const node = treeNode?.node; const node = treeNode?.node;
const npm = node?.componentMeta?.npm; const npm = node?.componentMeta?.npm;
const selected = const selected =

View File

@ -67,7 +67,8 @@ export default class TreeView extends Component<{ tree: Tree }> {
} }
} else { } else {
selection.select(id); selection.select(id);
const editor = globalContext.get(Editor); const workSpace = globalContext.get('workSpace');
const editor = workSpace.isActive ? workSpace.window.editor : globalContext.get('editor');
const selectedNode = designer.currentSelection?.getNodes()?.[0]; const selectedNode = designer.currentSelection?.getNodes()?.[0];
const npm = selectedNode?.componentMeta?.npm; const npm = selectedNode?.componentMeta?.npm;
const selected = const selected =

View File

@ -245,7 +245,6 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
constructor() { constructor() {
this.dispose = host.connect(this, () => { this.dispose = host.connect(this, () => {
// sync layout config // sync layout config
// debugger;
this._layout = host.project.get('config').layout; this._layout = host.project.get('config').layout;
// todo: split with others, not all should recompute // todo: split with others, not all should recompute
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) { if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {

View File

@ -171,6 +171,7 @@ class Renderer extends Component<{
this.schemaChangedSymbol = false; this.schemaChangedSymbol = false;
if (!container.autoRender || isRendererDetached()) return null; if (!container.autoRender || isRendererDetached()) return null;
return ( return (
<LowCodeRenderer <LowCodeRenderer
locale={locale} locale={locale}

View File

@ -48,7 +48,7 @@ import {
shallowIntl as innerShallowIntl, shallowIntl as innerShallowIntl,
createIntl as innerCreateIntl, createIntl as innerCreateIntl,
intl as innerIntl, intl as innerIntl,
createSetterContent as innerCreateSetterContent, // createSetterContent as innerCreateSetterContent,
globalLocale as innerGlobalLocale, globalLocale as innerGlobalLocale,
obx as innerObx, obx as innerObx,
observable as innerObservable, observable as innerObservable,
@ -59,7 +59,6 @@ import {
} from '@alilc/lowcode-editor-core'; } from '@alilc/lowcode-editor-core';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
class DesignerCabin implements IPublicCommonDesignerCabin { class DesignerCabin implements IPublicCommonDesignerCabin {
private readonly [editorSymbol]: Editor; private readonly [editorSymbol]: Editor;
/** /**
@ -225,6 +224,11 @@ class Utils implements IPublicCommonUtils {
} }
class EditorCabin { class EditorCabin {
private readonly [editorSymbol]: Editor;
constructor(editor: Editor) {
this[editorSymbol] = editor;
}
/** /**
* @deprecated * @deprecated
*/ */
@ -263,9 +267,10 @@ class EditorCabin {
/** /**
* @deprecated * @deprecated
*/ */
createSetterContent(setter: any, props: Record<string, any>): ReactNode { createSetterContent = (setter: any, props: Record<string, any>): ReactNode => {
return innerCreateSetterContent(setter, props); const setters = this[editorSymbol].get('setters');
} return setters.createSetterContent(setter, props);
};
/** /**
* @deprecated * @deprecated
@ -327,7 +332,7 @@ export default class Common implements IPublicApiCommon {
constructor(editor: Editor, skeleton: InnerSkeleton) { constructor(editor: Editor, skeleton: InnerSkeleton) {
this.__designerCabin = new DesignerCabin(editor); this.__designerCabin = new DesignerCabin(editor);
this.__skeletonCabin = new SkeletonCabin(skeleton); this.__skeletonCabin = new SkeletonCabin(skeleton);
this.__editorCabin = new EditorCabin(); this.__editorCabin = new EditorCabin(editor);
this.__utils = new Utils(); this.__utils = new Utils();
} }

View File

@ -106,6 +106,7 @@ export default class ComponentMeta implements IPublicModelComponentMeta {
return this[componentMetaSymbol].getMetadata(); return this[componentMetaSymbol].getMetadata();
} }
isComponentMeta = true;
/** /**
* check if the current node could be placed in parent node * check if the current node could be placed in parent node
* @param my * @param my

View File

@ -33,7 +33,7 @@ import Canvas from './canvas';
import ModalNodesManager from './modal-nodes-manager'; import ModalNodesManager from './modal-nodes-manager';
import { documentSymbol, editorSymbol, nodeSymbol } from './symbols'; import { documentSymbol, editorSymbol, nodeSymbol } from './symbols';
const Events = { export const Events = {
IMPORT_SCHEMA: 'shell.document.importSchema', IMPORT_SCHEMA: 'shell.document.importSchema',
}; };

View File

@ -5,6 +5,7 @@ import {
import { dragonSymbol } from './symbols'; import { dragonSymbol } from './symbols';
import LocateEvent from './locate-event'; import LocateEvent from './locate-event';
import DragObject from './drag-object'; import DragObject from './drag-object';
import { globalContext } from '@alilc/lowcode-editor-core';
import { import {
IPublicModelDragon, IPublicModelDragon,
IPublicModelLocateEvent, IPublicModelLocateEvent,
@ -13,11 +14,26 @@ import {
DragNodeDataObject, DragNodeDataObject,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
export default class Dragon implements IPublicModelDragon { export const innerDragonSymbol = Symbol('innerDragonSymbol');
private readonly [dragonSymbol]: InnerDragon;
export default class Dragon implements IPublicModelDragon {
constructor(dragon: InnerDragon) { constructor(dragon: InnerDragon) {
this[dragonSymbol] = dragon; this[innerDragonSymbol] = dragon;
}
private readonly [innerDragonSymbol]: InnerDragon;
get [dragonSymbol](): any {
const workSpace = globalContext.get('workSpace');
let editor = globalContext.get('editor');
if (workSpace.isActive) {
editor = workSpace.window.editor;
}
const designer = editor.get('designer');
return designer.dragon;
} }
static create(dragon: InnerDragon | null): IPublicModelDragon | null { static create(dragon: InnerDragon | null): IPublicModelDragon | null {

View File

@ -9,8 +9,11 @@ type EventOptions = {
prefix: string; prefix: string;
}; };
const innerEditorSymbol = Symbol('editor');
export default class Event implements IPublicApiEvent { export default class Event implements IPublicApiEvent {
private readonly [editorSymbol]: InnerEditor; private readonly [innerEditorSymbol]: InnerEditor;
// private readonly [editorSymbol]: InnerEditor;
private readonly options: EventOptions; private readonly options: EventOptions;
// TODO: // TODO:
@ -19,8 +22,20 @@ export default class Event implements IPublicApiEvent {
*/ */
readonly names = []; readonly names = [];
constructor(editor: InnerEditor, options: EventOptions) { get [editorSymbol](): InnerEditor {
this[editorSymbol] = editor; if (this.workspaceMode) {
return this[innerEditorSymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.editor;
}
return this[innerEditorSymbol];
}
constructor(editor: InnerEditor, options: EventOptions, public workspaceMode = false) {
this[innerEditorSymbol] = editor;
this.options = options; this.options = options;
if (!this.options.prefix) { if (!this.options.prefix) {
logger.warn('prefix is required while initializing Event'); logger.warn('prefix is required while initializing Event');

View File

@ -1,9 +1,29 @@
import { hotkey } from '@alilc/lowcode-editor-core'; import { globalContext, Hotkey as InnerHotkey } from '@alilc/lowcode-editor-core';
import { hotkeySymbol } from './symbols';
import { Disposable, HotkeyCallback, IPublicApiHotkey } from '@alilc/lowcode-types'; import { Disposable, HotkeyCallback, IPublicApiHotkey } from '@alilc/lowcode-types';
const innerHotkeySymbol = Symbol('innerHotkey');
export default class Hotkey implements IPublicApiHotkey { export default class Hotkey implements IPublicApiHotkey {
get callbacks() { private readonly [innerHotkeySymbol]: InnerHotkey;
return hotkey.callBacks; get [hotkeySymbol](): InnerHotkey {
if (this.workspaceMode) {
return this[innerHotkeySymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.innerHotkey;
}
return this[innerHotkeySymbol];
}
constructor(hotkey: InnerHotkey, public name: string = 'unknown', public workspaceMode: boolean = false) {
this[innerHotkeySymbol] = hotkey;
}
get callbacks(): any {
return this[hotkeySymbol].callBacks;
} }
/** /**
* @deprecated * @deprecated
@ -19,9 +39,9 @@ export default class Hotkey implements IPublicApiHotkey {
* @returns * @returns
*/ */
bind(combos: string[] | string, callback: HotkeyCallback, action?: string): Disposable { bind(combos: string[] | string, callback: HotkeyCallback, action?: string): Disposable {
hotkey.bind(combos, callback, action); this[hotkeySymbol].bind(combos, callback, action);
return () => { return () => {
hotkey.unbind(combos, callback, action); this[hotkeySymbol].unbind(combos, callback, action);
}; };
} }
} }

View File

@ -15,6 +15,7 @@ import Dragon from './dragon';
import SettingPropEntry from './setting-prop-entry'; import SettingPropEntry from './setting-prop-entry';
import SettingTopEntry from './setting-top-entry'; import SettingTopEntry from './setting-top-entry';
import Common from './common'; import Common from './common';
import Plugins from './plugins';
export * from './symbols'; export * from './symbols';
@ -44,4 +45,5 @@ export {
Dragon, Dragon,
Common, Common,
getEvent, getEvent,
Plugins,
}; };

View File

@ -1,4 +1,4 @@
import { Editor } from '@alilc/lowcode-editor-core'; import { Editor, globalContext } from '@alilc/lowcode-editor-core';
import { import {
Designer, Designer,
registerMetadataTransducer, registerMetadataTransducer,
@ -19,13 +19,31 @@ import {
import { editorSymbol, designerSymbol } from './symbols'; import { editorSymbol, designerSymbol } from './symbols';
import ComponentMeta from './component-meta'; import ComponentMeta from './component-meta';
const innerEditorSymbol = Symbol('editor');
export default class Material implements IPublicApiMaterial { export default class Material implements IPublicApiMaterial {
private readonly [editorSymbol]: Editor; // private readonly [editorSymbol]: Editor;
private readonly [designerSymbol]: Designer; private readonly [innerEditorSymbol]: Editor;
// private readonly [designerSymbol]: Designer;
constructor(editor: Editor) { get [editorSymbol](): Editor {
this[editorSymbol] = editor; if (this.workspaceMode) {
this[designerSymbol] = editor.get('designer')!; return this[innerEditorSymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.editor;
}
return this[innerEditorSymbol];
}
get [designerSymbol](): Designer {
return this[editorSymbol].get('designer')!;
}
constructor(editor: Editor, public workspaceMode: boolean = false, public name: string = 'unknown') {
this[innerEditorSymbol] = editor;
// this[designerSymbol] = editor.get('designer')!;
} }
/** /**
@ -52,6 +70,10 @@ export default class Material implements IPublicApiMaterial {
return this[editorSymbol].get('assets'); return this[editorSymbol].get('assets');
} }
async asyncGetAssets() {
return await this[editorSymbol].get('assets');
}
/** /**
* *
* @param incrementalAssets * @param incrementalAssets

View File

@ -10,11 +10,11 @@ export default class NodeChildren implements IPublicModelNodeChildren {
this[nodeChildrenSymbol] = nodeChildren; this[nodeChildrenSymbol] = nodeChildren;
} }
static create(nodeChldren: InnerNodeChildren | null): IPublicModelNodeChildren | null { static create(nodeChildren: InnerNodeChildren | null): IPublicModelNodeChildren | null {
if (!nodeChldren) { if (!nodeChildren) {
return null; return null;
} }
return new NodeChildren(nodeChldren); return new NodeChildren(nodeChildren);
} }
/** /**

View File

@ -527,4 +527,8 @@ export default class Node implements IPublicModelNode {
internalToShellNode() { internalToShellNode() {
return this; return this;
} }
canPerformAction(actionName: string): boolean {
return this[nodeSymbol].canPerformAction(actionName);
}
} }

View File

@ -0,0 +1,60 @@
import {
LowCodePluginManager,
} from '@alilc/lowcode-designer';
import { globalContext } from '@alilc/lowcode-editor-core';
import {
IPublicApiPlugins,
} from '@alilc/lowcode-types';
import { pluginsSymbol } from './symbols';
const innerPluginsSymbol = Symbol('plugin');
export default class Plugins implements IPublicApiPlugins {
private readonly [innerPluginsSymbol]: LowCodePluginManager;
get [pluginsSymbol](): LowCodePluginManager {
if (this.workspaceMode) {
return this[innerPluginsSymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.innerPlugins;
}
return this[innerPluginsSymbol];
}
constructor(plugins: LowCodePluginManager, public workspaceMode: boolean = false) {
this[innerPluginsSymbol] = plugins;
}
async register(
pluginConfigCreator: (ctx: any, options: any) => any,
options?: any,
registerOptions?: any,
): Promise<void> {
await this[pluginsSymbol].register(pluginConfigCreator, options, registerOptions);
}
async init(registerOptions: any) {
await this[pluginsSymbol].init(registerOptions);
}
async getPluginPreference(pluginName: string) {
await this[pluginsSymbol].getPluginPreference(pluginName);
}
toProxy() {
return new Proxy(this, {
get(target, prop, receiver) {
const _target = target[pluginsSymbol];
if (_target.pluginsMap.has(prop as string)) {
// 禁用态的插件,直接返回 undefined
if (_target.pluginsMap.get(prop as string)!.disabled) {
return undefined;
}
return _target.pluginsMap.get(prop as string)?.toProxy();
}
return Reflect.get(target, prop, receiver);
},
});
}
}

View File

@ -3,6 +3,7 @@ import {
Project as InnerProject, Project as InnerProject,
TransformStage, TransformStage,
} from '@alilc/lowcode-designer'; } from '@alilc/lowcode-designer';
import { globalContext } from '@alilc/lowcode-editor-core';
import { import {
RootSchema, RootSchema,
ProjectSchema, ProjectSchema,
@ -12,18 +13,32 @@ import {
IPublicModelDocumentModel, IPublicModelDocumentModel,
PropsTransducer, PropsTransducer,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import DocumentModel from './document-model'; import DocumentModel from './document-model';
import SimulatorHost from './simulator-host'; import SimulatorHost from './simulator-host';
import { editorSymbol, projectSymbol, simulatorHostSymbol, simulatorRendererSymbol, documentSymbol } from './symbols'; import { editorSymbol, projectSymbol, simulatorHostSymbol, simulatorRendererSymbol, documentSymbol } from './symbols';
const innerProjectSymbol = Symbol('project');
export default class Project implements IPublicApiProject { export default class Project implements IPublicApiProject {
private readonly [projectSymbol]: InnerProject;
private readonly [editorSymbol]: IEditor; private readonly [editorSymbol]: IEditor;
private readonly [innerProjectSymbol]: InnerProject;
private [simulatorHostSymbol]: BuiltinSimulatorHost; private [simulatorHostSymbol]: BuiltinSimulatorHost;
private [simulatorRendererSymbol]: any; private [simulatorRendererSymbol]: any;
get [projectSymbol]() {
if (this.workspaceMode) {
return this[innerProjectSymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.innerProject;
}
constructor(project: InnerProject) { return this[innerProjectSymbol];
this[projectSymbol] = project; }
constructor(project: InnerProject, public workspaceMode: boolean = false) {
this[innerProjectSymbol] = project;
this[editorSymbol] = project?.designer.editor; this[editorSymbol] = project?.designer.editor;
} }
@ -122,6 +137,7 @@ export default class Project implements IPublicApiProject {
*/ */
importSchema(schema?: ProjectSchema): void { importSchema(schema?: ProjectSchema): void {
this[projectSymbol].load(schema, true); this[projectSymbol].load(schema, true);
// this[editorSymbol].emit(Events.IMPORT_SCHEMA, schema);
} }
/** /**

View File

@ -92,7 +92,7 @@ export default class Selection implements IPublicModelSelection {
* getTopNodes() will return [A, B], subA will be removed * getTopNodes() will return [A, B], subA will be removed
* @returns * @returns
*/ */
getTopNodes(): Array<IPublicModelNode | null> { getTopNodes(includeRoot: boolean = false): Array<IPublicModelNode | null> {
return this[selectionSymbol].getTopNodes().map((node: InnerNode) => Node.create(node)); return this[selectionSymbol].getTopNodes(includeRoot).map((node: InnerNode) => Node.create(node));
} }
} }

View File

@ -1,15 +1,38 @@
import { getSetter, registerSetter, getSettersMap } from '@alilc/lowcode-editor-core';
import { CustomView, IPublicApiSetters, RegisteredSetter } from '@alilc/lowcode-types'; import { CustomView, IPublicApiSetters, RegisteredSetter } from '@alilc/lowcode-types';
import { Setters as InnerSetters, globalContext } from '@alilc/lowcode-editor-core';
import { ReactNode } from 'react';
const innerSettersSymbol = Symbol('setters');
const settersSymbol = Symbol('setters');
export default class Setters implements IPublicApiSetters { export default class Setters implements IPublicApiSetters {
readonly [innerSettersSymbol]: InnerSetters;
get [settersSymbol](): InnerSetters {
if (this.workspaceMode) {
return this[innerSettersSymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.innerSetters;
}
return this[innerSettersSymbol];
}
constructor(innerSetters: InnerSetters, readonly workspaceMode = false) {
this[innerSettersSymbol] = innerSetters;
}
/** /**
* setter * setter
* @param type * @param type
* @returns * @returns
*/ */
getSetter(type: string): RegisteredSetter | null { getSetter = (type: string) => {
return getSetter(type); return this[settersSymbol].getSetter(type);
} };
/** /**
* settersMap * settersMap
@ -18,7 +41,7 @@ export default class Setters implements IPublicApiSetters {
getSettersMap(): Map<string, RegisteredSetter & { getSettersMap(): Map<string, RegisteredSetter & {
type: string; type: string;
}> { }> {
return getSettersMap(); return this[settersSymbol].getSettersMap();
} }
/** /**
@ -27,10 +50,14 @@ export default class Setters implements IPublicApiSetters {
* @param setter * @param setter
* @returns * @returns
*/ */
registerSetter( registerSetter = (
typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter }, typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter },
setter?: CustomView | RegisteredSetter | undefined, setter?: CustomView | RegisteredSetter | undefined,
) { ) => {
return registerSetter(typeOrMaps, setter); return this[settersSymbol].registerSetter(typeOrMaps, setter);
} };
createSetterContent = (setter: any, props: Record<string, any>): ReactNode => {
return this[settersSymbol].createSetterContent(setter, props);
};
} }

View File

@ -1,3 +1,4 @@
import { globalContext } from '@alilc/lowcode-editor-core';
import { import {
Skeleton as InnerSkeleton, Skeleton as InnerSkeleton,
SkeletonEvents, SkeletonEvents,
@ -5,11 +6,24 @@ import {
import { skeletonSymbol } from './symbols'; import { skeletonSymbol } from './symbols';
import { IPublicApiSkeleton, IWidgetBaseConfig, IWidgetConfigArea } from '@alilc/lowcode-types'; import { IPublicApiSkeleton, IWidgetBaseConfig, IWidgetConfigArea } from '@alilc/lowcode-types';
const innerSkeletonSymbol = Symbol('skeleton');
export default class Skeleton implements IPublicApiSkeleton { export default class Skeleton implements IPublicApiSkeleton {
private readonly [skeletonSymbol]: InnerSkeleton; private readonly [innerSkeletonSymbol]: InnerSkeleton;
constructor(skeleton: InnerSkeleton) { get [skeletonSymbol]() {
this[skeletonSymbol] = skeleton; if (this.workspaceMode) {
return this[innerSkeletonSymbol];
}
const workSpace = globalContext.get('workSpace');
if (workSpace.isActive) {
return workSpace.window.innerSkeleton;
}
return this[innerSkeletonSymbol];
}
constructor(skeleton: InnerSkeleton, readonly workspaceMode: boolean = false) {
this[innerSkeletonSymbol] = skeleton;
} }
/** /**

View File

@ -24,4 +24,6 @@ export const simulatorHostSymbol = Symbol('simulatorHost');
export const simulatorRendererSymbol = Symbol('simulatorRenderer'); export const simulatorRendererSymbol = Symbol('simulatorRenderer');
export const dragObjectSymbol = Symbol('dragObject'); export const dragObjectSymbol = Symbol('dragObject');
export const locateEventSymbol = Symbol('locateEvent'); export const locateEventSymbol = Symbol('locateEvent');
export const designerCabinSymbol = Symbol('designerCabin'); export const designerCabinSymbol = Symbol('designerCabin');
export const hotkeySymbol = Symbol('hotkey');
export const pluginsSymbol = Symbol('plugins');

View File

@ -132,6 +132,11 @@ export interface EngineOptions {
* *
*/ */
focusNodeSelector?: (rootNode: Node) => Node; focusNodeSelector?: (rootNode: Node) => Node;
/**
*
*/
enableWorkspaceMode?: boolean;
} }
export interface IEngineConfig { export interface IEngineConfig {

View File

@ -1,4 +1,5 @@
import { EitherOr } from './utils'; import { EitherOr } from './utils';
/** /**
* npm * npm
*/ */

View File

@ -21,6 +21,8 @@ export interface SettingTarget {
*/ */
readonly editor: IEditor; readonly editor: IEditor;
readonly setters: any;
/** /**
* 访 * 访
*/ */

View File

@ -5,4 +5,5 @@ export * from './material';
export * from './project'; export * from './project';
export * from './setters'; export * from './setters';
export * from './simulator-host'; export * from './simulator-host';
export * from './skeleton'; export * from './skeleton';
export * from './plugins';

View File

@ -0,0 +1,7 @@
export interface IPublicApiPlugins {
register(
pluginConfigCreator: (ctx: any, options: any) => any,
options?: any,
registerOptions?: any,
): Promise<void>;
}

View File

@ -5,6 +5,8 @@ import { IPublicApiProject } from '../api';
import { PropChangeOptions } from '../index'; import { PropChangeOptions } from '../index';
import { IPublicModelModalNodesManager } from './modal-nodes-manager'; import { IPublicModelModalNodesManager } from './modal-nodes-manager';
import { IPublicModelNode } from './node'; import { IPublicModelNode } from './node';
import { IPublicModelSelection } from './selection';
import { IPublicModelHistory } from './history';
export interface IPublicModelDocumentModel { export interface IPublicModelDocumentModel {
@ -16,6 +18,10 @@ export interface IPublicModelDocumentModel {
set id(id); set id(id);
selection: IPublicModelSelection;
history: IPublicModelHistory;
/** /**
* project * project
* @returns * @returns

View File

@ -12,7 +12,6 @@ import { IPublicModelNodeChildren } from './node-children';
import { IPublicModelComponentMeta } from './component-meta'; import { IPublicModelComponentMeta } from './component-meta';
export interface IPublicModelNode { export interface IPublicModelNode {
/** /**
* id * id
*/ */
@ -313,4 +312,11 @@ export interface IPublicModelNode {
* @returns Boolean * @returns Boolean
*/ */
get isRGLContainer(); get isRGLContainer();
/**
* action
* @param actionName action
* @returns boolean
*/
canPerformAction(actionName: string): boolean;
} }

View File

@ -61,5 +61,5 @@ export interface IPublicModelSelection {
* getTopNodes() will return [A, B], subA will be removed * getTopNodes() will return [A, B], subA will be removed
* @returns * @returns
*/ */
getTopNodes(): Array<IPublicModelNode | null>; getTopNodes(includeRoot?: boolean): Array<IPublicModelNode | null>;
} }

View File

@ -53,10 +53,9 @@ export function megreAssets(assets: AssetsJson, incrementalAssets: AssetsJson):
} }
if (incrementalAssets.components) { if (incrementalAssets.components) {
assets.components = [...assets.components, ...incrementalAssets.components]; assets.components = [...(assets.components || []), ...incrementalAssets.components];
} }
megreAssetsComponentList(assets, incrementalAssets, 'componentList'); megreAssetsComponentList(assets, incrementalAssets, 'componentList');
megreAssetsComponentList(assets, incrementalAssets, 'bizComponentList'); megreAssetsComponentList(assets, incrementalAssets, 'bizComponentList');

View File

@ -0,0 +1,5 @@
{
"plugins": [
"build-plugin-component"
]
}

View File

@ -0,0 +1,6 @@
{
"plugins": [
"build-plugin-component",
"@alilc/lowcode-test-mate/plugin/index.ts"
]
}

View File

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!**/node_modules/**',
'!**/vendor/**',
],
};

View File

@ -0,0 +1,62 @@
{
"name": "@alilc/lowcode-workspace",
"version": "1.0.15",
"description": "Shell Layer for AliLowCodeEngine",
"main": "lib/index.js",
"private": true,
"module": "es/index.js",
"files": [
"lib",
"es"
],
"scripts": {
"build": "build-scripts build --skip-demo",
"test": "build-scripts test --config build.test.json",
"test:cov": "build-scripts test --config build.test.json --jest-coverage"
},
"license": "MIT",
"dependencies": {
"@alilc/lowcode-designer": "1.0.15",
"@alilc/lowcode-editor-core": "1.0.15",
"@alilc/lowcode-editor-skeleton": "1.0.15",
"@alilc/lowcode-types": "1.0.15",
"@alilc/lowcode-utils": "1.0.15",
"classnames": "^2.2.6",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"react": "^16",
"react-dom": "^16.7.0",
"zen-logger": "^1.1.0"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.29",
"@alilc/lowcode-test-mate": "^1.0.1",
"@testing-library/react": "^11.2.2",
"@types/classnames": "^2.2.7",
"@types/jest": "^26.0.16",
"@types/lodash": "^4.14.165",
"@types/medium-editor": "^5.0.3",
"@types/node": "^13.7.1",
"@types/react": "^16",
"@types/react-dom": "^16",
"babel-jest": "^26.5.2",
"build-plugin-component": "^1.0.0",
"build-scripts-config": "^3.0.3",
"jest": "^26.6.3",
"lodash": "^4.17.20",
"moment": "^2.29.1",
"typescript": "^4.0.3"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"resolutions": {
"@builder/babel-preset-ice": "1.0.1"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/shell"
},
"gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6"
}

View File

@ -0,0 +1,133 @@
import { Editor, engineConfig, Setters as InnerSetters, Hotkey as InnerHotkey } from '@alilc/lowcode-editor-core';
import {
Designer,
ILowCodePluginContextApiAssembler,
LowCodePluginManager,
ILowCodePluginContextPrivate,
Project as InnerProject,
} from '@alilc/lowcode-designer';
import {
Skeleton as InnerSkeleton,
} from '@alilc/lowcode-editor-skeleton';
import {
// EditorWindow,
WorkSpace,
} from '@alilc/lowcode-workspace';
import {
Hotkey,
Plugins,
Project,
Skeleton,
Setters,
Material,
Event,
Common,
} from '@alilc/lowcode-shell';
import { getLogger } from '@alilc/lowcode-utils';
import { setterRegistry } from 'engine/src/inner-plugins/setter-registry';
import { componentMetaParser } from 'engine/src/inner-plugins/component-meta-parser';
import defaultPanelRegistry from 'engine/src/inner-plugins/default-panel-registry';
import { builtinHotkey } from 'engine/src/inner-plugins/builtin-hotkey';
import { EditorWindow } from './editor-window/context';
import { shellModelFactory } from './shell-model-factory';
export class BasicContext {
skeleton: Skeleton;
plugins: Plugins;
project: Project;
setters: Setters;
material: Material;
config;
event;
logger;
hotkey: Hotkey;
innerProject: InnerProject;
editor: Editor;
designer: Designer;
registerInnerPlugins: () => Promise<void>;
innerSetters: InnerSetters;
innerSkeleton: any;
innerHotkey: InnerHotkey;
innerPlugins: LowCodePluginManager;
constructor(workSpace: WorkSpace, name: string, public editorWindow?: EditorWindow) {
const editor = new Editor(name, true);
const innerSkeleton = new InnerSkeleton(editor, name);
editor.set('skeleton' as any, innerSkeleton);
const designer: Designer = new Designer({
editor,
name,
shellModelFactory,
});
editor.set('designer' as any, designer);
const { project: innerProject } = designer;
const innerHotkey = new InnerHotkey(name);
const hotkey = new Hotkey(innerHotkey, name, true);
const innerSetters = new InnerSetters(name);
const setters = new Setters(innerSetters, true);
const material = new Material(editor, true, name);
const project = new Project(innerProject, true);
const config = engineConfig;
const event = new Event(editor, { prefix: 'common' });
const logger = getLogger({ level: 'warn', bizName: 'common' });
const skeleton = new Skeleton(innerSkeleton, true);
editor.set('setters', setters);
editor.set('project', project);
editor.set('material', material);
editor.set('hotkey', hotkey);
editor.set('innerHotkey', innerHotkey);
this.innerSetters = innerSetters;
this.innerSkeleton = innerSkeleton;
this.skeleton = skeleton;
this.innerProject = innerProject;
this.project = project;
this.setters = setters;
this.material = material;
this.config = config;
this.event = event;
this.logger = logger;
this.hotkey = hotkey;
this.innerHotkey = innerHotkey;
this.editor = editor;
this.designer = designer;
const common = new Common(editor, innerSkeleton);
let plugins: any;
const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
assembleApis: (context: ILowCodePluginContextPrivate) => {
context.hotkey = hotkey;
context.project = project;
context.skeleton = skeleton;
context.setters = setters;
context.material = material;
context.event = event;
context.config = config;
context.common = common;
context.plugins = plugins;
},
};
const innerPlugins = new LowCodePluginManager(pluginContextApiAssembler, name);
this.innerPlugins = innerPlugins;
plugins = new Plugins(innerPlugins, true).toProxy();
editor.set('plugins' as any, plugins);
editor.set('innerPlugins' as any, innerPlugins);
this.plugins = plugins;
// 注册一批内置插件
this.registerInnerPlugins = async function registerPlugins() {
await plugins.register(componentMetaParser(designer));
await plugins.register(setterRegistry);
await plugins.register(defaultPanelRegistry(editor, designer));
await plugins.register(builtinHotkey);
};
}
// get project() {
// return this.editorWindow ? this.editorWindow.project : this._project;
// }
}

View File

@ -0,0 +1,4 @@
import { createContext } from 'react';
import { Skeleton } from './skeleton';
export const SkeletonContext = createContext<Skeleton>({} as any);

View File

@ -0,0 +1,36 @@
import { makeObservable, obx, runInAction } from '@alilc/lowcode-editor-core';
import { EditorViewOptions, EditorWindow } from '@alilc/lowcode-workspace';
import { flow } from 'mobx';
import { BasicContext } from '../base-context';
export class Context extends BasicContext {
name = 'editor-view';
constructor(public workspace: any, public editorWindow: EditorWindow, public editorView: EditorViewOptions) {
super(workspace, editorView.name, editorWindow);
this.name = editorView.name;
makeObservable(this);
}
@obx _activate = false;
setActivate = (_activate: boolean) => {
this._activate = _activate;
this.innerHotkey.activate(this._activate);
};
get active() {
return this._activate;
}
@obx isInit: boolean = false;
init = flow(function* (this: any) {
yield this.registerInnerPlugins();
yield this.editorView?.init(this.innerPlugins._getLowCodePluginContext({
pluginName: 'any',
}));
yield this.innerPlugins.init();
this.isInit = true;
});
}

View File

@ -0,0 +1,39 @@
import { skeletonSymbol } from '@alilc/lowcode-shell';
import { observer } from '@alilc/lowcode-editor-core';
import {
Workbench,
} from '@alilc/lowcode-editor-skeleton';
import { Component } from 'react';
import { Context } from './context';
export * from '../base-context';
@observer
export class EditorView extends Component<any, any> {
// 因为 document 数据在不同视图下使用的是同一份,所以这里通过 constructor 传入
constructor(props: any) {
super(props);
// this.ctx = new Context(props.editorView);
}
// ctx: Context;
render() {
const { active } = this.props;
const editorView = this.props.editorView;
const skeleton = editorView.innerSkeleton;
if (!editorView.isInit) {
return null;
}
return (
<>
<Workbench
skeleton={skeleton}
className={active ? 'active engine-editor-view' : 'engine-editor-view'}
topAreaItemClassName="engine-actionitem"
/>
</>
);
}
}

View File

@ -0,0 +1,16 @@
import { makeObservable, obx } from '@alilc/lowcode-editor-core';
import { Resource } from './resource';
export class EditorWindow {
constructor(public resource: Resource) {
// debugger
makeObservable(this);
this.editorView = resource.getEditorView(this.resource.defaultViewType);
}
@obx editorView;
changeViewType(name: string) {
this.editorView = this.resource.getEditorView(name);
}
}

View File

@ -0,0 +1,103 @@
import { computed, makeObservable, obx } from '@alilc/lowcode-editor-core';
import { Context } from '../editor-view/context';
import { WorkSpace } from '..';
import { Resource } from '../resource';
export class EditorWindow {
constructor(public resource: Resource, public workspace: WorkSpace) {
makeObservable(this);
this.init();
}
async importSchema(schema: any) {
const newSchema = await this.resource.import(schema);
Object.keys(newSchema).forEach(key => {
const view = this.editorViews.get(key);
view?.project.importSchema(newSchema[key]);
});
}
async init() {
await this.initViewTypes();
await this.execViewTypesInit();
this.setDefaultViewType();
}
initViewTypes = async () => {
const editorViews = this.resource.editorViews;
for (let i = 0; i < editorViews.length; i++) {
const name = editorViews[i].name;
await this.initViewType(name);
if (!this.editorView) {
this.changeViewType(name);
}
}
};
execViewTypesInit = async () => {
const editorViews = this.resource.editorViews;
for (let i = 0; i < editorViews.length; i++) {
const name = editorViews[i].name;
this.changeViewType(name);
await this.editorViews.get(name)?.init();
}
};
setDefaultViewType = () => {
this.changeViewType(this.resource.defaultViewType);
};
@obx.ref editorView: Context;
@obx editorViews: Map<string, Context> = new Map<string, Context>();
initViewType = async (name: string) => {
const viewInfo = this.resource.getEditorView(name);
if (this.editorViews.get(name)) {
return;
}
const editorView = new Context(this.workspace, this, viewInfo as any);
// await editorView.init();
this.editorViews.set(name, editorView);
};
changeViewType = (name: string) => {
this.editorView?.setActivate(false);
this.editorView = this.editorViews.get(name)!;
this.editorView.setActivate(true);
};
get project() {
return this.editorView?.project;
}
get innerProject() {
return this.editorView?.innerProject;
}
get innerSkeleton() {
return this.editorView?.innerSkeleton;
}
get innerSetters() {
return this.editorView?.innerSetters;
}
get innerHotkey() {
return this.editorView?.innerHotkey;
}
get editor() {
return this.editorView?.editor;
}
get designer() {
return this.editorView?.designer;
}
get innerPlugins() {
return this.editorView?.innerPlugins;
}
}

View File

@ -0,0 +1,45 @@
import { Component } from 'react';
import { EditorView } from '../editor-view/view';
import { observer } from '@alilc/lowcode-editor-core';
import { EditorWindow } from './context';
@observer
export class EditorWindowView extends Component<{
editorWindow: EditorWindow;
}, any> {
constructor(props: any) {
super(props);
}
render() {
const { resource, editorView, editorViews } = this.props.editorWindow;
// const editorViews = Array.from(editorViews.values())
if (!editorView) {
return null;
}
return (
<div className="workspace-engine-main">
{/* <EditorView
resource={resource}
key={editorView.name}
active={editorView.active}
editorView={editorView}
defaultViewType
/> */}
{
Array.from(editorViews.values()).map((editorView: any) => {
return (
<EditorView
resource={resource}
key={editorView.name}
active={editorView.active}
editorView={editorView}
defaultViewType
/>
);
})
}
</div>
);
}
}

View File

@ -0,0 +1,77 @@
import { Editor } from '@alilc/lowcode-editor-core';
import {
Skeleton as InnerSkeleton,
} from '@alilc/lowcode-editor-skeleton';
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 {
constructor() {
this.editor = new Editor();
this.skeleton = new InnerSkeleton(this.editor);
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: ResourceOptions): 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 interface ResourceOptions {
description: string;
defaultViewType?: string;
editorViews?: EditorViewOptions[];
init: (ctx: any) => Promise<void>;
dispose: (ctx: any) => Promise<void>;
import: (ctx: any) => Promise<any>;
}
export interface EditorViewOptions {
name: string;
init: (ctx: any) => Promise<void>;
save: (ctx: any) => Promise<void>;
}

View File

@ -0,0 +1,35 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@alilc/lowcode-editor-core';
import Area from '../area';
import Panel from '../widget/panel';
@observer
export default class BottomArea extends Component<{ area: Area<any, Panel> }> {
render() {
const { area } = this.props;
if (area.isEmpty()) {
return null;
}
return (
<div className={classNames('lc-bottom-area', {
'lc-area-visible': area.visible,
})}
>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area<any, Panel> }> {
render() {
const { area } = this.props;
return (
<Fragment>
{area.container.items.map((item) => item.content)}
</Fragment>
);
}
}

View File

@ -0,0 +1,47 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@alilc/lowcode-editor-core';
import Area from '../area';
@observer
export default class LeftArea extends Component<{ area: Area }> {
render() {
const { area } = this.props;
return (
<div className={classNames('lc-left-area', {
'lc-area-visible': area.visible,
})}
>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area }> {
render() {
const { area } = this.props;
const top: any[] = [];
const bottom: 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 key={`left-area-${item.name}`}>{item.content}</div>;
if (item.align === 'bottom') {
bottom.push(content);
} else {
top.push(content);
}
});
return (
<Fragment>
<div className="lc-left-area-top">{top}</div>
<div className="lc-left-area-bottom">{bottom}</div>
</Fragment>
);
}
}

View File

@ -0,0 +1,44 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@alilc/lowcode-editor-core';
import Area from '../area';
import { PanelConfig } from '../types';
import Panel from '../widget/panel';
@observer
export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> {
componentDidUpdate() {
// FIXME: dirty fix, need deep think
this.props.area.skeleton.editor.get('designer')?.touchOffsetObserver();
}
render() {
const { area } = this.props;
const width = area.current?.config.props?.width;
const style = width
? {
width,
}
: undefined;
return (
<div
className={classNames('lc-left-fixed-pane', {
'lc-area-visible': area.visible,
})}
style={style}
>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area<PanelConfig, Panel> }> {
render() {
const { area } = this.props;
return <Fragment>{area.container.items.map((panel) => panel.content)}</Fragment>;
}
}

View File

@ -0,0 +1,131 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer, Focusable, focusTracker } from '@alilc/lowcode-editor-core';
import Area from '../area';
import Panel from '../widget/panel';
@observer
export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }> {
private dispose?: () => void;
private focusing?: Focusable;
private shell: HTMLElement | null = null;
componentDidMount() {
const { area } = this.props;
const triggerClose = (e: any) => {
if (!area.visible) return;
// 当 MouseEvent 的 target 为「插入占位符」时,不关闭当前 panel
if (e.originalEvent?.target?.classList.contains('insertion')) return;
// 假如当前操作 target 祖先节点中有属性 data-keep-visible-while-dragging="true" 代表该 target 所属 panel
// 不希望 target 在 panel 范围内拖拽时关闭 panel
const panelElem = e.originalEvent?.target.closest('div[data-keep-visible-while-dragging="true"]');
if (panelElem) return;
area.setVisible(false);
};
area.skeleton.editor.on('designer.drag', triggerClose);
this.dispose = () => {
area.skeleton.editor.removeListener('designer.drag', triggerClose);
};
this.focusing = focusTracker.create({
range: (e) => {
const target = e.target as HTMLElement;
if (!target) {
return false;
}
if (this.shell?.contains(target)) {
return true;
}
// 点击了 iframe 内容,算失焦
if ((document.querySelector('.lc-simulator-content-frame') as HTMLIFrameElement)?.contentWindow?.document.documentElement.contains(target)) {
return false;
}
// 点击设置区
if (document.querySelector('.lc-right-area')?.contains(target)) {
return false;
}
// 点击非编辑区域的popup/dialog,插件栏左侧等不触发失焦
if (!document.querySelector('.lc-workbench')?.contains(target)) {
return true;
}
// 排除设置区iframe 之后,都不算失焦
if (document.querySelector('.lc-workbench-body')?.contains(target)) {
return true;
}
const docks = area.current?.getAssocDocks();
if (docks && docks?.length) {
return docks.some(dock => dock.getDOMNode()?.contains(target));
}
return false;
},
onEsc: () => {
this.props.area.setVisible(false);
},
onBlur: () => {
this.props.area.setVisible(false);
},
});
this.onEffect();
}
onEffect() {
const { area } = this.props;
if (area.visible) {
this.focusing?.active();
// 关闭当前fixed区域的面板
// TODO: 看看有没有更合适的地方
const fixedContainer = area?.skeleton?.leftFixedArea?.container;
const currentFixed = fixedContainer?.current;
if (currentFixed) {
fixedContainer.unactive(currentFixed);
}
} else {
this.focusing?.suspense();
}
}
componentDidUpdate() {
this.onEffect();
}
componentWillUnmount() {
this.focusing?.purge();
this.dispose?.();
}
render() {
const { area } = this.props;
const width = area.current?.config.props?.width;
const style = width ? {
width,
} : undefined;
return (
<div
ref={(ref) => { this.shell = ref; }}
className={classNames('lc-left-float-pane', {
'lc-area-visible': area.visible,
})}
style={style}
>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area<any, Panel> }> {
render() {
const { area } = this.props;
return (
<Fragment>
{area.container.items.map((panel) => panel.content)}
</Fragment>
);
}
}

View File

@ -0,0 +1,18 @@
import { Component } from 'react';
import classNames from 'classnames';
import { observer } from '@alilc/lowcode-editor-core';
import Area from '../area';
import Panel from '../widget/panel';
import Widget from '../widget/widget';
@observer
export default class MainArea extends Component<{ area: Area<any, Panel | Widget> }> {
render() {
const { area } = this.props;
return (
<div className={classNames('lc-main-area engine-workspacepane')}>
{area.container.items.map((item) => item.content)}
</div>
);
}
}

View File

@ -0,0 +1,62 @@
@import '../less-variables.less';
/*
* Theme Colors
*
* 乐高设计器的主要主题色变量
*/
:root {
--color-brand: @brand-color-1;
--color-brand-light: @brand-color-2;
--color-brand-dark: @brand-color-3;
--color-canvas-background: @normal-alpha-8;
--color-icon-normal: @normal-alpha-4;
--color-icon-hover: @normal-alpha-3;
--color-icon-active: @brand-color-1;
--color-icon-reverse: @white-alpha-1;
--color-line-normal: @normal-alpha-7;
--color-line-darken: darken(@normal-alpha-7, 10%);
--color-title: @dark-alpha-2;
--color-text: @dark-alpha-3;
--color-text-dark: darken(@dark-alpha-3, 10%);
--color-text-light: lighten(@dark-alpha-3, 10%);
--color-text-reverse: @white-alpha-2;
--color-text-regular: @normal-alpha-2;
--color-field-label: @dark-alpha-4;
--color-field-text: @dark-alpha-3;
--color-field-placeholder: @normal-alpha-5;
--color-field-border: @normal-alpha-5;
--color-field-border-hover: @normal-alpha-4;
--color-field-border-active: @normal-alpha-3;
--color-field-background: @white-alpha-1;
--color-function-success: @brand-success;
--color-function-success-dark: darken(@brand-success, 10%);
--color-function-success-light: lighten(@brand-success, 10%);
--color-function-warning: @brand-warning;
--color-function-warning-dark: darken(@brand-warning, 10%);
--color-function-warning-light: lighten(@brand-warning, 10%);
--color-function-information: @brand-link-hover;
--color-function-information-dark: darken(@brand-link-hover, 10%);
--color-function-information-light: lighten(@brand-link-hover, 10%);
--color-function-error: @brand-danger;
--color-function-error-dark: darken(@brand-danger, 10%);
--color-function-error-light: lighten(@brand-danger, 10%);
--color-pane-background: @white-alpha-1;
--color-block-background-normal: @white-alpha-1;
--color-block-background-light: @normal-alpha-9;
--color-block-background-shallow: @normal-alpha-8;
--color-block-background-dark: @normal-alpha-7;
--color-block-background-disabled: @normal-alpha-6;
--color-block-background-deep-dark: @normal-5;
--color-layer-mask-background: @dark-alpha-7;
--color-layer-tooltip-background: rgba(44,47,51,0.8);
--pane-title-bg-color: rgba(31,56,88,.04);
}

View File

@ -0,0 +1,62 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@alilc/lowcode-editor-core';
import Area from '../area';
@observer
export default class TopArea extends Component<{ area: Area; itemClassName?: string }> {
render() {
const { area, itemClassName } = this.props;
if (!area?.container?.items?.length) {
return null;
}
return (
<div className={classNames('lc-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);
}
});
if (!center || !center.length) {
return null;
}
return (
<Fragment>
<div className="lc-top-area-left">{left}</div>
<div className="lc-top-area-center">{center}</div>
<div className="lc-top-area-right">{right}</div>
</Fragment>
);
}
}

View File

@ -0,0 +1,398 @@
@import './theme.less';
:root {
--font-family: @font-family;
--font-size-label: @fontSize-4;
--font-size-text: @fontSize-5;
--font-size-btn-large: @fontSize-3;
--font-size-btn-medium: @fontSize-4;
--font-size-btn-small: @fontSize-5;
--global-border-radius: @global-border-radius;
--input-border-radius: @input-border-radius;
--popup-border-radius: @popup-border-radius;
--left-area-width: 48px;
--right-area-width: 300px;
--top-area-height: 48px;
--toolbar-height: 36px;
--dock-pane-width: 300px;
--dock-fixed-pane-width: 300px;
}
@media (min-width: 1860px) {
:root {
--right-area-width: 400px;
--dock-pane-width: 452px;
--dock-fixed-pane-width: 350px;
}
}
html,
body {
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
position: relative;
font-family: var(--font-family);
font-size: var(--font-size-text);
color: var(--color-text);
background-color: #edeff3;
}
* {
box-sizing: border-box;
}
.lc-titled-panel {
width: 100%;
height: 100%;
position: relative;
background-color: #fff;
background-color: var(--color-pane-background);
&.hidden {
display: none;
}
.lc-panel-title {
// background-color: var(--pane-title-bg-color,rgba(31,56,88,.04));
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 15px;
.lc-help-tip {
margin-left: 4px;
color: rgba(0, 0, 0, 0.4);
cursor: pointer;
}
}
> .lc-panel-title {
height: 48px;
font-size: 16px;
padding: 0 15px;
// border-bottom: 1px solid var(--color-line-normal,rgba(31,56,88,.1));
color: #0f1726;
font-weight: bold;
}
.lc-panel-body {
position: absolute;
top: 48px;
bottom: 0;
left: 0;
right: 0;
overflow: visible;
/*
.my-tabs {
width: 100%;
height: 100%;
position: relative;
.tabs-title {
display: flex;
height: var(--pane-title-height);
> .tab-title {
cursor: pointer;
padding: 0;
flex: 1;
min-width: 0;
justify-content: center;
border-bottom: 2px solid transparent;
&.actived {
cursor: default;
color: var(--color-text-avtived);
border-bottom-color: #3896ee;
}
}
}
.tabs-content {
position: absolute;
top: var(--pane-title-height);
bottom: 0;
left: 0;
right: 0;
height: calc(100% - var(--pane-title-height));
overflow: hidden;
}
}
*/
}
.lc-outline-tree-container {
border-top: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1));
}
}
.lc-panel {
height: 100%;
width: 100%;
position: relative;
background-color: #fff;
background-color: var(--color-pane-background);
// overflow: auto;
&.hidden {
display: none;
}
}
.lc-workspace-workbench {
height: 100%;
display: flex;
flex-direction: column;
background-color: #edeff3;
.lc-top-area {
height: var(--top-area-height);
background-color: var(--color-pane-background);
width: 100%;
display: none;
margin-bottom: 2px;
padding: 8px 12px 8px 16px;
&.lc-area-visible {
display: flex;
}
.lc-top-area-left {
display: flex;
align-items: center;
}
.lc-top-area-center {
flex: 1;
display: flex;
justify-content: center;
margin: 0 8px;
}
.lc-top-area-right {
display: flex;
align-items: center;
> * {
margin-left: 4px;
margin-right: 4px;
}
.ve-quick-search-trigger {
display: flex;
}
}
}
.lc-workspace-workbench-body {
flex: 1;
display: flex;
min-height: 0;
position: relative;
.lc-tabs-title {
width: 100%;
height: 32px;
position: relative;
display: center;
display: flex;
justify-content: center;
align-items: center;
// background: rgba(31,56,88,0.04);
border-bottom: 1px solid #edeff3;
.lc-tab-title {
flex: 1;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 2px solid transparent;
cursor: pointer;
font-size: 12px;
&.actived {
color: #0079f2;
border-bottom-color: #0079f2;
}
}
}
.lc-tabs-content {
position: absolute;
top: 32px;
bottom: 0;
left: 0;
right: 0;
}
.lc-pane-icon-close {
position: absolute;
right: 16px;
top: 14px;
height: auto;
z-index: 2;
.next-icon {
line-height: 1;
color: rgba(0, 0, 0, 0.6);
}
}
.lc-pane-icon-fix,
.lc-pane-icon-float {
position: absolute;
right: 38px;
top: 14px;
height: auto;
z-index: 2;
svg {
vertical-align: middle;
color: rgba(0, 0, 0, 0.6);
}
}
.lc-left-float-pane {
position: absolute;
top: 0;
bottom: 0;
width: var(--dock-pane-width);
// min-width: var(--dock-fixed-pane-width);
left: calc(var(--left-area-width) + 1px);
background-color: var(--color-pane-background);
box-shadow: 4px 6px 6px 0 rgba(31, 50, 88, 0.08);
z-index: 820;
display: none;
// padding-top: 36px;
&.lc-area-visible {
display: block;
}
}
.lc-left-area {
height: 100%;
width: var(--left-area-width);
background-color: var(--color-pane-background);
display: none;
flex-shrink: 0;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
&.lc-area-visible {
display: flex;
}
.lc-left-area-top,
.lc-left-area-bottom {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
.lc-title {
flex-direction: column;
width: 46px;
height: 46px;
display: flex;
align-items: center;
justify-content: center;
&.has-tip {
cursor: pointer;
}
&.actived {
color: #0079f2;
}
&.disabled {
opacity: 0.4;
}
.lc-title-icon {
height: 20px;
width: 20px;
margin: 0;
.next-icon:before {
line-height: 1 !important;
}
}
}
}
.lc-left-area-top {
padding-top: 12px;
}
.lc-left-area-bottom {
padding-bottom: 12px;
}
}
.lc-left-fixed-pane {
width: var(--dock-fixed-pane-width);
background-color: var(--color-pane-background);
height: 100%;
display: none;
flex-shrink: 0;
position: relative;
z-index: 820;
&.lc-area-visible {
display: block;
}
}
.lc-left-area.lc-area-visible ~ .lc-left-fixed-pane {
margin-left: 1px;
}
.lc-left-area.lc-area-visible ~ .lc-workspace-workbench-center {
margin-left: 2px;
}
.lc-outline-pane {
.lc-outline-tree .tree-node .tree-node-title {
border-bottom: none;
}
}
.lc-workspace-workbench-center {
flex: 1;
display: flex;
flex-direction: column;
z-index: 10;
.lc-toolbar {
display: flex;
height: var(--toolbar-height);
background-color: var(--color-pane-background);
padding: 8px 16px;
.lc-toolbar-center {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
}
}
.lc-main-area {
flex: 1;
}
.lc-bottom-area {
height: var(--bottom-area-height);
background-color: var(--color-pane-background);
display: none;
&.lc-area-visible {
display: block;
}
}
}
.lc-right-area {
height: 100%;
width: var(--right-area-width);
background-color: var(--color-pane-background);
display: none;
flex-shrink: 0;
margin-left: 2px;
position: relative;
> .lc-panel {
position: absolute;
left: 0;
top: 0;
}
&.lc-area-visible {
display: block;
}
.lc-settings-tabs {
> .next-tabs-nav-extra {
top: 36px !important;
}
.lc-settings-tab-item {
.next-tabs-tab-inner {
font-size: 12px;
line-height: 12px;
}
}
.lc-title {
color: inherit;
line-height: inherit !important;
}
}
.lc-settings-tabs-content {
top: 66px;
}
}
}
}

View File

@ -0,0 +1,56 @@
import { Component } from 'react';
import { TipContainer, observer } from '@alilc/lowcode-editor-core';
import { EditorWindowView } from '../editor-window/view';
import classNames from 'classnames';
import { Skeleton } from '../skeleton';
import TopArea from './top-area';
import LeftArea from './left-area';
import LeftFixedPane from './left-fixed-pane';
import LeftFloatPane from './left-float-pane';
// import Toolbar from './toolbar';
import MainArea from './main-area';
import BottomArea from './bottom-area';
// import RightArea from './right-area';
import './workbench.less';
import { SkeletonContext } from '../context';
import { EditorConfig, PluginClassSet } from '@alilc/lowcode-types';
import { WorkSpace } from '..';
@observer
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;
const { skeleton } = workSpace;
skeleton.buildFromConfig(config, components);
}
// componentDidCatch(error: any) {
// globalContext.get(Editor).emit('editor.skeleton.workbench.error', error);
// }
render() {
const { workSpace, className, topAreaItemClassName } = this.props;
const { skeleton } = workSpace;
return (
<div className={classNames('lc-workspace-workbench', className)}>
<SkeletonContext.Provider value={skeleton}>
<TopArea area={skeleton.topArea} itemClassName={topAreaItemClassName} />
<div className="lc-workspace-workbench-body">
<LeftArea area={skeleton.leftArea} />
<LeftFloatPane area={skeleton.leftFloatArea} />
<LeftFixedPane area={skeleton.leftFixedArea} />
<div className="lc-workspace-workbench-center">
{/* <Toolbar area={skeleton.toolbar} /> */}
<EditorWindowView editorWindow={workSpace.window} />
<MainArea area={skeleton.mainArea} />
<BottomArea area={skeleton.bottomArea} />
</div>
{/* <RightArea area={skeleton.rightArea} /> */}
</div>
<TipContainer />
</SkeletonContext.Provider>
</div>
);
}
}

View File

@ -0,0 +1,215 @@
/*
* 基础的 DPL 定义使用了 kuma base 的定义,参考:
* https://github.com/uxcore/kuma-base/tree/master/variables
*/
/**
* ===========================================================
* ==================== Font Family ==========================
* ===========================================================
*/
/*
* @font-family: "STHeiti", "Microsoft Yahei", "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
*/
@font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Helvetica, Arial, sans-serif;
@font-family-code: Monaco, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Helvetica, Arial,
sans-serif;
/**
* ===========================================================
* ===================== Color DPL ===========================
* ===========================================================
*/
@brand-color-1: rgba(0, 108, 255, 1);
@brand-color-2: rgba(25, 122, 255, 1);
@brand-color-3: rgba(0, 96, 229, 1);
@brand-color-1-3: rgba(0, 108, 255, 0.6);
@brand-color-1-4: rgba(0, 108, 255, 0.4);
@brand-color-1-5: rgba(0, 108, 255, 0.3);
@brand-color-1-6: rgba(0, 108, 255, 0.2);
@brand-color-1-7: rgba(0, 108, 255, 0.1);
@brand-color: @brand-color-1;
@white-alpha-1: rgb(255, 255, 255); // W-1
@white-alpha-2: rgba(255, 255, 255, 0.8); // W-2 A80
@white-alpha-3: rgba(255, 255, 255, 0.6); // W-3 A60
@white-alpha-4: rgba(255, 255, 255, 0.4); // W-4 A40
@white-alpha-5: rgba(255, 255, 255, 0.3); // W-5 A30
@white-alpha-6: rgba(255, 255, 255, 0.2); // W-6 A20
@white-alpha-7: rgba(255, 255, 255, 0.1); // W-7 A10
@white-alpha-8: rgba(255, 255, 255, 0.06); // W-8 A6
@dark-alpha-1: rgba(0, 0, 0, 1); // D-1 A100
@dark-alpha-2: rgba(0, 0, 0, 0.8); // D-2 A80
@dark-alpha-3: rgba(0, 0, 0, 0.6); // D-3 A60
@dark-alpha-4: rgba(0, 0, 0, 0.4); // D-4 A40
@dark-alpha-5: rgba(0, 0, 0, 0.3); // D-5 A30
@dark-alpha-6: rgba(0, 0, 0, 0.2); // D-6 A20
@dark-alpha-7: rgba(0, 0, 0, 0.1); // D-7 A10
@dark-alpha-8: rgba(0, 0, 0, 0.06); // D-8 A6
@dark-alpha-9: rgba(0, 0, 0, 0.04); // D-9 A4
@normal-alpha-1: rgba(31, 56, 88, 1); // N-1 A100
@normal-alpha-2: rgba(31, 56, 88, 0.8); // N-2 A80
@normal-alpha-3: rgba(31, 56, 88, 0.6); // N-3 A60
@normal-alpha-4: rgba(31, 56, 88, 0.4); // N-4 A40
@normal-alpha-5: rgba(31, 56, 88, 0.3); // N-5 A30
@normal-alpha-6: rgba(31, 56, 88, 0.2); // N-6 A20
@normal-alpha-7: rgba(31, 56, 88, 0.1); // N-7 A10
@normal-alpha-8: rgba(31, 56, 88, 0.06); // N-8 A6
@normal-alpha-9: rgba(31, 56, 88, 0.04); // N-9 A4
@normal-3: #77879c;
@normal-4: #a3aebd;
@normal-5: #bac3cc;
@normal-6: #d1d7de;
@gray-dark: #333; // N2_4
@gray: #666; // N2_3
@gray-light: #999; // N2_2
@gray-lighter: #ccc; // N2_1
@brand-secondary: #2c2f33; // B2_3
// 补色
@brand-complement: #00b3e8; // B3_1
// 复合
@brand-comosite: #00c587; // B3_2
// 浓度
@brand-deep: #73461d; // B3_3
// F1-1
@brand-danger: rgb(240, 70, 49);
// F1-2 (10% white)
@brand-danger-hover: rgba(240, 70, 49, 0.9);
// F1-3 (5% black)
@brand-danger-focus: rgba(240, 70, 49, 0.95);
// F2-1
@brand-warning: rgb(250, 189, 14);
// F3-1
@brand-success: rgb(102, 188, 92);
// F4-1
@brand-link: rgb(102, 188, 92);
// F4-2
@brand-link-hover: #2e76a6;
// F1-1-7 A10
@brand-danger-alpha-7: rgba(240, 70, 49, 0.9);
// F1-1-8 A6
@brand-danger-alpha-8: rgba(240, 70, 49, 0.8);
// F2-1-2 A80
@brand-warning-alpha-2: rgba(250, 189, 14, 0.8);
// F2-1-7 A10
@brand-warning-alpha-7: rgba(250, 189, 14, 0.9);
// F3-1-2 A80
@brand-success-alpha-2: rgba(102, 188, 92, 0.8);
// F3-1-7 A10
@brand-success-alpha-7: rgba(102, 188, 92, 0.9);
// F4-1-7 A10
@brand-link-alpha-7: rgba(102, 188, 92, 0.9);
// 文本色
@text-primary-color: @dark-alpha-3;
@text-secondary-color: @normal-alpha-3;
@text-thirdary-color: @dark-alpha-4;
@text-disabled-color: @normal-alpha-5;
@text-helper-color: @dark-alpha-4;
@text-danger-color: @brand-danger;
@text-ali-color: #ec6c00;
/**
* ===========================================================
* =================== Shadow Box ============================
* ===========================================================
*/
@box-shadow-1: 0 1px 4px 0 rgba(31, 56, 88, 0.15); // 1 级阴影,物体由原来存在于底面的物体展开,物体和底面关联紧密
@box-shadow-2: 0 2px 10px 0 rgba(31, 56, 88, 0.15); // 2 级阴影hover状态物体层级较高
@box-shadow-3: 0 4px 15px 0 rgba(31, 56, 88, 0.15); // 3 级阴影,当物体层级高于所有界面元素,弹窗用
/**
* ===========================================================
* ================= FontSize of Level =======================
* ===========================================================
*/
@fontSize-1: 26px;
@fontSize-2: 20px;
@fontSize-3: 16px;
@fontSize-4: 14px;
@fontSize-5: 12px;
@fontLineHeight-1: 38px;
@fontLineHeight-2: 30px;
@fontLineHeight-3: 26px;
@fontLineHeight-4: 24px;
@fontLineHeight-5: 20px;
/**
* ===========================================================
* ================= FontSize of Level =======================
* ===========================================================
*/
@global-border-radius: 3px;
@input-border-radius: 3px;
@popup-border-radius: 6px;
/**
* ===========================================================
* ===================== Transistion =========================
* ===========================================================
*/
@transition-duration: 0.3s;
@transition-ease: cubic-bezier(0.23, 1, 0.32, 1);
@transition-delay: 0s;
/**
* ===========================================================
* ================ Global Configruations ====================
* ===========================================================
*/
@topPaneHeight: 48px;
@actionpane-height: 48px;
@tabPaneWidth: 260px;
@input-standard-height: 32px;
@dockpane-width: 48px;
/**
* ===========================================================
* =================== Deprecated Items ======================
* ===========================================================
*/
@head-bgcolor: @white-alpha-1;
@pane-bgcolor: @white-alpha-1;
@pane-dark-bgcolor: @white-alpha-1;
@pane-bdcolor: @normal-4;
@blank-bgcolor: @normal-5;
@title-bgcolor: @white-alpha-1;
@title-bdcolor: transparent;
@section-bgcolor: transparent;
@section-bdcolor: @white-alpha-1;
@button-bgcolor: @white-alpha-1;
@button-bdcolor: transparent;
@button-blue-color: @brand-color;
@button-blue-hover-color: @brand-color;
@sub-title-bgcolor: @white-alpha-1;
@sub-title-bdcolor: transparent;
@text-color: @text-primary-color;
@icon-color: @gray;
@icon-color-active: @gray-light;
@ghost-bgcolor: @dark-alpha-3;
@input-bgcolor: transparent;
@input-bdcolor: @normal-alpha-5;
@hover-color: #5a99cc;
@active-color: #5a99cc;
@disabled-color: #666;
@setter-popup-bg: rgb(80, 86, 109);

View File

@ -0,0 +1,37 @@
import { EditorViewOptions, ResourceOptions } from '.';
export class Resource {
constructor(options: ResourceOptions) {
if (options.editorViews) {
options.editorViews.forEach((d: any) => {
this.editorViewMap.set(d.name, d);
});
}
this.options = options;
}
options: ResourceOptions;
editorViewMap: Map<string, EditorViewOptions> = new Map<string, EditorViewOptions>();
init(ctx: any) {
this.options.init(ctx);
}
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());
}
}

View File

@ -0,0 +1,18 @@
import {
Node as InnerNode,
SettingField as InnerSettingField,
} from '@alilc/lowcode-designer';
import { IShellModelFactory, IPublicModelNode, IPublicModelSettingPropEntry } from '@alilc/lowcode-types';
import {
Node,
SettingPropEntry,
} from '@alilc/lowcode-shell';
class ShellModelFactory implements IShellModelFactory {
createNode(node: InnerNode | null | undefined): IPublicModelNode | null {
return Node.create(node);
}
createSettingPropEntry(prop: InnerSettingField): IPublicModelSettingPropEntry {
return SettingPropEntry.create(prop);
}
}
export const shellModelFactory = new ShellModelFactory();

View File

@ -0,0 +1,406 @@
import { Editor, action, makeObservable } from '@alilc/lowcode-editor-core';
import {
DockConfig,
PanelConfig,
WidgetConfig,
IWidgetBaseConfig,
PanelDockConfig,
DialogDockConfig,
isDockConfig,
isPanelDockConfig,
isPanelConfig,
DividerConfig,
isDividerConfig,
IWidgetConfigArea,
} from './types';
import Panel, { isPanel } from './widget/panel';
import WidgetContainer from './widget/widget-container';
import Area from './area';
import Widget, { isWidget, IWidget } from './widget/widget';
import PanelDock from './widget/panel-dock';
import Dock from './widget/dock';
import { Stage, StageConfig } from './widget/stage';
import { isValidElement } from 'react';
import { isPlainObject, uniqueId } from '@alilc/lowcode-utils';
import { Divider } from '@alifd/next';
import { EditorConfig, PluginClassSet } from '@alilc/lowcode-types';
export enum SkeletonEvents {
PANEL_DOCK_ACTIVE = 'skeleton.panel-dock.active',
PANEL_DOCK_UNACTIVE = 'skeleton.panel-dock.unactive',
PANEL_SHOW = 'skeleton.panel.show',
PANEL_HIDE = 'skeleton.panel.hide',
WIDGET_SHOW = 'skeleton.widget.show',
WIDGET_HIDE = 'skeleton.widget.hide',
WIDGET_DISABLE = 'skeleton.widget.disable',
WIDGET_ENABLE = 'skeleton.widget.enable',
}
export class Skeleton {
private panels = new Map<string, Panel>();
private containers = new Map<string, WidgetContainer<any>>();
readonly leftArea: Area<DockConfig | PanelDockConfig | DialogDockConfig>;
readonly topArea: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
readonly toolbar: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
readonly leftFixedArea: Area<PanelConfig, Panel>;
readonly leftFloatArea: Area<PanelConfig, Panel>;
readonly rightArea: Area<PanelConfig, Panel>;
readonly mainArea: Area<WidgetConfig | PanelConfig, Widget | Panel>;
readonly bottomArea: Area<PanelConfig, Panel>;
readonly stages: Area<StageConfig, Stage>;
constructor(readonly editor: Editor) {
makeObservable(this);
this.leftArea = new Area(
this,
'leftArea',
(config) => {
if (isWidget(config)) {
return config;
}
return this.createWidget(config);
},
false,
);
this.topArea = new Area(
this,
'topArea',
(config) => {
if (isWidget(config)) {
return config;
}
return this.createWidget(config);
},
false,
);
this.toolbar = new Area(
this,
'toolbar',
(config) => {
if (isWidget(config)) {
return config;
}
return this.createWidget(config);
},
false,
);
this.leftFixedArea = new Area(
this,
'leftFixedArea',
(config) => {
if (isPanel(config)) {
return config;
}
return this.createPanel(config);
},
true,
);
this.leftFloatArea = new Area(
this,
'leftFloatArea',
(config) => {
if (isPanel(config)) {
return config;
}
return this.createPanel(config);
},
true,
);
this.rightArea = new Area(
this,
'rightArea',
(config) => {
if (isPanel(config)) {
return config;
}
return this.createPanel(config);
},
false,
true,
);
this.mainArea = new Area(
this,
'mainArea',
(config) => {
if (isWidget(config)) {
return config as Widget;
}
return this.createWidget(config) as Widget;
},
true,
true,
);
this.bottomArea = new Area(
this,
'bottomArea',
(config) => {
if (isPanel(config)) {
return config;
}
return this.createPanel(config);
},
true,
);
this.stages = new Area(this, 'stages', (config) => {
if (isWidget(config)) {
return config;
}
return new Stage(this, config);
});
this.setupPlugins();
this.setupEvents();
}
/**
* setup events
*
* @memberof Skeleton
*/
setupEvents() {
// adjust pinned status when panel shown
this.editor.on('skeleton.panel.show', (panelName, panel) => {
const panelNameKey = `${panelName}-pinned-status-isFloat`;
const isInFloatAreaPreferenceExists = this.editor?.getPreference()?.contains(panelNameKey, 'skeleton');
if (isInFloatAreaPreferenceExists) {
const isInFloatAreaFromPreference = this.editor?.getPreference()?.get(panelNameKey, 'skeleton');
const isCurrentInFloatArea = panel?.isChildOfFloatArea();
if (isInFloatAreaFromPreference !== isCurrentInFloatArea) {
this.toggleFloatStatus(panel);
}
}
});
}
/**
* set isFloat status for panel
*
* @param {*} panel
* @memberof Skeleton
*/
@action
toggleFloatStatus(panel: Panel) {
const isFloat = panel?.parent?.name === 'leftFloatArea';
if (isFloat) {
this.leftFloatArea.remove(panel);
this.leftFixedArea.add(panel);
this.leftFixedArea.container.active(panel);
} else {
this.leftFixedArea.remove(panel);
this.leftFloatArea.add(panel);
this.leftFloatArea.container.active(panel);
}
this.editor?.getPreference()?.set(`${panel.name}-pinned-status-isFloat`, !isFloat, 'skeleton');
}
buildFromConfig(config?: EditorConfig, components: PluginClassSet = {}) {
if (config) {
this.editor.init(config, components);
}
this.setupPlugins();
}
private setupPlugins() {
const { config, components = {} } = this.editor;
if (!config) {
return;
}
const { plugins } = config;
if (!plugins) {
return;
}
Object.keys(plugins).forEach((area) => {
plugins[area].forEach((item) => {
const { pluginKey, type, props = {}, pluginProps } = item;
const config: Partial<IWidgetBaseConfig> = {
area: area as IWidgetConfigArea,
type: 'Widget',
name: pluginKey,
contentProps: pluginProps,
};
const { dialogProps, balloonProps, panelProps, linkProps, ...restProps } = props;
config.props = restProps;
if (dialogProps) {
config.dialogProps = dialogProps;
}
if (balloonProps) {
config.balloonProps = balloonProps;
}
if (panelProps) {
config.panelProps = panelProps;
}
if (linkProps) {
config.linkProps = linkProps;
}
if (type === 'TabPanel') {
config.type = 'Panel';
} else if (/Icon$/.test(type)) {
config.type = type.replace('Icon', 'Dock');
}
if (pluginKey in components) {
config.content = components[pluginKey];
}
this.add(config as IWidgetBaseConfig);
});
});
}
postEvent(event: SkeletonEvents, ...args: any[]) {
this.editor.emit(event, ...args);
}
readonly widgets: IWidget[] = [];
createWidget(config: IWidgetBaseConfig | IWidget) {
if (isWidget(config)) {
return config;
}
config = this.parseConfig(config);
let widget: IWidget;
if (isDockConfig(config)) {
if (isPanelDockConfig(config)) {
widget = new PanelDock(this, config);
} else if (false) {
// DialogDock
// others...
} else {
widget = new Dock(this, config);
}
} else if (isDividerConfig(config)) {
widget = new Widget(this, {
...config,
type: 'Widget',
content: Divider,
});
} else if (isPanelConfig(config)) {
widget = this.createPanel(config);
} else {
widget = new Widget(this, config as WidgetConfig);
}
this.widgets.push(widget);
return widget;
}
getWidget(name: string): IWidget | undefined {
return this.widgets.find(widget => widget.name === name);
}
createPanel(config: PanelConfig) {
const parsedConfig = this.parseConfig(config);
const panel = new Panel(this, parsedConfig as PanelConfig);
this.panels.set(panel.name, panel);
return panel;
}
getPanel(name: string): Panel | undefined {
return this.panels.get(name);
}
getStage(name: string) {
return this.stages.container.get(name);
}
createStage(config: any) {
const stage = this.add({
name: uniqueId('stage'),
area: 'stages',
...config,
});
return stage?.getName?.();
}
createContainer(
name: string,
handle: (item: any) => any,
exclusive = false,
checkVisible: () => boolean = () => true,
defaultSetCurrent = false,
) {
const container = new WidgetContainer(name, handle, exclusive, checkVisible, defaultSetCurrent);
this.containers.set(name, container);
return container;
}
private parseConfig(config: IWidgetBaseConfig) {
if (config.parsed) {
return config;
}
const { content, ...restConfig } = config;
if (content) {
if (isPlainObject(content) && !isValidElement(content)) {
Object.keys(content).forEach((key) => {
if (/props$/i.test(key) && restConfig[key]) {
restConfig[key] = {
...restConfig[key],
...content[key],
};
} else {
restConfig[key] = content[key];
}
});
} else {
restConfig.content = content;
}
}
restConfig.pluginKey = restConfig.name;
restConfig.parsed = true;
return restConfig;
}
add(config: IWidgetBaseConfig, extraConfig?: Record<string, any>) {
const parsedConfig = {
...this.parseConfig(config),
...extraConfig,
};
let { area } = parsedConfig;
if (!area) {
if (parsedConfig.type === 'Panel') {
area = 'leftFloatArea';
} else if (parsedConfig.type === 'Widget') {
area = 'mainArea';
} else {
area = 'leftArea';
}
}
switch (area) {
case 'leftArea':
case 'left':
return this.leftArea.add(parsedConfig as PanelDockConfig);
case 'rightArea':
case 'right':
return this.rightArea.add(parsedConfig as PanelConfig);
case 'topArea':
case 'top':
return this.topArea.add(parsedConfig as PanelDockConfig);
case 'toolbar':
return this.toolbar.add(parsedConfig as PanelDockConfig);
case 'mainArea':
case 'main':
case 'center':
case 'centerArea':
return this.mainArea.add(parsedConfig as PanelConfig);
case 'bottomArea':
case 'bottom':
return this.bottomArea.add(parsedConfig as PanelConfig);
case 'leftFixedArea':
return this.leftFixedArea.add(parsedConfig as PanelConfig);
case 'leftFloatArea':
return this.leftFloatArea.add(parsedConfig as PanelConfig);
case 'stages':
return this.stages.add(parsedConfig as StageConfig);
default:
// do nothing
}
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": [
"./src/"
]
}