Merge branch 'feat/joint-editor' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into feat/joint-editor

This commit is contained in:
kangwei 2020-03-15 19:30:54 +08:00
commit 4a673068ee
37 changed files with 941 additions and 477 deletions

View File

@ -1,8 +1,16 @@
const { eslint, deepmerge } = require('@ice/spec'); const { tslint, deepmerge } = require('@ice/spec');
module.exports = deepmerge(eslint, { module.exports = deepmerge(tslint, {
rules: { rules: {
"global-require": 0, "global-require": 0,
"interface-name" : [true, "never-prefix"] "comma-dangle": 0,
"no-unused-expressions": 0,
"object-shorthand": 0,
"jsx-a11y/anchor-has-content": 0,
"react/sort-comp": 0,
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx", "ts"] }],
"@typescript-eslint/interface-name-prefix": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-member-accessibility": 0
}, },
}); });

View File

@ -12,7 +12,7 @@ module.exports = {
}, },
plugins: [ plugins: [
['ice-plugin-fusion', { ['ice-plugin-fusion', {
themePackage: '@alife/dpl-iceluna', themePackage: '@alife/theme-lowcode-light',
}], }],
['ice-plugin-moment-locales', { ['ice-plugin-moment-locales', {
locales: ['zh-cn'], locales: ['zh-cn'],

View File

@ -4,9 +4,12 @@
"description": "低代码编辑器", "description": "低代码编辑器",
"dependencies": { "dependencies": {
"@ali/iceluna-addon-2": "^1.0.3", "@ali/iceluna-addon-2": "^1.0.3",
"@ali/iceluna-addon-component-list": "^1.0.10",
"@ali/iceluna-sdk": "^1.0.5-beta.26", "@ali/iceluna-sdk": "^1.0.5-beta.26",
"@alifd/next": "^1.x", "@alifd/next": "^1.x",
"@alife/dpl-iceluna": "^2.3.2", "@alife/dpl-iceluna": "^2.3.2",
"@alife/theme-lowcode-dark": "^0.1.0",
"@alife/theme-lowcode-light": "^0.1.0",
"@icedesign/theme": "^1.x", "@icedesign/theme": "^1.x",
"events": "^3.1.0", "events": "^3.1.0",
"intl-messageformat": "^8.2.1", "intl-messageformat": "^8.2.1",
@ -21,13 +24,14 @@
}, },
"devDependencies": { "devDependencies": {
"@ice/spec": "^0.1.1", "@ice/spec": "^0.1.1",
"@types/react": "^16.8.3",
"@types/react-dom": "^16.8.2",
"@types/debug": "^4.1.5", "@types/debug": "^4.1.5",
"@types/events": "^3.0.0", "@types/events": "^3.0.0",
"@types/react": "^16.8.3",
"@types/react-dom": "^16.8.2",
"@types/store": "^2.0.2", "@types/store": "^2.0.2",
"css-modules-typescript-loader": "^2.0.4", "css-modules-typescript-loader": "^2.0.4",
"eslint": "^6.0.1", "eslint": "^6.0.1",
"husky": "^4.2.3",
"ice-plugin-fusion": "^0.1.4", "ice-plugin-fusion": "^0.1.4",
"ice-plugin-moment-locales": "^0.1.0", "ice-plugin-moment-locales": "^0.1.0",
"ice-scripts": "^2.0.0", "ice-scripts": "^2.0.0",

View File

@ -15,7 +15,7 @@
<script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script> <script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script>
</head> </head>
<body> <body class="dark">
<div id="ice-container"></div> <div id="ice-container"></div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,152 @@
export default {
version: '1.0.0',
packages: {
'@alifd/next': {
title: 'fusion组件库',
package: '@alifd/next',
version: '1.19.18',
urls: [
'https://unpkg.antfin-inc.com/@alife/next@1.19.18/dist/next.js',
'https://unpkg.antfin-inc.com/@alife/next@1.19.18/dist/next.css'
],
library: 'Next'
}
},
components: {
Button: {
componentName: 'Button',
title: '按钮',
devMode: 'proCode',
npm: {
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Button'
},
props: [
{
name: 'type',
propType: 'string'
},
{
name: 'children',
propType: 'string'
}
],
configure: {
props: [
{
name: 'type',
setter: {
componentName: 'Input'
}
},
{
name: 'children',
setter: {
componentName: 'Input'
}
}
]
}
},
Input: {
componentName: 'Input',
title: '输入框',
devMode: 'proCode',
npm: {
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Input'
},
props: [
{
name: 'placeholder',
propType: 'string'
}
],
configure: {
props: [
{
name: 'placeholder',
setter: {
componentName: 'Input'
}
}
]
}
}
},
componentList: [
{
title: '基础',
icon: '',
children: [
{
componentName: 'Button',
title: '按钮',
icon: '',
package: '@alife/next',
snippets: [
{
title: 'private',
screenshort: '',
schema: {
componentName: 'Button',
props: {
type: 'primary'
},
children: 'Primary'
}
},
{
title: 'secondary',
screenshort: '',
schema: {
componentName: 'Button',
props: {
type: 'secondary'
},
children: 'secondary'
}
},
{
title: 'normal',
screenshort: '',
schema: {
componentName: 'Button',
props: {
type: 'normal'
},
children: 'normal'
}
}
]
}
]
},
{
title: '表单',
icon: '',
children: [
{
componentName: 'Input',
title: '输入框',
icon: '',
package: '@alife/next',
snippets: [
{
title: '普通',
screenshort: '',
schema: {
componentName: 'Input',
props: {}
}
}
]
}
]
}
]
};

View File

@ -1,7 +1,3 @@
import logo from '../plugins/logo';
import Designer from '../plugins/designer';
import undoRedo from '../plugins/undoRedo';
import Settings from '../../../plugin-settings';
import topBalloonIcon from '@ali/iceluna-addon-2'; import topBalloonIcon from '@ali/iceluna-addon-2';
import topDialogIcon from '@ali/iceluna-addon-2'; import topDialogIcon from '@ali/iceluna-addon-2';
import leftPanelIcon from '@ali/iceluna-addon-2'; import leftPanelIcon from '@ali/iceluna-addon-2';
@ -12,6 +8,11 @@ import rightPanel1 from '@ali/iceluna-addon-2';
import rightPanel2 from '@ali/iceluna-addon-2'; import rightPanel2 from '@ali/iceluna-addon-2';
import rightPanel3 from '@ali/iceluna-addon-2'; import rightPanel3 from '@ali/iceluna-addon-2';
import rightPanel4 from '@ali/iceluna-addon-2'; import rightPanel4 from '@ali/iceluna-addon-2';
import componentList from '@ali/iceluna-addon-component-list';
import Settings from '../../../plugin-settings';
import undoRedo from '../plugins/undoRedo';
import Designer from '../plugins/designer';
import logo from '../plugins/logo';
import PluginFactory from '../framework/pluginFactory'; import PluginFactory from '../framework/pluginFactory';
@ -29,5 +30,6 @@ export default {
rightPanel1: PluginFactory(rightPanel1), rightPanel1: PluginFactory(rightPanel1),
rightPanel2: PluginFactory(rightPanel2), rightPanel2: PluginFactory(rightPanel2),
rightPanel3: PluginFactory(rightPanel3), rightPanel3: PluginFactory(rightPanel3),
rightPanel4: PluginFactory(rightPanel4) rightPanel4: PluginFactory(rightPanel4),
componentList: PluginFactory(componentList)
}; };

View File

@ -4,13 +4,17 @@ import { registerSetter } from '../../../plugin-settings/src';
import { createElement } from 'react'; import { createElement } from 'react';
registerSetter('ClassNameSetter', () => { registerSetter('ClassNameSetter', () => {
return createElement('div', { return createElement(
'div',
{
className: 'lc-block-setter' className: 'lc-block-setter'
}, '这里是类名绑定'); },
'这里是类名绑定'
);
}); });
registerSetter('EventsSetter', Input); registerSetter('EventsSetter', Input);
registerSetter('StringSetter', { component: Input, props: { placeholder: "请输入" } }); registerSetter('StringSetter', { component: Input, props: { placeholder: '请输入' } });
registerSetter('NumberSetter', NumberSetter as any); registerSetter('NumberSetter', NumberSetter as any);

View File

@ -1,3 +1,5 @@
import assets from './assets';
export default { export default {
version: '^1.0.2', version: '^1.0.2',
theme: { theme: {
@ -25,7 +27,8 @@ export default {
version: '1.0.0' version: '1.0.0'
}, },
pluginProps: { pluginProps: {
logo: 'https://img.alicdn.com/tfs/TB1mHYDxQP2gK0jSZPxXXacQpXa-112-64.png' logo: 'https://img.alicdn.com/tfs/TB1hoI9x1H2gK0jSZFEXXcqMpXa-146-40.png',
href: '/'
} }
}, },
{ {
@ -71,7 +74,7 @@ export default {
type: 'Custom', type: 'Custom',
props: { props: {
align: 'right', align: 'right',
width: 90 width: 88
}, },
config: { config: {
package: '@ali/lowcode-plugin-undo-redo', package: '@ali/lowcode-plugin-undo-redo',
@ -107,8 +110,8 @@ export default {
align: 'right', align: 'right',
title: 'icon', title: 'icon',
icon: 'dengpao', icon: 'dengpao',
onClick: function(editor) { onClick(editor) {
alert('icon addon invoke, current activeKey: ' + editor.activeKey); alert(`icon addon invoke, current activeKey: ${editor.activeKey}`);
} }
}, },
config: {}, config: {},
@ -116,6 +119,22 @@ export default {
} }
], ],
leftArea: [ leftArea: [
{
pluginKey: 'componentList',
type: 'PanelIcon',
props: {
align: 'top',
icon: 'zujianku',
title: '组件库'
},
config: {
package: '@ali/iceluna-addon-component-list',
version: '^1.0.4'
},
pluginProps: {
disableAppComponent: true
}
},
{ {
pluginKey: 'leftPanelIcon', pluginKey: 'leftPanelIcon',
type: 'PanelIcon', type: 'PanelIcon',
@ -150,7 +169,11 @@ export default {
props: { props: {
align: 'top', align: 'top',
title: 'panel2', title: 'panel2',
icon: 'dengpao' icon: 'dengpao',
panelProps: {
defaultWidth: 400,
floatable: true
}
}, },
config: { config: {
package: '@ali/iceluna-addon-2', package: '@ali/iceluna-addon-2',
@ -194,8 +217,8 @@ export default {
align: 'bottom', align: 'bottom',
title: 'icon', title: 'icon',
icon: 'dengpao', icon: 'dengpao',
onClick: function(editor) { onClick(editor) {
alert('icon addon invoke, current activeKey: ' + editor.activeKey); alert(`icon addon invoke, current activeKey: ${editor.activeKey}`);
} }
}, },
config: {}, config: {},
@ -211,7 +234,52 @@ export default {
version: '^1.0.0' version: '^1.0.0'
}, },
pluginProps: {} pluginProps: {}
}, }
// {
// pluginKey: 'rightPanel1',
// type: 'TabPanel',
// props: {
// title: '样式'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// },
// {
// pluginKey: 'rightPanel2',
// type: 'TabPanel',
// props: {
// title: '属性',
// icon: 'dengpao'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// },
// {
// pluginKey: 'rightPanel3',
// type: 'TabPanel',
// props: {
// title: '事件'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// },
// {
// pluginKey: 'rightPanel4',
// type: 'TabPanel',
// props: {
// title: '数据'
// },
// config: {
// version: '^1.0.0'
// },
// pluginProps: {}
// }
], ],
centerArea: [ centerArea: [
{ {
@ -224,5 +292,51 @@ export default {
] ]
}, },
hooks: [], hooks: [],
shortCuts: [] shortCuts: [],
lifeCycles: {
init: function init(editor) {
const transformMaterial = componentList => {
return componentList.map(category => {
return {
name: category.title,
items: category.children.map(comp => {
return {
...comp,
name: comp.componentName,
libraryId: 1,
snippets: comp.snippets.map(snippet => {
return {
name: snippet.title,
screenshort: snippet.screenshort,
code: JSON.stringify(snippet.schema)
};
})
};
})
};
});
};
const list = transformMaterial(assets.componentList);
editor.set({
componentsMap: assets.components,
componentMaterial: {
library: [
{
name: 'Fusion组件库',
id: 1
}
],
list
}
});
editor.set('dndHelper', {
handleResourceDragStart: function(ev, tagName, schema) {
// 物料面板中组件snippet的dragStart回调
// ev: 原始的domEventtagName: 组件的描述文案schema: snippet的schema
}
});
}
}
}; };

View File

@ -4,26 +4,36 @@ import { clone, deepEqual } from './utils';
export default class AreaManager { export default class AreaManager {
private pluginStatus: PluginStatus; private pluginStatus: PluginStatus;
private config: PluginConfig[]; private config: PluginConfig[];
constructor(private editor: Editor, private area: string) {
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]) || []; this.config = (editor && editor.config && editor.config.plugins && editor.config.plugins[this.area]) || [];
this.pluginStatus = clone(editor.pluginStatus); this.pluginStatus = clone(editor.pluginStatus);
} }
public isPluginStatusUpdate(pluginType?: string): boolean { public isPluginStatusUpdate(pluginType?: string): boolean {
const { pluginStatus } = this.editor; const { pluginStatus } = this.editor;
const list = pluginType ? this.config.filter(item => item.type === pluginType) : this.config; const list = pluginType ? this.config.filter((item): boolean => item.type === pluginType) : this.config;
const isUpdate = list.some(item => !deepEqual(pluginStatus[item.pluginKey], this.pluginStatus[item.pluginKey])); const isUpdate = list.some(
(item): boolean => !deepEqual(pluginStatus[item.pluginKey], this.pluginStatus[item.pluginKey])
);
this.pluginStatus = clone(pluginStatus); this.pluginStatus = clone(pluginStatus);
return isUpdate; return isUpdate;
} }
public getVisiblePluginList(pluginType?: string): PluginConfig[] { public getVisiblePluginList(pluginType?: string): PluginConfig[] {
const res = this.config.filter(item => { const res = this.config.filter((item): boolean => {
return !this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible; return !!(!this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible);
}); });
return pluginType ? res.filter(item => item.type === pluginType) : res; return pluginType ? res.filter((item): boolean => item.type === pluginType) : res;
} }
public getPluginConfig(): PluginConfig[] { public getPluginConfig(): PluginConfig[] {

View File

@ -1,3 +1,4 @@
import { createContext } from 'react'; import { createContext } from 'react';
const context = createContext({}); const context = createContext({});
export default context; export default context;

View File

@ -9,7 +9,7 @@ export interface EditorConfig {
shortCuts?: ShortCutsConfig; shortCuts?: ShortCutsConfig;
utils?: UtilsConfig; utils?: UtilsConfig;
constants?: ConstantsConfig; constants?: ConstantsConfig;
lifeCycles?: lifeCyclesConfig; lifeCycles?: LifeCyclesConfig;
i18n?: I18nConfig; i18n?: I18nConfig;
} }
@ -38,7 +38,7 @@ export interface ThemeConfig {
} }
export interface PluginsConfig { export interface PluginsConfig {
[propName: string]: Array<PluginConfig>; [propName: string]: PluginConfig[];
} }
export interface PluginConfig { export interface PluginConfig {
@ -63,7 +63,7 @@ export interface PluginConfig {
pluginProps?: object; pluginProps?: object;
} }
export type HooksConfig = Array<HookConfig>; export type HooksConfig = HookConfig[];
export interface HookConfig { export interface HookConfig {
message: string; message: string;
@ -71,14 +71,14 @@ export interface HookConfig {
handler: (editor: Editor, ...args) => void; handler: (editor: Editor, ...args) => void;
} }
export type ShortCutsConfig = Array<ShortCutConfig>; export type ShortCutsConfig = ShortCutConfig[];
export interface ShortCutConfig { export interface ShortCutConfig {
keyboard: string; keyboard: string;
handler: (editor: Editor, ev: React.KeyboardEventHandler<HTMLElement>, keymaster: any) => void; handler: (editor: Editor, ev: Event, keymaster: any) => void;
} }
export type UtilsConfig = Array<UtilConfig>; export type UtilsConfig = UtilConfig[];
export interface UtilConfig { export interface UtilConfig {
name: string; name: string;
@ -88,7 +88,7 @@ export interface UtilConfig {
export type ConstantsConfig = object; export type ConstantsConfig = object;
export interface lifeCyclesConfig { export interface LifeCyclesConfig {
init?: (editor: Editor) => any; init?: (editor: Editor) => any;
destroy?: (editor: Editor) => any; destroy?: (editor: Editor) => any;
} }
@ -96,7 +96,7 @@ export interface lifeCyclesConfig {
export type LocaleType = 'zh-CN' | 'zh-TW' | 'en-US' | 'ja-JP'; export type LocaleType = 'zh-CN' | 'zh-TW' | 'en-US' | 'ja-JP';
export interface I18nMessages { export interface I18nMessages {
[propName: string]: string; [key: string]: string;
} }
export interface I18nConfig { export interface I18nConfig {
@ -109,27 +109,46 @@ export interface I18nConfig {
export type I18nFunction = (key: string, params: any) => string; export type I18nFunction = (key: string, params: any) => string;
export interface Utils { export interface Utils {
[propName: string]: (...args) => any; [key: string]: (...args) => any;
} }
export interface PluginClass extends React.ComponentClass<{ export interface PluginProps {
editor: Editor; editor: Editor;
[key: string]: any config: PluginConfig;
}> { i18n?: I18nFunction;
init?: (editor: Editor) => void; ref?: React.RefObject<React.ReactElement>;
open?: () => any; [key: string]: any;
close?: () => any;
} }
export interface PluginComponents { export type Plugin = React.ReactNode & {
[propName: string]: PluginClass; open?: () => boolean | void | Promise<any>;
close?: () => boolean | void | Promise<any>;
};
export type HOCPlugin = React.ReactNode & {
open: () => Promise<any>;
close: () => Promise<any>;
};
export interface PluginSet {
[key: string]: HOCPlugin;
}
export type PluginClass = React.ComponentType<PluginProps> & {
init?: (editor: Editor) => void;
};
export interface PluginClassSet {
[key: string]: PluginClass;
} }
export interface PluginStatus { export interface PluginStatus {
[propName: string]: {
disabled?: boolean; disabled?: boolean;
visible?: boolean; visible?: boolean;
marked?: boolean; marked?: boolean;
locked?: boolean; locked?: boolean;
}; }
export interface PluginStatusSet {
[key: string]: PluginStatus;
} }

View File

@ -1,35 +1,56 @@
import Debug from 'debug'; import Debug from 'debug';
import EventEmitter from 'events'; import EventEmitter from 'events';
import store from 'store'; import store from 'store';
import { EditorConfig, HooksConfig, LocaleType, PluginComponents, PluginStatus, Utils } from './definitions'; import {
EditorConfig,
HooksConfig,
LocaleType,
PluginStatusSet,
Utils,
PluginClassSet,
PluginSet
} from './definitions';
import { registShortCuts, transformToPromise, unRegistShortCuts } from './utils'; import * as editorUtils from './utils';
const { registShortCuts, transformToPromise, unRegistShortCuts } = editorUtils;
declare global {
interface Window {
__isDebug?: boolean;
__newFunc?: (funcStr: string) => (...args: any[]) => any;
}
}
// 根据url参数设置debug选项 // 根据url参数设置debug选项
const res = /_?debug=(.*?)(&|$)/.exec(location.search); const debugRegRes = /_?debug=(.*?)(&|$)/.exec(location.search);
if (res && res[1]) { if (debugRegRes && debugRegRes[1]) {
// eslint-disable-next-line no-underscore-dangle
window.__isDebug = true; window.__isDebug = true;
store.storage.write('debug', res[1] === 'true' ? '*' : res[1]); store.storage.write('debug', debugRegRes[1] === 'true' ? '*' : debugRegRes[1]);
} else { } else {
// eslint-disable-next-line no-underscore-dangle
window.__isDebug = false; window.__isDebug = false;
store.remove('debug'); store.remove('debug');
} }
// 重要用于矫正画布执行new Function的window对象上下文 // 重要用于矫正画布执行new Function的window对象上下文
window.__newFunc = funContext => { // eslint-disable-next-line no-underscore-dangle
return new Function(funContext); window.__newFunc = (funContext: string): ((...args: any[]) => any) => {
// eslint-disable-next-line no-new-func
return new Function(funContext) as (...args: any[]) => any;
}; };
// 关闭浏览器前提醒,只有产生过交互才会生效 // 关闭浏览器前提醒,只有产生过交互才会生效
window.onbeforeunload = function(e) { window.onbeforeunload = function(e: Event): string | void {
e = e || window.event; const ev = e || window.event;
// 本地调试不生效 // 本地调试不生效
if (location.href.indexOf('localhost') > 0) { if (location.href.indexOf('localhost') > 0) {
return; return;
} }
const msg = '您确定要离开此页面吗?'; const msg = '您确定要离开此页面吗?';
e.cancelBubble = true; ev.cancelBubble = true;
e.returnValue = msg; ev.returnValue = true;
if (e.stopPropagation) { if (e.stopPropagation) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -47,26 +68,40 @@ export interface HooksFuncs {
} }
export default class Editor extends EventEmitter { export default class Editor extends EventEmitter {
public static getInstance = (config: EditorConfig, components: PluginComponents, utils?: Utils): Editor => { public static getInstance = (config: EditorConfig, components: PluginClassSet, utils?: Utils): Editor => {
if (!instance) { if (!instance) {
instance = new Editor(config, components, utils); instance = new Editor(config, components, utils);
} }
return instance; return instance;
}; };
public pluginStatus: PluginStatus; public config: EditorConfig;
public plugins: PluginComponents;
public components: PluginClassSet;
public utils: Utils;
public pluginStatus: PluginStatusSet;
public plugins: PluginSet;
public locale: LocaleType; public locale: LocaleType;
public emit: (msg: string, ...args) => void; public emit: (msg: string, ...args) => void;
public on: (msg: string, handler: (...args) => void) => void; public on: (msg: string, handler: (...args) => void) => void;
public once: (msg: string, handler: (...args) => void) => void; public once: (msg: string, handler: (...args) => void) => void;
public off: (msg: string, handler: (...args) => void) => void; public off: (msg: string, handler: (...args) => void) => void;
private hooksFuncs: HooksFuncs; private hooksFuncs: HooksFuncs;
constructor(public config: EditorConfig, public components: PluginComponents, public utils?: Utils) { constructor(config: EditorConfig, components: PluginClassSet, utils?: Utils) {
super(); super();
this.config = config;
this.components = components;
this.utils = { ...editorUtils, ...utils };
instance = this; instance = this;
this.init(); this.init();
} }
@ -80,21 +115,21 @@ export default class Editor extends EventEmitter {
this.initHooks(hooks || []); this.initHooks(hooks || []);
this.emit('editor.beforeInit'); this.emit('editor.beforeInit');
const init = (lifeCycles && lifeCycles.init) || (() => {}); const init = (lifeCycles && lifeCycles.init) || ((): void => {});
// 用户可以通过设置extensions.init自定义初始化流程 // 用户可以通过设置extensions.init自定义初始化流程
return transformToPromise(init(this)) return transformToPromise(init(this))
.then(() => { .then((): boolean => {
// 注册快捷键 // 注册快捷键
registShortCuts(shortCuts, this); registShortCuts(shortCuts, this);
this.emit('editor.afterInit'); this.emit('editor.afterInit');
return true; return true;
}) })
.catch(err => { .catch((err): void => {
console.error(err); console.error(err);
}); });
} }
public destroy() { public destroy(): void {
debug('destroy'); debug('destroy');
try { try {
const { hooks = [], shortCuts = [], lifeCycles = {} } = this.config; const { hooks = [], shortCuts = [], lifeCycles = {} } = this.config;
@ -105,7 +140,6 @@ export default class Editor extends EventEmitter {
} }
} catch (err) { } catch (err) {
console.warn(err); console.warn(err);
return;
} }
} }
@ -121,7 +155,7 @@ export default class Editor extends EventEmitter {
} }
this[key] = val; this[key] = val;
} else if (typeof key === 'object') { } else if (typeof key === 'object') {
Object.keys(key).forEach(item => { Object.keys(key).forEach((item): void => {
this[item] = key[item]; this[item] = key[item];
}); });
} }
@ -131,26 +165,26 @@ export default class Editor extends EventEmitter {
if (!Array.isArray(events)) { if (!Array.isArray(events)) {
return; return;
} }
events.forEach(event => this.on(event, lisenter)); events.forEach((event): void => this.on(event, lisenter));
} }
public batchOnce(events: string[], lisenter: (...args) => void): void { public batchOnce(events: string[], lisenter: (...args) => void): void {
if (!Array.isArray(events)) { if (!Array.isArray(events)) {
return; return;
} }
events.forEach(event => this.once(event, lisenter)); events.forEach((event): void => this.once(event, lisenter));
} }
public batchOff(events: string[], lisenter: (...args) => void): void { public batchOff(events: string[], lisenter: (...args) => void): void {
if (!Array.isArray(events)) { if (!Array.isArray(events)) {
return; return;
} }
events.forEach(event => this.off(event, lisenter)); events.forEach((event): void => this.off(event, lisenter));
} }
// 销毁hooks中的消息监听 // 销毁hooks中的消息监听
private destroyHooks(hooks: HooksConfig = []) { private destroyHooks(hooks: HooksConfig = []): void {
hooks.forEach((item, idx) => { hooks.forEach((item, idx): void => {
if (typeof this.hooksFuncs[idx] === 'function') { if (typeof this.hooksFuncs[idx] === 'function') {
this.off(item.message, this.hooksFuncs[idx]); this.off(item.message, this.hooksFuncs[idx]);
} }
@ -160,8 +194,8 @@ export default class Editor extends EventEmitter {
// 初始化hooks中的消息监听 // 初始化hooks中的消息监听
private initHooks(hooks: HooksConfig = []): void { private initHooks(hooks: HooksConfig = []): void {
this.hooksFuncs = hooks.map(item => { this.hooksFuncs = hooks.map((item): ((...arg) => void) => {
const func = (...args) => { const func = (...args): void => {
item.handler(this, ...args); item.handler(this, ...args);
}; };
this[item.type](item.message, func); this[item.type](item.message, func);
@ -169,12 +203,12 @@ export default class Editor extends EventEmitter {
}); });
} }
private initPluginStatus() { private initPluginStatus(): PluginStatusSet {
const { plugins = {} } = this.config; const { plugins = {} } = this.config;
const pluginAreas = Object.keys(plugins); const pluginAreas = Object.keys(plugins);
const res: PluginStatus = {}; const res: PluginStatusSet = {};
pluginAreas.forEach(area => { pluginAreas.forEach((area): void => {
(plugins[area] || []).forEach(plugin => { (plugins[area] || []).forEach((plugin): void => {
if (plugin.type === 'Divider') { if (plugin.type === 'Divider') {
return; return;
} }

View File

@ -1,9 +1,11 @@
import Editor from './editor'; import Editor from './editor';
export { default as PluginFactory } from './pluginFactory';
export { default as EditorContext } from './context';
import * as editorUtils from './utils'; import * as editorUtils from './utils';
import * as editorDefinitions from './definitions'; import * as editorDefinitions from './definitions';
export { default as PluginFactory } from './pluginFactory';
export { default as EditorContext } from './context';
export default Editor; export default Editor;
export const utils = editorUtils; export const utils = editorUtils;

View File

@ -1,32 +1,24 @@
import React, { createRef, PureComponent } from 'react'; import React, { createRef, PureComponent } from 'react';
import EditorContext from './context'; import EditorContext from './context';
import { I18nFunction, PluginConfig } from './definitions'; import { I18nFunction, PluginProps, PluginClass, Plugin } from './definitions';
import Editor from './editor'; import Editor from './editor';
import { acceptsRef, generateI18n, isEmpty, transformToPromise } from './utils'; import { acceptsRef, generateI18n, isEmpty, transformToPromise } from './utils';
export interface PluginProps { export default function pluginFactory(Comp: PluginClass): React.ComponentType<PluginProps> {
editor: Editor;
config: PluginConfig;
}
export interface InjectedPluginProps {
i18n?: I18nFunction;
}
export default function pluginFactory(
Comp: React.ComponentType<PluginProps & InjectedPluginProps>
): React.ComponentType<PluginProps> {
class LowcodePlugin extends PureComponent<PluginProps> { class LowcodePlugin extends PureComponent<PluginProps> {
public static displayName = 'LowcodeEditorPlugin'; public static displayName = 'LowcodeEditorPlugin';
public static defaultProps = {
config: {}
};
public static contextType = EditorContext; public static contextType = EditorContext;
public static init = Comp.init; public static init = Comp.init;
public ref = createRef<React.Component>();
public ref: React.RefObject<React.ReactElement> & Plugin;
private editor: Editor; private editor: Editor;
private pluginKey: string; private pluginKey: string;
private i18n: I18nFunction; private i18n: I18nFunction;
constructor(props, context) { constructor(props, context) {
@ -36,6 +28,7 @@ export default function pluginFactory(
return; return;
} }
const { locale, messages, editor } = props; const { locale, messages, editor } = props;
this.ref = createRef<React.ReactElement>();
// 注册插件 // 注册插件
this.editor = editor; this.editor = editor;
this.i18n = generateI18n(locale, messages); this.i18n = generateI18n(locale, messages);
@ -46,7 +39,7 @@ export default function pluginFactory(
}); });
} }
public componentWillUnmount() { public componentWillUnmount(): void {
// 销毁插件 // 销毁插件
if (this.editor && this.editor.plugins) { if (this.editor && this.editor.plugins) {
delete this.editor.plugins[this.pluginKey]; delete this.editor.plugins[this.pluginKey];
@ -60,14 +53,14 @@ export default function pluginFactory(
return Promise.resolve(); return Promise.resolve();
}; };
public close = () => { public close = (): Promise<any> => {
if (this.ref && this.ref.close && typeof this.ref.close === 'function') { if (this.ref && this.ref.close && typeof this.ref.close === 'function') {
return transformToPromise(this.ref.close()); return transformToPromise(this.ref.close());
} }
return Promise.resolve(); return Promise.resolve();
}; };
public render() { public render(): React.ReactNode {
const { config } = this.props; const { config } = this.props;
const props = { const props = {
i18n: this.i18n, i18n: this.i18n,

View File

@ -1,7 +1,5 @@
import IntlMessageFormat from 'intl-messageformat'; import IntlMessageFormat from 'intl-messageformat';
import keymaster from 'keymaster'; import keymaster from 'keymaster';
import { EditorConfig, I18nFunction, I18nMessages, LocaleType, ShortCutsConfig } from './definitions';
import Editor from './editor';
import _clone from 'lodash/cloneDeep'; import _clone from 'lodash/cloneDeep';
import _debounce from 'lodash/debounce'; import _debounce from 'lodash/debounce';
@ -10,6 +8,10 @@ import _deepEqual from 'lodash/isEqualWith';
import _pick from 'lodash/pick'; import _pick from 'lodash/pick';
import _throttle from 'lodash/throttle'; import _throttle from 'lodash/throttle';
import _serialize from 'serialize-javascript';
import Editor from './editor';
import { EditorConfig, I18nFunction, I18nMessages, LocaleType, ShortCutsConfig } from './definitions';
export const pick = _pick; export const pick = _pick;
export const deepEqual = _deepEqual; export const deepEqual = _deepEqual;
export const clone = _clone; export const clone = _clone;
@ -17,7 +19,6 @@ export const isEmpty = _isEmpty;
export const throttle = _throttle; export const throttle = _throttle;
export const debounce = _debounce; export const debounce = _debounce;
import _serialize from 'serialize-javascript';
export const serialize = _serialize; export const serialize = _serialize;
const ENV = { const ENV = {
@ -27,6 +28,17 @@ const ENV = {
WEB: 'WEB' WEB: 'WEB'
}; };
declare global {
interface Window {
sendIDEMessage?: (params: IDEMessageParams) => void;
goldlog?: {
record: (logKey: string, gmKey: string, goKey: string, method: 'POST' | 'GET') => (...args: any[]) => any;
};
is_theia?: boolean;
vscode?: boolean;
}
}
export interface IDEMessageParams { export interface IDEMessageParams {
action: string; action: string;
data: { data: {
@ -57,7 +69,7 @@ export function serializeParams(obj: object): string {
return ''; return '';
} }
const res: string[] = []; const res: string[] = [];
Object.entries(obj).forEach(([key, val]) => { Object.entries(obj).forEach(([key, val]): void => {
if (val === null || val === undefined || val === '') { if (val === null || val === undefined || val === '') {
return; return;
} }
@ -115,8 +127,8 @@ export function getEnv(): string {
// 注册快捷键 // 注册快捷键
export function registShortCuts(config: ShortCutsConfig, editor: Editor): void { export function registShortCuts(config: ShortCutsConfig, editor: Editor): void {
(config || []).forEach(item => { (config || []).forEach((item): void => {
keymaster(item.keyboard, ev => { keymaster(item.keyboard, (ev: Event): void => {
ev.preventDefault(); ev.preventDefault();
item.handler(editor, ev, keymaster); item.handler(editor, ev, keymaster);
}); });
@ -125,7 +137,7 @@ export function registShortCuts(config: ShortCutsConfig, editor: Editor): void {
// 取消注册快捷 // 取消注册快捷
export function unRegistShortCuts(config: ShortCutsConfig): void { export function unRegistShortCuts(config: ShortCutsConfig): void {
(config || []).forEach(item => { (config || []).forEach((item): void => {
keymaster.unbind(item.keyboard); keymaster.unbind(item.keyboard);
}); });
if (window.parent.vscode) { if (window.parent.vscode) {
@ -141,7 +153,7 @@ export function transformToPromise(input: any): Promise<{}> {
if (input instanceof Promise) { if (input instanceof Promise) {
return input; return input;
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject): void => {
if (input || input === undefined) { if (input || input === undefined) {
resolve(); resolve();
} else { } else {
@ -161,7 +173,7 @@ export function transformArrayToMap<T>(arr: T[], key: string, overwrite: boolean
return {}; return {};
} }
const res = {}; const res = {};
arr.forEach(item => { arr.forEach((item): void => {
const curKey = item[key]; const curKey = item[key];
if (item[key] === undefined) { if (item[key] === undefined) {
return; return;
@ -187,7 +199,7 @@ export function parseSearch(search: string): Query {
const str = search.replace(/^\?/, ''); const str = search.replace(/^\?/, '');
const paramStr = str.split('&'); const paramStr = str.split('&');
const res = {}; const res = {};
paramStr.forEach(item => { paramStr.forEach((item): void => {
const regRes = item.split('='); const regRes = item.split('=');
if (regRes[0] && regRes[1]) { if (regRes[0] && regRes[1]) {
res[regRes[0]] = decodeURIComponent(regRes[1]); res[regRes[0]] = decodeURIComponent(regRes[1]);
@ -210,7 +222,7 @@ export function comboEditorConfig(defaultConfig: EditorConfig = {}, customConfig
const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard'); const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard');
const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP']; const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP'];
const i18nConfig = {}; const i18nConfig = {};
localeList.forEach(key => { localeList.forEach((key): void => {
i18nConfig[key] = { i18nConfig[key] = {
...(defaultConfig.i18n && defaultConfig.i18n[key]), ...(defaultConfig.i18n && defaultConfig.i18n[key]),
...(i18n && i18n[key]) ...(i18n && i18n[key])
@ -248,9 +260,12 @@ export function comboEditorConfig(defaultConfig: EditorConfig = {}, customConfig
* ref * ref
* @param {*} Comp * @param {*} Comp
*/ */
export function acceptsRef(Comp: React.ComponentType) { export function acceptsRef(Comp: React.ReactNode): boolean {
const hasSymbol = typeof Symbol === 'function' && Symbol.for; const hasSymbol = typeof Symbol === 'function' && Symbol.for;
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0; const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
if (!Comp || typeof Comp !== 'object' || isEmpty(Comp)) {
return false;
}
return ( return (
(Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent) (Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent)
); );

View File

@ -10,12 +10,9 @@ import constants from './config/constants';
import './config/locale'; import './config/locale';
import './config/setters'; import './config/setters';
import pkg from '../package.json';
import './global.scss'; import './global.scss';
import './config/theme.scss'; import './config/theme.scss';
(window as any).__pkg = pkg;
const ICE_CONTAINER = document.getElementById('ice-container'); const ICE_CONTAINER = document.getElementById('ice-container');
if (!ICE_CONTAINER) { if (!ICE_CONTAINER) {
@ -26,7 +23,7 @@ ReactDOM.render(
<Router> <Router>
<Route <Route
path="/*" path="/*"
component={props => ( component={(props): React.ReactNode => (
<Skeleton <Skeleton
{...props} {...props}
{...(config.skeleton && config.skeleton.props)} {...(config.skeleton && config.skeleton.props)}

View File

@ -163,16 +163,19 @@ const SCHEMA = {
}; };
export default class DesignerPlugin extends PureComponent<PluginProps> { export default class DesignerPlugin extends PureComponent<PluginProps> {
static displayName: 'LowcodePluginDesigner'; displayName: 'LowcodePluginDesigner';
constructor(props) { handleDesignerMount = (designer): void => {
super(props); const { editor } = this.props;
} editor.set('designer', designer);
editor.emit('designer.ready', designer);
};
render() { render(): React.ReactNode {
const { editor } = this.props; const { editor } = this.props;
return ( return (
<Designer <Designer
onMount={this.handleDesignerMount}
className="lowcode-plugin-designer" className="lowcode-plugin-designer"
defaultSchema={SCHEMA as any} defaultSchema={SCHEMA as any}
eventPipe={editor as any} eventPipe={editor as any}

View File

@ -1,9 +1,13 @@
.lowcode-plugin-logo { .lowcode-plugin-logo {
padding: 8px 16px; padding: 14px 8px;
padding-left: 8px;
.logo { .logo {
display: block;
width: 56px; width: 56px;
height: 32px; height: 20px;
cursor: pointer;
background-size: contain; background-size: contain;
background-position: center; background-position: center;
background-repeat: no-repeat;
} }
} }

View File

@ -1,18 +1,18 @@
import React from 'react'; import React from 'react';
import './index.scss'; import './index.scss';
import Editor from '../../framework/index'; import { PluginProps } from '../../framework/definitions';
import { PluginConfig } from '../../framework/definitions';
export interface PluginProps { export interface IProps {
editor: Editor;
config: PluginConfig;
logo?: string; logo?: string;
href?: string;
} }
export default function(props: PluginProps) { const Logo: React.FC<IProps & PluginProps> = (props): React.ReactElement => {
return ( return (
<div className="lowcode-plugin-logo"> <div className="lowcode-plugin-logo">
<div className="logo" style={{ backgroundImage: `url(${props.logo})` }} /> <a className="logo" target="blank" href={props.href || '/'} style={{ backgroundImage: `url(${props.logo})` }} />
</div> </div>
); );
} };
export default Logo;

View File

@ -1,22 +1,75 @@
import React, { useState } from 'react'; import React, { PureComponent } from 'react';
import './index.scss'; import './index.scss';
import Editor from '../../framework/index'; import { PluginProps } from '../../framework/definitions';
import { PluginConfig } from '../../framework/definitions';
import TopIcon from '../../skeleton/components/TopIcon/index'; import TopIcon from '../../skeleton/components/TopIcon/index';
export interface PluginProps { export interface IProps {
editor: Editor;
config: PluginConfig;
logo?: string; logo?: string;
} }
export default function(props: PluginProps) { export interface IState {
const [backEnable, setBackEnable] = useState(true); undoEnable: boolean;
const [forwardEnable, setForwardEnable] = useState(true); redoEnable: boolean;
}
export default class UndoRedo extends PureComponent<IProps & PluginProps, IState> {
public static display = 'LowcodeUndoRedo';
private history: any;
constructor(props) {
super(props);
this.state = {
undoEnable: false,
redoEnable: false
};
if (props.editor.designer) {
this.init();
} else {
props.editor.on('designer.ready', (): void => {
this.init();
});
}
}
init = (): void => {
const { editor } = this.props;
this.history = editor.designer.currentHistory;
this.updateState(this.history.getState());
editor.on('designer.history-change', (history): void => {
this.history = history;
this.history.onStateChange(this.updateState);
});
this.history.onStateChange(this.updateState);
};
updateState = (state: number): void => {
console.log('++++', !!(state & 1), !!(state & 2));
this.setState({
undoEnable: !!(state & 1),
redoEnable: !!(state & 2)
});
};
handleUndoClick = (): void => {
if (this.history) {
this.history.back();
}
};
handleRedoClick = (): void => {
if (this.history) {
this.history.forward();
}
};
render(): React.ReactNode {
const { undoEnable, redoEnable } = this.state;
return ( return (
<div className="lowcode-plugin-undo-redo"> <div className="lowcode-plugin-undo-redo">
<TopIcon icon="houtui" title="后退" disabled={!backEnable} /> <TopIcon icon="houtui" title="后退" disabled={!undoEnable} onClick={this.handleUndoClick} />
<TopIcon icon="qianjin" title="前进" disabled={!forwardEnable} /> <TopIcon icon="qianjin" title="前进" disabled={!redoEnable} onClick={this.handleRedoClick} />
</div> </div>
); );
}
} }

View File

@ -1,34 +1,16 @@
.lowcode-left-plugin { .lowcode-left-plugin {
font-size: 16px; font-size: 20px;
text-align: center; text-align: center;
line-height: 36px; line-height: 44px;
height: 36px; height: 44px;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
color: #777; color: $color-text1-3;
&.collapse {
height: 40px;
color: #8c8c8c;
border-bottom: 1px solid #bfbfbf;
}
&.locked { &.locked {
color: red !important; color: red !important;
} }
&.active {
color: #fff !important;
background-color: $color-brand1-9 !important;
&.disabled {
color: #fff;
background-color: $color-fill1-7;
}
}
&.disabled {
cursor: not-allowed;
color: $color-text1-1;
}
&:hover { &:hover {
background-color: $color-brand1-1;
color: $color-brand1-6; color: $color-brand1-6;
&:before { &:before {
content: attr(data-tooltip); content: attr(data-tooltip);
@ -41,8 +23,8 @@
white-space: nowrap; white-space: nowrap;
padding: 6px 8px; padding: 6px 8px;
border-radius: 4px; border-radius: 4px;
background: rgba(0, 0, 0, 0.75); background: $balloon-tooltip-color-bg;
color: #fff; color: $color-text1-3;
z-index: 100; z-index: 100;
} }
&:after { &:after {
@ -52,8 +34,21 @@
left: 40px; left: 40px;
top: 15px; top: 15px;
border: 5px solid transparent; border: 5px solid transparent;
border-right-color: rgba(0, 0, 0, 0.75); border-right: 5px solid $balloon-tooltip-color-bg;
z-index: 100; z-index: 100;
} }
} }
&.active {
color: $color-brand1-9;
&.disabled {
color: $color-text1-1;
}
&:hover {
color: $color-brand1-6;
}
}
&.disabled {
cursor: not-allowed;
color: $color-text1-1;
}
} }

View File

@ -30,7 +30,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
disabled: false, disabled: false,
marked: false, marked: false,
locked: false, locked: false,
onClick: () => {} onClick: (): void => {}
}; };
constructor(props, context) { constructor(props, context) {
@ -40,7 +40,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
}; };
} }
componentDidMount() { componentDidMount(): void {
const { config, editor } = this.props; const { config, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
if (editor && pluginKey) { if (editor && pluginKey) {
@ -49,7 +49,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
} }
} }
componentWillUnmount() { componentWillUnmount(): void {
const { config, editor } = this.props; const { config, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
if (editor && pluginKey) { if (editor && pluginKey) {
@ -58,12 +58,12 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
} }
} }
handleClose = () => { handleClose = (): void => {
const { config, editor } = this.props; const { config, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
const plugin = editor.plugins && editor.plugins[pluginKey]; const plugin = editor.plugins && editor.plugins[pluginKey];
if (plugin) { if (plugin && plugin.close) {
plugin.close().then(() => { plugin.close().then((): void => {
this.setState({ this.setState({
dialogVisible: false dialogVisible: false
}); });
@ -71,24 +71,26 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
} }
}; };
handleOpen = () => { handleOpen = (): void => {
// todo 对话框类型的插件初始时拿不到插件实例 // todo 对话框类型的插件初始时拿不到插件实例
this.setState({ this.setState({
dialogVisible: true dialogVisible: true
}); });
}; };
handleShow = () => { handleShow = (): void => {
const { disabled, config, onClick, editor } = this.props; const { disabled, config, onClick, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
if (disabled || !pluginKey) return; if (disabled || !pluginKey) return;
//考虑到弹窗情况,延时发送消息 // 考虑到弹窗情况,延时发送消息
setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); setTimeout((): void => editor.emit(`${pluginKey}.plugin.activate`), 0);
this.handleOpen(); this.handleOpen();
onClick && onClick(); if (onClick) {
onClick();
}
}; };
renderIcon = clickCallback => { renderIcon = (clickCallback): React.ReactNode => {
const { active, disabled, marked, locked, onClick, config } = this.props; const { active, disabled, marked, locked, onClick, config } = this.props;
const { pluginKey, props } = config || {}; const { pluginKey, props } = config || {};
const { icon, title } = props || {}; const { icon, title } = props || {};
@ -100,10 +102,11 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
locked locked
})} })}
data-tooltip={title} data-tooltip={title}
onClick={() => { onClick={(): void => {
if (disabled) return; if (disabled) return;
//考虑到弹窗情况,延时发送消息 // 考虑到弹窗情况,延时发送消息
clickCallback && clickCallback(); clickCallback && clickCallback();
onClick && onClick(); onClick && onClick();
}} }}
> >
@ -118,7 +121,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
); );
}; };
render() { render(): React.ReactNode {
const { marked, locked, active, disabled, config, editor, pluginClass: Comp } = this.props; const { marked, locked, active, disabled, config, editor, pluginClass: Comp } = this.props;
const { pluginKey, props, type, pluginProps } = config || {}; const { pluginKey, props, type, pluginProps } = config || {};
const { onClick, title } = props || {}; const { onClick, title } = props || {};
@ -133,7 +136,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
locked={locked} locked={locked}
disabled={disabled} disabled={disabled}
config={config} config={config}
onClick={() => { onClick={(): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
}} }}
{...pluginProps} {...pluginProps}
@ -145,30 +148,34 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
case 'LinkIcon': case 'LinkIcon':
return ( return (
<a {...(props.linkProps || {})}> <a {...(props.linkProps || {})}>
{this.renderIcon(() => { {this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
})} })}
</a> </a>
); );
case 'Icon': case 'Icon':
return this.renderIcon(() => { return this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
}); });
case 'DialogIcon': case 'DialogIcon':
return ( return (
<Fragment> <Fragment>
{this.renderIcon(() => { {this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
this.handleOpen(); this.handleOpen();
})} })}
<Dialog <Dialog
onOk={() => { onOk={(): void => {
editor.emit(`${pluginKey}.dialog.onOk`); editor.emit(`${pluginKey}.dialog.onOk`);
this.handleClose(); this.handleClose();
}} }}
onCancel={this.handleClose} onCancel={this.handleClose}
onClose={this.handleClose} onClose={this.handleClose}
title={title} title={title}
style={{
width: 500,
...(props.dialogProps && props.dialogProps.style)
}}
{...(props.dialogProps || {})} {...(props.dialogProps || {})}
visible={dialogVisible} visible={dialogVisible}
> >
@ -179,7 +186,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
case 'BalloonIcon': case 'BalloonIcon':
return ( return (
<Balloon <Balloon
trigger={this.renderIcon(() => { trigger={this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
})} })}
align="r" align="r"
@ -190,7 +197,7 @@ export default class LeftPlugin extends PureComponent<LeftPluginProps, LeftPlugi
</Balloon> </Balloon>
); );
case 'PanelIcon': case 'PanelIcon':
return this.renderIcon(() => { return this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
this.handleOpen(); this.handleOpen();
}); });

View File

@ -2,12 +2,12 @@
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: #ffffff; background: $card-background;
transition: width 0.3s ease; transition: width 0.3s ease;
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
height: 100%; height: 100%;
&.visible { &.visible {
border-right: 1px solid #bfbfbf; border-right: 1px solid $color-line1-1;
} }
.drag-area { .drag-area {
display: none; display: none;
@ -16,7 +16,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
z-index: 999; z-index: 99;
} }
&.draggable { &.draggable {
.drag-area { .drag-area {
@ -41,12 +41,12 @@
} }
&.left { &.left {
&.floatable { &.floatable {
left: 44px; left: 48px;
} }
} }
&.right { &.right {
&.floatable { &.floatable {
right: 44px; right: 48px;
} }
} }
} }

View File

@ -37,7 +37,7 @@ export default class Panel extends PureComponent<PanelProps, PanelState> {
}; };
} }
render() { render(): React.ReactNode {
const { align, draggable, floatable, visible } = this.props; const { align, draggable, floatable, visible } = this.props;
const { width } = this.state; const { width } = this.state;
return ( return (

View File

@ -1,21 +1,27 @@
.next-btn.next-large.lowcode-top-btn { .lowcode-top-icon {
display: inline-block;
width: 44px; width: 44px;
height: 44px; font-size: 20px;
padding: 0; line-height: 48px;
margin: 2px -2px; color: $color-text1-3;
text-align: center; position: relative;
border-radius: 8px;
border: 1px solid transparent;
color: #777;
&.disabled { &.disabled {
cursor: not-allowed; cursor: not-allowed;
color: $color-text1-1; color: $color-text1-1;
&:hover {
color: $color-text1-1;
}
}
&.active {
color: $color-brand1-9;
&:hover {
color: $color-brand1-6;
}
} }
&.locked { &.locked {
color: red !important; color: red !important;
} }
&:hover { &:hover {
background-color: $color-brand1-1;
color: $color-brand1-6; color: $color-brand1-6;
&:before { &:before {
content: attr(data-tooltip); content: attr(data-tooltip);
@ -31,8 +37,8 @@
white-space: nowrap; white-space: nowrap;
padding: 6px 8px; padding: 6px 8px;
border-radius: 4px; border-radius: 4px;
background: rgba(0, 0, 0, 0.75); background: $balloon-tooltip-color-bg;
color: #fff; color: $color-text1-3;
z-index: 100; z-index: 100;
} }
&:after { &:after {
@ -43,7 +49,7 @@
transform: translate(-50%, 0); transform: translate(-50%, 0);
bottom: -5px; bottom: -5px;
border: 5px solid transparent; border: 5px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.75); border-bottom-color: $balloon-tooltip-color-bg;
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
z-index: 100; z-index: 100;
@ -51,7 +57,7 @@
} }
i.next-icon { i.next-icon {
&:before { &:before {
font-size: 17px; font-size: 16px;
} }
margin-right: 0; margin-right: 0;
line-height: 18px; line-height: 18px;

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon, Button } from '@alifd/next'; import { Icon } from '@alifd/next';
import './index.scss'; import './index.scss';
@ -13,13 +13,13 @@ export interface TopIconProps {
locked?: boolean; locked?: boolean;
marked?: boolean; marked?: boolean;
onClick?: () => void; onClick?: () => void;
showTitle?: boolean;
style?: React.CSSProperties; style?: React.CSSProperties;
title?: string; title?: string;
} }
export default class TopIcon extends PureComponent<TopIconProps> { export default class TopIcon extends PureComponent<TopIconProps> {
static displayName = 'LowcodeTopIcon'; static displayName = 'LowcodeTopIcon';
static defaultProps = { static defaultProps = {
active: false, active: false,
className: '', className: '',
@ -27,20 +27,16 @@ export default class TopIcon extends PureComponent<TopIconProps> {
icon: '', icon: '',
id: '', id: '',
locked: false, locked: false,
onClick: () => {}, onClick: (): void => {},
showTitle: false,
style: {}, style: {},
title: '' title: ''
}; };
render() { render(): React.ReactNode {
const { active, disabled, icon, locked, title, className, id, style, showTitle, onClick } = this.props; const { active, disabled, icon, locked, title, className, id, style, onClick } = this.props;
return ( return (
<Button <div
type="normal" className={classNames('lowcode-top-icon', className, {
size="large"
text={true}
className={classNames('lowcode-top-btn', className, {
active, active,
disabled, disabled,
locked locked
@ -50,11 +46,8 @@ export default class TopIcon extends PureComponent<TopIconProps> {
style={style} style={style}
onClick={disabled ? undefined : onClick} onClick={disabled ? undefined : onClick}
> >
<div> <Icon type={icon} />
<Icon size="large" type={icon} />
{showTitle && <span>{title}</span>}
</div> </div>
</Button>
); );
} }
} }

View File

@ -1,7 +1,7 @@
import React, { PureComponent, Fragment } from 'react'; import React, { PureComponent, Fragment, CSSProperties } from 'react';
import TopIcon from '../TopIcon';
import { Balloon, Badge, Dialog } from '@alifd/next'; import { Balloon, Badge, Dialog } from '@alifd/next';
import TopIcon from '../TopIcon';
import './index.scss'; import './index.scss';
import { PluginConfig, PluginClass } from '../../../framework/definitions'; import { PluginConfig, PluginClass } from '../../../framework/definitions';
@ -31,7 +31,7 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
disabled: false, disabled: false,
marked: false, marked: false,
locked: false, locked: false,
onClick: () => {} onClick: (): void => {}
}; };
constructor(props, context) { constructor(props, context) {
@ -41,7 +41,7 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
}; };
} }
componentDidMount() { componentDidMount(): void {
const { config, editor } = this.props; const { config, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
if (editor && pluginKey) { if (editor && pluginKey) {
@ -50,7 +50,7 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
} }
} }
componentWillUnmount() { componentWillUnmount(): void {
const { config, editor } = this.props; const { config, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
if (editor && pluginKey) { if (editor && pluginKey) {
@ -59,22 +59,22 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
} }
} }
handleShow = () => { handleShow = (): void => {
const { disabled, config, onClick, editor } = this.props; const { disabled, config, onClick, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
if (disabled || !pluginKey) return; if (disabled || !pluginKey) return;
//考虑到弹窗情况,延时发送消息 // 考虑到弹窗情况,延时发送消息
setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); setTimeout((): void => editor.emit(`${pluginKey}.plugin.activate`), 0);
this.handleOpen(); this.handleOpen();
onClick && onClick(); onClick && onClick();
}; };
handleClose = () => { handleClose = (): void => {
const { config, editor } = this.props; const { config, editor } = this.props;
const pluginKey = config && config.pluginKey; const pluginKey = config && config.pluginKey;
const plugin = editor.plugins && editor.plugins[pluginKey]; const plugin = editor.plugins && editor.plugins[pluginKey];
if (plugin) { if (plugin && plugin.close) {
plugin.close().then(() => { plugin.close().then((): void => {
this.setState({ this.setState({
dialogVisible: false dialogVisible: false
}); });
@ -82,29 +82,29 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
} }
}; };
handleOpen = () => { handleOpen = (): void => {
// todo dialog类型的插件初始时拿不动插件实例 // todo dialog类型的插件初始时拿不动插件实例
this.setState({ this.setState({
dialogVisible: true dialogVisible: true
}); });
}; };
renderIcon = clickCallback => { renderIcon = (clickCallback): React.ReactNode => {
const { active, disabled, marked, locked, config, onClick, editor } = this.props; const { active, disabled, marked, locked, config, onClick, editor } = this.props;
const { pluginKey, props } = config || {}; const { pluginKey, props } = config || {};
const { icon, title } = props || {}; const { icon, title } = props || {};
const node = ( const node = (
<TopIcon <TopIcon
className={`lowcode-top-addon ${pluginKey}`} className={`lowcode-top-plugin ${pluginKey}`}
active={active} active={active}
disabled={disabled} disabled={disabled}
locked={locked} locked={locked}
icon={icon} icon={icon}
title={title} title={title}
onClick={() => { onClick={(): void => {
if (disabled) return; if (disabled) return;
//考虑到弹窗情况,延时发送消息 // 考虑到弹窗情况,延时发送消息
setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); setTimeout((): void => editor.emit(`${pluginKey}.plugin.activate`), 0);
clickCallback && clickCallback(); clickCallback && clickCallback();
onClick && onClick(); onClick && onClick();
}} }}
@ -113,8 +113,8 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
return marked ? <Badge dot>{node}</Badge> : node; return marked ? <Badge dot>{node}</Badge> : node;
}; };
render() { render(): React.ReactNode {
const { active, marked, locked, disabled, config, editor, pluginClass: Comp } = this.props; const { active, marked, locked, disabled, config, editor, pluginClass: Comp, style } = this.props;
const { pluginKey, pluginProps, props, type } = config || {}; const { pluginKey, pluginProps, props, type } = config || {};
const { onClick, title } = props || {}; const { onClick, title } = props || {};
const { dialogVisible } = this.state; const { dialogVisible } = this.state;
@ -127,7 +127,7 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
locked={locked} locked={locked}
disabled={disabled} disabled={disabled}
config={config} config={config}
onClick={() => { onClick={(): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
}} }}
{...pluginProps} {...pluginProps}
@ -139,30 +139,34 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
case 'LinkIcon': case 'LinkIcon':
return ( return (
<a {...props.linkProps}> <a {...props.linkProps}>
{this.renderIcon(() => { {this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
})} })}
</a> </a>
); );
case 'Icon': case 'Icon':
return this.renderIcon(() => { return this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
}); });
case 'DialogIcon': case 'DialogIcon':
return ( return (
<Fragment> <Fragment>
{this.renderIcon(() => { {this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
this.handleOpen(); this.handleOpen();
})} })}
<Dialog <Dialog
onOk={() => { onOk={(): void => {
editor.emit(`${pluginKey}.dialog.onOk`); editor.emit(`${pluginKey}.dialog.onOk`);
this.handleClose(); this.handleClose();
}} }}
onCancel={this.handleClose} onCancel={this.handleClose}
onClose={this.handleClose} onClose={this.handleClose}
title={title} title={title}
style={{
width: 500,
...(props.dialogProps && props.dialogProps.style)
}}
{...props.dialogProps} {...props.dialogProps}
visible={dialogVisible} visible={dialogVisible}
> >
@ -173,7 +177,7 @@ export default class TopPlugin extends PureComponent<TopPluginProps, TopPluginSt
case 'BalloonIcon': case 'BalloonIcon':
return ( return (
<Balloon <Balloon
trigger={this.renderIcon(() => { trigger={this.renderIcon((): void => {
onClick && onClick.call(null, editor); onClick && onClick.call(null, editor);
})} })}
triggerType={['click', 'hover']} triggerType={['click', 'hover']}

View File

@ -6,7 +6,9 @@ body {
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
color: $color-text1-3;
} }
.next-loading { .next-loading {
.next-loading-wrap { .next-loading-wrap {
height: 100%; height: 100%;
@ -15,7 +17,7 @@ body {
.lowcode-editor { .lowcode-editor {
.lowcode-main-content { .lowcode-main-content {
position: absolute; position: absolute;
top: 48px; top: 50px;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;

View File

@ -1,10 +1,9 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { HashRouter as Router, Route } from 'react-router-dom';
import { Loading, ConfigProvider } from '@alifd/next'; import { Loading, ConfigProvider } from '@alifd/next';
import Editor from '../framework/editor'; import Editor from '../framework/editor';
import { EditorConfig, Utils, PluginComponents } from '../framework/definitions'; import { EditorConfig, Utils, PluginClassSet } from '../framework/definitions';
import { comboEditorConfig, parseSearch } from '../framework/utils'; import { comboEditorConfig, parseSearch } from '../framework/utils';
import defaultConfig from './config/skeleton'; import defaultConfig from './config/skeleton';
@ -19,8 +18,17 @@ import './global.scss';
let renderIdx = 0; let renderIdx = 0;
declare global {
interface Window {
__ctx: {
editor: Editor;
appHelper: Editor;
};
}
}
export interface SkeletonProps { export interface SkeletonProps {
components: PluginComponents; components: PluginClassSet;
config: EditorConfig; config: EditorConfig;
history: object; history: object;
location: object; location: object;
@ -29,15 +37,15 @@ export interface SkeletonProps {
} }
export interface SkeletonState { export interface SkeletonState {
initReady: boolean; initReady?: boolean;
skeletonKey: string; skeletonKey?: string;
__hasError?: boolean; __hasError?: boolean;
} }
export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState> { export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState> {
static displayName = 'LowcodeEditorSkeleton'; static displayName = 'LowcodeEditorSkeleton';
static getDerivedStateFromError() { static getDerivedStateFromError(): SkeletonState {
return { return {
__hasError: true __hasError: true
}; };
@ -56,11 +64,11 @@ export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState
this.init(); this.init();
} }
componentWillUnmount() { componentWillUnmount(): void {
this.editor && this.editor.destroy(); this.editor && this.editor.destroy();
} }
componentDidCatch(err) { componentDidCatch(err): void {
console.error(err); console.error(err);
} }
@ -69,15 +77,17 @@ export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState
this.editor.destroy(); this.editor.destroy();
} }
const { utils, config, components } = this.props; const { utils, config, components } = this.props;
const editor = (this.editor = new Editor(comboEditorConfig(defaultConfig, config), components, { const editor = new Editor(comboEditorConfig(defaultConfig, config), components, {
...skeletonUtils, ...skeletonUtils,
...utils ...utils
})); });
this.editor = editor;
// eslint-disable-next-line no-underscore-dangle
window.__ctx = { window.__ctx = {
editor, editor,
appHelper: editor appHelper: editor
}; };
editor.once('editor.reset', () => { editor.once('editor.reset', (): void => {
this.setState({ this.setState({
initReady: false initReady: false
}); });
@ -85,22 +95,23 @@ export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState
this.init(true); this.init(true);
}); });
this.editor.init().then(() => { this.editor.init().then((): void => {
this.setState( this.setState(
{ {
initReady: true, initReady: true,
//刷新IDE时生成新的skeletonKey保证插件生命周期重新执行 // 刷新IDE时生成新的skeletonKey保证插件生命周期重新执行
skeletonKey: isReset ? `skeleton${++renderIdx}` : this.state.skeletonKey skeletonKey: isReset ? `skeleton${++renderIdx}` : this.state.skeletonKey
}, },
() => { (): void => {
editor.emit('editor.ready'); editor.emit('editor.ready');
editor.emit('ide.ready');
isReset && editor.emit('ide.afterReset'); isReset && editor.emit('ide.afterReset');
} }
); );
}); });
}; };
render() { render(): React.ReactNode {
const { initReady, skeletonKey, __hasError } = this.state; const { initReady, skeletonKey, __hasError } = this.state;
const { location, history, match } = this.props; const { location, history, match } = this.props;
if (__hasError || !this.editor) { if (__hasError || !this.editor) {

View File

@ -12,6 +12,7 @@ export default class CenterArea extends PureComponent<CenterAreaProps> {
static displayName = 'LowcodeCenterArea'; static displayName = 'LowcodeCenterArea';
private editor: Editor; private editor: Editor;
private areaManager: AreaManager; private areaManager: AreaManager;
constructor(props) { constructor(props) {
@ -20,10 +21,11 @@ export default class CenterArea extends PureComponent<CenterAreaProps> {
this.areaManager = new AreaManager(this.editor, 'centerArea'); this.areaManager = new AreaManager(this.editor, 'centerArea');
} }
componentDidMount() { componentDidMount(): void {
this.editor.on('skeleton.update', this.handleSkeletonUpdate); this.editor.on('skeleton.update', this.handleSkeletonUpdate);
} }
componentWillUnmount() {
componentWillUnmount(): void {
this.editor.off('skeleton.update', this.handleSkeletonUpdate); this.editor.off('skeleton.update', this.handleSkeletonUpdate);
} }
@ -34,14 +36,16 @@ export default class CenterArea extends PureComponent<CenterAreaProps> {
} }
}; };
render() { render(): React.ReactNode {
const visiblePluginList = this.areaManager.getVisiblePluginList(); const visiblePluginList = this.areaManager.getVisiblePluginList();
return ( return (
<div className="lowcode-center-area"> <div className="lowcode-center-area">
{visiblePluginList.map(item => { {visiblePluginList.map(
(item): React.ReactNode => {
const Comp = this.editor.components[item.pluginKey]; const Comp = this.editor.components[item.pluginKey];
return <Comp key={item.pluginKey} editor={this.editor} config={item} {...item.pluginProps} />; return <Comp key={item.pluginKey} editor={this.editor} config={item} {...item.pluginProps} />;
})} }
)}
</div> </div>
); );
} }

View File

@ -1,21 +1,23 @@
.lowcode-left-area-nav { .lowcode-left-area-nav {
width: 48px; width: 50px;
height: 100%; height: 100%;
background: #ffffff; background-color: $card-background;
border-right: 1px solid #e8ebee; border-right: 2px solid $color-line1-1;
position: relative; position: relative;
.top-area { .top-area {
position: absolute; position: absolute;
top: 0; top: 0;
width: 100%; width: 100%;
background: #ffffff; padding: 12px 0;
background-color: $card-background;
max-height: 100%; max-height: 100%;
} }
.bottom-area { .bottom-area {
position: absolute; position: absolute;
bottom: 20px; bottom: 0;
width: 100%; width: 100%;
background: #ffffff; padding: 12px 0;
background-color: $card-background;
max-height: calc(100% - 20px); max-height: calc(100% - 20px);
} }
} }

View File

@ -4,6 +4,7 @@ import './index.scss';
import Editor from '../../../framework/editor'; import Editor from '../../../framework/editor';
import { PluginConfig } from '../../../framework/definitions'; import { PluginConfig } from '../../../framework/definitions';
import AreaManager from '../../../framework/areaManager'; import AreaManager from '../../../framework/areaManager';
import { isEmpty } from '../../../framework/utils';
export interface LeftAreaNavProps { export interface LeftAreaNavProps {
editor: Editor; editor: Editor;
@ -17,8 +18,10 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
static displayName = 'LowcodeLeftAreaNav'; static displayName = 'LowcodeLeftAreaNav';
private editor: Editor; private editor: Editor;
private areaManager: AreaManager; private areaManager: AreaManager;
private cacheActiveKey: string;
// private cacheActiveKey: string;
constructor(props) { constructor(props) {
super(props); super(props);
@ -28,17 +31,18 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
this.state = { this.state = {
activeKey: 'none' activeKey: 'none'
}; };
this.cacheActiveKey = 'none'; // this.cacheActiveKey = 'none';
} }
componentDidMount() { componentDidMount(): void {
this.editor.on('skeleton.update', this.handleSkeletonUpdate); this.editor.on('skeleton.update', this.handleSkeletonUpdate);
this.editor.on('leftNav.change', this.handlePluginChange); this.editor.on('leftNav.change', this.handlePluginChange);
const visiblePanelPluginList = this.areaManager.getVisiblePluginList().filter(item => item.type === 'IconPanel'); const visiblePanelPluginList = this.areaManager.getVisiblePluginList().filter(item => item.type === 'IconPanel');
const defaultKey = (visiblePanelPluginList[0] && visiblePanelPluginList[0].pluginKey) || 'componentAttr'; const defaultKey = (visiblePanelPluginList[0] && visiblePanelPluginList[0].pluginKey) || 'componentAttr';
this.handlePluginChange(defaultKey); this.handlePluginChange(defaultKey);
} }
componentWillUnmount() {
componentWillUnmount(): void {
this.editor.off('skeleton.update', this.handleSkeletonUpdate); this.editor.off('skeleton.update', this.handleSkeletonUpdate);
this.editor.off('leftNav.change', this.handlePluginChange); this.editor.off('leftNav.change', this.handlePluginChange);
} }
@ -57,48 +61,46 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
const nextPlugin = plugins[key]; const nextPlugin = plugins[key];
if (activeKey === 'none') { if (activeKey === 'none') {
if (nextPlugin) { if (nextPlugin) {
nextPlugin.open().then(() => { nextPlugin.open().then((): void => {
this.updateActiveKey(key); this.updateActiveKey(key);
}); });
} }
} else if (activeKey === key) { } else if (activeKey === key) {
if (prePlugin) { if (prePlugin) {
prePlugin.close().then(() => { prePlugin.close().then((): void => {
this.updateActiveKey('none'); this.updateActiveKey('none');
}); });
} }
} else { } else if (prePlugin) {
// 先关后开 // 先关后开
if (prePlugin) { prePlugin.close().then((): void => {
prePlugin.close().then(() => {
if (nextPlugin) { if (nextPlugin) {
nextPlugin.open().then(() => { nextPlugin.open().then((): void => {
this.updateActiveKey(key); this.updateActiveKey(key);
}); });
} }
}); });
} }
}
}; };
handleCollapseClick = (): void => { // handleCollapseClick = (): void => {
const { activeKey } = this.state; // const { activeKey } = this.state;
if (activeKey === 'none') { // if (activeKey === 'none') {
const plugin = this.editor.plugins[this.cacheActiveKey]; // const plugin = this.editor.plugins[this.cacheActiveKey];
if (plugin) { // if (plugin) {
plugin.open().then(() => { // plugin.open().then(() => {
this.updateActiveKey(this.cacheActiveKey); // this.updateActiveKey(this.cacheActiveKey);
}); // });
} // }
} else { // } else {
const plugin = this.editor.plugins[activeKey]; // const plugin = this.editor.plugins[activeKey];
if (plugin) { // if (plugin) {
plugin.close().then(() => { // plugin.close().then(() => {
this.updateActiveKey('none'); // this.updateActiveKey('none');
}); // });
} // }
} // }
}; // };
handlePluginClick = (item: PluginConfig): void => { handlePluginClick = (item: PluginConfig): void => {
if (item.type === 'PanelIcon') { if (item.type === 'PanelIcon') {
@ -107,17 +109,18 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
}; };
updateActiveKey = (key: string): void => { updateActiveKey = (key: string): void => {
if (key === 'none') { // if (key === 'none') {
this.cacheActiveKey = this.state.activeKey; // this.cacheActiveKey = this.state.activeKey;
} // }
this.editor.set('leftNav', key); this.editor.set('leftNav', key);
this.setState({ activeKey: key }); this.setState({ activeKey: key });
this.editor.emit('leftPanel.show', key); this.editor.emit('leftPanel.show', key);
}; };
renderPluginList = (list: Array<PluginConfig> = []): Array<React.ReactElement> => { renderPluginList = (list: PluginConfig[] = []): React.ReactElement[] => {
const { activeKey } = this.state; const { activeKey } = this.state;
return list.map((item, idx) => { return list.map(
(item): React.ReactElement => {
const pluginStatus = this.editor.pluginStatus[item.pluginKey]; const pluginStatus = this.editor.pluginStatus[item.pluginKey];
return ( return (
<LeftPlugin <LeftPlugin
@ -125,20 +128,23 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
config={item} config={item}
editor={this.editor} editor={this.editor}
pluginClass={this.editor.components[item.pluginKey]} pluginClass={this.editor.components[item.pluginKey]}
onClick={() => this.handlePluginClick(item)} onClick={(): void => this.handlePluginClick(item)}
active={activeKey === item.pluginKey} active={activeKey === item.pluginKey}
{...pluginStatus} {...pluginStatus}
/> />
); );
}); }
);
}; };
render() { render(): React.ReactNode {
const { activeKey } = this.state; const topList: PluginConfig[] = [];
const topList: Array<PluginConfig> = []; const bottomList: PluginConfig[] = [];
const bottomList: Array<PluginConfig> = [];
const visiblePluginList = this.areaManager.getVisiblePluginList(); const visiblePluginList = this.areaManager.getVisiblePluginList();
visiblePluginList.forEach(item => { if (isEmpty(visiblePluginList)) {
return null;
}
visiblePluginList.forEach((item): void => {
const align = item.props && item.props.align === 'bottom' ? 'bottom' : 'top'; const align = item.props && item.props.align === 'bottom' ? 'bottom' : 'top';
if (align === 'bottom') { if (align === 'bottom') {
bottomList.push(item); bottomList.push(item);
@ -151,7 +157,7 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
<div className="lowcode-left-area-nav"> <div className="lowcode-left-area-nav">
<div className="bottom-area">{this.renderPluginList(bottomList)}</div> <div className="bottom-area">{this.renderPluginList(bottomList)}</div>
<div className="top-area"> <div className="top-area">
<LeftPlugin {/* <LeftPlugin
editor={this.editor} editor={this.editor}
key="collapse" key="collapse"
config={{ config={{
@ -163,7 +169,7 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps, LeftAre
} }
}} }}
onClick={this.handleCollapseClick} onClick={this.handleCollapseClick}
/> /> */}
{this.renderPluginList(topList)} {this.renderPluginList(topList)}
</div> </div>
</div> </div>

View File

@ -16,6 +16,7 @@ export default class LeftAreaPanel extends PureComponent<LeftAreaPanelProps, Lef
static displayName = 'LowcodeLeftAreaPanel'; static displayName = 'LowcodeLeftAreaPanel';
private editor: Editor; private editor: Editor;
private areaManager: AreaManager; private areaManager: AreaManager;
constructor(props) { constructor(props) {
@ -28,11 +29,12 @@ export default class LeftAreaPanel extends PureComponent<LeftAreaPanelProps, Lef
}; };
} }
componentDidMount() { componentDidMount(): void {
this.editor.on('skeleton.update', this.handleSkeletonUpdate); this.editor.on('skeleton.update', this.handleSkeletonUpdate);
this.editor.on('leftPanel.show', this.handlePluginChange); this.editor.on('leftPanel.show', this.handlePluginChange);
} }
componentWillUnmount() {
componentWillUnmount(): void {
this.editor.off('skeleton.update', this.handleSkeletonUpdate); this.editor.off('skeleton.update', this.handleSkeletonUpdate);
this.editor.off('leftPanel.show', this.handlePluginChange); this.editor.off('leftPanel.show', this.handlePluginChange);
} }
@ -50,20 +52,26 @@ export default class LeftAreaPanel extends PureComponent<LeftAreaPanelProps, Lef
}); });
}; };
render() { render(): React.ReactNode {
const { activeKey } = this.state; const { activeKey } = this.state;
const list = this.areaManager.getVisiblePluginList('PanelIcon'); const list = this.areaManager.getVisiblePluginList('PanelIcon');
return ( return (
<Fragment> <Fragment>
{list.map((item, idx) => { {list.map(
(item): React.ReactElement => {
const Comp = this.editor.components[item.pluginKey]; const Comp = this.editor.components[item.pluginKey];
return ( return (
<Panel key={item.pluginKey} visible={item.pluginKey === activeKey}> <Panel
key={item.pluginKey}
visible={item.pluginKey === activeKey}
{...(item.props && item.props.panelProps)}
>
<Comp editor={this.editor} config={item} {...item.pluginProps} /> <Comp editor={this.editor} config={item} {...item.pluginProps} />
</Panel> </Panel>
); );
})} }
)}
</Fragment> </Fragment>
); );
} }

View File

@ -1,9 +1,24 @@
.lowcode-right-area { .lowcode-right-area {
width: 300px; width: 262px;
height: 100%; height: 100%;
background-color: #ffffff; background-color: $card-background;
border-left: 1px solid #e8ebee; border-left: 2px solid $color-line1-1;
.right-panel {
overflow: auto;
// border-top: 2px solid $color-line1-1;
}
//tab定义
.right-tabs.next-tabs {
.next-tabs-nav {
width: 100%;
.next-tabs-tab-inner {
padding-left: 0;
padding-right: 0;
}
.right-plugin-title { .right-plugin-title {
text-align: center;
&.locked { &.locked {
color: red !important; color: red !important;
} }
@ -14,39 +29,11 @@
cursor: not-allowed; cursor: not-allowed;
color: $color-text1-1; color: $color-text1-1;
} }
} .next-icon {
line-height: 15px;
//tab定义 margin-right: 2px;
.next-tabs-wrapped.right-tabs {
display: flex;
flex-direction: column;
margin-top: -1px;
.next-tabs-bar {
z-index: 1;
}
.next-tabs-nav {
display: block;
.next-tabs-tab {
&:first-child {
border-left: none;
}
font-size: 14px;
text-align: center;
border-right: none !important;
margin-right: 0 !important;
width: 25%;
&.active {
background: none;
border-bottom-color: #f7f7f7 !important;
} }
} }
} }
} }
.next-tabs-content {
flex: 1;
.next-tabs-tabpane.active {
height: 100%;
overflow-y: auto;
}
}
} }

View File

@ -4,6 +4,7 @@ import './index.scss';
import Editor from '../../../framework/editor'; import Editor from '../../../framework/editor';
import AreaManager from '../../../framework/areaManager'; import AreaManager from '../../../framework/areaManager';
import { PluginConfig } from '../../../framework/definitions'; import { PluginConfig } from '../../../framework/definitions';
import { isEmpty } from '../../../framework/utils';
export interface RightAreaProps { export interface RightAreaProps {
editor: Editor; editor: Editor;
@ -17,6 +18,7 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
static displayName = 'LowcodeRightArea'; static displayName = 'LowcodeRightArea';
private editor: Editor; private editor: Editor;
private areaManager: AreaManager; private areaManager: AreaManager;
constructor(props) { constructor(props) {
@ -28,14 +30,15 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
}; };
} }
componentDidMount() { componentDidMount(): void {
this.editor.on('skeleton.update', this.handleSkeletonUpdate); this.editor.on('skeleton.update', this.handleSkeletonUpdate);
this.editor.on('rightNav.change', this.handlePluginChange); this.editor.on('rightNav.change', this.handlePluginChange);
const visiblePluginList = this.areaManager.getVisiblePluginList(); const visiblePluginList = this.areaManager.getVisiblePluginList('TabPanel');
const defaultKey = (visiblePluginList[0] && visiblePluginList[0].pluginKey) || 'componentAttr'; const defaultKey = (visiblePluginList[0] && visiblePluginList[0].pluginKey) || 'componentAttr';
this.handlePluginChange(defaultKey, true); this.handlePluginChange(defaultKey, true);
} }
componentWillUnmount() {
componentWillUnmount(): void {
this.editor.off('skeleton.update', this.handleSkeletonUpdate); this.editor.off('skeleton.update', this.handleSkeletonUpdate);
this.editor.off('rightNav.change', this.handlePluginChange); this.editor.off('rightNav.change', this.handlePluginChange);
} }
@ -50,12 +53,12 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
} else { } else {
const currentPlugin = this.editor.plugins[activeKey]; const currentPlugin = this.editor.plugins[activeKey];
if (currentPlugin) { if (currentPlugin) {
currentPlugin.close().then(() => { currentPlugin.close().then((): void => {
this.setState( this.setState(
{ {
activeKey: '' activeKey: ''
}, },
() => { (): void => {
const visiblePluginList = this.areaManager.getVisiblePluginList(); const visiblePluginList = this.areaManager.getVisiblePluginList();
const firstPlugin = visiblePluginList && visiblePluginList[0]; const firstPlugin = visiblePluginList && visiblePluginList[0];
if (firstPlugin) { if (firstPlugin) {
@ -72,12 +75,12 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
handlePluginChange = (key: string, isinit?: boolean): void => { handlePluginChange = (key: string, isinit?: boolean): void => {
const activeKey = this.state.activeKey; const activeKey = this.state.activeKey;
const plugins = this.editor.plugins || {}; const plugins = this.editor.plugins || {};
const openPlugin = () => { const openPlugin = (): void => {
if (!plugins[key]) { if (!plugins[key]) {
console.error(`plugin ${key} has not regist in the editor`); console.error(`plugin ${key} has not regist in the editor`);
return; return;
} }
plugins[key].open().then(() => { plugins[key].open().then((): void => {
this.editor.set('rightNav', key); this.editor.set('rightNav', key);
this.setState({ this.setState({
activeKey: key activeKey: key
@ -86,7 +89,7 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
}; };
if (key === activeKey && !isinit) return; if (key === activeKey && !isinit) return;
if (activeKey && plugins[activeKey]) { if (activeKey && plugins[activeKey]) {
plugins[activeKey].close().then(() => { plugins[activeKey].close().then((): void => {
openPlugin(); openPlugin();
}); });
} else { } else {
@ -102,21 +105,11 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
const renderTitle = (): React.ReactElement => ( const renderTitle = (): React.ReactElement => (
<div <div
className={`right-addon-title ${active ? 'active' : ''} ${locked ? 'locked' : ''} ${ className={`right-plugin-title ${active ? 'active' : ''} ${locked ? 'locked' : ''} ${
disabled ? 'disabled' : '' disabled ? 'disabled' : ''
}`} }`}
> >
{!!icon && ( {!!icon && <Icon size="xs" type={icon} />}
<Icon
type={icon}
style={{
marginRight: 2,
fontSize: '14px',
lineHeight: '14px',
verticalAlign: 'top'
}}
/>
)}
{title} {title}
</div> </div>
); );
@ -126,45 +119,68 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
return renderTitle(); return renderTitle();
}; };
render() { renderTabPanels = (list: PluginConfig[], height: string): React.ReactNode => {
const visiblePluginList = this.areaManager.getVisiblePluginList(); if (isEmpty(list)) {
if (visiblePluginList.length < 2) { return null;
const pane = visiblePluginList[0];
if (!pane) {
return <div className="lowcode-right-area"></div>;
}
const Comp = this.editor.components[pane.pluginKey];
return (
<div className="lowcode-right-area">
<Comp editor={this.editor} config={pane} {...pane.pluginProps} />
</div>
);
} }
return ( return (
<div className="lowcode-right-area">
<Tab <Tab
shape="wrapped"
className="right-tabs" className="right-tabs"
style={{ style={{
height: '100%' height
}} }}
activeKey={this.state.activeKey} activeKey={this.state.activeKey}
lazyLoad={false} lazyLoad={false}
onChange={this.handlePluginChange} onChange={this.handlePluginChange}
> >
{visiblePluginList.map((item, idx) => { {list.map(
(item): React.ReactElement => {
const Comp = this.editor.components[item.pluginKey]; const Comp = this.editor.components[item.pluginKey];
return ( return (
<Tab.Item <Tab.Item
key={item.pluginKey} key={item.pluginKey}
title={this.renderTabTitle(item)} title={this.renderTabTitle(item)}
disabled={this.editor.pluginStatus[item.pluginKey].disabled} disabled={this.editor.pluginStatus[item.pluginKey].disabled}
style={{
width: `${100 / list.length}%`
}}
> >
<Comp editor={this.editor} config={item} {...item.pluginProps} /> <Comp editor={this.editor} config={item} {...item.pluginProps} />
</Tab.Item> </Tab.Item>
); );
})} }
)}
</Tab> </Tab>
);
};
renderPanels = (list: PluginConfig[], height: string): React.ReactNode => {
return list.map(
(item): React.ReactElement => {
const Comp = this.editor.components[item.pluginKey];
return (
<div className="right-panel" style={{ height }} key={item.pluginKey}>
<Comp editor={this.editor} config={item} {...item.pluginProps} />
</div>
);
}
);
};
render(): React.ReactNode {
const tabList = this.areaManager.getVisiblePluginList('TabPanel');
const panelList = this.areaManager.getVisiblePluginList('Panel');
if (isEmpty(panelList) && isEmpty(tabList)) {
return null;
} else if (tabList.length === 1) {
panelList.unshift(tabList[0]);
tabList.splice(0, 1);
}
const height = `${Math.floor(100 / (panelList.length + (tabList.length > 0 ? 1 : 0)))}%`;
return (
<div className="lowcode-right-area">
{this.renderTabPanels(tabList, height)}
{this.renderPanels(panelList, height)}
</div> </div>
); );
} }

View File

@ -3,24 +3,28 @@
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 48px; height: 50px;
background-color: #ffffff; background-color: $card-background;
border-bottom: 1px solid #e8ebee; border-bottom: 2px solid $color-line1-1;
user-select: none; user-select: none;
.divider { .divider {
max-width: 0; max-width: 0;
margin: 8px 12px; margin: 12px 16px;
height: 30px; height: 24px;
border-right: 1px solid rgba(191, 191, 191, 0.3); border-right: 1px solid $color-line1-2;
} }
.next-col { .next-col {
text-align: center; text-align: center;
} }
.left-area {
padding: 0 16px;
}
.right-area { .right-area {
position: absolute; position: absolute;
right: 12px; right: 12px;
top: 0; top: 0;
padding: 0 16px;
height: 100%; height: 100%;
background: #ffffff; background: $card-background;
} }
} }

View File

@ -16,6 +16,7 @@ export default class TopArea extends PureComponent<TopAreaProps> {
static displayName = 'LowcodeTopArea'; static displayName = 'LowcodeTopArea';
private areaManager: AreaManager; private areaManager: AreaManager;
private editor: Editor; private editor: Editor;
constructor(props) { constructor(props) {
@ -24,10 +25,11 @@ export default class TopArea extends PureComponent<TopAreaProps> {
this.areaManager = new AreaManager(props.editor, 'topArea'); this.areaManager = new AreaManager(props.editor, 'topArea');
} }
componentDidMount() { componentDidMount(): void {
this.editor.on('skeleton.update', this.handleSkeletonUpdate); this.editor.on('skeleton.update', this.handleSkeletonUpdate);
} }
componentWillUnmount() {
componentWillUnmount(): void {
this.editor.off('skeleton.update', this.handleSkeletonUpdate); this.editor.off('skeleton.update', this.handleSkeletonUpdate);
} }
@ -38,15 +40,16 @@ export default class TopArea extends PureComponent<TopAreaProps> {
} }
}; };
renderPluginList = (list: Array<PluginConfig> = []): Array<React.ReactElement> => { renderPluginList = (list: PluginConfig[] = []): React.ReactElement[] => {
return list.map((item, idx) => { return list.map(
(item, idx): React.ReactElement => {
const isDivider = item.type === 'Divider'; const isDivider = item.type === 'Divider';
return ( return (
<Col <Col
className={isDivider ? 'divider' : ''} className={isDivider ? 'divider' : ''}
key={isDivider ? idx : item.pluginKey} key={isDivider ? idx : item.pluginKey}
style={{ style={{
width: (item.props && item.props.width) || 40, width: (item.props && item.props.width) || 36,
flex: 'none' flex: 'none'
}} }}
> >
@ -55,14 +58,15 @@ export default class TopArea extends PureComponent<TopAreaProps> {
)} )}
</Col> </Col>
); );
}); }
);
}; };
render() { render(): React.ReactNode {
const leftList: Array<PluginConfig> = []; const leftList: PluginConfig[] = [];
const rightList: Array<PluginConfig> = []; const rightList: PluginConfig[] = [];
const visiblePluginList = this.areaManager.getVisiblePluginList(); const visiblePluginList = this.areaManager.getVisiblePluginList();
visiblePluginList.forEach(item => { visiblePluginList.forEach((item): void => {
const align = item.props && item.props.align === 'right' ? 'right' : 'left'; const align = item.props && item.props.align === 'right' ? 'right' : 'left';
// 分隔符不允许相邻 // 分隔符不允许相邻
if (item.type === 'Divider') { if (item.type === 'Divider') {