diff --git a/packages/editor/.eslintrc.js b/packages/editor/.eslintrc.js index 1c034b884..4d6be3c66 100644 --- a/packages/editor/.eslintrc.js +++ b/packages/editor/.eslintrc.js @@ -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: { "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 }, }); diff --git a/packages/editor/ice.config.js b/packages/editor/ice.config.js index 71d81b8b1..6843f35bb 100644 --- a/packages/editor/ice.config.js +++ b/packages/editor/ice.config.js @@ -12,7 +12,7 @@ module.exports = { }, plugins: [ ['ice-plugin-fusion', { - themePackage: '@alife/dpl-iceluna', + themePackage: '@alife/theme-lowcode-light', }], ['ice-plugin-moment-locales', { locales: ['zh-cn'], diff --git a/packages/editor/package.json b/packages/editor/package.json index 62c301598..19c333e76 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -4,9 +4,12 @@ "description": "低代码编辑器", "dependencies": { "@ali/iceluna-addon-2": "^1.0.3", + "@ali/iceluna-addon-component-list": "^1.0.10", "@ali/iceluna-sdk": "^1.0.5-beta.26", "@alifd/next": "^1.x", "@alife/dpl-iceluna": "^2.3.2", + "@alife/theme-lowcode-dark": "^0.1.0", + "@alife/theme-lowcode-light": "^0.1.0", "@icedesign/theme": "^1.x", "events": "^3.1.0", "intl-messageformat": "^8.2.1", @@ -21,13 +24,14 @@ }, "devDependencies": { "@ice/spec": "^0.1.1", - "@types/react": "^16.8.3", - "@types/react-dom": "^16.8.2", "@types/debug": "^4.1.5", "@types/events": "^3.0.0", + "@types/react": "^16.8.3", + "@types/react-dom": "^16.8.2", "@types/store": "^2.0.2", "css-modules-typescript-loader": "^2.0.4", "eslint": "^6.0.1", + "husky": "^4.2.3", "ice-plugin-fusion": "^0.1.4", "ice-plugin-moment-locales": "^0.1.0", "ice-scripts": "^2.0.0", diff --git a/packages/editor/public/index.html b/packages/editor/public/index.html index 9c2cc0c1a..04c747a37 100644 --- a/packages/editor/public/index.html +++ b/packages/editor/public/index.html @@ -15,7 +15,7 @@ - +
diff --git a/packages/editor/src/config/assets.js b/packages/editor/src/config/assets.js new file mode 100644 index 000000000..5724f4742 --- /dev/null +++ b/packages/editor/src/config/assets.js @@ -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: {} + } + } + ] + } + ] + } + ] +}; diff --git a/packages/editor/src/config/components.js b/packages/editor/src/config/components.js index 15edcae76..823238113 100644 --- a/packages/editor/src/config/components.js +++ b/packages/editor/src/config/components.js @@ -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 topDialogIcon 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 rightPanel3 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'; @@ -29,5 +30,6 @@ export default { rightPanel1: PluginFactory(rightPanel1), rightPanel2: PluginFactory(rightPanel2), rightPanel3: PluginFactory(rightPanel3), - rightPanel4: PluginFactory(rightPanel4) + rightPanel4: PluginFactory(rightPanel4), + componentList: PluginFactory(componentList) }; diff --git a/packages/editor/src/config/setters.ts b/packages/editor/src/config/setters.ts index 122792ca6..dc3ae6cb0 100644 --- a/packages/editor/src/config/setters.ts +++ b/packages/editor/src/config/setters.ts @@ -4,13 +4,17 @@ import { registerSetter } from '../../../plugin-settings/src'; import { createElement } from 'react'; registerSetter('ClassNameSetter', () => { - return createElement('div', { - className: 'lc-block-setter' - }, '这里是类名绑定'); + return createElement( + 'div', + { + className: 'lc-block-setter' + }, + '这里是类名绑定' + ); }); registerSetter('EventsSetter', Input); -registerSetter('StringSetter', { component: Input, props: { placeholder: "请输入" } }); +registerSetter('StringSetter', { component: Input, props: { placeholder: '请输入' } }); registerSetter('NumberSetter', NumberSetter as any); diff --git a/packages/editor/src/config/skeleton.js b/packages/editor/src/config/skeleton.js index 64826af1c..e633f3a47 100644 --- a/packages/editor/src/config/skeleton.js +++ b/packages/editor/src/config/skeleton.js @@ -1,3 +1,5 @@ +import assets from './assets'; + export default { version: '^1.0.2', theme: { @@ -25,7 +27,8 @@ export default { version: '1.0.0' }, 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', props: { align: 'right', - width: 90 + width: 88 }, config: { package: '@ali/lowcode-plugin-undo-redo', @@ -107,8 +110,8 @@ export default { align: 'right', title: 'icon', icon: 'dengpao', - onClick: function(editor) { - alert('icon addon invoke, current activeKey: ' + editor.activeKey); + onClick(editor) { + alert(`icon addon invoke, current activeKey: ${editor.activeKey}`); } }, config: {}, @@ -116,6 +119,22 @@ export default { } ], 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', type: 'PanelIcon', @@ -150,7 +169,11 @@ export default { props: { align: 'top', title: 'panel2', - icon: 'dengpao' + icon: 'dengpao', + panelProps: { + defaultWidth: 400, + floatable: true + } }, config: { package: '@ali/iceluna-addon-2', @@ -194,8 +217,8 @@ export default { align: 'bottom', title: 'icon', icon: 'dengpao', - onClick: function(editor) { - alert('icon addon invoke, current activeKey: ' + editor.activeKey); + onClick(editor) { + alert(`icon addon invoke, current activeKey: ${editor.activeKey}`); } }, config: {}, @@ -211,7 +234,52 @@ export default { version: '^1.0.0' }, 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: [ { @@ -224,5 +292,51 @@ export default { ] }, 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: 原始的domEvent;tagName: 组件的描述文案;schema: snippet的schema + } + }); + } + } }; diff --git a/packages/editor/src/framework/areaManager.ts b/packages/editor/src/framework/areaManager.ts index ac7f1ed3c..28f24eb0e 100644 --- a/packages/editor/src/framework/areaManager.ts +++ b/packages/editor/src/framework/areaManager.ts @@ -4,26 +4,36 @@ import { clone, deepEqual } from './utils'; export default class AreaManager { private pluginStatus: PluginStatus; + 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.pluginStatus = clone(editor.pluginStatus); } public isPluginStatusUpdate(pluginType?: string): boolean { 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); return isUpdate; } public getVisiblePluginList(pluginType?: string): PluginConfig[] { - const res = this.config.filter(item => { - return !this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible; + const res = this.config.filter((item): boolean => { + 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[] { diff --git a/packages/editor/src/framework/context.ts b/packages/editor/src/framework/context.ts index 78d3ce177..859b79dc4 100644 --- a/packages/editor/src/framework/context.ts +++ b/packages/editor/src/framework/context.ts @@ -1,3 +1,4 @@ import { createContext } from 'react'; + const context = createContext({}); export default context; diff --git a/packages/editor/src/framework/definitions.ts b/packages/editor/src/framework/definitions.ts index 968a1935e..6bc6d2653 100644 --- a/packages/editor/src/framework/definitions.ts +++ b/packages/editor/src/framework/definitions.ts @@ -9,7 +9,7 @@ export interface EditorConfig { shortCuts?: ShortCutsConfig; utils?: UtilsConfig; constants?: ConstantsConfig; - lifeCycles?: lifeCyclesConfig; + lifeCycles?: LifeCyclesConfig; i18n?: I18nConfig; } @@ -38,7 +38,7 @@ export interface ThemeConfig { } export interface PluginsConfig { - [propName: string]: Array; + [propName: string]: PluginConfig[]; } export interface PluginConfig { @@ -63,7 +63,7 @@ export interface PluginConfig { pluginProps?: object; } -export type HooksConfig = Array; +export type HooksConfig = HookConfig[]; export interface HookConfig { message: string; @@ -71,14 +71,14 @@ export interface HookConfig { handler: (editor: Editor, ...args) => void; } -export type ShortCutsConfig = Array; +export type ShortCutsConfig = ShortCutConfig[]; export interface ShortCutConfig { keyboard: string; - handler: (editor: Editor, ev: React.KeyboardEventHandler, keymaster: any) => void; + handler: (editor: Editor, ev: Event, keymaster: any) => void; } -export type UtilsConfig = Array; +export type UtilsConfig = UtilConfig[]; export interface UtilConfig { name: string; @@ -88,7 +88,7 @@ export interface UtilConfig { export type ConstantsConfig = object; -export interface lifeCyclesConfig { +export interface LifeCyclesConfig { init?: (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 interface I18nMessages { - [propName: string]: string; + [key: string]: string; } export interface I18nConfig { @@ -109,27 +109,46 @@ export interface I18nConfig { export type I18nFunction = (key: string, params: any) => string; export interface Utils { - [propName: string]: (...args) => any; + [key: string]: (...args) => any; } -export interface PluginClass extends React.ComponentClass<{ +export interface PluginProps { editor: Editor; - [key: string]: any -}> { - init?: (editor: Editor) => void; - open?: () => any; - close?: () => any; + config: PluginConfig; + i18n?: I18nFunction; + ref?: React.RefObject; + [key: string]: any; } -export interface PluginComponents { - [propName: string]: PluginClass; +export type Plugin = React.ReactNode & { + open?: () => boolean | void | Promise; + close?: () => boolean | void | Promise; +}; + +export type HOCPlugin = React.ReactNode & { + open: () => Promise; + close: () => Promise; +}; + +export interface PluginSet { + [key: string]: HOCPlugin; +} + +export type PluginClass = React.ComponentType & { + init?: (editor: Editor) => void; +}; + +export interface PluginClassSet { + [key: string]: PluginClass; } export interface PluginStatus { - [propName: string]: { - disabled?: boolean; - visible?: boolean; - marked?: boolean; - locked?: boolean; - }; + disabled?: boolean; + visible?: boolean; + marked?: boolean; + locked?: boolean; +} + +export interface PluginStatusSet { + [key: string]: PluginStatus; } diff --git a/packages/editor/src/framework/editor.ts b/packages/editor/src/framework/editor.ts index 9fe46806a..cf1147e2c 100644 --- a/packages/editor/src/framework/editor.ts +++ b/packages/editor/src/framework/editor.ts @@ -1,35 +1,56 @@ import Debug from 'debug'; import EventEmitter from 'events'; 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选项 -const res = /_?debug=(.*?)(&|$)/.exec(location.search); -if (res && res[1]) { +const debugRegRes = /_?debug=(.*?)(&|$)/.exec(location.search); +if (debugRegRes && debugRegRes[1]) { + // eslint-disable-next-line no-underscore-dangle window.__isDebug = true; - store.storage.write('debug', res[1] === 'true' ? '*' : res[1]); + store.storage.write('debug', debugRegRes[1] === 'true' ? '*' : debugRegRes[1]); } else { + // eslint-disable-next-line no-underscore-dangle window.__isDebug = false; store.remove('debug'); } // 重要,用于矫正画布执行new Function的window对象上下文 -window.__newFunc = funContext => { - return new Function(funContext); +// eslint-disable-next-line no-underscore-dangle +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) { - e = e || window.event; +window.onbeforeunload = function(e: Event): string | void { + const ev = e || window.event; // 本地调试不生效 if (location.href.indexOf('localhost') > 0) { return; } const msg = '您确定要离开此页面吗?'; - e.cancelBubble = true; - e.returnValue = msg; + ev.cancelBubble = true; + ev.returnValue = true; if (e.stopPropagation) { e.stopPropagation(); e.preventDefault(); @@ -47,26 +68,40 @@ export interface HooksFuncs { } 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) { instance = new Editor(config, components, utils); } return instance; }; - public pluginStatus: PluginStatus; - public plugins: PluginComponents; + public config: EditorConfig; + + public components: PluginClassSet; + + public utils: Utils; + + public pluginStatus: PluginStatusSet; + + public plugins: PluginSet; + public locale: LocaleType; public emit: (msg: string, ...args) => void; + public on: (msg: string, handler: (...args) => void) => void; + public once: (msg: string, handler: (...args) => void) => void; + public off: (msg: string, handler: (...args) => void) => void; private hooksFuncs: HooksFuncs; - constructor(public config: EditorConfig, public components: PluginComponents, public utils?: Utils) { + constructor(config: EditorConfig, components: PluginClassSet, utils?: Utils) { super(); + this.config = config; + this.components = components; + this.utils = { ...editorUtils, ...utils }; instance = this; this.init(); } @@ -80,21 +115,21 @@ export default class Editor extends EventEmitter { this.initHooks(hooks || []); this.emit('editor.beforeInit'); - const init = (lifeCycles && lifeCycles.init) || (() => {}); + const init = (lifeCycles && lifeCycles.init) || ((): void => {}); // 用户可以通过设置extensions.init自定义初始化流程; return transformToPromise(init(this)) - .then(() => { + .then((): boolean => { // 注册快捷键 registShortCuts(shortCuts, this); this.emit('editor.afterInit'); return true; }) - .catch(err => { + .catch((err): void => { console.error(err); }); } - public destroy() { + public destroy(): void { debug('destroy'); try { const { hooks = [], shortCuts = [], lifeCycles = {} } = this.config; @@ -105,7 +140,6 @@ export default class Editor extends EventEmitter { } } catch (err) { console.warn(err); - return; } } @@ -121,7 +155,7 @@ export default class Editor extends EventEmitter { } this[key] = val; } else if (typeof key === 'object') { - Object.keys(key).forEach(item => { + Object.keys(key).forEach((item): void => { this[item] = key[item]; }); } @@ -131,26 +165,26 @@ export default class Editor extends EventEmitter { if (!Array.isArray(events)) { return; } - events.forEach(event => this.on(event, lisenter)); + events.forEach((event): void => this.on(event, lisenter)); } public batchOnce(events: string[], lisenter: (...args) => void): void { if (!Array.isArray(events)) { return; } - events.forEach(event => this.once(event, lisenter)); + events.forEach((event): void => this.once(event, lisenter)); } public batchOff(events: string[], lisenter: (...args) => void): void { if (!Array.isArray(events)) { return; } - events.forEach(event => this.off(event, lisenter)); + events.forEach((event): void => this.off(event, lisenter)); } // 销毁hooks中的消息监听 - private destroyHooks(hooks: HooksConfig = []) { - hooks.forEach((item, idx) => { + private destroyHooks(hooks: HooksConfig = []): void { + hooks.forEach((item, idx): void => { if (typeof this.hooksFuncs[idx] === 'function') { this.off(item.message, this.hooksFuncs[idx]); } @@ -160,8 +194,8 @@ export default class Editor extends EventEmitter { // 初始化hooks中的消息监听 private initHooks(hooks: HooksConfig = []): void { - this.hooksFuncs = hooks.map(item => { - const func = (...args) => { + this.hooksFuncs = hooks.map((item): ((...arg) => void) => { + const func = (...args): void => { item.handler(this, ...args); }; 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 pluginAreas = Object.keys(plugins); - const res: PluginStatus = {}; - pluginAreas.forEach(area => { - (plugins[area] || []).forEach(plugin => { + const res: PluginStatusSet = {}; + pluginAreas.forEach((area): void => { + (plugins[area] || []).forEach((plugin): void => { if (plugin.type === 'Divider') { return; } diff --git a/packages/editor/src/framework/index.ts b/packages/editor/src/framework/index.ts index be8b55d0a..72af6e859 100644 --- a/packages/editor/src/framework/index.ts +++ b/packages/editor/src/framework/index.ts @@ -1,9 +1,11 @@ import Editor from './editor'; -export { default as PluginFactory } from './pluginFactory'; -export { default as EditorContext } from './context'; import * as editorUtils from './utils'; import * as editorDefinitions from './definitions'; + +export { default as PluginFactory } from './pluginFactory'; +export { default as EditorContext } from './context'; + export default Editor; export const utils = editorUtils; diff --git a/packages/editor/src/framework/pluginFactory.tsx b/packages/editor/src/framework/pluginFactory.tsx index dea2cd602..f32edd9e9 100644 --- a/packages/editor/src/framework/pluginFactory.tsx +++ b/packages/editor/src/framework/pluginFactory.tsx @@ -1,32 +1,24 @@ import React, { createRef, PureComponent } from 'react'; import EditorContext from './context'; -import { I18nFunction, PluginConfig } from './definitions'; +import { I18nFunction, PluginProps, PluginClass, Plugin } from './definitions'; import Editor from './editor'; import { acceptsRef, generateI18n, isEmpty, transformToPromise } from './utils'; -export interface PluginProps { - editor: Editor; - config: PluginConfig; -} - -export interface InjectedPluginProps { - i18n?: I18nFunction; -} - -export default function pluginFactory( - Comp: React.ComponentType -): React.ComponentType { +export default function pluginFactory(Comp: PluginClass): React.ComponentType { class LowcodePlugin extends PureComponent { public static displayName = 'LowcodeEditorPlugin'; - public static defaultProps = { - config: {} - }; + public static contextType = EditorContext; + public static init = Comp.init; - public ref = createRef(); + + public ref: React.RefObject & Plugin; + private editor: Editor; + private pluginKey: string; + private i18n: I18nFunction; constructor(props, context) { @@ -36,6 +28,7 @@ export default function pluginFactory( return; } const { locale, messages, editor } = props; + this.ref = createRef(); // 注册插件 this.editor = editor; this.i18n = generateI18n(locale, messages); @@ -46,7 +39,7 @@ export default function pluginFactory( }); } - public componentWillUnmount() { + public componentWillUnmount(): void { // 销毁插件 if (this.editor && this.editor.plugins) { delete this.editor.plugins[this.pluginKey]; @@ -60,14 +53,14 @@ export default function pluginFactory( return Promise.resolve(); }; - public close = () => { + public close = (): Promise => { if (this.ref && this.ref.close && typeof this.ref.close === 'function') { return transformToPromise(this.ref.close()); } return Promise.resolve(); }; - public render() { + public render(): React.ReactNode { const { config } = this.props; const props = { i18n: this.i18n, diff --git a/packages/editor/src/framework/utils.ts b/packages/editor/src/framework/utils.ts index 794b0c9d3..aced7d318 100644 --- a/packages/editor/src/framework/utils.ts +++ b/packages/editor/src/framework/utils.ts @@ -1,7 +1,5 @@ import IntlMessageFormat from 'intl-messageformat'; import keymaster from 'keymaster'; -import { EditorConfig, I18nFunction, I18nMessages, LocaleType, ShortCutsConfig } from './definitions'; -import Editor from './editor'; import _clone from 'lodash/cloneDeep'; import _debounce from 'lodash/debounce'; @@ -10,6 +8,10 @@ import _deepEqual from 'lodash/isEqualWith'; import _pick from 'lodash/pick'; 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 deepEqual = _deepEqual; export const clone = _clone; @@ -17,7 +19,6 @@ export const isEmpty = _isEmpty; export const throttle = _throttle; export const debounce = _debounce; -import _serialize from 'serialize-javascript'; export const serialize = _serialize; const ENV = { @@ -27,6 +28,17 @@ const ENV = { 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 { action: string; data: { @@ -57,7 +69,7 @@ export function serializeParams(obj: object): string { return ''; } const res: string[] = []; - Object.entries(obj).forEach(([key, val]) => { + Object.entries(obj).forEach(([key, val]): void => { if (val === null || val === undefined || val === '') { return; } @@ -115,8 +127,8 @@ export function getEnv(): string { // 注册快捷键 export function registShortCuts(config: ShortCutsConfig, editor: Editor): void { - (config || []).forEach(item => { - keymaster(item.keyboard, ev => { + (config || []).forEach((item): void => { + keymaster(item.keyboard, (ev: Event): void => { ev.preventDefault(); item.handler(editor, ev, keymaster); }); @@ -125,7 +137,7 @@ export function registShortCuts(config: ShortCutsConfig, editor: Editor): void { // 取消注册快捷 export function unRegistShortCuts(config: ShortCutsConfig): void { - (config || []).forEach(item => { + (config || []).forEach((item): void => { keymaster.unbind(item.keyboard); }); if (window.parent.vscode) { @@ -141,7 +153,7 @@ export function transformToPromise(input: any): Promise<{}> { if (input instanceof Promise) { return input; } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { if (input || input === undefined) { resolve(); } else { @@ -161,7 +173,7 @@ export function transformArrayToMap(arr: T[], key: string, overwrite: boolean return {}; } const res = {}; - arr.forEach(item => { + arr.forEach((item): void => { const curKey = item[key]; if (item[key] === undefined) { return; @@ -187,7 +199,7 @@ export function parseSearch(search: string): Query { const str = search.replace(/^\?/, ''); const paramStr = str.split('&'); const res = {}; - paramStr.forEach(item => { + paramStr.forEach((item): void => { const regRes = item.split('='); if (regRes[0] && regRes[1]) { res[regRes[0]] = decodeURIComponent(regRes[1]); @@ -210,7 +222,7 @@ export function comboEditorConfig(defaultConfig: EditorConfig = {}, customConfig const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard'); const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP']; const i18nConfig = {}; - localeList.forEach(key => { + localeList.forEach((key): void => { i18nConfig[key] = { ...(defaultConfig.i18n && defaultConfig.i18n[key]), ...(i18n && i18n[key]) @@ -248,9 +260,12 @@ export function comboEditorConfig(defaultConfig: EditorConfig = {}, customConfig * 判断当前组件是否能够设置ref * @param {*} Comp 需要判断的组件 */ -export function acceptsRef(Comp: React.ComponentType) { +export function acceptsRef(Comp: React.ReactNode): boolean { const hasSymbol = typeof Symbol === 'function' && Symbol.for; const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0; + if (!Comp || typeof Comp !== 'object' || isEmpty(Comp)) { + return false; + } return ( (Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent) ); diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index f93e9b0e5..22e76df35 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -10,12 +10,9 @@ import constants from './config/constants'; import './config/locale'; import './config/setters'; -import pkg from '../package.json'; import './global.scss'; import './config/theme.scss'; -(window as any).__pkg = pkg; - const ICE_CONTAINER = document.getElementById('ice-container'); if (!ICE_CONTAINER) { @@ -26,7 +23,7 @@ ReactDOM.render( ( + component={(props): React.ReactNode => ( { - static displayName: 'LowcodePluginDesigner'; + displayName: 'LowcodePluginDesigner'; - constructor(props) { - super(props); - } + handleDesignerMount = (designer): void => { + const { editor } = this.props; + editor.set('designer', designer); + editor.emit('designer.ready', designer); + }; - render() { + render(): React.ReactNode { const { editor } = this.props; return ( = (props): React.ReactElement => { return (
-
+
); -} +}; + +export default Logo; diff --git a/packages/editor/src/plugins/undoRedo/index.tsx b/packages/editor/src/plugins/undoRedo/index.tsx index d5af0badf..0def89cfe 100644 --- a/packages/editor/src/plugins/undoRedo/index.tsx +++ b/packages/editor/src/plugins/undoRedo/index.tsx @@ -1,22 +1,75 @@ -import React, { useState } from 'react'; +import React, { PureComponent } from 'react'; import './index.scss'; -import Editor from '../../framework/index'; -import { PluginConfig } from '../../framework/definitions'; +import { PluginProps } from '../../framework/definitions'; import TopIcon from '../../skeleton/components/TopIcon/index'; -export interface PluginProps { - editor: Editor; - config: PluginConfig; +export interface IProps { logo?: string; } -export default function(props: PluginProps) { - const [backEnable, setBackEnable] = useState(true); - const [forwardEnable, setForwardEnable] = useState(true); - return ( -
- - -
- ); +export interface IState { + undoEnable: boolean; + redoEnable: boolean; +} + +export default class UndoRedo extends PureComponent { + 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 ( +
+ + +
+ ); + } } diff --git a/packages/editor/src/skeleton/components/LeftPlugin/index.scss b/packages/editor/src/skeleton/components/LeftPlugin/index.scss index 06b6ef63a..f1e7876d3 100644 --- a/packages/editor/src/skeleton/components/LeftPlugin/index.scss +++ b/packages/editor/src/skeleton/components/LeftPlugin/index.scss @@ -1,34 +1,16 @@ .lowcode-left-plugin { - font-size: 16px; + font-size: 20px; text-align: center; - line-height: 36px; - height: 36px; + line-height: 44px; + height: 44px; position: relative; cursor: pointer; transition: all 0.3s ease; - color: #777; - &.collapse { - height: 40px; - color: #8c8c8c; - border-bottom: 1px solid #bfbfbf; - } + color: $color-text1-3; &.locked { 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 { - background-color: $color-brand1-1; color: $color-brand1-6; &:before { content: attr(data-tooltip); @@ -41,8 +23,8 @@ white-space: nowrap; padding: 6px 8px; border-radius: 4px; - background: rgba(0, 0, 0, 0.75); - color: #fff; + background: $balloon-tooltip-color-bg; + color: $color-text1-3; z-index: 100; } &:after { @@ -52,8 +34,21 @@ left: 40px; top: 15px; border: 5px solid transparent; - border-right-color: rgba(0, 0, 0, 0.75); + border-right: 5px solid $balloon-tooltip-color-bg; 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; + } } diff --git a/packages/editor/src/skeleton/components/LeftPlugin/index.tsx b/packages/editor/src/skeleton/components/LeftPlugin/index.tsx index 6d807548c..eafe894ee 100644 --- a/packages/editor/src/skeleton/components/LeftPlugin/index.tsx +++ b/packages/editor/src/skeleton/components/LeftPlugin/index.tsx @@ -30,7 +30,7 @@ export default class LeftPlugin extends PureComponent {} + onClick: (): void => {} }; constructor(props, context) { @@ -40,7 +40,7 @@ export default class LeftPlugin extends PureComponent { + handleClose = (): void => { const { config, editor } = this.props; const pluginKey = config && config.pluginKey; const plugin = editor.plugins && editor.plugins[pluginKey]; - if (plugin) { - plugin.close().then(() => { + if (plugin && plugin.close) { + plugin.close().then((): void => { this.setState({ dialogVisible: false }); @@ -71,24 +71,26 @@ export default class LeftPlugin extends PureComponent { + handleOpen = (): void => { // todo 对话框类型的插件初始时拿不到插件实例 this.setState({ dialogVisible: true }); }; - handleShow = () => { + handleShow = (): void => { const { disabled, config, onClick, editor } = this.props; const pluginKey = config && config.pluginKey; if (disabled || !pluginKey) return; - //考虑到弹窗情况,延时发送消息 - setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); + // 考虑到弹窗情况,延时发送消息 + setTimeout((): void => editor.emit(`${pluginKey}.plugin.activate`), 0); this.handleOpen(); - onClick && onClick(); + if (onClick) { + onClick(); + } }; - renderIcon = clickCallback => { + renderIcon = (clickCallback): React.ReactNode => { const { active, disabled, marked, locked, onClick, config } = this.props; const { pluginKey, props } = config || {}; const { icon, title } = props || {}; @@ -100,10 +102,11 @@ export default class LeftPlugin extends PureComponent { + onClick={(): void => { if (disabled) return; - //考虑到弹窗情况,延时发送消息 + // 考虑到弹窗情况,延时发送消息 clickCallback && clickCallback(); + onClick && onClick(); }} > @@ -118,7 +121,7 @@ export default class LeftPlugin extends PureComponent { + onClick={(): void => { onClick && onClick.call(null, editor); }} {...pluginProps} @@ -145,30 +148,34 @@ export default class LeftPlugin extends PureComponent - {this.renderIcon(() => { + {this.renderIcon((): void => { onClick && onClick.call(null, editor); })}
); case 'Icon': - return this.renderIcon(() => { + return this.renderIcon((): void => { onClick && onClick.call(null, editor); }); case 'DialogIcon': return ( - {this.renderIcon(() => { + {this.renderIcon((): void => { onClick && onClick.call(null, editor); this.handleOpen(); })} { + onOk={(): void => { editor.emit(`${pluginKey}.dialog.onOk`); this.handleClose(); }} onCancel={this.handleClose} onClose={this.handleClose} title={title} + style={{ + width: 500, + ...(props.dialogProps && props.dialogProps.style) + }} {...(props.dialogProps || {})} visible={dialogVisible} > @@ -179,7 +186,7 @@ export default class LeftPlugin extends PureComponent { + trigger={this.renderIcon((): void => { onClick && onClick.call(null, editor); })} align="r" @@ -190,7 +197,7 @@ export default class LeftPlugin extends PureComponent ); case 'PanelIcon': - return this.renderIcon(() => { + return this.renderIcon((): void => { onClick && onClick.call(null, editor); this.handleOpen(); }); diff --git a/packages/editor/src/skeleton/components/Panel/index.scss b/packages/editor/src/skeleton/components/Panel/index.scss index cd3211ab4..5f38863a9 100644 --- a/packages/editor/src/skeleton/components/Panel/index.scss +++ b/packages/editor/src/skeleton/components/Panel/index.scss @@ -2,12 +2,12 @@ user-select: none; overflow: hidden; position: relative; - background: #ffffff; + background: $card-background; transition: width 0.3s ease; transform: translate3d(0, 0, 0); height: 100%; &.visible { - border-right: 1px solid #bfbfbf; + border-right: 1px solid $color-line1-1; } .drag-area { display: none; @@ -16,7 +16,7 @@ position: absolute; top: 0; bottom: 0; - z-index: 999; + z-index: 99; } &.draggable { .drag-area { @@ -41,12 +41,12 @@ } &.left { &.floatable { - left: 44px; + left: 48px; } } &.right { &.floatable { - right: 44px; + right: 48px; } } } diff --git a/packages/editor/src/skeleton/components/Panel/index.tsx b/packages/editor/src/skeleton/components/Panel/index.tsx index a1b9a9a8e..611877d46 100644 --- a/packages/editor/src/skeleton/components/Panel/index.tsx +++ b/packages/editor/src/skeleton/components/Panel/index.tsx @@ -37,7 +37,7 @@ export default class Panel extends PureComponent { }; } - render() { + render(): React.ReactNode { const { align, draggable, floatable, visible } = this.props; const { width } = this.state; return ( diff --git a/packages/editor/src/skeleton/components/TopIcon/index.scss b/packages/editor/src/skeleton/components/TopIcon/index.scss index 67622fb64..2dd2e3bb7 100644 --- a/packages/editor/src/skeleton/components/TopIcon/index.scss +++ b/packages/editor/src/skeleton/components/TopIcon/index.scss @@ -1,21 +1,27 @@ -.next-btn.next-large.lowcode-top-btn { +.lowcode-top-icon { + display: inline-block; width: 44px; - height: 44px; - padding: 0; - margin: 2px -2px; - text-align: center; - border-radius: 8px; - border: 1px solid transparent; - color: #777; + font-size: 20px; + line-height: 48px; + color: $color-text1-3; + position: relative; &.disabled { cursor: not-allowed; color: $color-text1-1; + &:hover { + color: $color-text1-1; + } + } + &.active { + color: $color-brand1-9; + &:hover { + color: $color-brand1-6; + } } &.locked { color: red !important; } &:hover { - background-color: $color-brand1-1; color: $color-brand1-6; &:before { content: attr(data-tooltip); @@ -31,8 +37,8 @@ white-space: nowrap; padding: 6px 8px; border-radius: 4px; - background: rgba(0, 0, 0, 0.75); - color: #fff; + background: $balloon-tooltip-color-bg; + color: $color-text1-3; z-index: 100; } &:after { @@ -43,7 +49,7 @@ transform: translate(-50%, 0); bottom: -5px; border: 5px solid transparent; - border-bottom-color: rgba(0, 0, 0, 0.75); + border-bottom-color: $balloon-tooltip-color-bg; opacity: 1; visibility: visible; z-index: 100; @@ -51,7 +57,7 @@ } i.next-icon { &:before { - font-size: 17px; + font-size: 16px; } margin-right: 0; line-height: 18px; diff --git a/packages/editor/src/skeleton/components/TopIcon/index.tsx b/packages/editor/src/skeleton/components/TopIcon/index.tsx index 64d27a6d7..2c14d8d33 100644 --- a/packages/editor/src/skeleton/components/TopIcon/index.tsx +++ b/packages/editor/src/skeleton/components/TopIcon/index.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import { Icon, Button } from '@alifd/next'; +import { Icon } from '@alifd/next'; import './index.scss'; @@ -13,13 +13,13 @@ export interface TopIconProps { locked?: boolean; marked?: boolean; onClick?: () => void; - showTitle?: boolean; style?: React.CSSProperties; title?: string; } export default class TopIcon extends PureComponent { static displayName = 'LowcodeTopIcon'; + static defaultProps = { active: false, className: '', @@ -27,20 +27,16 @@ export default class TopIcon extends PureComponent { icon: '', id: '', locked: false, - onClick: () => {}, - showTitle: false, + onClick: (): void => {}, style: {}, title: '' }; - render() { - const { active, disabled, icon, locked, title, className, id, style, showTitle, onClick } = this.props; + render(): React.ReactNode { + const { active, disabled, icon, locked, title, className, id, style, onClick } = this.props; return ( - + +
); } } diff --git a/packages/editor/src/skeleton/components/TopPlugin/index.tsx b/packages/editor/src/skeleton/components/TopPlugin/index.tsx index 6c4f280d0..70c653f4f 100644 --- a/packages/editor/src/skeleton/components/TopPlugin/index.tsx +++ b/packages/editor/src/skeleton/components/TopPlugin/index.tsx @@ -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 TopIcon from '../TopIcon'; import './index.scss'; import { PluginConfig, PluginClass } from '../../../framework/definitions'; @@ -31,7 +31,7 @@ export default class TopPlugin extends PureComponent {} + onClick: (): void => {} }; constructor(props, context) { @@ -41,7 +41,7 @@ export default class TopPlugin extends PureComponent { + handleShow = (): void => { const { disabled, config, onClick, editor } = this.props; const pluginKey = config && config.pluginKey; if (disabled || !pluginKey) return; - //考虑到弹窗情况,延时发送消息 - setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); + // 考虑到弹窗情况,延时发送消息 + setTimeout((): void => editor.emit(`${pluginKey}.plugin.activate`), 0); this.handleOpen(); onClick && onClick(); }; - handleClose = () => { + handleClose = (): void => { const { config, editor } = this.props; const pluginKey = config && config.pluginKey; const plugin = editor.plugins && editor.plugins[pluginKey]; - if (plugin) { - plugin.close().then(() => { + if (plugin && plugin.close) { + plugin.close().then((): void => { this.setState({ dialogVisible: false }); @@ -82,29 +82,29 @@ export default class TopPlugin extends PureComponent { + handleOpen = (): void => { // todo dialog类型的插件初始时拿不动插件实例 this.setState({ dialogVisible: true }); }; - renderIcon = clickCallback => { + renderIcon = (clickCallback): React.ReactNode => { const { active, disabled, marked, locked, config, onClick, editor } = this.props; const { pluginKey, props } = config || {}; const { icon, title } = props || {}; const node = ( { + onClick={(): void => { if (disabled) return; - //考虑到弹窗情况,延时发送消息 - setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); + // 考虑到弹窗情况,延时发送消息 + setTimeout((): void => editor.emit(`${pluginKey}.plugin.activate`), 0); clickCallback && clickCallback(); onClick && onClick(); }} @@ -113,8 +113,8 @@ export default class TopPlugin extends PureComponent{node} : node; }; - render() { - const { active, marked, locked, disabled, config, editor, pluginClass: Comp } = this.props; + render(): React.ReactNode { + const { active, marked, locked, disabled, config, editor, pluginClass: Comp, style } = this.props; const { pluginKey, pluginProps, props, type } = config || {}; const { onClick, title } = props || {}; const { dialogVisible } = this.state; @@ -127,7 +127,7 @@ export default class TopPlugin extends PureComponent { + onClick={(): void => { onClick && onClick.call(null, editor); }} {...pluginProps} @@ -139,30 +139,34 @@ export default class TopPlugin extends PureComponent - {this.renderIcon(() => { + {this.renderIcon((): void => { onClick && onClick.call(null, editor); })} ); case 'Icon': - return this.renderIcon(() => { + return this.renderIcon((): void => { onClick && onClick.call(null, editor); }); case 'DialogIcon': return ( - {this.renderIcon(() => { + {this.renderIcon((): void => { onClick && onClick.call(null, editor); this.handleOpen(); })} { + onOk={(): void => { editor.emit(`${pluginKey}.dialog.onOk`); this.handleClose(); }} onCancel={this.handleClose} onClose={this.handleClose} title={title} + style={{ + width: 500, + ...(props.dialogProps && props.dialogProps.style) + }} {...props.dialogProps} visible={dialogVisible} > @@ -173,7 +177,7 @@ export default class TopPlugin extends PureComponent { + trigger={this.renderIcon((): void => { onClick && onClick.call(null, editor); })} triggerType={['click', 'hover']} diff --git a/packages/editor/src/skeleton/global.scss b/packages/editor/src/skeleton/global.scss index d825900bc..72f7b8525 100644 --- a/packages/editor/src/skeleton/global.scss +++ b/packages/editor/src/skeleton/global.scss @@ -6,7 +6,9 @@ body { * { box-sizing: border-box; } + color: $color-text1-3; } + .next-loading { .next-loading-wrap { height: 100%; @@ -15,7 +17,7 @@ body { .lowcode-editor { .lowcode-main-content { position: absolute; - top: 48px; + top: 50px; left: 0; right: 0; bottom: 0; diff --git a/packages/editor/src/skeleton/index.tsx b/packages/editor/src/skeleton/index.tsx index b721c36bb..c30a0fc88 100644 --- a/packages/editor/src/skeleton/index.tsx +++ b/packages/editor/src/skeleton/index.tsx @@ -1,10 +1,9 @@ import React, { PureComponent } from 'react'; -import { HashRouter as Router, Route } from 'react-router-dom'; import { Loading, ConfigProvider } from '@alifd/next'; 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 defaultConfig from './config/skeleton'; @@ -19,8 +18,17 @@ import './global.scss'; let renderIdx = 0; +declare global { + interface Window { + __ctx: { + editor: Editor; + appHelper: Editor; + }; + } +} + export interface SkeletonProps { - components: PluginComponents; + components: PluginClassSet; config: EditorConfig; history: object; location: object; @@ -29,15 +37,15 @@ export interface SkeletonProps { } export interface SkeletonState { - initReady: boolean; - skeletonKey: string; + initReady?: boolean; + skeletonKey?: string; __hasError?: boolean; } export default class Skeleton extends PureComponent { static displayName = 'LowcodeEditorSkeleton'; - static getDerivedStateFromError() { + static getDerivedStateFromError(): SkeletonState { return { __hasError: true }; @@ -56,11 +64,11 @@ export default class Skeleton extends PureComponent { + editor.once('editor.reset', (): void => { this.setState({ initReady: false }); @@ -85,22 +95,23 @@ export default class Skeleton extends PureComponent { + this.editor.init().then((): void => { this.setState( { initReady: true, - //刷新IDE时生成新的skeletonKey保证插件生命周期重新执行 + // 刷新IDE时生成新的skeletonKey保证插件生命周期重新执行 skeletonKey: isReset ? `skeleton${++renderIdx}` : this.state.skeletonKey }, - () => { + (): void => { editor.emit('editor.ready'); + editor.emit('ide.ready'); isReset && editor.emit('ide.afterReset'); } ); }); }; - render() { + render(): React.ReactNode { const { initReady, skeletonKey, __hasError } = this.state; const { location, history, match } = this.props; if (__hasError || !this.editor) { diff --git a/packages/editor/src/skeleton/layouts/CenterArea/index.tsx b/packages/editor/src/skeleton/layouts/CenterArea/index.tsx index 1389a00e6..cf27d66db 100644 --- a/packages/editor/src/skeleton/layouts/CenterArea/index.tsx +++ b/packages/editor/src/skeleton/layouts/CenterArea/index.tsx @@ -12,6 +12,7 @@ export default class CenterArea extends PureComponent { static displayName = 'LowcodeCenterArea'; private editor: Editor; + private areaManager: AreaManager; constructor(props) { @@ -20,10 +21,11 @@ export default class CenterArea extends PureComponent { this.areaManager = new AreaManager(this.editor, 'centerArea'); } - componentDidMount() { + componentDidMount(): void { this.editor.on('skeleton.update', this.handleSkeletonUpdate); } - componentWillUnmount() { + + componentWillUnmount(): void { this.editor.off('skeleton.update', this.handleSkeletonUpdate); } @@ -34,14 +36,16 @@ export default class CenterArea extends PureComponent { } }; - render() { + render(): React.ReactNode { const visiblePluginList = this.areaManager.getVisiblePluginList(); return (
- {visiblePluginList.map(item => { - const Comp = this.editor.components[item.pluginKey]; - return ; - })} + {visiblePluginList.map( + (item): React.ReactNode => { + const Comp = this.editor.components[item.pluginKey]; + return ; + } + )}
); } diff --git a/packages/editor/src/skeleton/layouts/LeftArea/index.scss b/packages/editor/src/skeleton/layouts/LeftArea/index.scss index dac1b6b0a..8c69f42fc 100644 --- a/packages/editor/src/skeleton/layouts/LeftArea/index.scss +++ b/packages/editor/src/skeleton/layouts/LeftArea/index.scss @@ -1,21 +1,23 @@ .lowcode-left-area-nav { - width: 48px; + width: 50px; height: 100%; - background: #ffffff; - border-right: 1px solid #e8ebee; + background-color: $card-background; + border-right: 2px solid $color-line1-1; position: relative; .top-area { position: absolute; top: 0; width: 100%; - background: #ffffff; + padding: 12px 0; + background-color: $card-background; max-height: 100%; } .bottom-area { position: absolute; - bottom: 20px; + bottom: 0; width: 100%; - background: #ffffff; + padding: 12px 0; + background-color: $card-background; max-height: calc(100% - 20px); } } diff --git a/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx b/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx index a5ed5cd8f..cd749073a 100644 --- a/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx +++ b/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx @@ -4,6 +4,7 @@ import './index.scss'; import Editor from '../../../framework/editor'; import { PluginConfig } from '../../../framework/definitions'; import AreaManager from '../../../framework/areaManager'; +import { isEmpty } from '../../../framework/utils'; export interface LeftAreaNavProps { editor: Editor; @@ -17,8 +18,10 @@ export default class LeftAreaNav extends PureComponent item.type === 'IconPanel'); const defaultKey = (visiblePanelPluginList[0] && visiblePanelPluginList[0].pluginKey) || 'componentAttr'; this.handlePluginChange(defaultKey); } - componentWillUnmount() { + + componentWillUnmount(): void { this.editor.off('skeleton.update', this.handleSkeletonUpdate); this.editor.off('leftNav.change', this.handlePluginChange); } @@ -57,48 +61,46 @@ export default class LeftAreaNav extends PureComponent { + nextPlugin.open().then((): void => { this.updateActiveKey(key); }); } } else if (activeKey === key) { if (prePlugin) { - prePlugin.close().then(() => { + prePlugin.close().then((): void => { this.updateActiveKey('none'); }); } - } else { + } else if (prePlugin) { // 先关后开 - if (prePlugin) { - prePlugin.close().then(() => { - if (nextPlugin) { - nextPlugin.open().then(() => { - this.updateActiveKey(key); - }); - } - }); - } + prePlugin.close().then((): void => { + if (nextPlugin) { + nextPlugin.open().then((): void => { + this.updateActiveKey(key); + }); + } + }); } }; - handleCollapseClick = (): void => { - const { activeKey } = this.state; - if (activeKey === 'none') { - const plugin = this.editor.plugins[this.cacheActiveKey]; - if (plugin) { - plugin.open().then(() => { - this.updateActiveKey(this.cacheActiveKey); - }); - } - } else { - const plugin = this.editor.plugins[activeKey]; - if (plugin) { - plugin.close().then(() => { - this.updateActiveKey('none'); - }); - } - } - }; + // handleCollapseClick = (): void => { + // const { activeKey } = this.state; + // if (activeKey === 'none') { + // const plugin = this.editor.plugins[this.cacheActiveKey]; + // if (plugin) { + // plugin.open().then(() => { + // this.updateActiveKey(this.cacheActiveKey); + // }); + // } + // } else { + // const plugin = this.editor.plugins[activeKey]; + // if (plugin) { + // plugin.close().then(() => { + // this.updateActiveKey('none'); + // }); + // } + // } + // }; handlePluginClick = (item: PluginConfig): void => { if (item.type === 'PanelIcon') { @@ -107,38 +109,42 @@ export default class LeftAreaNav extends PureComponent { - if (key === 'none') { - this.cacheActiveKey = this.state.activeKey; - } + // if (key === 'none') { + // this.cacheActiveKey = this.state.activeKey; + // } this.editor.set('leftNav', key); this.setState({ activeKey: key }); this.editor.emit('leftPanel.show', key); }; - renderPluginList = (list: Array = []): Array => { + renderPluginList = (list: PluginConfig[] = []): React.ReactElement[] => { const { activeKey } = this.state; - return list.map((item, idx) => { - const pluginStatus = this.editor.pluginStatus[item.pluginKey]; - return ( - this.handlePluginClick(item)} - active={activeKey === item.pluginKey} - {...pluginStatus} - /> - ); - }); + return list.map( + (item): React.ReactElement => { + const pluginStatus = this.editor.pluginStatus[item.pluginKey]; + return ( + this.handlePluginClick(item)} + active={activeKey === item.pluginKey} + {...pluginStatus} + /> + ); + } + ); }; - render() { - const { activeKey } = this.state; - const topList: Array = []; - const bottomList: Array = []; + render(): React.ReactNode { + const topList: PluginConfig[] = []; + const bottomList: PluginConfig[] = []; 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'; if (align === 'bottom') { bottomList.push(item); @@ -151,7 +157,7 @@ export default class LeftAreaNav extends PureComponent
{this.renderPluginList(bottomList)}
- + /> */} {this.renderPluginList(topList)}
diff --git a/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx b/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx index 8c03247fd..31f2c5343 100644 --- a/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx +++ b/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx @@ -16,6 +16,7 @@ export default class LeftAreaPanel extends PureComponent - {list.map((item, idx) => { - const Comp = this.editor.components[item.pluginKey]; - return ( - - - - ); - })} + {list.map( + (item): React.ReactElement => { + const Comp = this.editor.components[item.pluginKey]; + return ( + + + + ); + } + )}
); } diff --git a/packages/editor/src/skeleton/layouts/RightArea/index.scss b/packages/editor/src/skeleton/layouts/RightArea/index.scss index fd05f5644..7373933b0 100644 --- a/packages/editor/src/skeleton/layouts/RightArea/index.scss +++ b/packages/editor/src/skeleton/layouts/RightArea/index.scss @@ -1,52 +1,39 @@ .lowcode-right-area { - width: 300px; + width: 262px; height: 100%; - background-color: #ffffff; - border-left: 1px solid #e8ebee; - .right-plugin-title { - &.locked { - color: red !important; - } - &.active { - color: $color-brand1-9 !important; - } - &.disabled { - cursor: not-allowed; - color: $color-text1-1; - } + background-color: $card-background; + border-left: 2px solid $color-line1-1; + + .right-panel { + overflow: auto; + // border-top: 2px solid $color-line1-1; } //tab定义 - .next-tabs-wrapped.right-tabs { - display: flex; - flex-direction: column; - margin-top: -1px; - .next-tabs-bar { - z-index: 1; - } + .right-tabs.next-tabs { .next-tabs-nav { - display: block; - .next-tabs-tab { - &:first-child { - border-left: none; - } - font-size: 14px; + width: 100%; + .next-tabs-tab-inner { + padding-left: 0; + padding-right: 0; + } + .right-plugin-title { text-align: center; - border-right: none !important; - margin-right: 0 !important; - width: 25%; + &.locked { + color: red !important; + } &.active { - background: none; - border-bottom-color: #f7f7f7 !important; + color: $color-brand1-9 !important; + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + .next-icon { + line-height: 15px; + margin-right: 2px; } } } } - .next-tabs-content { - flex: 1; - .next-tabs-tabpane.active { - height: 100%; - overflow-y: auto; - } - } } diff --git a/packages/editor/src/skeleton/layouts/RightArea/index.tsx b/packages/editor/src/skeleton/layouts/RightArea/index.tsx index 00fd7fbe6..64c44af50 100644 --- a/packages/editor/src/skeleton/layouts/RightArea/index.tsx +++ b/packages/editor/src/skeleton/layouts/RightArea/index.tsx @@ -4,6 +4,7 @@ import './index.scss'; import Editor from '../../../framework/editor'; import AreaManager from '../../../framework/areaManager'; import { PluginConfig } from '../../../framework/definitions'; +import { isEmpty } from '../../../framework/utils'; export interface RightAreaProps { editor: Editor; @@ -17,6 +18,7 @@ export default class RightArea extends PureComponent { + currentPlugin.close().then((): void => { this.setState( { activeKey: '' }, - () => { + (): void => { const visiblePluginList = this.areaManager.getVisiblePluginList(); const firstPlugin = visiblePluginList && visiblePluginList[0]; if (firstPlugin) { @@ -72,12 +75,12 @@ export default class RightArea extends PureComponent { const activeKey = this.state.activeKey; const plugins = this.editor.plugins || {}; - const openPlugin = () => { + const openPlugin = (): void => { if (!plugins[key]) { console.error(`plugin ${key} has not regist in the editor`); return; } - plugins[key].open().then(() => { + plugins[key].open().then((): void => { this.editor.set('rightNav', key); this.setState({ activeKey: key @@ -86,7 +89,7 @@ export default class RightArea extends PureComponent { + plugins[activeKey].close().then((): void => { openPlugin(); }); } else { @@ -102,21 +105,11 @@ export default class RightArea extends PureComponent (
- {!!icon && ( - - )} + {!!icon && } {title}
); @@ -126,45 +119,68 @@ export default class RightArea extends PureComponent; - } - const Comp = this.editor.components[pane.pluginKey]; - return ( -
- -
- ); + renderTabPanels = (list: PluginConfig[], height: string): React.ReactNode => { + if (isEmpty(list)) { + return null; } return ( -
- - {visiblePluginList.map((item, idx) => { + + {list.map( + (item): React.ReactElement => { const Comp = this.editor.components[item.pluginKey]; return ( ); - })} - + } + )} + + ); + }; + + renderPanels = (list: PluginConfig[], height: string): React.ReactNode => { + return list.map( + (item): React.ReactElement => { + const Comp = this.editor.components[item.pluginKey]; + return ( +
+ +
+ ); + } + ); + }; + + 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 ( +
+ {this.renderTabPanels(tabList, height)} + {this.renderPanels(panelList, height)}
); } diff --git a/packages/editor/src/skeleton/layouts/TopArea/index.scss b/packages/editor/src/skeleton/layouts/TopArea/index.scss index 98af961da..a22456894 100644 --- a/packages/editor/src/skeleton/layouts/TopArea/index.scss +++ b/packages/editor/src/skeleton/layouts/TopArea/index.scss @@ -3,24 +3,28 @@ top: 0; left: 0; width: 100%; - height: 48px; - background-color: #ffffff; - border-bottom: 1px solid #e8ebee; + height: 50px; + background-color: $card-background; + border-bottom: 2px solid $color-line1-1; user-select: none; .divider { max-width: 0; - margin: 8px 12px; - height: 30px; - border-right: 1px solid rgba(191, 191, 191, 0.3); + margin: 12px 16px; + height: 24px; + border-right: 1px solid $color-line1-2; } .next-col { text-align: center; } + .left-area { + padding: 0 16px; + } .right-area { position: absolute; right: 12px; top: 0; + padding: 0 16px; height: 100%; - background: #ffffff; + background: $card-background; } } diff --git a/packages/editor/src/skeleton/layouts/TopArea/index.tsx b/packages/editor/src/skeleton/layouts/TopArea/index.tsx index df8828a9d..d6b33453c 100644 --- a/packages/editor/src/skeleton/layouts/TopArea/index.tsx +++ b/packages/editor/src/skeleton/layouts/TopArea/index.tsx @@ -16,6 +16,7 @@ export default class TopArea extends PureComponent { static displayName = 'LowcodeTopArea'; private areaManager: AreaManager; + private editor: Editor; constructor(props) { @@ -24,10 +25,11 @@ export default class TopArea extends PureComponent { this.areaManager = new AreaManager(props.editor, 'topArea'); } - componentDidMount() { + componentDidMount(): void { this.editor.on('skeleton.update', this.handleSkeletonUpdate); } - componentWillUnmount() { + + componentWillUnmount(): void { this.editor.off('skeleton.update', this.handleSkeletonUpdate); } @@ -38,31 +40,33 @@ export default class TopArea extends PureComponent { } }; - renderPluginList = (list: Array = []): Array => { - return list.map((item, idx) => { - const isDivider = item.type === 'Divider'; - return ( - - {!isDivider && ( - - )} - - ); - }); + renderPluginList = (list: PluginConfig[] = []): React.ReactElement[] => { + return list.map( + (item, idx): React.ReactElement => { + const isDivider = item.type === 'Divider'; + return ( + + {!isDivider && ( + + )} + + ); + } + ); }; - render() { - const leftList: Array = []; - const rightList: Array = []; + render(): React.ReactNode { + const leftList: PluginConfig[] = []; + const rightList: PluginConfig[] = []; const visiblePluginList = this.areaManager.getVisiblePluginList(); - visiblePluginList.forEach(item => { + visiblePluginList.forEach((item): void => { const align = item.props && item.props.align === 'right' ? 'right' : 'left'; // 分隔符不允许相邻 if (item.type === 'Divider') {