polyfill panes

This commit is contained in:
kangwei 2020-04-15 01:44:45 +08:00
parent 33750b7fef
commit 89196c536c
35 changed files with 2232 additions and 226 deletions

View File

@ -46,12 +46,12 @@
}
&-device-default {
top: 15px;
right: 15px;
bottom: 15px;
left: 15px;
top: 16px;
right: 16px;
bottom: 16px;
left: 16px;
width: auto;
box-shadow: 0 2px 10px 0 rgba(31,56,88,.15);
box-shadow: 0 1px 4px 0 rgba(31, 50, 88, 0.125);
}
&-content {

View File

@ -8,7 +8,7 @@ import './designer.less';
import clipboard from './clipboard';
export class DesignerView extends Component<DesignerProps & {
designer: Designer;
designer?: Designer;
}> {
readonly designer: Designer;

View File

@ -7,18 +7,28 @@ export default class AreaManager {
private config: PluginConfig[];
private editor: Editor;
private area: string;
constructor(editor: Editor, area: string) {
this.editor = editor;
this.area = area;
this.config = (editor && editor.config && editor.config.plugins && editor.config.plugins[this.area]) || [];
constructor(private editor: Editor, private name: string) {
this.config = (editor && editor.config && editor.config.plugins && editor.config.plugins[name]) || [];
this.pluginStatus = clone(editor.pluginStatus);
}
public isPluginStatusUpdate(pluginType?: string, notUpdateStatus?: boolean): boolean {
setVisible(flag: boolean) {
}
isEnable() {
}
isVisible() {
}
isEmpty() {
}
isPluginStatusUpdate(pluginType?: string, notUpdateStatus?: boolean): boolean {
const { pluginStatus } = this.editor;
const list = pluginType ? this.config.filter((item): boolean => item.type === pluginType) : this.config;
@ -31,33 +41,33 @@ export default class AreaManager {
return isUpdate;
}
public getVisiblePluginList(pluginType?: string): PluginConfig[] {
getVisiblePluginList(pluginType?: string): PluginConfig[] {
const res = this.config.filter((item): boolean => {
return !!(!this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible);
});
return pluginType ? res.filter((item): boolean => item.type === pluginType) : res;
}
public getPlugin(pluginKey: string): HOCPlugin | void {
getPlugin(pluginKey: string): HOCPlugin | void {
if (pluginKey) {
return this.editor && this.editor.plugins && this.editor.plugins[pluginKey];
}
}
public getPluginConfig(pluginKey?: string): PluginConfig[] | PluginConfig | undefined {
getPluginConfig(pluginKey?: string): PluginConfig[] | PluginConfig | undefined {
if (pluginKey) {
return this.config.find(item => item.pluginKey === pluginKey);
}
return this.config;
}
public getPluginClass(pluginKey: string): PluginClass | void {
getPluginClass(pluginKey: string): PluginClass | void {
if (pluginKey) {
return this.editor && this.editor.components && this.editor.components[pluginKey];
}
}
public getPluginStatus(pluginKey: string): PluginStatus | void {
getPluginStatus(pluginKey: string): PluginStatus | void {
if (pluginKey) {
return this.editor && this.editor.pluginStatus && this.editor.pluginStatus[pluginKey];
}

View File

@ -91,7 +91,7 @@ export default class Editor extends EventEmitter {
private hooksFuncs: HooksFuncs;
constructor(config: EditorConfig, components: PluginClassSet, utils?: Utils) {
constructor(config: EditorConfig = {}, components: PluginClassSet = {}, utils?: Utils) {
super();
this.config = config;
this.components = {};

View File

@ -1,152 +1,12 @@
import React, { PureComponent } from 'react';
import { Editor, PluginConfig } from '@ali/lowcode-editor-core';
import { Editor } from '@ali/lowcode-editor-core';
import { DesignerView, Designer } from '@ali/lowcode-designer';
import './index.scss';
export interface PluginProps {
editor: Editor;
config: PluginConfig;
}
const SCHEMA = {
version: '1.0',
componentsMap: [],
componentsTree: [
{
componentName: 'Page',
fileName: 'test',
dataSource: {
list: [],
},
state: {
text: 'outter',
},
props: {
ref: 'outterView',
autoLoading: true,
style: {
padding: 20,
},
},
children: [
{
componentName: 'Form',
props: {
labelCol: 3,
style: {},
ref: 'testForm',
},
children: [
{
componentName: 'Form.Item',
props: {
label: '姓名:',
name: 'name',
initValue: '李雷',
},
children: [
{
componentName: 'Input',
props: {
placeholder: '请输入',
size: 'medium',
style: {
width: 320,
},
},
},
],
},
{
componentName: 'Form.Item',
props: {
label: '年龄:',
name: 'age',
initValue: '22',
},
children: [
{
componentName: 'NumberPicker',
props: {
size: 'medium',
type: 'normal',
},
},
],
},
{
componentName: 'Form.Item',
props: {
label: '职业:',
name: 'profession',
},
children: [
{
componentName: 'Select',
props: {
dataSource: [
{
label: '教师',
value: 't',
},
{
label: '医生',
value: 'd',
},
{
label: '歌手',
value: 's',
},
],
},
},
],
},
{
componentName: 'Div',
props: {
style: {
textAlign: 'center',
},
},
children: [
{
componentName: 'Button.Group',
props: {},
children: [
{
componentName: 'Button',
props: {
type: 'primary',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'submit',
},
children: '提交',
},
{
componentName: 'Button',
props: {
type: 'normal',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'reset',
},
children: '重置',
},
],
},
],
},
],
},
],
},
],
};
interface DesignerPluginState {
componentMetadatas?: any[] | null;
library?: any[] | null;

View File

@ -168,7 +168,6 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
}
}
});
// editor.once('outlinePane.visible', setup);
}
}

View File

@ -28,6 +28,7 @@
"@ali/lowcode-plugin-zh-en": "^0.8.6",
"@ali/lowcode-setters": "^0.8.6",
"@alifd/next": "^1.19.12",
"@ali/ve-less-variables": "2.0.3",
"@alife/theme-lowcode-dark": "^0.1.0",
"@alife/theme-lowcode-light": "^0.1.0",
"react": "^16.8.1",

View File

@ -1,5 +1,5 @@
import { init } from "./vision";
import editor from './editor';
import { init } from './vision'; // VisualEngine
import { editor } from './editor';
init();

View File

@ -1,68 +1,44 @@
import Editor from '@ali/lowcode-editor-core';
import outlinePane from '@ali/lowcode-plugin-outline-pane';
import settingsPane from '@ali/lowcode-plugin-settings-pane';
import designer from '@ali/lowcode-plugin-designer';
import OutlinePane from '@ali/lowcode-plugin-outline-pane';
import SettingsPane from '@ali/lowcode-plugin-settings-pane';
import Designer from '@ali/lowcode-plugin-designer';
import { registerSetters } from '@ali/lowcode-setters';
import { Skeleton } from './skeleton/skeleton';
registerSetters();
export default new Editor(
{
plugins: {
topArea: [],
leftArea: [
{
pluginKey: 'outlinePane',
type: 'PanelIcon',
props: {
align: 'top',
icon: 'shuxingkongjian',
title: '大纲树',
},
config: {
package: '@ali/lowcode-plugin-outline-pane',
version: '^0.8.0',
},
pluginProps: {},
},
],
rightArea: [
{
pluginKey: 'settingsPane',
type: 'Panel',
props: {},
config: {
package: '@ali/lowcode-plugin-settings-pane',
version: '^0.8.0',
},
pluginProps: {},
},
],
centerArea: [
{
pluginKey: 'designer',
type: '',
props: {},
config: {
package: '@ali/lowcode-plugin-designer',
version: '^0.8.0',
},
},
],
},
},
{
outlinePane,
settingsPane,
designer,
},
);
export const editor = new Editor();
export const skeleton = new Skeleton(editor);
skeleton.mainArea.add({
name: 'designer',
type: 'Widget',
content: Designer,
});
skeleton.rightArea.add({
name: 'settingsPane',
type: 'Panel',
content: SettingsPane,
});
skeleton.leftArea.add({
name: 'outlinePane',
type: 'PanelDock',
props: {
align: 'top',
icon: 'shuxingkongjian',
description: '大纲树',
},
content: OutlinePane,
panelProps: {
area: 'leftFloatArea'
}
});
// editor-core
// 1. di 实现
// 2. skeleton 区域管理
// 3. general bus: pub/sub
// 2. general bus: pub/sub
// editor-skeleton/workbench 视图实现
// 1. skeleton 区域划分 panes
// provide fixed left pane
// provide float left pane

View File

@ -0,0 +1,148 @@
import domReady = require('domready');
import * as EventEmitter from 'events';
const Shells = ['iphone6'];
export class Flags {
public emitter: EventEmitter;
public flags: string[];
public ready: boolean;
public lastFlags: string[];
public lastShell: string;
private lastSimulatorDevice: string;
constructor() {
this.emitter = new EventEmitter();
this.flags = ['design-mode'];
domReady(() => {
this.ready = true;
this.applyFlags();
});
}
public setDragMode(flag: boolean) {
if (flag) {
this.add('drag-mode');
} else {
this.remove('drag-mode');
}
}
public setPreviewMode(flag: boolean) {
if (flag) {
this.add('preview-mode');
this.remove('design-mode');
} else {
this.add('design-mode');
this.remove('preview-mode');
}
}
public setWithShell(shell: string) {
if (shell === this.lastShell) {
return;
}
if (this.lastShell) {
this.remove(`with-${this.lastShell}shell`);
}
if (shell) {
if (Shells.indexOf(shell) < 0) {
shell = Shells[0];
}
this.add(`with-${shell}shell`);
this.lastShell = shell;
}
}
public setSimulator(device: string) {
if (this.lastSimulatorDevice) {
this.remove(`simulator-${this.lastSimulatorDevice}`);
}
if (device !== '' && device !== 'pc') {
this.add(`simulator-${device}`);
}
this.lastSimulatorDevice = device;
}
public setHideSlate(flag: boolean) {
if (this.has('slate-fixed')) {
return;
}
if (flag) {
this.add('hide-slate');
} else {
this.remove('hide-slate');
}
}
public setSlateFixedMode(flag: boolean) {
if (flag) {
this.remove('hide-slate');
this.add('slate-fixed');
} else {
this.remove('slate-fixed');
}
}
public setSlateFullMode(flag: boolean) {
if (flag) {
this.add('slate-full-screen');
} else {
this.remove('slate-full-screen');
}
}
public getFlags() {
return this.flags;
}
public applyFlags(modifiedFlag?: string) {
if (!this.ready) {
return;
}
const doe = document.documentElement;
if (this.lastFlags) {
this.lastFlags.filter((flag: string) => this.flags.indexOf(flag) < 0).forEach((flag) => {
doe.classList.remove(`engine-${flag}`);
});
}
this.flags.forEach((flag) => {
doe.classList.add(`engine-${flag}`);
});
this.lastFlags = this.flags.slice(0);
this.emitter.emit('flagschange', this.flags, modifiedFlag);
}
public has(flag: string) {
return this.flags.indexOf(flag) > -1;
}
public add(flag: string) {
if (!this.has(flag)) {
this.flags.push(flag);
this.applyFlags(flag);
}
}
public remove(flag: string) {
const i = this.flags.indexOf(flag);
if (i > -1) {
this.flags.splice(i, 1);
this.applyFlags(flag);
}
}
public onFlagsChange(func: () => any) {
this.emitter.on('flagschange', func);
return () => {
this.emitter.removeListener('flagschange', func);
};
}
}
export default new Flags();

View File

@ -0,0 +1,206 @@
import { skeleton } from './editor';
import { ReactElement } from 'react';
import { IWidgetBaseConfig } from './skeleton/types';
export interface IContentItemConfig {
title: string;
content: JSX.Element;
tip?: {
content: string;
url?: string;
};
}
export interface OldPaneConfig {
// 'dock' | 'action' | 'tab' | 'widget' | 'stage'
type?: string; // where
id?: string;
name: string;
title?: string;
content?: any;
place?: string; // align: left|right|top|center|bottom
description?: string; // tip?
tip?:
| string
| {
// as help tip
url?: string;
content?: string | JSX.Element;
}; // help
init?: () => any;
destroy?: () => any;
props?: any;
contents?: IContentItemConfig[];
hideTitleBar?: boolean;
width?: number;
maxWidth?: number;
height?: number;
maxHeight?: number;
position?: string | string[]; // todo
menu?: JSX.Element; // as title
index?: number; // todo
isAction?: boolean; // as normal dock
fullScreen?: boolean; // todo
}
function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: string } {
const { type, id, name, title, content, place, description, init, destroy, props, index } = config;
const newConfig: any = {
id,
name,
content,
props: {
title,
description,
align: place,
onInit: init,
onDestroy: destroy,
},
contentProps: props,
index,
};
if (type === 'dock') {
newConfig.type = 'PanelDock';
newConfig.area = 'left';
const { contents, hideTitleBar, tip, width, maxWidth, height, maxHeight, position, menu, isAction } = config;
if (menu) {
newConfig.props.title = menu;
}
if (!isAction) {
newConfig.panelProps = {
hideTitleBar,
help: tip,
width,
maxWidth,
height,
maxHeight,
};
if (contents && Array.isArray(contents)) {
newConfig.content = contents.map(({ title, content, tip }) => {
return {
type: "Panel",
content,
props: {
title,
help: tip,
}
}
});
}
}
} else if (type === 'action') {
newConfig.area = 'top';
newConfig.type = 'Dock';
} else if (type === 'tab') {
newConfig.area = 'right';
newConfig.type = 'Panel';
} else if (type === 'stage') {
newConfig.area = 'stages';
newConfig.type = 'Widget';
} else {
newConfig.area = 'main';
newConfig.type = 'Widget';
}
return newConfig;
}
function add(config: (() => OldPaneConfig) | OldPaneConfig, extraConfig?: any) {
if (typeof config === 'function') {
config = config.call(null);
}
if (!config || !config.type) {
return null;
}
if (extraConfig) {
config = { ...config, ...extraConfig };
}
skeleton.add(upgradeConfig(config));
}
const actionPane = Object.assign(skeleton.topArea, {
/**
* compatible *VE.actionPane.getActions*
*/
getActions(): any {
return skeleton.topArea.container.items;
},
/**
* compatible *VE.actionPane.activeDock*
*/
setActions() {
// empty
},
});
const dockPane = Object.assign(skeleton.leftArea, {
/**
* compatible *VE.dockPane.activeDock*
*/
activeDock(item: any) {
const name = item.name || item;
skeleton.getPanel(name)?.active();
},
/**
* compatible *VE.dockPane.onDockShow*
*/
onDockShow() {},
/**
* compatible *VE.dockPane.onDockHide*
*/
onDockHide() {},
/**
* compatible *VE.dockPane.setFixed*
*/
setFixed(flag: boolean) {
// todo:
},
});
const tabPane = Object.assign(skeleton.rightArea, {
setFloat(flag: boolean) {
// todo:
},
});
const toolbar = Object.assign(skeleton.toolbar, {
setContents(contents: ReactElement) {
// todo:
},
});
const widgets = skeleton.mainArea;
const stages = Object.assign(skeleton.stages, {
getStage(name: string) {
skeleton.stages.container.get(name);
},
createStage(config: any) {
config = upgradeConfig(config);
if (config.id) {
config.name = config.id;
}
const stage = skeleton.stages.add(config);
return stage.getName();
},
});
export default {
ActionPane: actionPane, // topArea
actionPane, //
DockPane: dockPane, // leftArea
dockPane,
TabPane: tabPane, // rightArea
tabPane,
add,
toolbar, // toolbar
Stages: stages,
Widgets: widgets, // centerArea
widgets,
};

View File

@ -0,0 +1,48 @@
import { obx, computed } from '@ali/lowcode-globals';
import WidgetContainer from './widget-container';
import { Skeleton } from './skeleton';
import { IWidget } from './widget';
import { IWidgetBaseConfig } from './types';
export default class Area<C extends IWidgetBaseConfig = any, T extends IWidget = IWidget> {
@obx private _visible: boolean = true;
@computed get visible() {
if (this.exclusive) {
return this.container.current != null;
}
return this._visible;
}
readonly container: WidgetContainer<T, C>;
constructor(readonly skeleton: Skeleton, readonly name: string, handle: (item: T | C) => T, private exclusive?: boolean, defaultSetCurrent: boolean = false) {
this.container = skeleton.createContainer(name, handle, exclusive, () => this.visible, defaultSetCurrent);
}
@computed isEmpty(): boolean {
return this.container.items.length < 1;
}
add(config: T | C): T {
return this.container.add(config);
}
private lastCurrent: T | null = null;
setVisible(flag: boolean) {
if (this.exclusive) {
const current = this.container.current;
if (flag && !current) {
this.container.active(this.lastCurrent || this.container.getAt(0))
} else if (current) {
this.lastCurrent = this.container.current;
this.container.unactive();
}
return;
}
this._visible = flag;
}
hide() {
this.setVisible(false);
}
}

View File

@ -0,0 +1,38 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/recore';
import Area from './area';
import Panel from './panel';
import { PanelWrapper } from './widget-views';
@observer
export default class BottomArea extends Component<{ area: Area<any, Panel> }> {
shouldComponentUpdate() {
return false;
}
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) => <PanelWrapper key={item.id} panel={item} />)}
</Fragment>
);
}
}

View File

@ -0,0 +1,78 @@
import { ReactNode, createElement } from 'react';
import { uniqueId, createContent, obx } from '@ali/lowcode-globals';
import { DockConfig } from "./types";
import { Skeleton } from './skeleton';
import { DockView } from './widget-views';
import { IWidget } from './widget';
/**
* /
*/
export default class Dock implements IWidget {
readonly isWidget = true;
readonly id = uniqueId('dock');
readonly name: string;
readonly align?: string;
@obx.ref private _visible: boolean = true;
get visible(): boolean {
return this._visible;
}
private inited: boolean = false;
private _content: ReactNode;
get content() {
if (this.inited) {
return this._content;
}
this.inited = true;
const { props, content, contentProps } = this.config;
if (content) {
this._content = createContent(content, {
...contentProps,
editor: this.skeleton.editor,
key: this.id,
});
} else {
this._content = createElement(DockView, {
...props,
key: this.id,
});
}
return this._content;
}
constructor(readonly skeleton: Skeleton, private config: DockConfig) {
const { props = {}, name } = config;
this.name = name;
this.align = props.align;
}
setVisible(flag: boolean) {
if (flag === this._visible) {
return;
}
if (flag) {
this._visible = true;
} else if (this.inited) {
this._visible = false;
}
}
getContent() {
return this.content;
}
getName() {
return this.name;
}
hide() {
this.setVisible(false);
}
show() {
this.setVisible(true);
}
}

View File

@ -0,0 +1,41 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-globals';
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.forEach(item => {
if (item.align === 'bottom') {
bottom.push(item.content);
} else {
top.push(item.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,50 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-globals';
import { Button } from '@alifd/next';
import Area from './area';
import { PanelConfig } from './types';
import Panel from './panel';
import { PanelWrapper } from './widget-views';
@observer
export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> {
shouldComponentUpdate() {
return false;
}
render() {
const { area } = this.props;
return (
<div
className={classNames('lc-left-fixed-pane', {
'lc-area-visible': area.visible,
})}
>
<Button
className="lc-pane-close"
onClick={() => {
area.setVisible(false);
}}
/>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area<PanelConfig, Panel> }> {
shouldComponentUpdate() {
return false;
}
render() {
const { area } = this.props;
return (
<Fragment>
{area.container.items.map((panel) => (
<PanelWrapper key={panel.id} panel={panel} />
))}
</Fragment>
);
}
}

View File

@ -0,0 +1,51 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-globals';
import { Button } from '@alifd/next';
import Area from './area';
import Panel from './panel';
import { PanelWrapper } from './widget-views';
@observer
export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }> {
shouldComponentUpdate() {
return false;
}
render() {
const { area } = this.props;
// TODO: add focusingManager
// TODO: dragstart close
return (
<div
className={classNames('lc-left-float-pane', {
'lc-area-visible': area.visible,
})}
>
<Button
className="lc-pane-close"
onClick={() => {
area.setVisible(false);
}}
/>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area<any, Panel> }> {
shouldComponentUpdate() {
return false;
}
render() {
const { area } = this.props;
return (
<Fragment>
{area.container.items.map((panel) => (
<PanelWrapper key={panel.id} panel={panel} />
))}
</Fragment>
);
}
}

View File

@ -0,0 +1,28 @@
import { Component } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/recore';
import Area from './area';
import Panel, { isPanel } from './panel';
import { PanelWrapper } from './widget-views';
import Widget from './widget';
@observer
export default class MainArea extends Component<{ area: Area<any, Panel | Widget> }> {
shouldComponentUpdate() {
return false;
}
render() {
const { area } = this.props;
return (
<div className={classNames('lc-main-area')}>
{area.container.items.map((item) => {
// todo?
if (isPanel(item)) {
return <PanelWrapper key={item.id} panel={item} />;
}
return item.content;
})}
</div>
);
}
}

View File

@ -0,0 +1,79 @@
import { uniqueId, obx, computed } from '@ali/lowcode-globals';
import { createElement, ReactNode } from 'react';
import { Skeleton } from './skeleton';
import { PanelDockConfig } from './types';
import Panel from './panel';
import { PanelDockView } from './widget-views';
import { IWidget } from './widget';
export default class PanelDock implements IWidget {
readonly isWidget = true;
readonly id: string;
readonly name: string;
readonly align?: string;
private inited: boolean = false;
private _content: ReactNode;
get content() {
if (this.inited) {
return this._content;
}
this.inited = true;
const { props } = this.config;
this._content = createElement(PanelDockView, {
...props,
key: this.id,
dock: this,
});
return this._content;
}
@computed get actived(): boolean {
return this.panel?.visible || false;
}
readonly panelName: string;
private _panel?: Panel;
@computed get panel() {
return this._panel || this.skeleton.getPanel(this.panelName);
}
constructor(readonly skeleton: Skeleton, private config: PanelDockConfig) {
const { content, contentProps, panelProps, name } = config;
this.name = name;
this.id = uniqueId(`dock:${name}$`);
this.panelName = config.panelName || name;
if (content) {
this._panel = this.skeleton.add({
type: "Panel",
name: this.panelName,
props: panelProps || {},
contentProps,
content,
area: panelProps?.area || 'leftFloatArea'
}) as Panel;
}
}
toggle() {
this.panel?.toggle();
}
getName() {
return this.name;
}
getContent() {
return this.content;
}
hide() {
this.panel?.setActive(false);
}
show() {
this.panel?.setActive(true);
}
}

View File

@ -0,0 +1,150 @@
import {createElement, ReactNode } from 'react';
import { obx, uniqueId, createContent, TitleContent } from '@ali/lowcode-globals';
import WidgetContainer from './widget-container';
import { PanelConfig, HelpTipConfig } from './types';
import { PanelView, TabsPanelView } from './widget-views';
import { Skeleton } from './skeleton';
import { composeTitle } from './utils';
import { IWidget } from './widget';
export default class Panel implements IWidget {
readonly isWidget = true;
readonly name: string;
readonly id: string;
@obx.ref inited: boolean = false;
@obx.ref private _actived: boolean = false;
get actived(): boolean {
return this._actived;
}
get visible(): boolean {
if (this.parent?.visible) {
return this._actived;
}
return false;
}
setActive(flag: boolean) {
if (flag === this._actived) {
// TODO: 如果移动到另外一个 container会有问题
return;
}
if (flag) {
if (!this.inited) {
this.initBody();
}
this._actived = true;
this.parent?.active(this);
} else if (this.inited) {
this._actived = false;
this.parent?.unactive(this);
}
}
toggle() {
this.setActive(!this._actived);
}
readonly isPanel = true;
private _body?: ReactNode;
get body() {
this.initBody();
return this._body;
}
get content() {
return this.plain ? this.body : createElement(PanelView, { panel: this });
}
readonly title: TitleContent;
readonly help?: HelpTipConfig;
private plain: boolean = false;
private container?: WidgetContainer<Panel, PanelConfig>;
private parent?: WidgetContainer;
constructor(readonly skeleton: Skeleton, private config: PanelConfig) {
const { name, content, props = {} } = config;
const { hideTitleBar, title, icon, description, help, shortcut } = props;
this.name = name;
this.id = uniqueId(`pane:${name}$`);
this.title = composeTitle(title || name, icon, description);
this.plain = hideTitleBar || !title;
this.help = help;
if (Array.isArray(content)) {
this.container = this.skeleton.createContainer(name, (item) => {
if (isPanel(item)) {
return item;
}
return this.skeleton.createPanel(item);
}, true, () => this.visible, true);
content.forEach(item => this.add(item));
}
// todo: process shortcut
}
private initBody() {
if (this.inited) {
return;
}
this.inited = true;
if (this.container) {
this._body = createElement(TabsPanelView, {
container: this.container,
key: this.id,
});
} else {
const { content, contentProps } = this.config;
this._body = createContent(content, {
...contentProps,
editor: this.skeleton.editor,
panel: this,
key: this.id,
});
}
}
setParent(parent: WidgetContainer) {
if (parent === this.parent) {
return;
}
if (this.parent) {
this.parent.remove(this);
}
this.parent = parent;
}
add(item: Panel | PanelConfig) {
return this.container?.add(item);
}
getPane(name: string): Panel | null {
return this.container?.get(name) || null;
}
remove(item: Panel | string) {
return this.container?.remove(item);
}
active(item?: Panel | string | null) {
this.container?.active(item);
}
getName() {
return this.name;
}
getContent() {
return this.content;
}
hide() {
this.setActive(false);
}
show() {
this.setActive(true);
}
}
export function isPanel(obj: any): obj is Panel {
return obj && obj.isPanel;
}

View File

@ -0,0 +1,36 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/recore';
import Area from './area';
import Panel from './panel';
import { PanelWrapper } from './widget-views';
@observer
export default class RightArea extends Component<{ area: Area<any, Panel> }> {
shouldComponentUpdate() {
return false;
}
render() {
const { area } = this.props;
return (
<div className={classNames('lc-right-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) => <PanelWrapper key={item.id} panel={item} />)}
</Fragment>
);
}
}

View File

@ -0,0 +1,194 @@
import {
DockConfig,
PanelConfig,
WidgetConfig,
IWidgetBaseConfig,
PanelDockConfig,
DialogDockConfig,
isDockConfig,
isPanelDockConfig,
isPanelConfig,
} from './types';
import Editor from '@ali/lowcode-editor-core';
import Panel, { isPanel } from './panel';
import WidgetContainer from './widget-container';
import Area from './area';
import Widget, { isWidget, IWidget } from './widget';
import PanelDock from './panel-dock';
import Dock from './dock';
import { Stage, StageConfig } from './stage';
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 | PanelDockConfig | DialogDockConfig>;
readonly toolbar: Area<DockConfig | 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) {
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);
},
true,
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);
});
}
createWidget(config: IWidgetBaseConfig | IWidget) {
if (isWidget(config)) {
return config;
}
if (isDockConfig(config)) {
if (isPanelDockConfig(config)) {
return new PanelDock(this, config);
}
return new Dock(this, config);
}
if (isPanelConfig(config)) {
return this.createPanel(config);
}
return new Widget(this, config as WidgetConfig);
}
createPanel(config: PanelConfig) {
const panel = new Panel(this, config);
this.panels.set(panel.name, panel);
return panel;
}
getPanel(name: string): Panel | undefined {
return this.panels.get(name);
}
createContainer(
name: string,
handle: (item: any) => any,
exclusive: boolean = false,
checkVisible: () => boolean = () => true,
defaultSetCurrent: boolean = false,
) {
const container = new WidgetContainer(name, handle, exclusive, checkVisible, defaultSetCurrent);
this.containers.set(name, container);
return container;
}
add(config: IWidgetBaseConfig & { area: string }) {
const { area } = config;
switch (area) {
case 'leftArea': case 'left':
return this.leftArea.add(config as any);
case 'rightArea': case 'right':
return this.rightArea.add(config as any);
case 'topArea': case 'top':
return this.topArea.add(config as any);
case 'toolbar':
return this.toolbar.add(config as any);
case 'mainArea': case 'main': case 'center': case 'centerArea':
return this.mainArea.add(config as any);
case 'bottomArea': case 'bottom':
return this.bottomArea.add(config as any);
case 'leftFixedArea':
return this.leftFixedArea.add(config as any);
case 'leftFloatArea':
return this.leftFloatArea.add(config as any);
case 'stages':
return this.stages.add(config as any);
}
}
}

View File

@ -0,0 +1,51 @@
import Widget from './widget';
import { Skeleton } from './skeleton';
import { WidgetConfig } from './types';
export interface StageConfig extends WidgetConfig {
isRoot?: boolean;
}
export class Stage extends Widget {
readonly isRoot: boolean;
private previous?: Stage;
private refer?: {
stage?: Stage;
direction?: 'right' | 'left';
};
constructor(skeleton: Skeleton, config: StageConfig) {
super(skeleton, config);
this.isRoot = config.isRoot || false;
}
setPrevious(stage: Stage) {
this.previous = stage;
}
getPrevious() {
return this.previous;
}
hasBack(): boolean {
return this.previous && !this.isRoot ? true : false;
}
setRefer(stage: Stage, direction: 'right' | 'left') {
this.refer = { stage, direction };
}
setReferRight(stage: Stage) {
this.setRefer(stage, 'right');
}
setReferLeft(stage: Stage) {
this.setRefer(stage, 'left');
}
getRefer() {
const refer = this.refer;
this.refer = undefined;
return refer;
}
}

View File

@ -0,0 +1,60 @@
@import '~@ali/ve-less-variables/index.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);
}

View File

@ -0,0 +1,49 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-globals';
import Area from './area';
@observer
export default class Toolbar extends Component<{ area: Area }> {
render() {
const { area } = this.props;
if (area.isEmpty()) {
return null;
}
return (
<div
className={classNames('lc-toolbar', {
'lc-area-visible': area.visible,
})}
>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area }> {
render() {
const { area } = this.props;
const left: any[] = [];
const center: any[] = [];
const right: any[] = [];
area.container.items.forEach((item) => {
if (item.align === 'center') {
center.push(item.content);
} else if (item.align === 'right') {
right.push(item.content);
} else {
left.push(item.content);
}
});
return (
<Fragment>
<div className="lc-toolbar-left">{left}</div>
<div className="lc-toolbar-center">{center}</div>
<div className="lc-toolbar-right">{right}</div>
</Fragment>
);
}
}

View File

@ -0,0 +1,44 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-globals';
import Area from './area';
@observer
export default class TopArea extends Component<{ area: Area }> {
render() {
const { area } = this.props;
return (
<div className={classNames("lc-top-area", {
'lc-area-visible': area.visible
})}>
<Contents area={area} />
</div>
);
}
}
@observer
class Contents extends Component<{ area: Area }> {
render() {
const { area } = this.props;
const left: any[] = [];
const center: any[] = [];
const right: any[] = [];
area.container.items.forEach(item => {
if (item.align === 'center') {
center.push(item.content);
} else if (item.align === 'left') {
right.push(item.content);
} else {
left.push(item.content);
}
});
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,104 @@
import { ReactElement, ComponentType } from 'react';
import { TitleContent, IconType, I18nData } from '@ali/lowcode-globals';
export interface IWidgetBaseConfig {
type: string;
name: string;
area?: string; // 停靠位置, 默认 float, 如果添加非固定区,
props?: object;
content?: any;
contentProps?: object;
// index?: number;
[extra: string]: any;
}
export interface WidgetConfig extends IWidgetBaseConfig {
type: "Widget";
name: string; // as pluginKey
props?: {
align?: "left" | "right" | "bottom" | "center" | "top";
};
content?: string | ReactElement | ComponentType<any>; // children
}
export function isWidgetConfig(obj: any): obj is WidgetConfig {
return obj && obj.type === "Custom";
}
export interface DockProps {
title?: TitleContent;
icon?: IconType;
size?: 'small' | 'medium' | 'large';
className?: string;
description?: string | I18nData;
onClick?: () => void;
}
export interface IDockBaseConfig extends IWidgetBaseConfig {
props?: DockProps & {
align?: "left" | "right" | "bottom" | "center" | "top";
};
}
export interface DockConfig extends IDockBaseConfig {
type: "Dock";
content?: string | ReactElement | ComponentType<any>;
}
export function isDockConfig(obj: any): obj is DockConfig {
return obj && /Dock$/.test(obj.type);
}
// 按钮弹窗扩展
export interface DialogDockConfig extends IDockBaseConfig {
type: "DialogDock";
dialogProps?: {
title?: TitleContent;
[key: string]: any;
};
}
export function isDialogDockConfig(obj: any): obj is DialogDockConfig {
return obj && obj.type === 'DialogDock';
}
// 窗格扩展
export interface PanelConfig extends IWidgetBaseConfig {
type: "Panel";
content?: string | ReactElement | ComponentType<any> | PanelConfig[]; // as children
props?: PanelProps;
}
export function isPanelConfig(obj: any): obj is PanelConfig {
return obj && obj.type === 'Panel';
}
export type HelpTipConfig = string | { url?: string; content?: string | ReactElement };
export interface PanelProps {
title?: TitleContent;
icon?: any; // 冗余字段
description?: string | I18nData;
hideTitleBar?: boolean; // panel.props 兼容,不暴露
help?: HelpTipConfig; // 显示问号帮助
width?: number; // panel.props
height?: number; // panel.props
maxWidth?: number; // panel.props
maxHeight?: number; // panel.props
onInit?: () => any;
onDestroy?: () => any;
shortcut?: string; // 只有在特定位置,可触发 toggle show
}
export interface PanelDockConfig extends IDockBaseConfig {
type: "PanelDock";
panelName?: string;
panelProps?: PanelProps & {
area?: string;
};
content?: string | ReactElement | ComponentType<any> | PanelConfig[]; // content for pane
}
export function isPanelDockConfig(obj: any): obj is PanelDockConfig {
return obj && obj.type === 'PanelDock';
}

View File

@ -0,0 +1,28 @@
import { IconType, TitleContent, I18nData, isI18nData } from '@ali/lowcode-globals';
import { isValidElement } from 'react';
export function composeTitle(title?: TitleContent, icon?: IconType, tip?: string | I18nData) {
if (!title) {
title = {};
if (!icon) {
title.label = tip;
tip = undefined;
}
}
if (icon || tip) {
if (typeof title !== 'object' || isValidElement(title) || isI18nData(title)) {
title = {
label: title,
icon,
tip,
};
} else {
title = {
...title,
icon,
tip
};
}
}
return title;
}

View File

@ -0,0 +1,134 @@
import { obx, hasOwnProperty, computed } from '@ali/lowcode-globals';
import { isPanel } from './panel';
export interface WidgetItem {
name: string;
}
export interface Activeable {
setActive(flag: boolean): void;
}
function isActiveable(obj: any): obj is Activeable {
return obj && obj.setActive;
}
export default class WidgetContainer<T extends WidgetItem = any, G extends WidgetItem = any> {
@obx.val items: T[] = [];
private maps: { [name: string]: T } = {};
@obx.ref private _current: T & Activeable | null = null;
get current() {
return this._current;
}
constructor(
readonly name: string,
private handle: (item: T | G) => T,
private exclusive: boolean = false,
private checkVisible: () => boolean = () => true,
private defaultSetCurrent: boolean = false,
) {}
@computed get visible() {
return this.checkVisible();
}
active(nameOrItem?: T | string | null) {
let item: any = nameOrItem;
if (nameOrItem && typeof nameOrItem === 'string') {
item = this.get(nameOrItem);
}
if (!isActiveable(nameOrItem)) {
item = null;
}
if (this.exclusive) {
if (this._current === item) {
return;
}
if (this._current) {
this._current.setActive(false);
}
this._current = item;
}
if (item) {
item.setActive(true);
}
}
unactive(nameOrItem?: T | string | null) {
let item: any = nameOrItem;
if (nameOrItem && typeof nameOrItem === 'string') {
item = this.get(nameOrItem);
}
if (!isActiveable(nameOrItem)) {
item = null;
}
if (this._current === item) {
this._current = null;
}
if (item) {
item.setActive(false);
}
}
add(item: T | G): T {
item = this.handle(item);
const origin = this.get(item.name);
if (origin === item) {
return origin;
}
const i = origin ? this.items.indexOf(origin) : -1;
if (i > -1) {
this.items[i] = item;
} else {
this.items.push(item);
}
this.maps[item.name] = item;
if (isPanel(item)) {
item.setParent(this);
}
if (this.defaultSetCurrent) {
if (!this._current) {
this.active(item);
}
}
return item;
}
get(name: string): T | null {
return this.maps[name] || null;
}
getAt(index: number): T | null {
return this.items[index] || null;
}
has(name: string): boolean {
return hasOwnProperty(this.maps, name);
}
indexOf(item: T): number {
return this.items.indexOf(item);
}
/**
* return indexOf the deletion
*/
remove(item: string | T): number {
const thing = typeof item === 'string' ? this.get(item) : item;
if (!thing) {
return -1;
}
const i = this.items.indexOf(thing);
if (i > -1) {
this.items.splice(i, 1);
}
delete this.maps[thing.name];
if (thing === this.current) {
this._current = null;
}
return i;
}
}

View File

@ -0,0 +1,143 @@
import { Component, ReactElement } from 'react';
import classNames from 'classnames';
import { Title, observer } from '@ali/lowcode-globals';
import { DockProps } from './types';
import PanelDock from './panel-dock';
import { composeTitle } from './utils';
import WidgetContainer from './widget-container';
import Panel from './panel';
import Widget from './widget';
export function DockView({ title, icon, description, size, className, onClick }: DockProps) {
return (
<Title
title={composeTitle(title, icon, description)}
className={classNames('lc-dock', className, {
[`lc-dock-${size}`]: size,
})}
onClick={onClick}
/>
);
}
@observer
export class PanelDockView extends Component<DockProps & { dock: PanelDock }> {
render() {
const { dock, className, onClick, ...props } = this.props;
return DockView({
...props,
className: classNames(className, {
actived: dock.actived,
}),
onClick: () => {
onClick && onClick();
dock.toggle();
},
});
}
}
export class DialogDockView extends Component {
}
export class PanelView extends Component<{ panel: Panel }> {
shouldComponentUpdate() {
return false;
}
render() {
const { panel } = this.props;
return (
<div className="lc-panel">
<PanelTitle panel={panel} />
<div className="lc-pane-body">{panel.body}</div>
</div>
);
}
}
@observer
export class TabsPanelView extends Component<{ container: WidgetContainer<Panel> }> {
render() {
const { container } = this.props;
const titles: ReactElement[] = [];
const contents: ReactElement[] = [];
container.items.forEach((item: any) => {
titles.push(<PanelTitle key={item.id} panel={item} className="lc-tab-title" />);
contents.push(<PanelWrapper key={item.id} panel={item} />);
});
return (
<div className="lc-tabs">
<div
className="lc-tabs-title"
onClick={(e) => {
const shell = e.currentTarget;
const t = e.target as Element;
let elt = shell.firstElementChild;
while (elt) {
if (elt.contains(t)) {
break;
}
elt = elt.nextElementSibling;
}
if (elt) {
container.active((elt as any).dataset.name);
}
}}
>
{titles}
</div>
<div className="lc-tabs-content">{contents}</div>
</div>
);
}
}
@observer
class PanelTitle extends Component<{ panel: Panel; className?: string }> {
render() {
const { panel, className } = this.props;
return (
<div
className={classNames('lc-panel-title', className, {
actived: panel.actived,
})}
data-name={panel.name}
>
<Title title={panel.title || panel.name} />
{/*pane.help ? <HelpTip tip={panel.help} /> : null*/}
</div>
);
}
}
@observer
export class PanelWrapper extends Component<{ panel: Panel }> {
render() {
const { panel } = this.props;
if (!panel.inited) {
return null;
}
return (
<div
className={classNames('lc-panel-wrapper', {
hidden: !panel.actived,
})}
>
{panel.body}
</div>
);
}
}
@observer
export class WidgetWrapper extends Component<{ widget: Widget }> {
render() {
const { widget } = this.props;
if (!widget.visible) {
return null;
}
return widget.body;
}
}

View File

@ -0,0 +1,94 @@
import { ReactNode, createElement } from 'react';
import { createContent, uniqueId, obx } from '@ali/lowcode-globals';
import { WidgetConfig } from './types';
import { Skeleton } from './skeleton';
import { WidgetWrapper } from './widget-views';
export interface IWidget {
readonly name: string;
readonly content: any;
readonly align?: string;
readonly isWidget: true;
getName(): string;
getContent(): any;
show(): void;
hide(): void;
}
export default class Widget implements IWidget {
readonly isWidget = true;
readonly id = uniqueId('widget');
readonly name: string;
readonly align?: string;
@obx.ref private _visible: boolean = true;
get visible(): boolean {
return this._visible;
}
@obx.ref inited: boolean = false;
private _body: ReactNode;
get body() {
if (this.inited) {
return this._body;
}
this.inited = true;
const { content, contentProps } = this.config;
this._body = createContent(content, {
...contentProps,
editor: this.skeleton.editor,
});
return this._body;
}
get content() {
return createElement(WidgetWrapper, {
widget: this,
key: this.id,
});
}
constructor(readonly skeleton: Skeleton, private config: WidgetConfig) {
const { props = {}, name } = config;
this.name = name;
this.align = props.align;
}
getName() {
return this.name;
}
getContent() {
return this.content;
}
hide() {
this.setVisible(false);
}
show() {
this.setVisible(true);
}
setVisible(flag: boolean) {
if (flag === this._visible) {
return;
}
if (flag) {
this._visible = true;
} else if (this.inited) {
this._visible = false;
}
}
toggle() {
this.setVisible(!this._visible);
}
}
export function isWidget(obj: any): obj is IWidget {
return obj && obj.isWidget;
}

View File

@ -0,0 +1,261 @@
@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: 46px;
--right-area-width: 300px;
--top-area-height: 48px;
--toolbar-height: 32px;
--dock-pane-width: 372px;
}
@media (min-width: 1860px) {
:root {
--right-area-width: 400px;
--dock-pane-width: 452px;
}
}
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;
}
.my-pane {
width: 100%;
height: 100%;
position: relative;
.pane-title {
// height: var(--pane-title-height);
background-color: var(--pane-title-bg-color);
display: flex;
align-items: center;
padding: 0 15px;
.my-help-tip {
margin-left: 4px;
}
}
.pane-body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: auto;
.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;
}
}
}
&.titled > .pane-body {
top: var(--pane-title-height);
}
}
.lc-panel-wrapper {
height: 100%;
width: 100%;
overflow: auto;
&.hidden {
display: none;
}
.pane-title {
height: var(--pane-title-height);
background-color: var(--pane-title-bg-color);
display: flex;
align-items: center;
padding: 0 15px;
.my-help-tip {
margin-left: 4px;
}
}
.my-tabs {
width: 100%;
height: 100%;
position: relative;
.tabs-title {
display: flex;
height: var(--pane-title-height);
margin-right: 30px;
> .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;
}
}
}
.my-dock {
padding: 0px 10px;
cursor: pointer;
align-self: stretch;
display: flex;
align-items: center;
.my-title-label {
user-select: none;
}
&.actived, &:hover {
background-color: var(--pane-title-bg-color);
.my-title {
color: var(--color-actived);
}
}
}
.lc-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: flex;
margin-bottom: 2px;
}
.lc-workbench-body {
flex: 1;
display: flex;
position: relative;
.lc-left-float-pane {
position: absolute;
top: 0;
bottom: 0;
width: var(--dock-pane-width);
left: calc(var(--left-area-width) + 1px);
z-index: 20;
display: none;
&.lc-area-visible {
display: block;
}
}
.lc-left-area {
height: 100%;
width: var(--left-area-width);
background-color: var(--color-pane-background);
display: none;
&.lc-area-visible {
display: flex;
}
}
.lc-left-fixed-pane {
width: var(--dock-pane-width);
background-color: var(--color-pane-background);
height: 100%;
display: none;
&.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-workbench-center {
margin-left: 2px;
}
.lc-workbench-center {
flex: 1;
display: flex;
flex-direction: column;
.lc-toolbar {
height: var(--toolbar-height);
background-color: var(--color-pane-background);
}
.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;
margin-left: 2px;
&.lc-area-visible {
display: block;
}
}
}
}

View File

@ -0,0 +1,40 @@
import { Component } from 'react';
import { TipContainer, observer } from '@ali/lowcode-globals';
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';
@observer
export class VisionWorkbench extends Component<{ skeleton: Skeleton}> {
shouldComponentUpdate() {
return false;
}
render() {
const { skeleton } = this.props;
return (
<div className="lc-workbench">
<TopArea area={skeleton.topArea} />
<div className="lc-workbench-body">
<LeftArea area={skeleton.leftArea} />
<LeftFloatPane area={skeleton.leftFloatArea} />
<LeftFixedPane area={skeleton.leftFixedArea} />
<div className="lc-workbench-center">
<Toolbar area={skeleton.toolbar} />
<MainArea area={skeleton.mainArea} />
<BottomArea area={skeleton.bottomArea} />
</div>
<RightArea area={skeleton.rightArea} />
</div>
<TipContainer />
</div>
);
}
}

View File

@ -6,8 +6,9 @@ import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS } from './const';
import Bus from './bus';
import Symbols from './symbols';
import Skeleton from '@ali/lowcode-editor-skeleton';
import editor from './editor';
import { editor, skeleton } from './editor';
import { VisionWorkbench } from './skeleton/workbench';
import Panes from './panes';
function init(container?: Element) {
if (!container) {
@ -16,9 +17,12 @@ function init(container?: Element) {
}
container.id = 'engine';
render(createElement(Skeleton, {
editor,
}), container);
render(
createElement(VisionWorkbench, {
skeleton,
}),
container,
);
}
const ui = {
@ -51,4 +55,5 @@ export {
*/
init,
ui,
Panes,
};