diff --git a/packages/editor-framework/.eslintignore b/packages/editor-framework/.eslintignore
new file mode 100644
index 000000000..f6ee039b9
--- /dev/null
+++ b/packages/editor-framework/.eslintignore
@@ -0,0 +1,6 @@
+# 忽略目录
+build/
+node_modules/
+**/*-min.js
+**/*.min.js
+coverage/
diff --git a/packages/editor-framework/.eslintrc.js b/packages/editor-framework/.eslintrc.js
new file mode 100644
index 000000000..ebda54735
--- /dev/null
+++ b/packages/editor-framework/.eslintrc.js
@@ -0,0 +1,5 @@
+const { eslint, deepmerge } = require('@ice/spec');
+
+module.exports = deepmerge(eslint, {
+ rules: {},
+});
diff --git a/packages/editor-framework/.gitignore b/packages/editor-framework/.gitignore
new file mode 100644
index 000000000..84d2d58ff
--- /dev/null
+++ b/packages/editor-framework/.gitignore
@@ -0,0 +1,22 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+node_modules/
+
+# production
+build/
+dist/
+tmp/
+lib/
+
+# misc
+.idea/
+.happypack
+.DS_Store
+*.swp
+*.dia~
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+CHANGELOG.md
diff --git a/packages/editor-framework/.prettierrc b/packages/editor-framework/.prettierrc
new file mode 100644
index 000000000..8748c5ed3
--- /dev/null
+++ b/packages/editor-framework/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "printWidth": 120,
+ "trailingComma": "all"
+}
diff --git a/packages/editor-framework/README copy.md b/packages/editor-framework/README copy.md
new file mode 100644
index 000000000..6e9e79f5b
--- /dev/null
+++ b/packages/editor-framework/README copy.md
@@ -0,0 +1,11 @@
+# demo component
+
+t-s-demo
+
+intro component
+
+## API
+
+| 参数名 | 说明 | 必填 | 类型 | 默认值 | 备注 |
+| ------ | ---- | ---- | ---- | ------ | ---- |
+| | | | | | |
diff --git a/packages/editor-framework/README.md b/packages/editor-framework/README.md
index eb99c606a..8a6fb13f0 100644
--- a/packages/editor-framework/README.md
+++ b/packages/editor-framework/README.md
@@ -1 +1 @@
-编辑器框架
+## todo
diff --git a/packages/editor-framework/build.json b/packages/editor-framework/build.json
new file mode 100644
index 000000000..77627cdf9
--- /dev/null
+++ b/packages/editor-framework/build.json
@@ -0,0 +1,9 @@
+{
+ "plugins": [
+ "build-plugin-component",
+ "build-plugin-fusion",
+ ["build-plugin-moment-locales", {
+ "locales": ["zh-cn"]
+ }]
+ ]
+}
\ No newline at end of file
diff --git a/packages/editor-framework/demo/usage.md b/packages/editor-framework/demo/usage.md
new file mode 100644
index 000000000..9f19eae0b
--- /dev/null
+++ b/packages/editor-framework/demo/usage.md
@@ -0,0 +1,24 @@
+---
+title: Simple Usage
+order: 1
+---
+
+本 Demo 演示一行文字的用法。
+
+````jsx
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+
+class App extends Component {
+ render() {
+ return (
+
+
+ );
+ }
+}
+
+ReactDOM.render((
+
+), mountNode);
+````
diff --git a/packages/editor-framework/es/context.d.ts b/packages/editor-framework/es/context.d.ts
new file mode 100644
index 000000000..7836d2580
--- /dev/null
+++ b/packages/editor-framework/es/context.d.ts
@@ -0,0 +1,3 @@
+///
+declare const context: import("react").Context<{}>;
+export default context;
diff --git a/packages/editor-framework/es/context.js b/packages/editor-framework/es/context.js
new file mode 100644
index 000000000..450ae72fb
--- /dev/null
+++ b/packages/editor-framework/es/context.js
@@ -0,0 +1,3 @@
+import { createContext } from 'react';
+var context = createContext({});
+export default context;
\ No newline at end of file
diff --git a/packages/editor-framework/package.json b/packages/editor-framework/package.json
new file mode 100644
index 000000000..3d1465113
--- /dev/null
+++ b/packages/editor-framework/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "@ali/lowcode-engine-editor",
+ "version": "0.0.1",
+ "description": "alibaba lowcode editor core",
+ "files": [
+ "demo/",
+ "es/",
+ "lib/",
+ "build/"
+ ],
+ "main": "lib/index.js",
+ "module": "es/index.js",
+ "stylePath": "style.js",
+ "scripts": {
+ "start": "build-scripts start",
+ "build": "build-scripts build",
+ "prepublishOnly": "npm run prettier && npm run build",
+ "lint": "eslint --cache --ext .js,.jsx ./",
+ "prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,ejs,less,css,scss,json}\" "
+ },
+ "keywords": [
+ "lowcode",
+ "editor"
+ ],
+ "author": "xiayang.xy",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "events": "^3.1.0",
+ "intl-messageformat": "^7.8.4",
+ "lodash": "^4.17.15",
+ "prop-types": "^15.5.8",
+ "store": "^2.0.12"
+ },
+ "devDependencies": {
+ "@alib/build-scripts": "^0.1.3",
+ "@alifd/next": "1.x",
+ "@ice/spec": "^0.1.1",
+ "@types/lodash": "^4.14.149",
+ "@types/react": "^16.9.13",
+ "@types/react-dom": "^16.9.4",
+ "build-plugin-component": "^0.2.7-1",
+ "build-plugin-fusion": "^0.1.0",
+ "build-plugin-moment-locales": "^0.1.0",
+ "eslint": "^6.0.1",
+ "prettier": "^1.19.1",
+ "react": "^16.8.0",
+ "react-dom": "^16.8.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0",
+ "@alifd/next": "1.x"
+ },
+ "license": "MIT",
+ "homepage": "https://unpkg.com/editor-framework@0.0.1/build/index.html"
+}
diff --git a/packages/editor-framework/src/context.ts b/packages/editor-framework/src/context.ts
new file mode 100644
index 000000000..78d3ce177
--- /dev/null
+++ b/packages/editor-framework/src/context.ts
@@ -0,0 +1,3 @@
+import { createContext } from 'react';
+const context = createContext({});
+export default context;
diff --git a/packages/editor-framework/src/definitions.ts b/packages/editor-framework/src/definitions.ts
new file mode 100644
index 000000000..87dd5dca2
--- /dev/null
+++ b/packages/editor-framework/src/definitions.ts
@@ -0,0 +1,48 @@
+
+export interface EditorConfig {
+
+};
+
+export interface NpmConfig {
+ version: string,
+ package: string,
+ main?: string,
+ exportName?: string,
+ subName?: string,
+ destructuring?: boolean
+};
+
+export interface SkeletonConfig {
+ config: NpmConfig,
+ props?: object,
+ handler?: (EditorConfig) => EditorConfig
+};
+
+export interface FusionTheme {
+ package: string,
+ version: string
+};
+
+export interface ThemeConfig {
+ fusion?: FusionTheme
+}
+
+export interface PluginsConfig {
+ [key]: Array
+};
+
+export interface PluginConfig {
+ pluginKey: string,
+ type: string,
+ props: object,
+ config: NpmConfig,
+ pluginProps: object
+};
+
+export type HooksConfig = Array;
+
+export interface HookConfig {
+
+};
+
+
diff --git a/packages/editor-framework/src/editor.ts b/packages/editor-framework/src/editor.ts
new file mode 100644
index 000000000..b3328df2d
--- /dev/null
+++ b/packages/editor-framework/src/editor.ts
@@ -0,0 +1,186 @@
+import EventEmitter from 'events';
+import Debug from 'debug';
+import store from 'store';
+
+import {
+ unRegistShortCuts,
+ registShortCuts,
+ transformToPromise,
+ generateI18n
+} from './utils';
+
+// 根据url参数设置debug选项
+const res = /_?debug=(.*?)(&|$)/.exec(location.search);
+if (res && res[1]) {
+ window.__isDebug = true;
+ store.storage.write('debug', res[1] === 'true' ? '*' : res[1]);
+} else {
+ window.__isDebug = false;
+ store.remove('debug');
+}
+
+//重要,用于矫正画布执行new Function的window对象上下文
+window.__newFunc = funContext => {
+ return new Function(funContext);
+};
+
+//关闭浏览器前提醒,只有产生过交互才会生效
+window.onbeforeunload = function(e) {
+ e = e || window.event;
+ // 本地调试不生效
+ if (location.href.indexOf('localhost') > 0) return;
+ var msg = '您确定要离开此页面吗?';
+ e.cancelBubble = true;
+ e.returnValue = msg;
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ return msg;
+};
+
+
+let instance = null;
+const debug = Debug('editor');
+EventEmitter.defaultMaxListeners = 100;
+
+export interface editor {
+
+};
+
+export default class Editor extends EventEmitter {
+ static getInstance = () => {
+ if (!instance) {
+ instance = new Editor();
+ }
+ return instance;
+ };
+
+ constructor(config) {
+ super();
+ instance = this;
+ Object.assign(this, config);
+ this.init();
+ }
+
+ init() {
+ const {
+ hooks,
+ shortCuts,
+ lifeCycles
+ } = this.config || {};
+ this.destroy();
+ this.locale = store.get('lowcode-editor-locale') || 'zh-CN';
+ this.messages = this.messagesSet[this.locale];
+ this.i18n = generateI18n(this.locale, this.messages);
+ this.pluginStatus = this.initPluginStatus();
+ this.initHooks(hooks, appHelper);
+
+ appHelper.emit('editor.beforeInit');
+ const init = lifeCycles && lifeCycles.init || () => {};
+ // 用户可以通过设置extensions.init自定义初始化流程;
+ transformToPromise(init(this))
+ .then(() => {
+ // 注册快捷键
+ registShortCuts(shortCuts, this);
+ this.emit('editor.afterInit');
+ })
+ .catch(err => {
+ console.warn(err);
+ });
+ }
+
+ destroy() {
+ try {
+ const {
+ hooks = [],
+ shortCuts = [],
+ lifeCycles = {}
+ } = this.config;
+ unRegistShortCuts(shortCuts);
+ this.destroyHooks(hooks);
+ lifeCycles.destroy && lifeCycles.destroy();
+ } catch (err) {
+ console.warn(err);
+ return;
+ }
+ }
+
+ get(key:string):any {
+ return this[key];
+ }
+
+ set(key:string|object, val:any):void {
+ if (typeof key === 'string') {
+ if (['init', 'destroy', 'get', 'set', 'batchOn', 'batchOff', 'batchOnce'].includes(key)) {
+ console.warning('init, destroy, get, set, batchOn, batchOff, batchOnce is private attribute');
+ return;
+ }
+ this[key] = val;
+ } else if (typeof key === 'object') {
+ Object.keys(key).forEach(item => {
+ this[item] = key[item];
+ });
+ }
+ }
+
+ batchOn(events:Array, lisenter:function):void {
+ if (!Array.isArray(events)) return;
+ events.forEach(event => this.on(event, lisenter));
+ }
+
+ batchOnce(events:Array, lisenter:function):void {
+ if (!Array.isArray(events)) return;
+ events.forEach(event => this.once(event, lisenter));
+ }
+
+ batchOff(events:Array, lisenter:function):void {
+ if (!Array.isArray(events)) return;
+ events.forEach(event => this.off(event, lisenter));
+ }
+
+ //销毁hooks中的消息监听
+ private destroyHooks(hooks = []) {
+ hooks.forEach((item, idx) => {
+ if (typeof this.__hooksFuncs[idx] === 'function') {
+ this.appHelper.off(item.message, this.__hooksFuncs[idx]);
+ }
+ });
+ delete this.__hooksFuncs;
+ };
+
+ //初始化hooks中的消息监听
+ private initHooks(hooks = []) {
+ this.__hooksFuncs = hooks.map(item => {
+ const func = (...args) => {
+ item.handler(this, ...args);
+ };
+ this[item.type](item.message, func);
+ return func;
+ });
+ };
+
+
+ private initPluginStatus () {
+ const {plugins = {}} = this.config;
+ const pluginAreas = Object.keys(plugins);
+ const res = {};
+ pluginAreas.forEach(area => {
+ (plugins[area] || []).forEach(plugin => {
+ if (plugin.type === 'Divider') return;
+ const { visible, disabled, dotted } = plugin.props || {};
+ res[plugin.pluginKey] = {
+ visible: typeof visible === 'boolean' ? visible : true,
+ disabled: typeof disabled === 'boolean' ? disabled : false,
+ dotted: typeof dotted === 'boolean' ? dotted : false
+ };
+ const pluginClass = this.props.components[skeletonUtils.generateAddonCompName(addon.addonKey)];
+ // 判断如果编辑器插件有init静态方法,则在此执行init方法
+ if (pluginClass && pluginClass.init) {
+ pluginClass.init(this);
+ }
+ });
+ });
+ return res;
+ };
+}
diff --git a/packages/editor-framework/src/index.ts b/packages/editor-framework/src/index.ts
new file mode 100644
index 000000000..aac18d138
--- /dev/null
+++ b/packages/editor-framework/src/index.ts
@@ -0,0 +1,4 @@
+import Editor from './editor';
+
+
+export default Editor;
\ No newline at end of file
diff --git a/packages/editor-framework/src/plugin.ts b/packages/editor-framework/src/plugin.ts
new file mode 100644
index 000000000..33d81cc48
--- /dev/null
+++ b/packages/editor-framework/src/plugin.ts
@@ -0,0 +1,129 @@
+import { PureComponent } from 'react';
+
+import EditorContext from './context';
+import { isEmpty, generateI18n, goldlog } from './utils';
+
+export interface pluginProps {
+ config: object,
+ editor: object,
+ locale: string,
+ messages: object
+}
+
+export default function plugin(Comp) {
+
+ class Plugin extends PureComponent {
+ static displayName = 'lowcode-editor-plugin';
+ static defaultProps = {
+ config: {}
+ };
+ static contextType = EditorContext;
+ constructor(props, context) {
+ super(props, context);
+ if (isEmpty(props.config) || !props.config.pluginKey) {
+ console.warn('lowcode editor plugin has wrong config');
+ return;
+ }
+
+ const { locale, messages, editor } = props;
+ // 注册插件
+ this.editor = editor;
+ this.i18n = generateI18n(locale, messages);
+ this.pluginKey = props.config.pluginKey;
+ editor.plugins = editor.plugins || {};
+ editor.plugins[this.pluginKey] = this;
+ }
+
+ componentWillUnmount() {
+ // 销毁插件
+ if (this.editor && this.editor.plugins) {
+ delete this.editor.plugins[this.pluginKey];
+ }
+ }
+
+ render() {
+ const {
+ config
+ } = this.props;
+ return
+ }
+ }
+
+ return Plugin;
+}
+
+
+
+export class Plugin extends PureComponent {
+ static displayName = 'lowcode-editor-plugin';
+ static defaultProps = {
+ config: {}
+ };
+ static contextType = EditorContext;
+ constructor(props, context) {
+ super(props, context);
+ if (isEmpty(props.config) || !props.config.addonKey) {
+ console.warn('luna addon has wrong config');
+ return;
+ }
+
+
+ const { locale, messages, editor } = props;
+ // 注册插件
+ this.editor = editor;
+ this.i18n = generateI18n(locale, messages);
+ this.pluginKey = props.config.pluginKey;
+ editor.plugins = editor.plugins || {};
+ editor.plugins[this.pluginKey] = this;
+ }
+
+ async componentWillUnmount() {
+ // 销毁插件
+ if (this.editor && this.editor.plugins) {
+ delete this.editor.plugins[this.pluginKey];
+ }
+ }
+
+ open = () => {
+ return true;
+ };
+
+ close = () => {
+ return true;
+ };
+
+ goldlog = (goKey:string, params:any) => {
+ const { pluginKey, config = {} } = this.props.config || {};
+ goldlog(
+ goKey,
+ {
+ pluginKey,
+ package: config.package,
+ version: config.version,
+ ...this.editor.logParams,
+ ...params
+ },
+ 'addon'
+ );
+ };
+
+ get utils() {
+ return this.editor.utils;
+ }
+
+ get constants() {
+ return this.editor.constants;
+ }
+
+ get history() {
+ return this.editor.history;
+ }
+
+ get location() {
+ return this.editor.location;
+ }
+
+ render() {
+ return null;
+ }
+}
diff --git a/packages/editor-framework/src/utils.ts b/packages/editor-framework/src/utils.ts
new file mode 100644
index 000000000..d1c5eaf2b
--- /dev/null
+++ b/packages/editor-framework/src/utils.ts
@@ -0,0 +1,242 @@
+
+import IntlMessageFormat from 'intl-messageformat';
+import _isEmpty from 'lodash/isEmpty';
+
+export const isEmpty = _isEmpty;
+
+/**
+ * 用于构造国际化字符串处理函数
+ * @param {*} locale 国际化标识,例如 zh-CN、en-US
+ * @param {*} messages 国际化语言包
+ */
+export function generateI18n(locale = 'zh-CN', messages = {}) {
+ return (key, values = {}) => {
+ if (!messages || !messages[key]) return '';
+ const formater = new IntlMessageFormat(messages[key], locale);
+ return formater.format(values);
+ };
+}
+
+/**
+ * 序列化参数
+ * @param {*} obj 参数
+ */
+export function serializeParams(obj:object):string {
+ if (typeof obj !== 'object') return '';
+
+ const res:Array = [];
+ Object.entries(obj).forEach(([key, val]) => {
+ if (val === null || val === undefined || val === '') return;
+ if (typeof val === 'object') {
+ res.push(`${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}`);
+ } else {
+ res.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
+ }
+ });
+ return res.join('&');
+}
+
+/**
+ * 黄金令箭埋点
+ * @param {String} gmKey 为黄金令箭业务类型
+ * @param {Object} params 参数
+ * @param {String} logKey 属性串
+ */
+export function goldlog(gmKey, params = {}, logKey = 'other') {
+ const sendIDEMessage = window.sendIDEMessage || window.parent.sendIDEMessage;
+ const goKey = serializeParams({
+ sdkVersion: pkg.version,
+ env: getEnv(),
+ ...params
+ });
+ if (sendIDEMessage) {
+ sendIDEMessage({
+ action: 'goldlog',
+ data: {
+ logKey: `/iceluna.core.${logKey}`,
+ gmKey,
+ goKey
+ }
+ });
+ }
+ window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST');
+}
+
+/**
+ * 获取当前编辑器环境
+ */
+export function getEnv() {
+ const userAgent = navigator.userAgent;
+ const isVscode = /Electron\//.test(userAgent);
+ if (isVscode) return ENV.VSCODE;
+ const isTheia = window.is_theia === true;
+ if (isTheia) return ENV.WEBIDE;
+ return ENV.WEB;
+}
+
+// 注册快捷键
+export function registShortCuts(config, editor) {
+ const keyboardFilter = (keymaster.filter = event => {
+ let eTarget = event.target || event.srcElement;
+ let tagName = eTarget.tagName;
+ let isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
+ let isContenteditable = !!eTarget.getAttribute('contenteditable');
+ if (isInput || isContenteditable) {
+ if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); //禁止触发chrome原生的页面保存或查找
+ return false;
+ } else {
+ return true;
+ }
+ });
+
+ const ideMessage = appHelper.utils && appHelper.utils.ideMessage;
+
+ //复制
+ if (!document.copyListener) {
+ document.copyListener = e => {
+ if (!keyboardFilter(e) || appHelper.isCopying) return;
+ const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey];
+ if (!schema || !isSchema(schema)) return;
+ appHelper.isCopying = true;
+ const schemaStr = serialize(transformSchemaToPure(schema), {
+ unsafe: true
+ });
+ setClipboardData(schemaStr)
+ .then(() => {
+ ideMessage && ideMessage('success', '当前内容已复制到剪贴板,请使用快捷键Command+v进行粘贴');
+ appHelper.emit('schema.copy', schemaStr, schema);
+ appHelper.isCopying = false;
+ })
+ .catch(errMsg => {
+ ideMessage && ideMessage('error', errMsg);
+ appHelper.isCopying = false;
+ });
+ };
+ document.addEventListener('copy', document.copyListener);
+ if (window.parent.vscode) {
+ keymaster('command+c', document.copyListener);
+ }
+ }
+
+ //粘贴
+ if (!document.pasteListener) {
+ const doPaste = (e, text) => {
+ if (!keyboardFilter(e) || appHelper.isPasting) return;
+ const schemaHelper = appHelper.schemaHelper;
+ let targetKey = appHelper.activeKey;
+ let direction = 'after';
+ const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
+ if (!targetKey || topKey === targetKey) {
+ const schemaHelper = appHelper.schemaHelper;
+ const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
+ if (!topKey) return;
+ targetKey = topKey;
+ direction = 'in';
+ }
+ appHelper.isPasting = true;
+ const schema = parseObj(text);
+ if (!isSchema(schema)) {
+ appHelper.emit('illegalSchema.paste', text);
+ // ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!');
+ console.warn('paste schema illegal');
+ appHelper.isPasting = false;
+ return;
+ }
+ appHelper.emit('material.add', {
+ schema,
+ targetKey,
+ direction
+ });
+ appHelper.isPasting = false;
+ appHelper.emit('schema.paste', schema);
+ };
+ document.pasteListener = e => {
+ const clipboardData = e.clipboardData || window.clipboardData;
+ const text = clipboardData && clipboardData.getData('text');
+ doPaste(e, text);
+ };
+ document.addEventListener('paste', document.pasteListener);
+ if (window.parent.vscode) {
+ keymaster('command+v', e => {
+ const sendIDEMessage = window.parent.sendIDEMessage;
+ sendIDEMessage &&
+ sendIDEMessage({
+ action: 'readClipboard'
+ })
+ .then(text => {
+ doPaste(e, text);
+ })
+ .catch(err => {
+ console.warn(err);
+ });
+ });
+ }
+ }
+
+ (config || []).forEach(item => {
+ keymaster(item.keyboard, ev => {
+ ev.preventDefault();
+ item.handler(ev, appHelper, keymaster);
+ });
+ });
+}
+
+// 取消注册快捷
+export function unRegistShortCuts(config) {
+ (config || []).forEach(item => {
+ keymaster.unbind(item.keyboard);
+ });
+ if (window.parent.vscode) {
+ keymaster.unbind('command+c');
+ keymaster.unbind('command+v');
+ }
+ if (document.copyListener) {
+ document.removeEventListener('copy', document.copyListener);
+ delete document.copyListener;
+ }
+ if (document.pasteListener) {
+ document.removeEventListener('paste', document.pasteListener);
+ delete document.pasteListener;
+ }
+}
+
+// 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve
+export function transformToPromise(input) {
+ if (input instanceof Promise) return input;
+ return new Promise((resolve, reject) => {
+ if (input || input === undefined) {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+}
+
+export function comboEditorConfig(defaultConfig, customConfig) {
+ const { ideConfig = {}, utils = {} } = this.props;
+ const comboShortCuts = () => {
+ const defaultShortCuts = defaultIdeConfig.shortCuts;
+ const shortCuts = ideConfig.shortCuts || [];
+ const configMap = skeletonUtils.transformArrayToMap(defaultShortCuts, 'keyboard');
+ (shortCuts || []).forEach(item => {
+ configMap[item.keyboard] = item;
+ });
+ return Object.keys(configMap).map(key => configMap[key]);
+ };
+ return {
+ ...ideConfig,
+ utils: {
+ ...skeletonUtils,
+ ...utils
+ },
+ constants: {
+ ...defaultIdeConfig.constants,
+ ...ideConfig.constants
+ },
+ extensions: {
+ ...defaultIdeConfig.extensions,
+ ...ideConfig.extensions
+ },
+ shortCuts: comboShortCuts()
+ };
+}
\ No newline at end of file
diff --git a/packages/editor-framework/tsconfig.json b/packages/editor-framework/tsconfig.json
new file mode 100644
index 000000000..a511d68ba
--- /dev/null
+++ b/packages/editor-framework/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compileOnSave": false,
+ "buildOnSave": false,
+ "compilerOptions": {
+ "outDir": "build",
+ "module": "esnext",
+ "target": "es6",
+ "jsx": "react",
+ "moduleResolution": "node",
+ "lib": ["es6", "dom"],
+ "sourceMap": true,
+ "allowJs": true,
+ "noUnusedLocals": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/*.ts", "src/*.tsx"],
+ "exclude": ["node_modules", "build", "public"]
+}
diff --git a/packages/editor-skeleton/.editorconfig b/packages/editor-skeleton/.editorconfig
new file mode 100644
index 000000000..5760be583
--- /dev/null
+++ b/packages/editor-skeleton/.editorconfig
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/packages/editor-skeleton/.eslintignore b/packages/editor-skeleton/.eslintignore
new file mode 100644
index 000000000..3b437e614
--- /dev/null
+++ b/packages/editor-skeleton/.eslintignore
@@ -0,0 +1,11 @@
+# 忽略目录
+build/
+tests/
+demo/
+
+# node 覆盖率文件
+coverage/
+
+# 忽略文件
+**/*-min.js
+**/*.min.js
diff --git a/packages/editor-skeleton/.eslintrc.js b/packages/editor-skeleton/.eslintrc.js
new file mode 100644
index 000000000..18ae6baa7
--- /dev/null
+++ b/packages/editor-skeleton/.eslintrc.js
@@ -0,0 +1,7 @@
+const { eslint, deepmerge } = require('@ice/spec');
+
+module.exports = deepmerge(eslint, {
+ rules: {
+ "global-require": 0,
+ },
+});
diff --git a/packages/editor-skeleton/.gitignore b/packages/editor-skeleton/.gitignore
new file mode 100644
index 000000000..c590da5b0
--- /dev/null
+++ b/packages/editor-skeleton/.gitignore
@@ -0,0 +1,20 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# production
+/build
+/dist
+
+# misc
+.idea/
+.happypack
+.DS_Store
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# ignore d.ts auto generated by css-modules-typescript-loader
+*.module.scss.d.ts
\ No newline at end of file
diff --git a/packages/editor-skeleton/.stylelintignore b/packages/editor-skeleton/.stylelintignore
new file mode 100644
index 000000000..82af6f60d
--- /dev/null
+++ b/packages/editor-skeleton/.stylelintignore
@@ -0,0 +1,7 @@
+# 忽略目录
+build/
+tests/
+demo/
+
+# node 覆盖率文件
+coverage/
diff --git a/packages/editor-skeleton/.stylelintrc.js b/packages/editor-skeleton/.stylelintrc.js
new file mode 100644
index 000000000..eeb605b33
--- /dev/null
+++ b/packages/editor-skeleton/.stylelintrc.js
@@ -0,0 +1,3 @@
+const { stylelint } = require('@ice/spec');
+
+module.exports = stylelint;
diff --git a/packages/editor-skeleton/README.md b/packages/editor-skeleton/README.md
new file mode 100644
index 000000000..8a6fb13f0
--- /dev/null
+++ b/packages/editor-skeleton/README.md
@@ -0,0 +1 @@
+## todo
diff --git a/packages/editor-skeleton/abc.json b/packages/editor-skeleton/abc.json
new file mode 100644
index 000000000..dce1f92ed
--- /dev/null
+++ b/packages/editor-skeleton/abc.json
@@ -0,0 +1,4 @@
+{
+ "type": "ice-scripts",
+ "builder": "@ali/builder-ice-scripts"
+}
diff --git a/packages/editor-skeleton/build.json b/packages/editor-skeleton/build.json
new file mode 100644
index 000000000..77627cdf9
--- /dev/null
+++ b/packages/editor-skeleton/build.json
@@ -0,0 +1,9 @@
+{
+ "plugins": [
+ "build-plugin-component",
+ "build-plugin-fusion",
+ ["build-plugin-moment-locales", {
+ "locales": ["zh-cn"]
+ }]
+ ]
+}
\ No newline at end of file
diff --git a/packages/editor-skeleton/demo/usage.md b/packages/editor-skeleton/demo/usage.md
new file mode 100644
index 000000000..9f19eae0b
--- /dev/null
+++ b/packages/editor-skeleton/demo/usage.md
@@ -0,0 +1,24 @@
+---
+title: Simple Usage
+order: 1
+---
+
+本 Demo 演示一行文字的用法。
+
+````jsx
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+
+class App extends Component {
+ render() {
+ return (
+
+
+ );
+ }
+}
+
+ReactDOM.render((
+
+), mountNode);
+````
diff --git a/packages/editor-skeleton/es/components/LeftIcon/index.js b/packages/editor-skeleton/es/components/LeftIcon/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor-skeleton/es/components/LeftIcon/index.scss b/packages/editor-skeleton/es/components/LeftIcon/index.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor-skeleton/es/components/LeftPlugin/index.d.ts b/packages/editor-skeleton/es/components/LeftPlugin/index.d.ts
new file mode 100644
index 000000000..312fc40aa
--- /dev/null
+++ b/packages/editor-skeleton/es/components/LeftPlugin/index.d.ts
@@ -0,0 +1,30 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class LeftAddon extends PureComponent {
+ static displayName: string;
+ static propTypes: {
+ active: any;
+ config: any;
+ disabled: any;
+ dotted: any;
+ locked: any;
+ onClick: any;
+ };
+ static defaultProps: {
+ active: boolean;
+ config: {};
+ disabled: boolean;
+ dotted: boolean;
+ locked: boolean;
+ onClick: () => void;
+ };
+ static contextType: any;
+ constructor(props: any, context: any);
+ componentDidMount(): void;
+ componentWillUnmount(): void;
+ handleClose: () => void;
+ handleOpen: () => void;
+ handleShow: () => void;
+ renderIcon: (clickCallback: any) => JSX.Element;
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/components/LeftPlugin/index.js b/packages/editor-skeleton/es/components/LeftPlugin/index.js
new file mode 100644
index 000000000..517104928
--- /dev/null
+++ b/packages/editor-skeleton/es/components/LeftPlugin/index.js
@@ -0,0 +1,259 @@
+import _extends from "@babel/runtime/helpers/extends";
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import AppContext from '@ali/iceluna-sdk/lib/context/appContext';
+import { Balloon, Dialog, Icon, Badge } from '@alife/next';
+import './index.scss';
+
+var LeftAddon = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(LeftAddon, _PureComponent);
+
+ function LeftAddon(_props, context) {
+ var _this;
+
+ _this = _PureComponent.call(this, _props, context) || this;
+
+ _this.handleClose = function () {
+ var addonKey = _this.props.config && _this.props.config.addonKey;
+ var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey];
+
+ if (currentAddon) {
+ _this.utils.transformToPromise(currentAddon.close()).then(function () {
+ _this.setState({
+ dialogVisible: false
+ });
+ });
+ }
+ };
+
+ _this.handleOpen = function () {
+ // todo 对话框类型的插件初始时拿不到插件实例
+ _this.setState({
+ dialogVisible: true
+ });
+ };
+
+ _this.handleShow = function () {
+ var _this$props = _this.props,
+ disabled = _this$props.disabled,
+ config = _this$props.config,
+ onClick = _this$props.onClick;
+ var addonKey = config && config.addonKey;
+ if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息
+
+ setTimeout(function () {
+ return _this.appHelper.emit(addonKey + ".addon.activate");
+ }, 0);
+
+ _this.handleOpen();
+
+ onClick && onClick();
+ };
+
+ _this.renderIcon = function (clickCallback) {
+ var _this$props2 = _this.props,
+ active = _this$props2.active,
+ disabled = _this$props2.disabled,
+ dotted = _this$props2.dotted,
+ locked = _this$props2.locked,
+ _onClick = _this$props2.onClick,
+ config = _this$props2.config;
+
+ var _ref = config || {},
+ addonKey = _ref.addonKey,
+ props = _ref.props;
+
+ var _ref2 = props || {},
+ icon = _ref2.icon,
+ title = _ref2.title;
+
+ return React.createElement("div", {
+ className: classNames('luna-left-addon', addonKey, {
+ active: active,
+ disabled: disabled,
+ locked: locked
+ }),
+ "data-tooltip": title,
+ onClick: function onClick() {
+ if (disabled) return; //考虑到弹窗情况,延时发送消息
+
+ clickCallback && clickCallback();
+ _onClick && _onClick();
+ }
+ }, dotted ? React.createElement(Badge, {
+ dot: true
+ }, React.createElement(Icon, {
+ type: icon,
+ size: "small"
+ })) : React.createElement(Icon, {
+ type: icon,
+ size: "small"
+ }));
+ };
+
+ _this.state = {
+ dialogVisible: false
+ };
+ _this.appHelper = context.appHelper;
+ _this.utils = _this.appHelper.utils;
+ _this.constants = _this.appHelper.constants;
+ return _this;
+ }
+
+ var _proto = LeftAddon.prototype;
+
+ _proto.componentDidMount = function componentDidMount() {
+ var config = this.props.config;
+ var addonKey = config && config.addonKey;
+ var appHelper = this.appHelper;
+
+ if (appHelper && addonKey) {
+ appHelper.on(addonKey + ".dialog.show", this.handleShow);
+ appHelper.on(addonKey + ".dialog.close", this.handleClose);
+ }
+ };
+
+ _proto.componentWillUnmount = function componentWillUnmount() {
+ var config = this.props.config;
+ var appHelper = this.appHelper;
+ var addonKey = config && config.addonKey;
+
+ if (appHelper && addonKey) {
+ appHelper.off(addonKey + ".dialog.show", this.handleShow);
+ appHelper.off(addonKey + ".dialog.close", this.handleClose);
+ }
+ };
+
+ _proto.render = function render() {
+ var _this2 = this;
+
+ var _this$props3 = this.props,
+ dotted = _this$props3.dotted,
+ locked = _this$props3.locked,
+ active = _this$props3.active,
+ disabled = _this$props3.disabled,
+ config = _this$props3.config;
+
+ var _ref3 = config || {},
+ addonKey = _ref3.addonKey,
+ props = _ref3.props,
+ type = _ref3.type,
+ addonProps = _ref3.addonProps;
+
+ var _ref4 = props || {},
+ _onClick2 = _ref4.onClick,
+ title = _ref4.title;
+
+ var dialogVisible = this.state.dialogVisible;
+ var _this$context = this.context,
+ appHelper = _this$context.appHelper,
+ components = _this$context.components;
+ if (!addonKey || !type || !props) return null;
+ var componentName = appHelper.utils.generateAddonCompName(addonKey);
+ var localeProps = {};
+ var locale = appHelper.locale,
+ messages = appHelper.messages;
+
+ if (locale) {
+ localeProps.locale = locale;
+ }
+
+ if (messages && messages[componentName]) {
+ localeProps.messages = messages[componentName];
+ }
+
+ var AddonComp = components && components[componentName];
+ var node = AddonComp && React.createElement(AddonComp, _extends({
+ active: active,
+ locked: locked,
+ disabled: disabled,
+ config: config,
+ onClick: function onClick() {
+ _onClick2 && _onClick2.call(null, appHelper);
+ }
+ }, localeProps, addonProps || {})) || null;
+
+ switch (type) {
+ case 'LinkIcon':
+ return React.createElement("a", props.linkProps || {}, this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, appHelper);
+ }));
+
+ case 'Icon':
+ return this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, appHelper);
+ });
+
+ case 'DialogIcon':
+ return React.createElement(Fragment, null, this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, appHelper);
+
+ _this2.handleOpen();
+ }), React.createElement(Dialog, _extends({
+ onOk: function onOk() {
+ appHelper.emit(addonKey + ".dialog.onOk");
+
+ _this2.handleClose();
+ },
+ onCancel: this.handleClose,
+ onClose: this.handleClose,
+ title: title
+ }, props.dialogProps || {}, {
+ visible: dialogVisible
+ }), node));
+
+ case 'BalloonIcon':
+ return React.createElement(Balloon, _extends({
+ trigger: this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, appHelper);
+ }),
+ align: "r",
+ triggerType: ['click', 'hover']
+ }, props.balloonProps || {}), node);
+
+ case 'PanelIcon':
+ return this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, appHelper);
+
+ _this2.handleOpen();
+ });
+
+ case 'Custom':
+ return dotted ? React.createElement(Badge, {
+ dot: true
+ }, node) : node;
+
+ default:
+ return null;
+ }
+ };
+
+ return LeftAddon;
+}(PureComponent);
+
+LeftAddon.displayName = 'LunaLeftAddon';
+LeftAddon.propTypes = {
+ active: PropTypes.bool,
+ config: PropTypes.shape({
+ addonKey: PropTypes.string,
+ addonProps: PropTypes.object,
+ props: PropTypes.object,
+ type: PropTypes.oneOf(['DialogIcon', 'BalloonIcon', 'PanelIcon', 'LinkIcon', 'Icon', 'Custom'])
+ }),
+ disabled: PropTypes.bool,
+ dotted: PropTypes.bool,
+ locked: PropTypes.bool,
+ onClick: PropTypes.func
+};
+LeftAddon.defaultProps = {
+ active: false,
+ config: {},
+ disabled: false,
+ dotted: false,
+ locked: false,
+ onClick: function onClick() {}
+};
+LeftAddon.contextType = AppContext;
+export { LeftAddon as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/components/LeftPlugin/index.scss b/packages/editor-skeleton/es/components/LeftPlugin/index.scss
new file mode 100644
index 000000000..9c6922129
--- /dev/null
+++ b/packages/editor-skeleton/es/components/LeftPlugin/index.scss
@@ -0,0 +1,59 @@
+.luna-left-addon {
+ font-size: 16px;
+ text-align: center;
+ line-height: 36px;
+ height: 36px;
+ position: relative;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ color: #777;
+ &.collapse {
+ height: 40px;
+ color: #8c8c8c;
+ border-bottom: 1px solid #bfbfbf;
+ }
+ &.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);
+ display: block;
+ position: absolute;
+ left: 50px;
+ top: 5px;
+ line-height: 18px;
+ font-size: 12px;
+ white-space: nowrap;
+ padding: 6px 8px;
+ border-radius: 4px;
+ background: rgba(0, 0, 0, 0.75);
+ color: #fff;
+ z-index: 100;
+ }
+ &:after {
+ content: '';
+ display: block;
+ position: absolute;
+ left: 40px;
+ top: 15px;
+ border: 5px solid transparent;
+ border-right-color: rgba(0, 0, 0, 0.75);
+ z-index: 100;
+ }
+ }
+}
diff --git a/packages/editor-skeleton/es/components/Panel/index.js b/packages/editor-skeleton/es/components/Panel/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor-skeleton/es/components/TopIcon/index.d.ts b/packages/editor-skeleton/es/components/TopIcon/index.d.ts
new file mode 100644
index 000000000..2bb6854b4
--- /dev/null
+++ b/packages/editor-skeleton/es/components/TopIcon/index.d.ts
@@ -0,0 +1,30 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class TopIcon extends PureComponent {
+ static displayName: string;
+ static propTypes: {
+ active: any;
+ className: any;
+ disabled: any;
+ icon: any;
+ id: any;
+ locked: any;
+ onClick: any;
+ showTitle: any;
+ style: any;
+ title: any;
+ };
+ static defaultProps: {
+ active: boolean;
+ className: string;
+ disabled: boolean;
+ icon: string;
+ id: string;
+ locked: boolean;
+ onClick: () => void;
+ showTitle: boolean;
+ style: {};
+ title: string;
+ };
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/components/TopIcon/index.js b/packages/editor-skeleton/es/components/TopIcon/index.js
new file mode 100644
index 000000000..bfa50a96b
--- /dev/null
+++ b/packages/editor-skeleton/es/components/TopIcon/index.js
@@ -0,0 +1,76 @@
+import _Button from "@alifd/next/es/button";
+import _Icon from "@alifd/next/es/icon";
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import './index.scss';
+
+var TopIcon = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(TopIcon, _PureComponent);
+
+ function TopIcon() {
+ return _PureComponent.apply(this, arguments) || this;
+ }
+
+ var _proto = TopIcon.prototype;
+
+ _proto.render = function render() {
+ var _this$props = this.props,
+ active = _this$props.active,
+ disabled = _this$props.disabled,
+ icon = _this$props.icon,
+ locked = _this$props.locked,
+ title = _this$props.title,
+ className = _this$props.className,
+ id = _this$props.id,
+ style = _this$props.style,
+ showTitle = _this$props.showTitle,
+ onClick = _this$props.onClick;
+ return React.createElement(_Button, {
+ type: "normal",
+ size: "large",
+ text: true,
+ className: classNames('lowcode-top-btn', className, {
+ active: active,
+ disabled: disabled,
+ locked: locked
+ }),
+ id: id,
+ style: style,
+ onClick: disabled ? null : onClick
+ }, React.createElement("div", null, React.createElement(_Icon, {
+ size: "large",
+ type: icon
+ }), showTitle && React.createElement("span", null, title)));
+ };
+
+ return TopIcon;
+}(PureComponent);
+
+TopIcon.displayName = 'TopIcon';
+TopIcon.propTypes = {
+ active: PropTypes.bool,
+ className: PropTypes.string,
+ disabled: PropTypes.bool,
+ icon: PropTypes.string,
+ id: PropTypes.string,
+ locked: PropTypes.bool,
+ onClick: PropTypes.func,
+ showTitle: PropTypes.bool,
+ style: PropTypes.object,
+ title: PropTypes.string
+};
+TopIcon.defaultProps = {
+ active: false,
+ className: '',
+ disabled: false,
+ icon: '',
+ id: '',
+ locked: false,
+ onClick: function onClick() {},
+ showTitle: false,
+ style: {},
+ title: ''
+};
+export { TopIcon as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/components/TopIcon/index.scss b/packages/editor-skeleton/es/components/TopIcon/index.scss
new file mode 100644
index 000000000..1cb3bdfdf
--- /dev/null
+++ b/packages/editor-skeleton/es/components/TopIcon/index.scss
@@ -0,0 +1,32 @@
+.next-btn.next-large.lowcode-top-btn {
+ width: 44px;
+ height: 44px;
+ padding: 0;
+ margin: 4px -2px;
+ text-align: center;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ color: #777;
+ &.disabled {
+ cursor: not-allowed;
+ color: $color-text1-1;
+ }
+ &.locked {
+ color: red !important;
+ }
+ i.next-icon {
+ &:before {
+ font-size: 17px;
+ }
+ margin-right: 0;
+ line-height: 18px;
+ }
+ span {
+ display: block;
+ margin: 0px -5px 0;
+ line-height: 16px;
+ text-align: center;
+ font-size: 12px;
+ transform: scale(0.8);
+ }
+}
diff --git a/packages/editor-skeleton/es/components/TopPlugin/index.d.ts b/packages/editor-skeleton/es/components/TopPlugin/index.d.ts
new file mode 100644
index 000000000..dc09c377e
--- /dev/null
+++ b/packages/editor-skeleton/es/components/TopPlugin/index.d.ts
@@ -0,0 +1,21 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class TopPlugin extends PureComponent {
+ static displayName: string;
+ static defaultProps: {
+ active: boolean;
+ config: {};
+ disabled: boolean;
+ dotted: boolean;
+ locked: boolean;
+ onClick: () => void;
+ };
+ constructor(props: any, context: any);
+ componentDidMount(): void;
+ componentWillUnmount(): void;
+ handleShow: () => void;
+ handleClose: () => void;
+ handleOpen: () => void;
+ renderIcon: (clickCallback: any) => JSX.Element;
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/components/TopPlugin/index.js b/packages/editor-skeleton/es/components/TopPlugin/index.js
new file mode 100644
index 000000000..e881bb6c1
--- /dev/null
+++ b/packages/editor-skeleton/es/components/TopPlugin/index.js
@@ -0,0 +1,213 @@
+import _Balloon from "@alifd/next/es/balloon";
+import _Dialog from "@alifd/next/es/dialog";
+import _extends from "@babel/runtime/helpers/extends";
+import _Badge from "@alifd/next/es/badge";
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent, Fragment } from 'react';
+import TopIcon from '../TopIcon';
+import './index.scss';
+
+var TopPlugin = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(TopPlugin, _PureComponent);
+
+ function TopPlugin(_props, context) {
+ var _this;
+
+ _this = _PureComponent.call(this, _props, context) || this;
+
+ _this.handleShow = function () {
+ var _this$props = _this.props,
+ disabled = _this$props.disabled,
+ config = _this$props.config,
+ onClick = _this$props.onClick;
+ var addonKey = config && config.addonKey;
+ if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息
+
+ setTimeout(function () {
+ return _this.appHelper.emit(addonKey + ".addon.activate");
+ }, 0);
+
+ _this.handleOpen();
+
+ onClick && onClick();
+ };
+
+ _this.handleClose = function () {
+ var addonKey = _this.props.config && _this.props.config.addonKey;
+ var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey];
+
+ if (currentAddon) {
+ _this.utils.transformToPromise(currentAddon.close()).then(function () {
+ _this.setState({
+ dialogVisible: false
+ });
+ });
+ }
+ };
+
+ _this.handleOpen = function () {
+ // todo dialog类型的插件初始时拿不动插件实例
+ _this.setState({
+ dialogVisible: true
+ });
+ };
+
+ _this.renderIcon = function (clickCallback) {
+ var _this$props2 = _this.props,
+ active = _this$props2.active,
+ disabled = _this$props2.disabled,
+ dotted = _this$props2.dotted,
+ locked = _this$props2.locked,
+ config = _this$props2.config,
+ _onClick = _this$props2.onClick;
+
+ var _ref = config || {},
+ pluginKey = _ref.pluginKey,
+ props = _ref.props;
+
+ var _ref2 = props || {},
+ icon = _ref2.icon,
+ title = _ref2.title;
+
+ var node = React.createElement(TopIcon, {
+ className: "lowcode-top-addon " + pluginKey,
+ active: active,
+ disabled: disabled,
+ locked: locked,
+ icon: icon,
+ title: title,
+ onClick: function onClick() {
+ if (disabled) return; //考虑到弹窗情况,延时发送消息
+
+ setTimeout(function () {
+ return _this.appHelper.emit(pluginKey + ".addon.activate");
+ }, 0);
+ clickCallback && clickCallback();
+ _onClick && _onClick();
+ }
+ });
+ return dotted ? React.createElement(_Badge, {
+ dot: true
+ }, node) : node;
+ };
+
+ _this.state = {
+ dialogVisible: false
+ };
+ return _this;
+ }
+
+ var _proto = TopPlugin.prototype;
+
+ _proto.componentDidMount = function componentDidMount() {
+ var config = this.props.config;
+ var pluginKey = config && config.pluginKey; // const appHelper = this.appHelper;
+ // if (appHelper && addonKey) {
+ // appHelper.on(`${addonKey}.dialog.show`, this.handleShow);
+ // appHelper.on(`${addonKey}.dialog.close`, this.handleClose);
+ // }
+ };
+
+ _proto.componentWillUnmount = function componentWillUnmount() {// const { config } = this.props;
+ // const addonKey = config && config.addonKey;
+ // const appHelper = this.appHelper;
+ // if (appHelper && addonKey) {
+ // appHelper.off(`${addonKey}.dialog.show`, this.handleShow);
+ // appHelper.off(`${addonKey}.dialog.close`, this.handleClose);
+ // }
+ };
+
+ _proto.render = function render() {
+ var _this2 = this;
+
+ var _this$props3 = this.props,
+ active = _this$props3.active,
+ dotted = _this$props3.dotted,
+ locked = _this$props3.locked,
+ disabled = _this$props3.disabled,
+ config = _this$props3.config,
+ editor = _this$props3.editor,
+ Comp = _this$props3.pluginClass;
+
+ var _ref3 = config || {},
+ pluginKey = _ref3.pluginKey,
+ pluginProps = _ref3.pluginProps,
+ props = _ref3.props,
+ type = _ref3.type;
+
+ var _ref4 = props || {},
+ _onClick2 = _ref4.onClick,
+ title = _ref4.title;
+
+ var dialogVisible = this.state.dialogVisible;
+ if (!pluginKey || !type || !Comp) return null;
+ var node = React.createElement(Comp, _extends({
+ active: active,
+ locked: locked,
+ disabled: disabled,
+ config: config,
+ onClick: function onClick() {
+ _onClick2 && _onClick2.call(null, editor);
+ }
+ }, pluginProps));
+
+ switch (type) {
+ case 'LinkIcon':
+ return React.createElement("a", props.linkProps, this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, editor);
+ }));
+
+ case 'Icon':
+ return this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, editor);
+ });
+
+ case 'DialogIcon':
+ return React.createElement(Fragment, null, this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, editor);
+
+ _this2.handleOpen();
+ }), React.createElement(_Dialog, _extends({
+ onOk: function onOk() {
+ editor.emit(pluginKey + ".dialog.onOk");
+
+ _this2.handleClose();
+ },
+ onCancel: this.handleClose,
+ onClose: this.handleClose,
+ title: title
+ }, props.dialogProps, {
+ visible: dialogVisible
+ }), node));
+
+ case 'BalloonIcon':
+ return React.createElement(_Balloon, _extends({
+ trigger: this.renderIcon(function () {
+ _onClick2 && _onClick2.call(null, editor);
+ }),
+ triggerType: ['click', 'hover']
+ }, props.balloonProps), node);
+
+ case 'Custom':
+ return dotted ? React.createElement(_Badge, {
+ dot: true
+ }, node) : node;
+
+ default:
+ return null;
+ }
+ };
+
+ return TopPlugin;
+}(PureComponent);
+
+TopPlugin.displayName = 'lowcodeTopPlugin';
+TopPlugin.defaultProps = {
+ active: false,
+ config: {},
+ disabled: false,
+ dotted: false,
+ locked: false,
+ onClick: function onClick() {}
+};
+export { TopPlugin as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/components/TopPlugin/index.scss b/packages/editor-skeleton/es/components/TopPlugin/index.scss
new file mode 100644
index 000000000..4bdd7b8d2
--- /dev/null
+++ b/packages/editor-skeleton/es/components/TopPlugin/index.scss
@@ -0,0 +1,2 @@
+.lowcode-top-addon {
+}
diff --git a/packages/editor-skeleton/es/config/skeleton.d.ts b/packages/editor-skeleton/es/config/skeleton.d.ts
new file mode 100644
index 000000000..67ae8a0ae
--- /dev/null
+++ b/packages/editor-skeleton/es/config/skeleton.d.ts
@@ -0,0 +1,14 @@
+declare const routerConfig: {
+ path: string;
+ component: any;
+ children: ({
+ path: string;
+ component: any;
+ redirect?: undefined;
+ } | {
+ path: string;
+ redirect: string;
+ component?: undefined;
+ })[];
+}[];
+export default routerConfig;
diff --git a/packages/editor-skeleton/es/config/skeleton.js b/packages/editor-skeleton/es/config/skeleton.js
new file mode 100644
index 000000000..8fb0727ab
--- /dev/null
+++ b/packages/editor-skeleton/es/config/skeleton.js
@@ -0,0 +1,14 @@
+import Dashboard from '@/pages/Dashboard';
+import BasicLayout from '@/layouts/BasicLayout';
+var routerConfig = [{
+ path: '/',
+ component: BasicLayout,
+ children: [{
+ path: '/dashboard',
+ component: Dashboard
+ }, {
+ path: '/',
+ redirect: '/dashboard'
+ }]
+}];
+export default routerConfig;
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/config/utils.d.ts b/packages/editor-skeleton/es/config/utils.d.ts
new file mode 100644
index 000000000..5e5bda5ab
--- /dev/null
+++ b/packages/editor-skeleton/es/config/utils.d.ts
@@ -0,0 +1,2 @@
+declare const asideMenuConfig: any[];
+export { asideMenuConfig };
diff --git a/packages/editor-skeleton/es/config/utils.js b/packages/editor-skeleton/es/config/utils.js
new file mode 100644
index 000000000..39cee308b
--- /dev/null
+++ b/packages/editor-skeleton/es/config/utils.js
@@ -0,0 +1,3 @@
+// 菜单配置
+var asideMenuConfig = [];
+export { asideMenuConfig };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/global.scss b/packages/editor-skeleton/es/global.scss
new file mode 100644
index 000000000..0a710b895
--- /dev/null
+++ b/packages/editor-skeleton/es/global.scss
@@ -0,0 +1,33 @@
+body {
+ font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma,
+ Arial, PingFang SC-Light, Microsoft YaHei;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
+ * {
+ box-sizing: border-box;
+ }
+}
+.next-loading {
+ .next-loading-wrap {
+ height: 100%;
+ }
+}
+.lowcode-editor {
+ .lowcode-main-content {
+ position: absolute;
+ top: 48px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ background-color: #d8d8d8;
+ }
+ .lowcode-center-area {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 10px;
+ overflow: auto;
+ }
+}
diff --git a/packages/editor-skeleton/es/index.d.ts b/packages/editor-skeleton/es/index.d.ts
new file mode 100644
index 000000000..468afa29c
--- /dev/null
+++ b/packages/editor-skeleton/es/index.d.ts
@@ -0,0 +1,8 @@
+import { PureComponent } from 'react';
+import './global.scss';
+export default class Skeleton extends PureComponent {
+ static displayName: string;
+ constructor(props: any);
+ componentWillUnmount(): void;
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/index.js b/packages/editor-skeleton/es/index.js
new file mode 100644
index 000000000..f875f6604
--- /dev/null
+++ b/packages/editor-skeleton/es/index.js
@@ -0,0 +1,70 @@
+import _ConfigProvider from "@alifd/next/es/config-provider";
+import _Loading from "@alifd/next/es/loading";
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent } from 'react'; // import Editor from '@ali/lowcode-engine-editor';
+
+import TopArea from './layouts/TopArea';
+import LeftArea from './layouts/LeftArea';
+import CenterArea from './layouts/CenterArea';
+import RightArea from './layouts/RightArea';
+import './global.scss';
+
+var Skeleton = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(Skeleton, _PureComponent);
+
+ function Skeleton(props) {
+ var _this;
+
+ _this = _PureComponent.call(this, props) || this; // this.editor = new Editor(props.config, props.utils);
+
+ _this.editor = {
+ on: function on() {},
+ off: function off() {},
+ config: props.config,
+ pluginComponents: props.pluginComponents
+ };
+ return _this;
+ }
+
+ var _proto = Skeleton.prototype;
+
+ _proto.componentWillUnmount = function componentWillUnmount() {// this.editor && this.editor.destroy();
+ // this.editor = null;
+ };
+
+ _proto.render = function render() {
+ var _this$props = this.props,
+ location = _this$props.location,
+ history = _this$props.history,
+ messages = _this$props.messages;
+ this.editor.location = location;
+ this.editor.history = history;
+ this.editor.messages = messages;
+ return React.createElement(_ConfigProvider, null, React.createElement(_Loading, {
+ tip: "Loading",
+ size: "large",
+ visible: false,
+ shape: "fusion-reactor",
+ fullScreen: true
+ }, React.createElement("div", {
+ className: "lowcode-editor"
+ }, React.createElement(TopArea, {
+ editor: this.editor
+ }), React.createElement("div", {
+ className: "lowcode-main-content"
+ }, React.createElement(LeftArea.Nav, {
+ editor: this.editor
+ }), React.createElement(LeftArea.Panel, {
+ editor: this.editor
+ }), React.createElement(CenterArea, {
+ editor: this.editor
+ }), React.createElement(RightArea, {
+ editor: this.editor
+ })))));
+ };
+
+ return Skeleton;
+}(PureComponent);
+
+Skeleton.displayName = 'lowcodeEditorSkeleton';
+export { Skeleton as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/CenterArea/index.d.ts b/packages/editor-skeleton/es/layouts/CenterArea/index.d.ts
new file mode 100644
index 000000000..201db137a
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/CenterArea/index.d.ts
@@ -0,0 +1,7 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class CenterArea extends PureComponent {
+ static displayName: string;
+ constructor(props: any);
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/layouts/CenterArea/index.js b/packages/editor-skeleton/es/layouts/CenterArea/index.js
new file mode 100644
index 000000000..4b9710f81
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/CenterArea/index.js
@@ -0,0 +1,24 @@
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent } from 'react';
+import './index.scss';
+
+var CenterArea = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(CenterArea, _PureComponent);
+
+ function CenterArea(props) {
+ return _PureComponent.call(this, props) || this;
+ }
+
+ var _proto = CenterArea.prototype;
+
+ _proto.render = function render() {
+ return React.createElement("div", {
+ className: "lowcode-center-area"
+ });
+ };
+
+ return CenterArea;
+}(PureComponent);
+
+CenterArea.displayName = 'lowcodeCenterArea';
+export { CenterArea as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/CenterArea/index.scss b/packages/editor-skeleton/es/layouts/CenterArea/index.scss
new file mode 100644
index 000000000..b2584ed2b
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/CenterArea/index.scss
@@ -0,0 +1,3 @@
+.lowcode-center-area {
+ padding: 12px;
+}
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/index.d.ts b/packages/editor-skeleton/es/layouts/LeftArea/index.d.ts
new file mode 100644
index 000000000..d888d90f0
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/index.d.ts
@@ -0,0 +1,5 @@
+declare const _default: {
+ Nav: any;
+ Panel: any;
+};
+export default _default;
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/index.js b/packages/editor-skeleton/es/layouts/LeftArea/index.js
new file mode 100644
index 000000000..50ecfad2f
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/index.js
@@ -0,0 +1,6 @@
+import Nav from './nav';
+import Panel from './panel';
+export default {
+ Nav: Nav,
+ Panel: Panel
+};
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/index.scss b/packages/editor-skeleton/es/layouts/LeftArea/index.scss
new file mode 100644
index 000000000..dac1b6b0a
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/index.scss
@@ -0,0 +1,21 @@
+.lowcode-left-area-nav {
+ width: 48px;
+ height: 100%;
+ background: #ffffff;
+ border-right: 1px solid #e8ebee;
+ position: relative;
+ .top-area {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ background: #ffffff;
+ max-height: 100%;
+ }
+ .bottom-area {
+ position: absolute;
+ bottom: 20px;
+ width: 100%;
+ background: #ffffff;
+ max-height: calc(100% - 20px);
+ }
+}
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/nav.d.ts b/packages/editor-skeleton/es/layouts/LeftArea/nav.d.ts
new file mode 100644
index 000000000..c44a09527
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/nav.d.ts
@@ -0,0 +1,7 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class LeftAreaPanel extends PureComponent {
+ static displayName: string;
+ constructor(props: any);
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/nav.js b/packages/editor-skeleton/es/layouts/LeftArea/nav.js
new file mode 100644
index 000000000..962129007
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/nav.js
@@ -0,0 +1,24 @@
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent } from 'react';
+import './index.scss';
+
+var LeftAreaPanel = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(LeftAreaPanel, _PureComponent);
+
+ function LeftAreaPanel(props) {
+ return _PureComponent.call(this, props) || this;
+ }
+
+ var _proto = LeftAreaPanel.prototype;
+
+ _proto.render = function render() {
+ return React.createElement("div", {
+ className: "lowcode-left-area-nav"
+ });
+ };
+
+ return LeftAreaPanel;
+}(PureComponent);
+
+LeftAreaPanel.displayName = 'lowcodeLeftAreaNav';
+export { LeftAreaPanel as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/panel.d.ts b/packages/editor-skeleton/es/layouts/LeftArea/panel.d.ts
new file mode 100644
index 000000000..c44a09527
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/panel.d.ts
@@ -0,0 +1,7 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class LeftAreaPanel extends PureComponent {
+ static displayName: string;
+ constructor(props: any);
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/layouts/LeftArea/panel.js b/packages/editor-skeleton/es/layouts/LeftArea/panel.js
new file mode 100644
index 000000000..332529ce2
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/LeftArea/panel.js
@@ -0,0 +1,24 @@
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent } from 'react';
+import './index.scss';
+
+var LeftAreaPanel = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(LeftAreaPanel, _PureComponent);
+
+ function LeftAreaPanel(props) {
+ return _PureComponent.call(this, props) || this;
+ }
+
+ var _proto = LeftAreaPanel.prototype;
+
+ _proto.render = function render() {
+ return React.createElement("div", {
+ className: "lowcode-left-area-panel"
+ });
+ };
+
+ return LeftAreaPanel;
+}(PureComponent);
+
+LeftAreaPanel.displayName = 'lowcodeLeftAreaPanel';
+export { LeftAreaPanel as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/RightArea/index.d.ts b/packages/editor-skeleton/es/layouts/RightArea/index.d.ts
new file mode 100644
index 000000000..ad87a57de
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/RightArea/index.d.ts
@@ -0,0 +1,7 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class RightArea extends PureComponent {
+ static displayName: string;
+ constructor(props: any);
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/layouts/RightArea/index.js b/packages/editor-skeleton/es/layouts/RightArea/index.js
new file mode 100644
index 000000000..d1d38689d
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/RightArea/index.js
@@ -0,0 +1,24 @@
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import React, { PureComponent } from 'react';
+import './index.scss';
+
+var RightArea = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(RightArea, _PureComponent);
+
+ function RightArea(props) {
+ return _PureComponent.call(this, props) || this;
+ }
+
+ var _proto = RightArea.prototype;
+
+ _proto.render = function render() {
+ return React.createElement("div", {
+ className: "lowcode-right-area"
+ });
+ };
+
+ return RightArea;
+}(PureComponent);
+
+RightArea.displayName = 'lowcodeRightArea';
+export { RightArea as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/RightArea/index.scss b/packages/editor-skeleton/es/layouts/RightArea/index.scss
new file mode 100644
index 000000000..120ef4f11
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/RightArea/index.scss
@@ -0,0 +1,157 @@
+.lowcode-right-area {
+ width: 300px;
+ 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;
+ }
+ }
+
+ //tab定义
+ .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;
+ }
+ }
+ //组件
+ .select-comp {
+ padding: 10px 16px;
+ line-height: 16px;
+ color: #989a9c;
+ & > span {
+ font-size: 12px;
+ line-height: 16px;
+ font-weight: 400;
+ }
+ & > .btn-wrap,
+ & > .next-btn {
+ width: auto;
+ margin: 0 5px;
+ float: right;
+ }
+ }
+
+ .unselected {
+ padding: 60px 0;
+ text-align: center;
+ }
+ //右侧属性面板样式调整;
+ .offset-56 {
+ padding-left: 56px;
+ margin-bottom: 16px;
+ overflow: hidden;
+ }
+ .fixedSpan.next-form-item {
+ & > .next-form-item-label {
+ width: 56px;
+ flex: none;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ & > .next-form-item-control {
+ padding-right: 24px;
+ }
+ }
+ .fixedSpan.next-form-item,
+ .offset-56 .next-form-item {
+ display: flex;
+ & > .next-form-item-control {
+ width: auto;
+ flex: 1;
+ max-width: none;
+ .next-input,
+ .next-select,
+ .next-radio-group,
+ .next-number-picker,
+ .luna-reactnode-btn,
+ .luna-monaco-button button,
+ .luna-object-button button {
+ width: 100%;
+ }
+ .next-number-picker {
+ width: 100%;
+ .next-after {
+ padding-right: 5px;
+ }
+ }
+ .next-radio-group {
+ display: flex;
+ label {
+ flex: 1;
+ text-align: center;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
+ .topSpan.next-form-item {
+ margin-bottom: 4px;
+ & > .next-form-item-control {
+ padding-right: 24px;
+ .next-input,
+ .next-select,
+ .next-radio-group,
+ .next-number-picker,
+ .luna-reactnode-btn,
+ .luna-monaco-button button,
+ .luna-object-button button {
+ width: 100%;
+ }
+ .next-number-picker {
+ width: 100%;
+ .next-after {
+ padding-right: 5px;
+ }
+ }
+ .next-radio-group {
+ display: flex;
+ label {
+ flex: 1;
+ text-align: center;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/editor-skeleton/es/layouts/TopArea/index.d.ts b/packages/editor-skeleton/es/layouts/TopArea/index.d.ts
new file mode 100644
index 000000000..e800a1e74
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/TopArea/index.d.ts
@@ -0,0 +1,11 @@
+import { PureComponent } from 'react';
+import './index.scss';
+export default class TopArea extends PureComponent {
+ static displayName: string;
+ constructor(props: any);
+ componentDidMount(): void;
+ componentWillUnmount(): void;
+ handlePluginStatusChange: () => void;
+ renderPluginList: (list?: any[]) => JSX.Element[];
+ render(): JSX.Element;
+}
diff --git a/packages/editor-skeleton/es/layouts/TopArea/index.js b/packages/editor-skeleton/es/layouts/TopArea/index.js
new file mode 100644
index 000000000..c82eefc61
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/TopArea/index.js
@@ -0,0 +1,83 @@
+import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
+import _Grid from "@alifd/next/es/grid";
+import React, { PureComponent } from 'react';
+import TopPlugin from '../../components/TopPlugin';
+import './index.scss';
+var Row = _Grid.Row,
+ Col = _Grid.Col;
+
+var TopArea = /*#__PURE__*/function (_PureComponent) {
+ _inheritsLoose(TopArea, _PureComponent);
+
+ function TopArea(props) {
+ var _this;
+
+ _this = _PureComponent.call(this, props) || this;
+
+ _this.handlePluginStatusChange = function () {};
+
+ _this.renderPluginList = function (list) {
+ if (list === void 0) {
+ list = [];
+ }
+
+ return list.map(function (item, idx) {
+ var isDivider = item.type === 'Divider';
+ return React.createElement(Col, {
+ className: isDivider ? 'divider' : '',
+ key: isDivider ? idx : item.pluginKey,
+ style: {
+ width: item.props && item.props.width || 40,
+ flex: 'none'
+ }
+ }, !isDivider && React.createElement(TopPlugin, {
+ config: item,
+ pluginClass: _this.editor.pluginComponents[item.pluginKey],
+ status: _this.editor.pluginStatus[item.pluginKey]
+ }));
+ });
+ };
+
+ _this.editor = props.editor;
+ _this.config = _this.editor.config.plugins && _this.editor.config.plugins.topArea;
+ return _this;
+ }
+
+ var _proto = TopArea.prototype;
+
+ _proto.componentDidMount = function componentDidMount() {};
+
+ _proto.componentWillUnmount = function componentWillUnmount() {};
+
+ _proto.render = function render() {
+ if (!this.config) return null;
+ var leftList = [];
+ var rightList = [];
+ this.config.forEach(function (item) {
+ var align = item.props && item.props.align === 'right' ? 'right' : 'left'; // 分隔符不允许相邻
+
+ if (item.type === 'Divider') {
+ var currentList = align === 'right' ? rightList : leftList;
+ if (currList.length === 0 || currList[currList.length - 1].type === 'Divider') return;
+ }
+
+ if (align === 'right') {
+ rightList.push(item);
+ } else {
+ leftList.push(item);
+ }
+ });
+ return React.createElement("div", {
+ className: "lowcode-top-area"
+ }, React.createElement("div", {
+ className: "left-area"
+ }, this.renderPluginList(leftList)), React.createElement("div", {
+ classname: "right-area"
+ }, this.renderPluginList(rightList)));
+ };
+
+ return TopArea;
+}(PureComponent);
+
+TopArea.displayName = 'lowcodeTopArea';
+export { TopArea as default };
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/layouts/TopArea/index.scss b/packages/editor-skeleton/es/layouts/TopArea/index.scss
new file mode 100644
index 000000000..ca8bbd825
--- /dev/null
+++ b/packages/editor-skeleton/es/layouts/TopArea/index.scss
@@ -0,0 +1,5 @@
+.lowcode-top-area {
+ height: 48px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #e8ebee;
+}
diff --git a/packages/editor-skeleton/es/locale/en-US.js b/packages/editor-skeleton/es/locale/en-US.js
new file mode 100644
index 000000000..f190625da
--- /dev/null
+++ b/packages/editor-skeleton/es/locale/en-US.js
@@ -0,0 +1,10 @@
+export default {
+ loading: 'loading...',
+ rejectRedirect: 'Redirect is not allowed',
+ expand: 'Unfold',
+ fold: 'Fold',
+ pageNotExist: 'The current Page not exist',
+ enterFromAppCenter: 'Please enter from the app center',
+ noPermission: 'Sorry, you do not have the develop permission',
+ getPermission: 'Please connect the app owners {owners} to get the permission'
+};
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/locale/ja-JP.js b/packages/editor-skeleton/es/locale/ja-JP.js
new file mode 100644
index 000000000..7c645e42f
--- /dev/null
+++ b/packages/editor-skeleton/es/locale/ja-JP.js
@@ -0,0 +1 @@
+export default {};
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/locale/zh-CN.js b/packages/editor-skeleton/es/locale/zh-CN.js
new file mode 100644
index 000000000..51791f741
--- /dev/null
+++ b/packages/editor-skeleton/es/locale/zh-CN.js
@@ -0,0 +1,10 @@
+export default {
+ loading: '加载中...',
+ rejectRedirect: '开发中,已阻止发生跳转',
+ expand: '展开',
+ fold: '收起',
+ pageNotExist: '当前访问地址不存在',
+ enterFromAppCenter: '请从应用中心入口重新进入',
+ noPermission: '抱歉,您暂无开发权限',
+ getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限'
+};
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/locale/zh-TW.js b/packages/editor-skeleton/es/locale/zh-TW.js
new file mode 100644
index 000000000..7c645e42f
--- /dev/null
+++ b/packages/editor-skeleton/es/locale/zh-TW.js
@@ -0,0 +1 @@
+export default {};
\ No newline at end of file
diff --git a/packages/editor-skeleton/es/style.js b/packages/editor-skeleton/es/style.js
new file mode 100644
index 000000000..7f81d0018
--- /dev/null
+++ b/packages/editor-skeleton/es/style.js
@@ -0,0 +1,8 @@
+import '@alifd/next/es/config-provider/style';
+import '@alifd/next/es/loading/style';
+import '@alifd/next/es/grid/style';
+import '@alifd/next/es/balloon/style';
+import '@alifd/next/es/dialog/style';
+import '@alifd/next/es/badge/style';
+import '@alifd/next/es/button/style';
+import '@alifd/next/es/icon/style';
\ No newline at end of file
diff --git a/packages/editor-skeleton/jsconfig.json b/packages/editor-skeleton/jsconfig.json
new file mode 100644
index 000000000..9e0f3c03d
--- /dev/null
+++ b/packages/editor-skeleton/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "jsx": "react",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/packages/editor-skeleton/package.json b/packages/editor-skeleton/package.json
new file mode 100644
index 000000000..1320d2903
--- /dev/null
+++ b/packages/editor-skeleton/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "@ali/lowcode-engine-skeleton",
+ "version": "0.0.1",
+ "description": "alibaba lowcode editor skeleton",
+ "files": [
+ "demo/",
+ "es/",
+ "lib/",
+ "build/"
+ ],
+ "main": "lib/index.tsx",
+ "module": "es/index.js",
+ "stylePath": "style.js",
+ "scripts": {
+ "start": "build-scripts start",
+ "build": "build-scripts build --skip-demo",
+ "prepublishOnly": "npm run prettier && npm run build",
+ "lint": "eslint --cache --ext .js,.jsx ./",
+ "prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,ejs,less,css,scss,json}\" "
+ },
+ "keywords": [
+ "lowcode",
+ "editor"
+ ],
+ "author": "xiayang.xy",
+ "dependencies": {
+ "@alifd/next": "^1.x",
+ "@icedesign/theme": "^1.x",
+ "@types/react": "^16.8.3",
+ "@types/react-dom": "^16.8.2",
+ "moment": "^2.23.0",
+ "prop-types": "^15.5.8",
+ "react": "^16.4.1",
+ "react-dom": "^16.4.1",
+ "react-router-dom": "^5.0.1"
+ },
+ "devDependencies": {
+ "@alib/build-scripts": "^0.1.3",
+ "@alifd/next": "1.x",
+ "@ice/spec": "^0.1.1",
+ "@types/lodash": "^4.14.149",
+ "@types/react": "^16.9.13",
+ "@types/react-dom": "^16.9.4",
+ "build-plugin-component": "^0.2.7-1",
+ "build-plugin-fusion": "^0.1.0",
+ "build-plugin-moment-locales": "^0.1.0",
+ "eslint": "^6.0.1",
+ "prettier": "^1.19.1",
+ "react": "^16.8.0",
+ "react-dom": "^16.8.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ice-lab/react-materials/tree/master/scaffolds/ice-ts"
+ },
+ "homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-engine-skeleton@0.0.1/build/index.html"
+}
diff --git a/packages/editor-skeleton/src/components/LeftIcon/index.scss b/packages/editor-skeleton/src/components/LeftIcon/index.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor-skeleton/src/components/LeftIcon/index.tsx b/packages/editor-skeleton/src/components/LeftIcon/index.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor-skeleton/src/components/LeftPlugin/index.scss b/packages/editor-skeleton/src/components/LeftPlugin/index.scss
new file mode 100644
index 000000000..9c6922129
--- /dev/null
+++ b/packages/editor-skeleton/src/components/LeftPlugin/index.scss
@@ -0,0 +1,59 @@
+.luna-left-addon {
+ font-size: 16px;
+ text-align: center;
+ line-height: 36px;
+ height: 36px;
+ position: relative;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ color: #777;
+ &.collapse {
+ height: 40px;
+ color: #8c8c8c;
+ border-bottom: 1px solid #bfbfbf;
+ }
+ &.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);
+ display: block;
+ position: absolute;
+ left: 50px;
+ top: 5px;
+ line-height: 18px;
+ font-size: 12px;
+ white-space: nowrap;
+ padding: 6px 8px;
+ border-radius: 4px;
+ background: rgba(0, 0, 0, 0.75);
+ color: #fff;
+ z-index: 100;
+ }
+ &:after {
+ content: '';
+ display: block;
+ position: absolute;
+ left: 40px;
+ top: 15px;
+ border: 5px solid transparent;
+ border-right-color: rgba(0, 0, 0, 0.75);
+ z-index: 100;
+ }
+ }
+}
diff --git a/packages/editor-skeleton/src/components/LeftPlugin/index.tsx b/packages/editor-skeleton/src/components/LeftPlugin/index.tsx
new file mode 100644
index 000000000..d9a637a21
--- /dev/null
+++ b/packages/editor-skeleton/src/components/LeftPlugin/index.tsx
@@ -0,0 +1,223 @@
+import React, { PureComponent, Fragment } from 'react';
+
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import AppContext from '@ali/iceluna-sdk/lib/context/appContext';
+import { Balloon, Dialog, Icon, Badge } from '@alife/next';
+
+import './index.scss';
+export default class LeftAddon extends PureComponent {
+ static displayName = 'LunaLeftAddon';
+ static propTypes = {
+ active: PropTypes.bool,
+ config: PropTypes.shape({
+ addonKey: PropTypes.string,
+ addonProps: PropTypes.object,
+ props: PropTypes.object,
+ type: PropTypes.oneOf([
+ 'DialogIcon',
+ 'BalloonIcon',
+ 'PanelIcon',
+ 'LinkIcon',
+ 'Icon',
+ 'Custom',
+ ]),
+ }),
+ disabled: PropTypes.bool,
+ dotted: PropTypes.bool,
+ locked: PropTypes.bool,
+ onClick: PropTypes.func,
+ };
+ static defaultProps = {
+ active: false,
+ config: {},
+ disabled: false,
+ dotted: false,
+ locked: false,
+ onClick: () => {},
+ };
+ static contextType = AppContext;
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ dialogVisible: false,
+ };
+ this.appHelper = context.appHelper;
+ this.utils = this.appHelper.utils;
+ this.constants = this.appHelper.constants;
+ }
+
+ componentDidMount() {
+ const { config } = this.props;
+ const addonKey = config && config.addonKey;
+ const appHelper = this.appHelper;
+ if (appHelper && addonKey) {
+ appHelper.on(`${addonKey}.dialog.show`, this.handleShow);
+ appHelper.on(`${addonKey}.dialog.close`, this.handleClose);
+ }
+ }
+
+ componentWillUnmount() {
+ const { config } = this.props;
+ const appHelper = this.appHelper;
+ const addonKey = config && config.addonKey;
+ if (appHelper && addonKey) {
+ appHelper.off(`${addonKey}.dialog.show`, this.handleShow);
+ appHelper.off(`${addonKey}.dialog.close`, this.handleClose);
+ }
+ }
+
+ handleClose = () => {
+ const addonKey = this.props.config && this.props.config.addonKey;
+ const currentAddon =
+ this.appHelper.addons && this.appHelper.addons[addonKey];
+ if (currentAddon) {
+ this.utils.transformToPromise(currentAddon.close()).then(() => {
+ this.setState({
+ dialogVisible: false,
+ });
+ });
+ }
+ };
+
+ handleOpen = () => {
+ // todo 对话框类型的插件初始时拿不到插件实例
+ this.setState({
+ dialogVisible: true,
+ });
+ };
+
+ handleShow = () => {
+ const { disabled, config, onClick } = this.props;
+ const addonKey = config && config.addonKey;
+ if (disabled || !addonKey) return;
+ //考虑到弹窗情况,延时发送消息
+ setTimeout(() => this.appHelper.emit(`${addonKey}.addon.activate`), 0);
+ this.handleOpen();
+ onClick && onClick();
+ };
+
+ renderIcon = clickCallback => {
+ const { active, disabled, dotted, locked, onClick, config } = this.props;
+ const { addonKey, props } = config || {};
+ const { icon, title } = props || {};
+ return (
+ {
+ if (disabled) return;
+ //考虑到弹窗情况,延时发送消息
+ clickCallback && clickCallback();
+ onClick && onClick();
+ }}
+ >
+ {dotted ? (
+
+
+
+ ) : (
+
+ )}
+
+ );
+ };
+
+ render() {
+ const { dotted, locked, active, disabled, config } = this.props;
+ const { addonKey, props, type, addonProps } = config || {};
+ const { onClick, title } = props || {};
+ const { dialogVisible } = this.state;
+ const { appHelper, components } = this.context;
+ if (!addonKey || !type || !props) return null;
+ const componentName = appHelper.utils.generateAddonCompName(addonKey);
+ const localeProps = {};
+ const { locale, messages } = appHelper;
+ if (locale) {
+ localeProps.locale = locale;
+ }
+ if (messages && messages[componentName]) {
+ localeProps.messages = messages[componentName];
+ }
+ const AddonComp = components && components[componentName];
+ const node =
+ (AddonComp && (
+ {
+ onClick && onClick.call(null, appHelper);
+ }}
+ {...localeProps}
+ {...(addonProps || {})}
+ />
+ )) ||
+ null;
+
+ switch (type) {
+ case 'LinkIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, appHelper);
+ })}
+
+ );
+ case 'Icon':
+ return this.renderIcon(() => {
+ onClick && onClick.call(null, appHelper);
+ });
+ case 'DialogIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, appHelper);
+ this.handleOpen();
+ })}
+
+
+ );
+ case 'BalloonIcon':
+ return (
+ {
+ onClick && onClick.call(null, appHelper);
+ })}
+ align="r"
+ triggerType={['click', 'hover']}
+ {...(props.balloonProps || {})}
+ >
+ {node}
+
+ );
+ case 'PanelIcon':
+ return this.renderIcon(() => {
+ onClick && onClick.call(null, appHelper);
+ this.handleOpen();
+ });
+ case 'Custom':
+ return dotted ? {node} : node;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/packages/editor-skeleton/src/components/Panel/index.tsx b/packages/editor-skeleton/src/components/Panel/index.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor-skeleton/src/components/TopIcon/index.scss b/packages/editor-skeleton/src/components/TopIcon/index.scss
new file mode 100644
index 000000000..1cb3bdfdf
--- /dev/null
+++ b/packages/editor-skeleton/src/components/TopIcon/index.scss
@@ -0,0 +1,32 @@
+.next-btn.next-large.lowcode-top-btn {
+ width: 44px;
+ height: 44px;
+ padding: 0;
+ margin: 4px -2px;
+ text-align: center;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ color: #777;
+ &.disabled {
+ cursor: not-allowed;
+ color: $color-text1-1;
+ }
+ &.locked {
+ color: red !important;
+ }
+ i.next-icon {
+ &:before {
+ font-size: 17px;
+ }
+ margin-right: 0;
+ line-height: 18px;
+ }
+ span {
+ display: block;
+ margin: 0px -5px 0;
+ line-height: 16px;
+ text-align: center;
+ font-size: 12px;
+ transform: scale(0.8);
+ }
+}
diff --git a/packages/editor-skeleton/src/components/TopIcon/index.tsx b/packages/editor-skeleton/src/components/TopIcon/index.tsx
new file mode 100644
index 000000000..8e81c9c3a
--- /dev/null
+++ b/packages/editor-skeleton/src/components/TopIcon/index.tsx
@@ -0,0 +1,68 @@
+import React, { PureComponent } from 'react';
+
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { Icon, Button } from '@alifd/next';
+import './index.scss';
+export default class TopIcon extends PureComponent {
+ static displayName = 'TopIcon';
+ static propTypes = {
+ active: PropTypes.bool,
+ className: PropTypes.string,
+ disabled: PropTypes.bool,
+ icon: PropTypes.string,
+ id: PropTypes.string,
+ locked: PropTypes.bool,
+ onClick: PropTypes.func,
+ showTitle: PropTypes.bool,
+ style: PropTypes.object,
+ title: PropTypes.string,
+ };
+ static defaultProps = {
+ active: false,
+ className: '',
+ disabled: false,
+ icon: '',
+ id: '',
+ locked: false,
+ onClick: () => {},
+ showTitle: false,
+ style: {},
+ title: '',
+ };
+
+ render() {
+ const {
+ active,
+ disabled,
+ icon,
+ locked,
+ title,
+ className,
+ id,
+ style,
+ showTitle,
+ onClick,
+ } = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/packages/editor-skeleton/src/components/TopPlugin/index.scss b/packages/editor-skeleton/src/components/TopPlugin/index.scss
new file mode 100644
index 000000000..4bdd7b8d2
--- /dev/null
+++ b/packages/editor-skeleton/src/components/TopPlugin/index.scss
@@ -0,0 +1,2 @@
+.lowcode-top-addon {
+}
diff --git a/packages/editor-skeleton/src/components/TopPlugin/index.tsx b/packages/editor-skeleton/src/components/TopPlugin/index.tsx
new file mode 100644
index 000000000..04f5d0e59
--- /dev/null
+++ b/packages/editor-skeleton/src/components/TopPlugin/index.tsx
@@ -0,0 +1,174 @@
+import React, { PureComponent, Fragment } from 'react';
+
+import PropTypes from 'prop-types';
+import TopIcon from '../TopIcon';
+import { Balloon, Badge, Dialog } from '@alifd/next';
+
+import './index.scss';
+export default class TopPlugin extends PureComponent {
+ static displayName = 'lowcodeTopPlugin';
+
+ static defaultProps = {
+ active: false,
+ config: {},
+ disabled: false,
+ dotted: false,
+ locked: false,
+ onClick: () => {},
+ };
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ dialogVisible: false,
+ };
+ }
+
+ componentDidMount() {
+ const { config } = this.props;
+ const pluginKey = config && config.pluginKey;
+ // const appHelper = this.appHelper;
+ // if (appHelper && addonKey) {
+ // appHelper.on(`${addonKey}.dialog.show`, this.handleShow);
+ // appHelper.on(`${addonKey}.dialog.close`, this.handleClose);
+ // }
+ }
+
+ componentWillUnmount() {
+ // const { config } = this.props;
+ // const addonKey = config && config.addonKey;
+ // const appHelper = this.appHelper;
+ // if (appHelper && addonKey) {
+ // appHelper.off(`${addonKey}.dialog.show`, this.handleShow);
+ // appHelper.off(`${addonKey}.dialog.close`, this.handleClose);
+ // }
+ }
+
+ handleShow = () => {
+ const { disabled, config, onClick } = this.props;
+ const addonKey = config && config.addonKey;
+ if (disabled || !addonKey) return;
+ //考虑到弹窗情况,延时发送消息
+ setTimeout(() => this.appHelper.emit(`${addonKey}.addon.activate`), 0);
+ this.handleOpen();
+ onClick && onClick();
+ };
+
+ handleClose = () => {
+ const addonKey = this.props.config && this.props.config.addonKey;
+ const currentAddon =
+ this.appHelper.addons && this.appHelper.addons[addonKey];
+ if (currentAddon) {
+ this.utils.transformToPromise(currentAddon.close()).then(() => {
+ this.setState({
+ dialogVisible: false,
+ });
+ });
+ }
+ };
+
+ handleOpen = () => {
+ // todo dialog类型的插件初始时拿不动插件实例
+ this.setState({
+ dialogVisible: true,
+ });
+ };
+
+ renderIcon = clickCallback => {
+ const { active, disabled, dotted, locked, config, onClick } = this.props;
+ const { pluginKey, props } = config || {};
+ const { icon, title } = props || {};
+ const node = (
+ {
+ if (disabled) return;
+ //考虑到弹窗情况,延时发送消息
+ setTimeout(
+ () => this.appHelper.emit(`${pluginKey}.addon.activate`),
+ 0,
+ );
+ clickCallback && clickCallback();
+ onClick && onClick();
+ }}
+ />
+ );
+ return dotted ? {node} : node;
+ };
+
+ render() {
+ const { active, dotted, locked, disabled, config, editor, pluginClass: Comp } = this.props;
+ const { pluginKey, pluginProps, props, type } = config || {};
+ const { onClick, title } = props || {};
+ const { dialogVisible } = this.state;
+ if (!pluginKey || !type || !Comp) return null;
+ const node = {
+ onClick && onClick.call(null, editor);
+ }}
+ {...pluginProps}
+ />;
+
+ switch (type) {
+ case 'LinkIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ })}
+
+ );
+ case 'Icon':
+ return this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ });
+ case 'DialogIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ this.handleOpen();
+ })}
+
+
+ );
+ case 'BalloonIcon':
+ return (
+ {
+ onClick && onClick.call(null, editor);
+ })}
+ triggerType={['click', 'hover']}
+ {...props.balloonProps}
+ >
+ {node}
+
+ );
+ case 'Custom':
+ return dotted ? {node} : node;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/packages/editor-skeleton/src/config/skeleton.ts b/packages/editor-skeleton/src/config/skeleton.ts
new file mode 100644
index 000000000..9e3f6898f
--- /dev/null
+++ b/packages/editor-skeleton/src/config/skeleton.ts
@@ -0,0 +1,21 @@
+import Dashboard from '@/pages/Dashboard';
+import BasicLayout from '@/layouts/BasicLayout';
+
+const routerConfig = [
+ {
+ path: '/',
+ component: BasicLayout,
+ children: [
+ {
+ path: '/dashboard',
+ component: Dashboard,
+ },
+ {
+ path: '/',
+ redirect: '/dashboard',
+ },
+ ],
+ },
+];
+
+export default routerConfig;
diff --git a/packages/editor-skeleton/src/config/utils.ts b/packages/editor-skeleton/src/config/utils.ts
new file mode 100644
index 000000000..e3c4c9e37
--- /dev/null
+++ b/packages/editor-skeleton/src/config/utils.ts
@@ -0,0 +1,5 @@
+// 菜单配置
+
+const asideMenuConfig = [];
+
+export { asideMenuConfig };
diff --git a/packages/editor-skeleton/src/global.scss b/packages/editor-skeleton/src/global.scss
new file mode 100644
index 000000000..0a710b895
--- /dev/null
+++ b/packages/editor-skeleton/src/global.scss
@@ -0,0 +1,33 @@
+body {
+ font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma,
+ Arial, PingFang SC-Light, Microsoft YaHei;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
+ * {
+ box-sizing: border-box;
+ }
+}
+.next-loading {
+ .next-loading-wrap {
+ height: 100%;
+ }
+}
+.lowcode-editor {
+ .lowcode-main-content {
+ position: absolute;
+ top: 48px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ background-color: #d8d8d8;
+ }
+ .lowcode-center-area {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 10px;
+ overflow: auto;
+ }
+}
diff --git a/packages/editor-skeleton/src/index.tsx b/packages/editor-skeleton/src/index.tsx
new file mode 100644
index 000000000..3f1c89522
--- /dev/null
+++ b/packages/editor-skeleton/src/index.tsx
@@ -0,0 +1,60 @@
+import React, { PureComponent } from 'react';
+
+// import Editor from '@ali/lowcode-engine-editor';
+import { Loading, ConfigProvider } from '@alifd/next';
+import defaultConfig from './config/skeleton';
+
+import TopArea from './layouts/TopArea';
+import LeftArea from './layouts/LeftArea';
+import CenterArea from './layouts/CenterArea';
+import RightArea from './layouts/RightArea';
+
+import './global.scss';
+
+export default class Skeleton extends PureComponent {
+ static displayName = 'lowcodeEditorSkeleton';
+
+ constructor(props) {
+ super(props);
+ // this.editor = new Editor(props.config, props.utils);
+ this.editor = {
+ on: () => {},
+ off: () => {},
+ config: props.config,
+ pluginComponents: props.pluginComponents
+ };
+ }
+
+ componentWillUnmount() {
+ // this.editor && this.editor.destroy();
+ // this.editor = null;
+ }
+
+ render() {
+ const { location, history, messages } = this.props;
+ this.editor.location = location;
+ this.editor.history = history;
+ this.editor.messages = messages;
+ return (
+
+
+
+
+
+ );
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/CenterArea/index.scss b/packages/editor-skeleton/src/layouts/CenterArea/index.scss
new file mode 100644
index 000000000..b2584ed2b
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/CenterArea/index.scss
@@ -0,0 +1,3 @@
+.lowcode-center-area {
+ padding: 12px;
+}
diff --git a/packages/editor-skeleton/src/layouts/CenterArea/index.tsx b/packages/editor-skeleton/src/layouts/CenterArea/index.tsx
new file mode 100644
index 000000000..dc2b38d25
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/CenterArea/index.tsx
@@ -0,0 +1,15 @@
+import React, { PureComponent } from 'react';
+
+import './index.scss';
+
+export default class CenterArea extends PureComponent {
+ static displayName = 'lowcodeCenterArea';
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return ;
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/LeftArea/index.scss b/packages/editor-skeleton/src/layouts/LeftArea/index.scss
new file mode 100644
index 000000000..dac1b6b0a
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/LeftArea/index.scss
@@ -0,0 +1,21 @@
+.lowcode-left-area-nav {
+ width: 48px;
+ height: 100%;
+ background: #ffffff;
+ border-right: 1px solid #e8ebee;
+ position: relative;
+ .top-area {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ background: #ffffff;
+ max-height: 100%;
+ }
+ .bottom-area {
+ position: absolute;
+ bottom: 20px;
+ width: 100%;
+ background: #ffffff;
+ max-height: calc(100% - 20px);
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/LeftArea/index.tsx b/packages/editor-skeleton/src/layouts/LeftArea/index.tsx
new file mode 100644
index 000000000..805a5e014
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/LeftArea/index.tsx
@@ -0,0 +1,7 @@
+import Nav from './nav';
+import Panel from './panel';
+
+export default {
+ Nav,
+ Panel,
+};
diff --git a/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx b/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx
new file mode 100644
index 000000000..6c58c12ef
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx
@@ -0,0 +1,15 @@
+import React, { PureComponent } from 'react';
+
+import './index.scss';
+
+export default class LeftAreaPanel extends PureComponent {
+ static displayName = 'lowcodeLeftAreaNav';
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return ;
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx b/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx
new file mode 100644
index 000000000..ab41860fb
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx
@@ -0,0 +1,15 @@
+import React, { PureComponent } from 'react';
+
+import './index.scss';
+
+export default class LeftAreaPanel extends PureComponent {
+ static displayName = 'lowcodeLeftAreaPanel';
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return ;
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/RightArea/index.scss b/packages/editor-skeleton/src/layouts/RightArea/index.scss
new file mode 100644
index 000000000..120ef4f11
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/RightArea/index.scss
@@ -0,0 +1,157 @@
+.lowcode-right-area {
+ width: 300px;
+ 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;
+ }
+ }
+
+ //tab定义
+ .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;
+ }
+ }
+ //组件
+ .select-comp {
+ padding: 10px 16px;
+ line-height: 16px;
+ color: #989a9c;
+ & > span {
+ font-size: 12px;
+ line-height: 16px;
+ font-weight: 400;
+ }
+ & > .btn-wrap,
+ & > .next-btn {
+ width: auto;
+ margin: 0 5px;
+ float: right;
+ }
+ }
+
+ .unselected {
+ padding: 60px 0;
+ text-align: center;
+ }
+ //右侧属性面板样式调整;
+ .offset-56 {
+ padding-left: 56px;
+ margin-bottom: 16px;
+ overflow: hidden;
+ }
+ .fixedSpan.next-form-item {
+ & > .next-form-item-label {
+ width: 56px;
+ flex: none;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ & > .next-form-item-control {
+ padding-right: 24px;
+ }
+ }
+ .fixedSpan.next-form-item,
+ .offset-56 .next-form-item {
+ display: flex;
+ & > .next-form-item-control {
+ width: auto;
+ flex: 1;
+ max-width: none;
+ .next-input,
+ .next-select,
+ .next-radio-group,
+ .next-number-picker,
+ .luna-reactnode-btn,
+ .luna-monaco-button button,
+ .luna-object-button button {
+ width: 100%;
+ }
+ .next-number-picker {
+ width: 100%;
+ .next-after {
+ padding-right: 5px;
+ }
+ }
+ .next-radio-group {
+ display: flex;
+ label {
+ flex: 1;
+ text-align: center;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
+ .topSpan.next-form-item {
+ margin-bottom: 4px;
+ & > .next-form-item-control {
+ padding-right: 24px;
+ .next-input,
+ .next-select,
+ .next-radio-group,
+ .next-number-picker,
+ .luna-reactnode-btn,
+ .luna-monaco-button button,
+ .luna-object-button button {
+ width: 100%;
+ }
+ .next-number-picker {
+ width: 100%;
+ .next-after {
+ padding-right: 5px;
+ }
+ }
+ .next-radio-group {
+ display: flex;
+ label {
+ flex: 1;
+ text-align: center;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/RightArea/index.tsx b/packages/editor-skeleton/src/layouts/RightArea/index.tsx
new file mode 100644
index 000000000..31273a4e2
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/RightArea/index.tsx
@@ -0,0 +1,15 @@
+import React, { PureComponent } from 'react';
+
+import './index.scss';
+
+export default class RightArea extends PureComponent {
+ static displayName = 'lowcodeRightArea';
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return ;
+ }
+}
diff --git a/packages/editor-skeleton/src/layouts/TopArea/index.scss b/packages/editor-skeleton/src/layouts/TopArea/index.scss
new file mode 100644
index 000000000..ca8bbd825
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/TopArea/index.scss
@@ -0,0 +1,5 @@
+.lowcode-top-area {
+ height: 48px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #e8ebee;
+}
diff --git a/packages/editor-skeleton/src/layouts/TopArea/index.tsx b/packages/editor-skeleton/src/layouts/TopArea/index.tsx
new file mode 100644
index 000000000..c2f60a637
--- /dev/null
+++ b/packages/editor-skeleton/src/layouts/TopArea/index.tsx
@@ -0,0 +1,79 @@
+import React, { PureComponent } from 'react';
+import { Grid } from '@alifd/next';
+import TopPlugin from '../../components/TopPlugin';
+import './index.scss';
+
+const { Row, Col } = Grid;
+
+export default class TopArea extends PureComponent {
+ static displayName = 'lowcodeTopArea';
+
+ constructor(props) {
+ super(props);
+ this.editor = props.editor;
+ this.config = this.editor.config.plugins && this.editor.config.plugins.topArea;
+ }
+
+ componentDidMount() {
+ }
+ componentWillUnmount() {
+ }
+
+ handlePluginStatusChange = () => {};
+
+ renderPluginList = (list = []) => {
+ return list.map((item, idx) => {
+ const isDivider = item.type === 'Divider';
+
+ return (
+
+ {!isDivider && (
+
+ )}
+
+ );
+ });
+ };
+
+ render() {
+ if (!this.config) return null;
+ const leftList = [];
+ const rightList = [];
+ this.config.forEach(item => {
+ const align =
+ item.props && item.props.align === 'right' ? 'right' : 'left';
+ // 分隔符不允许相邻
+ if (item.type === 'Divider') {
+ const currentList = align === 'right' ? rightList : leftList;
+ if (
+ currList.length === 0 ||
+ currList[currList.length - 1].type === 'Divider'
+ )
+ return;
+ }
+ if (align === 'right') {
+ rightList.push(item);
+ } else {
+ leftList.push(item);
+ }
+ });
+
+ return (
+
+
{this.renderPluginList(leftList)}
+
{this.renderPluginList(rightList)}
+
+ );
+ }
+}
diff --git a/packages/editor-skeleton/src/locale/en-US.js b/packages/editor-skeleton/src/locale/en-US.js
new file mode 100644
index 000000000..36e3b219c
--- /dev/null
+++ b/packages/editor-skeleton/src/locale/en-US.js
@@ -0,0 +1,10 @@
+export default {
+ loading: 'loading...',
+ rejectRedirect: 'Redirect is not allowed',
+ expand: 'Unfold',
+ fold: 'Fold',
+ pageNotExist: 'The current Page not exist',
+ enterFromAppCenter: 'Please enter from the app center',
+ noPermission: 'Sorry, you do not have the develop permission',
+ getPermission: 'Please connect the app owners {owners} to get the permission',
+};
diff --git a/packages/editor-skeleton/src/locale/ja-JP.js b/packages/editor-skeleton/src/locale/ja-JP.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor-skeleton/src/locale/ja-JP.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor-skeleton/src/locale/zh-CN.js b/packages/editor-skeleton/src/locale/zh-CN.js
new file mode 100644
index 000000000..2d5229d2c
--- /dev/null
+++ b/packages/editor-skeleton/src/locale/zh-CN.js
@@ -0,0 +1,10 @@
+export default {
+ loading: '加载中...',
+ rejectRedirect: '开发中,已阻止发生跳转',
+ expand: '展开',
+ fold: '收起',
+ pageNotExist: '当前访问地址不存在',
+ enterFromAppCenter: '请从应用中心入口重新进入',
+ noPermission: '抱歉,您暂无开发权限',
+ getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限',
+};
diff --git a/packages/editor-skeleton/src/locale/zh-TW.js b/packages/editor-skeleton/src/locale/zh-TW.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor-skeleton/src/locale/zh-TW.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor-skeleton/tests/index.js b/packages/editor-skeleton/tests/index.js
new file mode 100644
index 000000000..346e384d2
--- /dev/null
+++ b/packages/editor-skeleton/tests/index.js
@@ -0,0 +1 @@
+// test file
diff --git a/packages/editor-skeleton/tsconfig.json b/packages/editor-skeleton/tsconfig.json
new file mode 100644
index 000000000..a511d68ba
--- /dev/null
+++ b/packages/editor-skeleton/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compileOnSave": false,
+ "buildOnSave": false,
+ "compilerOptions": {
+ "outDir": "build",
+ "module": "esnext",
+ "target": "es6",
+ "jsx": "react",
+ "moduleResolution": "node",
+ "lib": ["es6", "dom"],
+ "sourceMap": true,
+ "allowJs": true,
+ "noUnusedLocals": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/*.ts", "src/*.tsx"],
+ "exclude": ["node_modules", "build", "public"]
+}
diff --git a/packages/editor/.editorconfig b/packages/editor/.editorconfig
new file mode 100644
index 000000000..5760be583
--- /dev/null
+++ b/packages/editor/.editorconfig
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/packages/editor/.eslintignore b/packages/editor/.eslintignore
new file mode 100644
index 000000000..3b437e614
--- /dev/null
+++ b/packages/editor/.eslintignore
@@ -0,0 +1,11 @@
+# 忽略目录
+build/
+tests/
+demo/
+
+# node 覆盖率文件
+coverage/
+
+# 忽略文件
+**/*-min.js
+**/*.min.js
diff --git a/packages/editor/.eslintrc.js b/packages/editor/.eslintrc.js
new file mode 100644
index 000000000..18ae6baa7
--- /dev/null
+++ b/packages/editor/.eslintrc.js
@@ -0,0 +1,7 @@
+const { eslint, deepmerge } = require('@ice/spec');
+
+module.exports = deepmerge(eslint, {
+ rules: {
+ "global-require": 0,
+ },
+});
diff --git a/packages/editor/.gitignore b/packages/editor/.gitignore
new file mode 100644
index 000000000..c590da5b0
--- /dev/null
+++ b/packages/editor/.gitignore
@@ -0,0 +1,20 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# production
+/build
+/dist
+
+# misc
+.idea/
+.happypack
+.DS_Store
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# ignore d.ts auto generated by css-modules-typescript-loader
+*.module.scss.d.ts
\ No newline at end of file
diff --git a/packages/editor/.prettierrc b/packages/editor/.prettierrc
new file mode 100644
index 000000000..d3c963559
--- /dev/null
+++ b/packages/editor/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "printWidth": 120,
+ "singleQuote": true
+}
\ No newline at end of file
diff --git a/packages/editor/.stylelintignore b/packages/editor/.stylelintignore
new file mode 100644
index 000000000..82af6f60d
--- /dev/null
+++ b/packages/editor/.stylelintignore
@@ -0,0 +1,7 @@
+# 忽略目录
+build/
+tests/
+demo/
+
+# node 覆盖率文件
+coverage/
diff --git a/packages/editor/.stylelintrc.js b/packages/editor/.stylelintrc.js
new file mode 100644
index 000000000..eeb605b33
--- /dev/null
+++ b/packages/editor/.stylelintrc.js
@@ -0,0 +1,3 @@
+const { stylelint } = require('@ice/spec');
+
+module.exports = stylelint;
diff --git a/packages/editor/README.md b/packages/editor/README.md
new file mode 100644
index 000000000..6a1a5d0fa
--- /dev/null
+++ b/packages/editor/README.md
@@ -0,0 +1,20 @@
+# ice-typescript-starter
+
+## 使用
+
+- 启动调试服务: `npm start`
+- 构建 dist: `npm run build`
+
+## 目录结构
+
+- 入口文件: `src/index.jsx`
+- 导航配置: `src/config/menu.js`
+- 路由配置: `src/config/routes.js`
+- 路由入口: `src/router.jsx`
+- 布局文件: `src/layouts`
+- 通用组件: `src/components`
+- 页面文件: `src/pages`
+
+## 效果图
+
+
diff --git a/packages/editor/abc.json b/packages/editor/abc.json
new file mode 100644
index 000000000..dce1f92ed
--- /dev/null
+++ b/packages/editor/abc.json
@@ -0,0 +1,4 @@
+{
+ "type": "ice-scripts",
+ "builder": "@ali/builder-ice-scripts"
+}
diff --git a/packages/editor/ice.config.js b/packages/editor/ice.config.js
new file mode 100644
index 000000000..b40ce581a
--- /dev/null
+++ b/packages/editor/ice.config.js
@@ -0,0 +1,30 @@
+const path = require('path');
+
+module.exports = {
+ entry: 'src/index.tsx',
+ publicPath: './',
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ plugins: [
+ ['ice-plugin-fusion', {
+ themePackage: '@alife/dpl-iceluna',
+ }],
+ ['ice-plugin-moment-locales', {
+ locales: ['zh-cn'],
+ }],
+ ],
+ chainWebpack: (config) => {
+ // 修改对应 css module的 loader,默认修改 scss-module 同理可以修改 css-module 和 less-module 规则
+ ['scss-module'].forEach((rule) => {
+ if (config.module.rules.get(rule)) {
+ config.module.rule(rule).use('ts-css-module-loader')
+ .loader(require.resolve('css-modules-typescript-loader'))
+ .options({ modules: true, sass: true });
+ // 指定应用loader的位置
+ config.module.rule(rule).use('ts-css-module-loader').before('css-loader');
+ }
+ });
+ },
+};
+
diff --git a/packages/editor/jsconfig.json b/packages/editor/jsconfig.json
new file mode 100644
index 000000000..9e0f3c03d
--- /dev/null
+++ b/packages/editor/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "jsx": "react",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/packages/editor/package.json b/packages/editor/package.json
new file mode 100644
index 000000000..3ebc396ef
--- /dev/null
+++ b/packages/editor/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@icedesign/ts-scaffold",
+ "version": "0.0.1",
+ "description": "低代码编辑器",
+ "dependencies": {
+ "@ali/iceluna-addon-2": "^1.0.3",
+ "@ali/iceluna-sdk": "^1.0.5-beta.26",
+ "@alifd/next": "^1.x",
+ "@alife/dpl-iceluna": "^2.3.2",
+ "@icedesign/theme": "^1.x",
+ "@types/react": "^16.8.3",
+ "@types/react-dom": "^16.8.2",
+ "events": "^3.1.0",
+ "intl-messageformat": "^8.2.1",
+ "keymaster": "^1.6.2",
+ "moment": "^2.23.0",
+ "prop-types": "^15.5.8",
+ "react": "^16.8.1",
+ "react-dom": "^16.8.1",
+ "react-router-dom": "^5.1.2",
+ "store": "^2.0.12"
+ },
+ "devDependencies": {
+ "@ice/spec": "^0.1.1",
+ "@types/debug": "^4.1.5",
+ "@types/events": "^3.0.0",
+ "@types/store": "^2.0.2",
+ "css-modules-typescript-loader": "^2.0.4",
+ "eslint": "^6.0.1",
+ "ice-plugin-fusion": "^0.1.4",
+ "ice-plugin-moment-locales": "^0.1.0",
+ "ice-scripts": "^2.0.0",
+ "prettier": "^1.19.1",
+ "stylelint": "^10.1.0"
+ },
+ "scripts": {
+ "start": "ice-scripts dev",
+ "build": "ice-scripts build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss",
+ "prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,ejs,less,css,scss,json}\" "
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "iceworks": {
+ "type": "react",
+ "adapter": "adapter-react-v3"
+ },
+ "ideMode": {
+ "name": "ice-react"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ice-lab/react-materials/tree/master/scaffolds/ice-ts"
+ }
+}
diff --git a/packages/editor/public/favicon.png b/packages/editor/public/favicon.png
new file mode 100644
index 000000000..a2605c57e
Binary files /dev/null and b/packages/editor/public/favicon.png differ
diff --git a/packages/editor/public/index.html b/packages/editor/public/index.html
new file mode 100644
index 000000000..5c0692966
--- /dev/null
+++ b/packages/editor/public/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ ICE TypeScript Starter
+
+
+
+
+
+
diff --git a/packages/editor/src/config/components.js b/packages/editor/src/config/components.js
new file mode 100644
index 000000000..fe508c3e0
--- /dev/null
+++ b/packages/editor/src/config/components.js
@@ -0,0 +1,31 @@
+import logo from '../plugins/logo';
+import designer from '../plugins/designer';
+import undoRedo from '../plugins/undoRedo';
+import topBalloonIcon from '@ali/iceluna-addon-2';
+import topDialogIcon from '@ali/iceluna-addon-2';
+import leftPanelIcon from '@ali/iceluna-addon-2';
+import leftPanelIcon2 from '@ali/iceluna-addon-2';
+import leftBalloonIcon from '@ali/iceluna-addon-2';
+import leftDialogIcon from '@ali/iceluna-addon-2';
+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 PluginFactory from '../framework/pluginFactory';
+
+export default {
+ logo: PluginFactory(logo),
+ designer: PluginFactory(designer),
+ undoRedo: PluginFactory(undoRedo),
+ topBalloonIcon: PluginFactory(topBalloonIcon),
+ topDialogIcon: PluginFactory(topDialogIcon),
+ leftPanelIcon: PluginFactory(leftPanelIcon),
+ leftPanelIcon2: PluginFactory(leftPanelIcon2),
+ leftBalloonIcon: PluginFactory(leftBalloonIcon),
+ leftDialogIcon: PluginFactory(leftDialogIcon),
+ rightPanel1: PluginFactory(rightPanel1),
+ rightPanel2: PluginFactory(rightPanel2),
+ rightPanel3: PluginFactory(rightPanel3),
+ rightPanel4: PluginFactory(rightPanel4)
+};
diff --git a/packages/editor/src/config/constants.js b/packages/editor/src/config/constants.js
new file mode 100644
index 000000000..d7e16fed3
--- /dev/null
+++ b/packages/editor/src/config/constants.js
@@ -0,0 +1,3 @@
+export default {
+ namespace: 'page'
+};
diff --git a/packages/editor/src/config/locale/en-US.js b/packages/editor/src/config/locale/en-US.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/config/locale/en-US.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/config/locale/index.js b/packages/editor/src/config/locale/index.js
new file mode 100644
index 000000000..f4ad6c5da
--- /dev/null
+++ b/packages/editor/src/config/locale/index.js
@@ -0,0 +1,10 @@
+import en_us from './en-US';
+import zh_cn from './zh-CN';
+import zh_tw from './zh-TW';
+import ja_jp from './ja-JP';
+export default {
+ 'en-US': en_us,
+ 'zh-CN': zh_cn,
+ 'zh-TW': zh_tw,
+ 'ja-JP': ja_jp
+};
diff --git a/packages/editor/src/config/locale/ja-JP.js b/packages/editor/src/config/locale/ja-JP.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/config/locale/ja-JP.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/config/locale/zh-CN.js b/packages/editor/src/config/locale/zh-CN.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/config/locale/zh-CN.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/config/locale/zh-TW.js b/packages/editor/src/config/locale/zh-TW.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/config/locale/zh-TW.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/config/skeleton.js b/packages/editor/src/config/skeleton.js
new file mode 100644
index 000000000..765aa6c10
--- /dev/null
+++ b/packages/editor/src/config/skeleton.js
@@ -0,0 +1,266 @@
+export default {
+ version: '^1.0.2',
+ theme: {
+ dpl: {
+ package: '@alife/dpl-iceluna',
+ version: '^2.3.0'
+ },
+ scss: ''
+ },
+ constants: {
+ namespace: 'page'
+ },
+ utils: [],
+ plugins: {
+ topArea: [
+ {
+ pluginKey: 'logo',
+ type: 'Custom',
+ props: {
+ align: 'left',
+ width: 100
+ },
+ config: {
+ package: '@ali/lowcode-plugin-logo',
+ version: '1.0.0'
+ },
+ pluginProps: {
+ logo: 'https://img.alicdn.com/tfs/TB1mHYDxQP2gK0jSZPxXXacQpXa-112-64.png'
+ }
+ },
+ {
+ pluginKey: 'topBalloonIcon',
+ type: 'BalloonIcon',
+ props: {
+ align: 'left',
+ title: 'balloon',
+ icon: 'dengpao',
+ balloonProps: {
+ triggerType: 'click'
+ }
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'divider',
+ type: 'Divider',
+ props: {
+ align: 'left'
+ }
+ },
+ {
+ pluginKey: 'topDialogIcon',
+ type: 'DialogIcon',
+ props: {
+ align: 'left',
+ title: 'dialog',
+ icon: 'dengpao'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'undoRedo',
+ type: 'Custom',
+ props: {
+ align: 'right',
+ width: 90
+ },
+ config: {
+ package: '@ali/lowcode-plugin-undo-redo',
+ version: '1.0.0'
+ }
+ },
+ {
+ pluginKey: 'divider',
+ type: 'Divider',
+ props: {
+ align: 'right'
+ }
+ },
+ {
+ pluginKey: 'topLinkIcon',
+ type: 'LinkIcon',
+ props: {
+ align: 'right',
+ title: 'link',
+ icon: 'dengpao',
+ linkProps: {
+ href: '//www.taobao.com',
+ target: 'blank'
+ }
+ },
+ config: {},
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'topIcon',
+ type: 'Icon',
+ props: {
+ align: 'right',
+ title: 'icon',
+ icon: 'dengpao',
+ onClick: function(editor) {
+ alert('icon addon invoke, current activeKey: ' + editor.activeKey);
+ }
+ },
+ config: {},
+ pluginProps: {}
+ }
+ ],
+ leftArea: [
+ {
+ pluginKey: 'leftPanelIcon',
+ type: 'PanelIcon',
+ props: {
+ align: 'top',
+ title: 'panel',
+ icon: 'dengpao'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'leftBalloonIcon',
+ type: 'BalloonIcon',
+ props: {
+ align: 'top',
+ title: 'balloon',
+ icon: 'dengpao'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'leftPanelIcon2',
+ type: 'PanelIcon',
+ props: {
+ align: 'top',
+ title: 'panel2',
+ icon: 'dengpao'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'leftDialogIcon',
+ type: 'DialogIcon',
+ props: {
+ align: 'bottom',
+ title: 'dialog',
+ icon: 'dengpao'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'leftLinkIcon',
+ type: 'LinkIcon',
+ props: {
+ align: 'bottom',
+ title: 'link',
+ icon: 'dengpao',
+ linkProps: {
+ href: '//www.taobao.com',
+ target: 'blank'
+ }
+ },
+ config: {},
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'leftIcon',
+ type: 'Icon',
+ props: {
+ align: 'bottom',
+ title: 'icon',
+ icon: 'dengpao',
+ onClick: function(editor) {
+ alert('icon addon invoke, current activeKey: ' + editor.activeKey);
+ }
+ },
+ config: {},
+ pluginProps: {}
+ }
+ ],
+ rightArea: [
+ {
+ pluginKey: 'rightPanel1',
+ type: 'Panel',
+ props: {
+ title: '样式'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'rightPanel2',
+ type: 'Panel',
+ props: {
+ title: '属性',
+ icon: 'dengpao'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'rightPanel3',
+ type: 'Panel',
+ props: {
+ title: '事件'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ },
+ {
+ pluginKey: 'rightPanel4',
+ type: 'Panel',
+ props: {
+ title: '数据'
+ },
+ config: {
+ package: '@ali/iceluna-addon-2',
+ version: '^1.0.0'
+ },
+ pluginProps: {}
+ }
+ ],
+ centerArea: [{
+ pluginKey: 'designer',
+ config: {
+ package: '@ali/lowcode-plugin-designer',
+ version: '1.0.0'
+ }
+ }]
+ },
+ hooks: [],
+ shortCuts: []
+};
diff --git a/packages/editor/src/config/theme.scss b/packages/editor/src/config/theme.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor/src/config/utils.js b/packages/editor/src/config/utils.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/config/utils.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/framework/areaManager.ts b/packages/editor/src/framework/areaManager.ts
new file mode 100644
index 000000000..6dd029b45
--- /dev/null
+++ b/packages/editor/src/framework/areaManager.ts
@@ -0,0 +1,32 @@
+import Editor from './index';
+import { PluginConfig, PluginStatus } from './definitions';
+import { clone, deepEqual, transformToPromise } from './utils';
+
+export default class AreaManager {
+ private pluginStatus: PluginStatus;
+ private config: Array;
+ constructor(private editor: Editor, private area: string) {
+ this.config = (editor && editor.config && editor.config.plugins && editor.config.plugins[this.area]) || [];
+ this.pluginStatus = clone(editor.pluginStatus);
+ }
+
+ isPluginStatusUpdate(pluginType?: string): boolean {
+ const { pluginStatus } = this.editor;
+ const list = pluginType ? this.config.filter(item => item.type === pluginType) : this.config;
+
+ const isUpdate = list.some(item => !deepEqual(pluginStatus[item.pluginKey], this.pluginStatus[item.pluginKey]));
+ this.pluginStatus = clone(pluginStatus);
+ return isUpdate;
+ }
+
+ getVisiblePluginList(pluginType?: string): Array {
+ const res = this.config.filter(item => {
+ return !this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible;
+ });
+ return pluginType ? res.filter(item => item.type === pluginType) : res;
+ }
+
+ getPluginConfig(): Array {
+ return this.config;
+ }
+}
diff --git a/packages/editor/src/framework/context.ts b/packages/editor/src/framework/context.ts
new file mode 100644
index 000000000..78d3ce177
--- /dev/null
+++ b/packages/editor/src/framework/context.ts
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 000000000..5e30a4466
--- /dev/null
+++ b/packages/editor/src/framework/definitions.ts
@@ -0,0 +1,132 @@
+import * as React from 'react';
+import Editor from './editor';
+
+export interface EditorConfig {
+ skeleton?: SkeletonConfig;
+ theme?: ThemeConfig;
+ plugins?: PluginsConfig;
+ hooks?: HooksConfig;
+ shortCuts?: ShortCutsConfig;
+ utils?: UtilsConfig;
+ constants?: ConstantsConfig;
+ lifeCycles?: lifeCyclesConfig;
+ i18n?: I18nConfig;
+}
+
+export interface NpmConfig {
+ version: string;
+ package: string;
+ main?: string;
+ exportName?: string;
+ subName?: string;
+ destructuring?: boolean;
+}
+
+export interface SkeletonConfig {
+ config: NpmConfig;
+ props?: object;
+ handler?: (EditorConfig) => EditorConfig;
+}
+
+export interface FusionTheme {
+ package: string;
+ version: string;
+}
+
+export interface ThemeConfig {
+ fusion?: FusionTheme;
+}
+
+export interface PluginsConfig {
+ [propName: string]: Array;
+}
+
+export interface PluginConfig {
+ pluginKey: string;
+ type: string;
+ props: {
+ icon?: string;
+ title?: string;
+ width?: number;
+ height?: number;
+ visible?: boolean;
+ disabled?: boolean;
+ marked?: boolean;
+ align?: 'left' | 'right' | 'top' | 'bottom';
+ onClick?: () => void;
+ dialogProps?: object;
+ balloonProps?: object;
+ panelProps?: object;
+ linkProps?: object;
+ };
+ config?: NpmConfig;
+ pluginProps?: object;
+}
+
+export type HooksConfig = Array;
+
+export interface HookConfig {
+ message: string;
+ type: 'on' | 'once';
+ handler: (editor: Editor, ...args) => void;
+}
+
+export type ShortCutsConfig = Array;
+
+export interface ShortCutConfig {
+ keyboard: string;
+ handler: (editor: Editor, ev: React.KeyboardEventHandler, keymaster: any) => void;
+}
+
+export type UtilsConfig = Array;
+
+export interface UtilConfig {
+ name: string;
+ type: 'npm' | 'function';
+ content: NpmConfig | ((...args) => any);
+}
+
+export type ConstantsConfig = object;
+
+export interface lifeCyclesConfig {
+ init?: (editor: Editor) => any;
+ destroy?: (editor: Editor) => any;
+}
+
+export type LocaleType = 'zh-CN' | 'zh-TW' | 'en-US' | 'ja-JP';
+
+export interface I18nMessages {
+ [propName: string]: string;
+}
+
+export interface I18nConfig {
+ 'zh-CN'?: I18nMessages;
+ 'zh-TW'?: I18nMessages;
+ 'en-US'?: I18nMessages;
+ 'ja-JP'?: I18nMessages;
+}
+
+export type I18nFunction = (key: string, params: object) => string;
+
+export interface Utils {
+ [propName: string]: (...args) => any;
+}
+
+export interface PluginClass extends React.Component {
+ init?: (editor: Editor) => void;
+ open?: () => any;
+ close?: () => any;
+}
+
+export interface PluginComponents {
+ [propName: string]: PluginClass;
+}
+
+export interface PluginStatus {
+ [propName: string]: {
+ disabled: boolean;
+ visible: boolean;
+ marked: boolean;
+ locked: boolean;
+ };
+}
diff --git a/packages/editor/src/framework/editor.ts b/packages/editor/src/framework/editor.ts
new file mode 100644
index 000000000..4918a4f4b
--- /dev/null
+++ b/packages/editor/src/framework/editor.ts
@@ -0,0 +1,184 @@
+import EventEmitter from 'events';
+import Debug from 'debug';
+import store from 'store';
+import { EditorConfig, Utils, PluginComponents, PluginStatus, LocaleType, HooksConfig } from './definitions';
+
+import { unRegistShortCuts, registShortCuts, transformToPromise } from './utils';
+
+// 根据url参数设置debug选项
+const res = /_?debug=(.*?)(&|$)/.exec(location.search);
+if (res && res[1]) {
+ window.__isDebug = true;
+ store.storage.write('debug', res[1] === 'true' ? '*' : res[1]);
+} else {
+ window.__isDebug = false;
+ store.remove('debug');
+}
+
+//重要,用于矫正画布执行new Function的window对象上下文
+window.__newFunc = funContext => {
+ return new Function(funContext);
+};
+
+//关闭浏览器前提醒,只有产生过交互才会生效
+window.onbeforeunload = function(e) {
+ e = e || window.event;
+ // 本地调试不生效
+ if (location.href.indexOf('localhost') > 0) return;
+ var msg = '您确定要离开此页面吗?';
+ e.cancelBubble = true;
+ e.returnValue = msg;
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ return msg;
+};
+
+let instance: Editor;
+
+const debug = Debug('editor');
+EventEmitter.defaultMaxListeners = 100;
+
+export interface HooksFuncs {
+ [idx: number]: (msg: string, handler: (...args) => void) => void;
+}
+
+export default class Editor extends EventEmitter {
+ static getInstance = (config: EditorConfig, components: PluginComponents, utils?: Utils): Editor => {
+ if (!instance) {
+ instance = new Editor(config, components, utils);
+ }
+ return instance;
+ };
+
+ private hooksFuncs: HooksFuncs;
+
+ public pluginStatus: PluginStatus;
+ public plugins: PluginComponents;
+ 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;
+
+ constructor(public config: EditorConfig, public components: PluginComponents, public utils?: Utils) {
+ super();
+ instance = this;
+ this.init();
+ }
+
+ init(): Promise {
+ const { hooks, shortCuts = [], lifeCycles } = this.config || {};
+ this.locale = store.get('lowcode-editor-locale') || 'zh-CN';
+ // this.messages = this.messagesSet[this.locale];
+ // this.i18n = generateI18n(this.locale, this.messages);
+ this.pluginStatus = this.initPluginStatus();
+ this.initHooks(hooks || []);
+
+ this.emit('editor.beforeInit');
+ const init = (lifeCycles && lifeCycles.init) || (() => {});
+ // 用户可以通过设置extensions.init自定义初始化流程;
+ return transformToPromise(init(this))
+ .then(() => {
+ // 注册快捷键
+ registShortCuts(shortCuts, this);
+ this.emit('editor.afterInit');
+ return true;
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+
+ destroy() {
+ debug('destroy');
+ try {
+ const { hooks = [], shortCuts = [], lifeCycles = {} } = this.config;
+ unRegistShortCuts(shortCuts);
+ this.destroyHooks(hooks);
+ lifeCycles.destroy && lifeCycles.destroy(this);
+ } catch (err) {
+ console.warn(err);
+ return;
+ }
+ }
+
+ get(key: string): any {
+ return this[key];
+ }
+
+ set(key: string | object, val: any): void {
+ if (typeof key === 'string') {
+ if (['init', 'destroy', 'get', 'set', 'batchOn', 'batchOff', 'batchOnce'].includes(key)) {
+ console.error('init, destroy, get, set, batchOn, batchOff, batchOnce is private attribute');
+ return;
+ }
+ this[key] = val;
+ } else if (typeof key === 'object') {
+ Object.keys(key).forEach(item => {
+ this[item] = key[item];
+ });
+ }
+ }
+
+ batchOn(events: Array, lisenter: (...args) => void): void {
+ if (!Array.isArray(events)) return;
+ events.forEach(event => this.on(event, lisenter));
+ }
+
+ batchOnce(events: Array, lisenter: (...args) => void): void {
+ if (!Array.isArray(events)) return;
+ events.forEach(event => this.once(event, lisenter));
+ }
+
+ batchOff(events: Array, lisenter: (...args) => void): void {
+ if (!Array.isArray(events)) return;
+ events.forEach(event => this.off(event, lisenter));
+ }
+
+ //销毁hooks中的消息监听
+ private destroyHooks(hooks: HooksConfig = []) {
+ hooks.forEach((item, idx) => {
+ if (typeof this.hooksFuncs[idx] === 'function') {
+ this.off(item.message, this.hooksFuncs[idx]);
+ }
+ });
+ delete this.hooksFuncs;
+ }
+
+ //初始化hooks中的消息监听
+ private initHooks(hooks: HooksConfig = []): void {
+ this.hooksFuncs = hooks.map(item => {
+ const func = (...args) => {
+ item.handler(this, ...args);
+ };
+ this[item.type](item.message, func);
+ return func;
+ });
+ }
+
+ private initPluginStatus() {
+ const { plugins = {} } = this.config;
+ const pluginAreas = Object.keys(plugins);
+ const res = {};
+ pluginAreas.forEach(area => {
+ (plugins[area] || []).forEach(plugin => {
+ if (plugin.type === 'Divider') return;
+ const { visible, disabled, marked } = plugin.props || {};
+ res[plugin.pluginKey] = {
+ visible: typeof visible === 'boolean' ? visible : true,
+ disabled: typeof disabled === 'boolean' ? disabled : false,
+ marked: typeof marked === 'boolean' ? marked : false
+ };
+ const pluginClass = this.components[plugin.pluginKey];
+ // 判断如果编辑器插件有init静态方法,则在此执行init方法
+ if (pluginClass && pluginClass.init) {
+ pluginClass.init(this);
+ }
+ });
+ });
+ return res;
+ }
+}
diff --git a/packages/editor/src/framework/index.ts b/packages/editor/src/framework/index.ts
new file mode 100644
index 000000000..be8b55d0a
--- /dev/null
+++ b/packages/editor/src/framework/index.ts
@@ -0,0 +1,10 @@
+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 Editor;
+
+export const utils = editorUtils;
+export const definitions = editorDefinitions;
diff --git a/packages/editor/src/framework/pluginFactory.tsx b/packages/editor/src/framework/pluginFactory.tsx
new file mode 100644
index 000000000..b3a83ee83
--- /dev/null
+++ b/packages/editor/src/framework/pluginFactory.tsx
@@ -0,0 +1,87 @@
+import React, { PureComponent, createRef } from 'react';
+
+import EditorContext from './context';
+import Editor from './editor';
+import { isEmpty, generateI18n, transformToPromise, acceptsRef } from './utils';
+import { PluginConfig, I18nFunction } from './definitions';
+import Editor from './index';
+
+export interface PluginProps {
+ editor: Editor;
+ config: PluginConfig;
+}
+
+export interface InjectedPluginProps {
+ i18n?: I18nFunction;
+}
+
+export default function pluginFactory(
+ Comp: React.ComponentType
+): React.ComponentType {
+ class LowcodePlugin extends PureComponent {
+ static displayName = 'LowcodeEditorPlugin';
+ static defaultProps = {
+ config: {}
+ };
+ static contextType = EditorContext;
+ static init = Comp.init;
+ public ref = createRef();
+ private editor: Editor;
+ private pluginKey: string;
+ private i18n: I18nFunction;
+
+ constructor(props, context) {
+ super(props, context);
+ if (isEmpty(props.config) || !props.config.pluginKey) {
+ console.warn('lowcode editor plugin has wrong config');
+ return;
+ }
+ const { locale, messages, editor } = props;
+ // 注册插件
+ this.editor = editor;
+ this.i18n = generateI18n(locale, messages);
+ this.pluginKey = props.config.pluginKey;
+ editor.set('plugins', {
+ ...editor.plugins,
+ [this.pluginKey]: this
+ });
+ }
+
+ componentWillUnmount() {
+ // 销毁插件
+ if (this.editor && this.editor.plugins) {
+ delete this.editor.plugins[this.pluginKey];
+ }
+ }
+
+ open = (): Promise => {
+ if (this.ref && this.ref.open && typeof this.ref.open === 'function') {
+ return transformToPromise(this.ref.open());
+ }
+ return Promise.resolve();
+ };
+
+ close = () => {
+ if (this.ref && this.ref.close && typeof this.ref.close === 'function') {
+ return transformToPromise(this.ref.close());
+ }
+ return Promise.resolve();
+ };
+
+ render() {
+ const { config } = this.props;
+ const props = {
+ i18n: this.i18n,
+ editor: this.editor,
+ config,
+ ...config.pluginProps
+ };
+ if (acceptsRef(Comp)) {
+ props.ref = this.ref;
+ }
+ return ;
+ }
+ }
+
+ return LowcodePlugin;
+}
diff --git a/packages/editor/src/framework/utils.ts b/packages/editor/src/framework/utils.ts
new file mode 100644
index 000000000..d4a6e4061
--- /dev/null
+++ b/packages/editor/src/framework/utils.ts
@@ -0,0 +1,246 @@
+import IntlMessageFormat from 'intl-messageformat';
+import keymaster from 'keymaster';
+import { EditorConfig, LocaleType, I18nMessages, I18nFunction, ShortCutsConfig } from './definitions';
+import Editor from './editor';
+
+import _pick from 'lodash/pick';
+import _deepEqual from 'lodash/isEqualWith';
+import _clone from 'lodash/cloneDeep';
+import _isEmpty from 'lodash/isEmpty';
+import _throttle from 'lodash/throttle';
+import _debounce from 'lodash/debounce';
+
+export const pick = _pick;
+export const deepEqual = _deepEqual;
+export const clone = _clone;
+export const isEmpty = _isEmpty;
+export const throttle = _throttle;
+export const debounce = _debounce;
+
+import _serialize from 'serialize-javascript';
+export const serialize = _serialize;
+
+const ENV = {
+ TBE: 'TBE',
+ WEBIDE: 'WEB-IDE',
+ VSCODE: 'VSCODE',
+ WEB: 'WEB'
+};
+
+export interface IDEMessageParams {
+ action: string;
+ data: {
+ logKey: string;
+ gmKey: string;
+ goKey: string;
+ };
+}
+
+export interface Window {
+ sendIDEMessage: (IDEMessageParams) => void;
+ goldlog: {
+ record: (logKey: string, gmKey: string, goKey: string, method: 'GET' | 'POST') => void;
+ };
+ parent: Window;
+ is_theia: boolean;
+ vscode: boolean;
+}
+
+/**
+ * 用于构造国际化字符串处理函数
+ */
+export function generateI18n(locale: LocaleType = 'zh-CN', messages: I18nMessages = {}): I18nFunction {
+ return (key, values = {}) => {
+ if (!messages || !messages[key]) return '';
+ const formater = new IntlMessageFormat(messages[key], locale);
+ return formater.format(values);
+ };
+}
+
+/**
+ * 序列化参数
+ */
+export function serializeParams(obj: object): string {
+ if (typeof obj !== 'object') return '';
+ const res: Array = [];
+ Object.entries(obj).forEach(([key, val]) => {
+ if (val === null || val === undefined || val === '') return;
+ if (typeof val === 'object') {
+ res.push(`${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}`);
+ } else {
+ res.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
+ }
+ });
+ return res.join('&');
+}
+
+/**
+ * 黄金令箭埋点
+ * @param {String} gmKey 为黄金令箭业务类型
+ * @param {Object} params 参数
+ * @param {String} logKey 属性串
+ */
+export function goldlog(gmKey: string, params: object = {}, logKey: string = 'other'): void {
+ const global = window as Window;
+ const sendIDEMessage = global.sendIDEMessage || global.parent.sendIDEMessage;
+ const goKey = serializeParams({
+ env: getEnv(),
+ ...params
+ });
+ if (sendIDEMessage) {
+ sendIDEMessage({
+ action: 'goldlog',
+ data: {
+ logKey: `/iceluna.core.${logKey}`,
+ gmKey,
+ goKey
+ }
+ });
+ }
+ global.goldlog && global.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST');
+}
+
+/**
+ * 获取当前编辑器环境
+ */
+export function getEnv(): string {
+ const userAgent = navigator.userAgent;
+ const isVscode = /Electron\//.test(userAgent);
+ if (isVscode) return ENV.VSCODE;
+ const isTheia = window.is_theia === true;
+ if (isTheia) return ENV.WEBIDE;
+ return ENV.WEB;
+}
+
+// 注册快捷键
+export function registShortCuts(config: ShortCutsConfig, editor: Editor): void {
+ (config || []).forEach(item => {
+ keymaster(item.keyboard, ev => {
+ ev.preventDefault();
+ item.handler(editor, ev, keymaster);
+ });
+ });
+}
+
+// 取消注册快捷
+export function unRegistShortCuts(config: ShortCutsConfig): void {
+ (config || []).forEach(item => {
+ keymaster.unbind(item.keyboard);
+ });
+ if (window.parent.vscode) {
+ keymaster.unbind('command+c');
+ keymaster.unbind('command+v');
+ }
+}
+
+/**
+ * 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve
+ */
+export function transformToPromise(input: any): Promise<{}> {
+ if (input instanceof Promise) return input;
+ return new Promise((resolve, reject) => {
+ if (input || input === undefined) {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+}
+
+/**
+ * 将数组类型转换为Map类型
+ */
+interface MapOf {
+ [propName: string]: T;
+}
+export function transformArrayToMap(arr: Array, key: string, overwrite: boolean = true): MapOf {
+ if (isEmpty(arr) || !Array.isArray(arr)) return {};
+ const res = {};
+ arr.forEach(item => {
+ const curKey = item[key];
+ if (item[key] === undefined) return;
+ if (res[curKey] && !overwrite) return;
+ res[curKey] = item;
+ });
+ return res;
+}
+
+/**
+ * 解析url的查询参数
+ */
+interface Query {
+ [propName: string]: string;
+}
+export function parseSearch(search: string): Query {
+ if (!search || typeof search !== 'string') return {};
+ const str = search.replace(/^\?/, '');
+ let paramStr = str.split('&');
+ let res = {};
+ for (let i = 0; i < paramStr.length; i++) {
+ let regRes = paramStr[i].split('=');
+ if (regRes[0] && regRes[1]) {
+ res[regRes[0]] = decodeURIComponent(regRes[1]);
+ }
+ }
+ return res;
+}
+
+export function comboEditorConfig(defaultConfig: EditorConfig = {}, customConfig: EditorConfig): EditorConfig {
+ const { skeleton, theme, plugins, hooks, shortCuts, lifeCycles, constants, utils, i18n } = customConfig || {};
+
+ if (skeleton && skeleton.handler && typeof skeleton.handler === 'function') {
+ return skeleton.handler({
+ skeleton,
+ ...defaultConfig
+ });
+ }
+
+ const defaultShortCuts = transformArrayToMap(defaultConfig.shortCuts || [], 'keyboard');
+ const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard');
+ const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP'];
+ const i18nConfig = {};
+ localeList.forEach(key => {
+ i18nConfig[key] = {
+ ...(defaultConfig.i18n && defaultConfig.i18n[key]),
+ ...(i18n && i18n[key])
+ };
+ });
+ return {
+ skeleton,
+ theme: {
+ ...defaultConfig.theme,
+ ...theme
+ },
+ plugins: {
+ ...defaultConfig.plugins,
+ ...plugins
+ },
+ hooks: [...(defaultConfig.hooks || []), ...(hooks || [])],
+ shortCuts: Object.values({
+ ...defaultShortCuts,
+ ...customShortCuts
+ }),
+ lifeCycles: {
+ ...defaultConfig.lifeCycles,
+ ...lifeCycles
+ },
+ constants: {
+ ...defaultConfig.constants,
+ ...constants
+ },
+ utils: [...(defaultConfig.utils || []), ...(utils || [])],
+ i18n: i18nConfig
+ };
+}
+
+/**
+ * 判断当前组件是否能够设置ref
+ * @param {*} Comp 需要判断的组件
+ */
+export function acceptsRef(Comp: React.ComponentType) {
+ const hasSymbol = typeof Symbol === 'function' && Symbol['for'];
+ const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol['for']('react.forward_ref') : 0xead0;
+ return (
+ (Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent)
+ );
+}
diff --git a/packages/editor/src/global.scss b/packages/editor/src/global.scss
new file mode 100644
index 000000000..4802a89d4
--- /dev/null
+++ b/packages/editor/src/global.scss
@@ -0,0 +1,7 @@
+body {
+ font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma, Arial, PingFang SC-Light, Microsoft YaHei;
+ font-size: 12px;
+ * {
+ box-sizing: border-box;
+ }
+}
diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx
new file mode 100644
index 000000000..b0c5fee5a
--- /dev/null
+++ b/packages/editor/src/index.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+// import Skeleton from '@ali/lowcode-engine-skeleton';
+import { HashRouter as Router, Route } from 'react-router-dom';
+import Skeleton from './skeleton';
+import config from './config/skeleton';
+import components from './config/components';
+import utils from './config/utils';
+import constants from './config/constants';
+import messages from './config/locale';
+
+import pkg from '../package.json';
+import './global.scss';
+import './config/theme.scss';
+
+window.__pkg = pkg;
+
+const ICE_CONTAINER = document.getElementById('ice-container');
+
+if (!ICE_CONTAINER) {
+ throw new Error('当前页面不存在 节点.');
+}
+
+ReactDOM.render(
+
+ (
+
+ )}
+ />
+ ,
+ ICE_CONTAINER
+);
diff --git a/packages/editor/src/plugins/designer/index.scss b/packages/editor/src/plugins/designer/index.scss
new file mode 100644
index 000000000..0d512ea9a
--- /dev/null
+++ b/packages/editor/src/plugins/designer/index.scss
@@ -0,0 +1,5 @@
+.lowcode-plugin-designer {
+ background-color: #ffffff;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/packages/editor/src/plugins/designer/index.tsx b/packages/editor/src/plugins/designer/index.tsx
new file mode 100644
index 000000000..b5b0eda41
--- /dev/null
+++ b/packages/editor/src/plugins/designer/index.tsx
@@ -0,0 +1,152 @@
+import React, { PureComponent } from 'react';
+
+import Editor from '../../framework/index';
+import { PluginConfig } from '../../framework/definitions';
+
+import Designer from '../../../../designer/src/designer/designer-view';
+
+import './index.scss';
+
+export interface PluginProps {
+ editor: Editor;
+ config: PluginConfig;
+}
+
+const SCHEMA = {
+ "componentName": "Page",
+ "fileName": "test",
+ "dataSource": {
+ "list": [{
+ "id": "getComponentsMap",
+ "isInit": true,
+ "type": "doServer",
+ "options": {
+ "method": "POST",
+ "params": {
+ "libVersionIds": "1"
+ },
+ "uri": "getComponentsMap"
+ }
+ }]
+ },
+ "state": {
+ "text": "outter"
+ },
+ "props": {
+ "ref": "outterView",
+ "autoLoading": true
+ },
+ "children": [{
+ "componentName": "Form",
+ "props": {
+ "labelCol": 4,
+ "onSubmit": function onSubmit(value, error, field) {
+ //form内有htmlType="submit"的元素的时候会触发
+ alert(JSON.stringify(value))
+ },
+ "style": {},
+ "ref": "testForm"
+ },
+ "children": [{
+ "componentName": "FormItem",
+ "props": {
+ "label": "姓名:",
+ "name": "name",
+ "initValue": "李雷"
+ },
+ "children": [{
+ "componentName": "Input",
+ "props": {
+ "placeholder": "请输入",
+ "size": "medium",
+ "style": {
+ "width": 320
+ }
+ }
+ }]
+ }, {
+ "componentName": "FormItem",
+ "props": {
+ "label": "年龄:",
+ "name": "age",
+ "initValue": "22"
+ },
+ "children": [{
+ "componentName": "NumberPicker",
+ "props": {
+ "size": "medium",
+ "type": "normal"
+ }
+ }]
+ }, {
+ "componentName": "FormItem",
+ "props": {
+ "label": "职业:",
+ "name": "profession"
+ },
+ "children": [{
+ "componentName": "Select",
+ "props": {
+ "dataSource": [{
+ "label": "教师",
+ "value": "t"
+ }, {
+ "label": "医生",
+ "value": "d"
+ }, {
+ "label": "歌手",
+ "value": "s"
+ }]
+ }
+ }]
+ }, {
+ "componentName": "Div",
+ "props": {
+ "style": {
+ "textAlign": "center"
+ }
+ },
+ "children": [{
+ "componentName": "ButtonGroup",
+ "props": {},
+ "children": [{
+ "componentName": "Button",
+ "props": {
+ "type": "primary",
+ "style": {
+ "margin": "0 5px 0 5px"
+ },
+ "htmlType": "submit"
+ },
+ "children": "提交"
+ }, {
+ "componentName": "Button",
+ "props": {
+ "type": "normal",
+ "style": {
+ "margin": "0 5px 0 5px"
+ },
+ "htmlType": "reset"
+ },
+ "children": "重置"
+ }]
+ }]
+ }]
+ }]
+}
+
+export default class DesignerPlugin extends PureComponent {
+ static displayName: 'LowcodePluginDesigner';
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return (
+
+
+
+
);
+ }
+}
diff --git a/packages/editor/src/plugins/logo/index.scss b/packages/editor/src/plugins/logo/index.scss
new file mode 100644
index 000000000..e2701fbca
--- /dev/null
+++ b/packages/editor/src/plugins/logo/index.scss
@@ -0,0 +1,9 @@
+.lowcode-plugin-logo {
+ padding: 8px 16px;
+ .logo {
+ width: 56px;
+ height: 32px;
+ background-size: contain;
+ background-position: center;
+ }
+}
diff --git a/packages/editor/src/plugins/logo/index.tsx b/packages/editor/src/plugins/logo/index.tsx
new file mode 100644
index 000000000..c02bf7a71
--- /dev/null
+++ b/packages/editor/src/plugins/logo/index.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import './index.scss';
+import Editor from '../../framework/index';
+import { PluginConfig } from '../../framework/definitions';
+
+export interface PluginProps {
+ editor: Editor;
+ config: PluginConfig;
+ logo?: string;
+}
+
+export default function(props: PluginProps) {
+ return (
+
+ );
+}
diff --git a/packages/editor/src/plugins/undoRedo/index.scss b/packages/editor/src/plugins/undoRedo/index.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor/src/plugins/undoRedo/index.tsx b/packages/editor/src/plugins/undoRedo/index.tsx
new file mode 100644
index 000000000..2b1a47460
--- /dev/null
+++ b/packages/editor/src/plugins/undoRedo/index.tsx
@@ -0,0 +1,22 @@
+import React, {useState} from 'react';
+import './index.scss';
+import Editor from '../../framework/index';
+import { PluginConfig } from '../../framework/definitions';
+import TopIcon from '../../skeleton/components/TopIcon/index';
+
+export interface PluginProps {
+ editor: Editor;
+ config: PluginConfig;
+ logo?: string;
+}
+
+export default function(props: PluginProps) {
+ const [backEnable, setBackEnable] = useState(true);
+ const [forwardEnable, setForwardEnable] = useState(true);
+ return (
+
+
+
+
+ );
+}
diff --git a/packages/editor/src/skeleton/components/LeftPlugin/index.scss b/packages/editor/src/skeleton/components/LeftPlugin/index.scss
new file mode 100644
index 000000000..06b6ef63a
--- /dev/null
+++ b/packages/editor/src/skeleton/components/LeftPlugin/index.scss
@@ -0,0 +1,59 @@
+.lowcode-left-plugin {
+ font-size: 16px;
+ text-align: center;
+ line-height: 36px;
+ height: 36px;
+ position: relative;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ color: #777;
+ &.collapse {
+ height: 40px;
+ color: #8c8c8c;
+ border-bottom: 1px solid #bfbfbf;
+ }
+ &.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);
+ display: block;
+ position: absolute;
+ left: 50px;
+ top: 5px;
+ line-height: 18px;
+ font-size: 12px;
+ white-space: nowrap;
+ padding: 6px 8px;
+ border-radius: 4px;
+ background: rgba(0, 0, 0, 0.75);
+ color: #fff;
+ z-index: 100;
+ }
+ &:after {
+ content: '';
+ display: block;
+ position: absolute;
+ left: 40px;
+ top: 15px;
+ border: 5px solid transparent;
+ border-right-color: rgba(0, 0, 0, 0.75);
+ z-index: 100;
+ }
+ }
+}
diff --git a/packages/editor/src/skeleton/components/LeftPlugin/index.tsx b/packages/editor/src/skeleton/components/LeftPlugin/index.tsx
new file mode 100644
index 000000000..6d807548c
--- /dev/null
+++ b/packages/editor/src/skeleton/components/LeftPlugin/index.tsx
@@ -0,0 +1,203 @@
+import React, { PureComponent, Fragment } from 'react';
+import classNames from 'classnames';
+import { Balloon, Dialog, Icon, Badge } from '@alifd/next';
+
+import './index.scss';
+import Editor from '../../../framework/editor';
+import { PluginConfig, PluginClass } from '../../../framework/definitions';
+
+export interface LeftPluginProps {
+ active?: boolean;
+ config: PluginConfig;
+ disabled?: boolean;
+ editor: Editor;
+ locked?: boolean;
+ marked?: boolean;
+ onClick?: () => void;
+ pluginClass: PluginClass;
+}
+
+export interface LeftPluginState {
+ dialogVisible: boolean;
+}
+
+export default class LeftPlugin extends PureComponent {
+ static displayName = 'LowcodeLeftPlugin';
+
+ static defaultProps = {
+ active: false,
+ config: {},
+ disabled: false,
+ marked: false,
+ locked: false,
+ onClick: () => {}
+ };
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ dialogVisible: false
+ };
+ }
+
+ componentDidMount() {
+ const { config, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ if (editor && pluginKey) {
+ editor.on(`${pluginKey}.dialog.show`, this.handleShow);
+ editor.on(`${pluginKey}.dialog.close`, this.handleClose);
+ }
+ }
+
+ componentWillUnmount() {
+ const { config, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ if (editor && pluginKey) {
+ editor.off(`${pluginKey}.dialog.show`, this.handleShow);
+ editor.off(`${pluginKey}.dialog.close`, this.handleClose);
+ }
+ }
+
+ handleClose = () => {
+ const { config, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ const plugin = editor.plugins && editor.plugins[pluginKey];
+ if (plugin) {
+ plugin.close().then(() => {
+ this.setState({
+ dialogVisible: false
+ });
+ });
+ }
+ };
+
+ handleOpen = () => {
+ // todo 对话框类型的插件初始时拿不到插件实例
+ this.setState({
+ dialogVisible: true
+ });
+ };
+
+ handleShow = () => {
+ const { disabled, config, onClick, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ if (disabled || !pluginKey) return;
+ //考虑到弹窗情况,延时发送消息
+ setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0);
+ this.handleOpen();
+ onClick && onClick();
+ };
+
+ renderIcon = clickCallback => {
+ const { active, disabled, marked, locked, onClick, config } = this.props;
+ const { pluginKey, props } = config || {};
+ const { icon, title } = props || {};
+ return (
+ {
+ if (disabled) return;
+ //考虑到弹窗情况,延时发送消息
+ clickCallback && clickCallback();
+ onClick && onClick();
+ }}
+ >
+ {marked ? (
+
+
+
+ ) : (
+
+ )}
+
+ );
+ };
+
+ render() {
+ const { marked, locked, active, disabled, config, editor, pluginClass: Comp } = this.props;
+ const { pluginKey, props, type, pluginProps } = config || {};
+ const { onClick, title } = props || {};
+ const { dialogVisible } = this.state;
+ if (!pluginKey || !type || !props) return null;
+
+ const node =
+ (Comp && (
+ {
+ onClick && onClick.call(null, editor);
+ }}
+ {...pluginProps}
+ />
+ )) ||
+ null;
+
+ switch (type) {
+ case 'LinkIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ })}
+
+ );
+ case 'Icon':
+ return this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ });
+ case 'DialogIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ this.handleOpen();
+ })}
+
+
+ );
+ case 'BalloonIcon':
+ return (
+ {
+ onClick && onClick.call(null, editor);
+ })}
+ align="r"
+ triggerType={['click', 'hover']}
+ {...(props.balloonProps || {})}
+ >
+ {node}
+
+ );
+ case 'PanelIcon':
+ return this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ this.handleOpen();
+ });
+ case 'Custom':
+ return marked ? {node} : node;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/packages/editor/src/skeleton/components/Panel/index.scss b/packages/editor/src/skeleton/components/Panel/index.scss
new file mode 100644
index 000000000..cd3211ab4
--- /dev/null
+++ b/packages/editor/src/skeleton/components/Panel/index.scss
@@ -0,0 +1,52 @@
+.lowcode-panel {
+ user-select: none;
+ overflow: hidden;
+ position: relative;
+ background: #ffffff;
+ transition: width 0.3s ease;
+ transform: translate3d(0, 0, 0);
+ height: 100%;
+ &.visible {
+ border-right: 1px solid #bfbfbf;
+ }
+ .drag-area {
+ display: none;
+ }
+ &.floatable {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ z-index: 999;
+ }
+ &.draggable {
+ .drag-area {
+ display: block;
+ width: 10px;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ cursor: col-resize;
+ z-index: 9999;
+ }
+ &.left {
+ .drag-area {
+ right: 0;
+ }
+ }
+ &.right {
+ .drag-area {
+ left: 0;
+ }
+ }
+ }
+ &.left {
+ &.floatable {
+ left: 44px;
+ }
+ }
+ &.right {
+ &.floatable {
+ right: 44px;
+ }
+ }
+}
diff --git a/packages/editor/src/skeleton/components/Panel/index.tsx b/packages/editor/src/skeleton/components/Panel/index.tsx
new file mode 100644
index 000000000..a1b9a9a8e
--- /dev/null
+++ b/packages/editor/src/skeleton/components/Panel/index.tsx
@@ -0,0 +1,60 @@
+import React, { PureComponent } from 'react';
+import classNames from 'classnames';
+
+import './index.scss';
+
+export interface PanelProps {
+ align: 'left' | 'right';
+ defaultWidth: number;
+ minWidth: number;
+ draggable: boolean;
+ floatable: boolean;
+ children: Plugin;
+ visible: boolean;
+}
+
+export interface PanelState {
+ width: number;
+}
+
+export default class Panel extends PureComponent {
+ static displayName = 'LowcodePanel';
+
+ static defaultProps = {
+ align: 'left',
+ defaultWidth: 240,
+ minWidth: 100,
+ draggable: true,
+ floatable: false,
+ visible: true
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ width: props.defaultWidth
+ };
+ }
+
+ render() {
+ const { align, draggable, floatable, visible } = this.props;
+ const { width } = this.state;
+ return (
+
+ {this.props.children}
+
+
+ );
+ }
+}
diff --git a/packages/editor/src/skeleton/components/TopIcon/index.scss b/packages/editor/src/skeleton/components/TopIcon/index.scss
new file mode 100644
index 000000000..67622fb64
--- /dev/null
+++ b/packages/editor/src/skeleton/components/TopIcon/index.scss
@@ -0,0 +1,67 @@
+.next-btn.next-large.lowcode-top-btn {
+ width: 44px;
+ height: 44px;
+ padding: 0;
+ margin: 2px -2px;
+ text-align: center;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ color: #777;
+ &.disabled {
+ cursor: not-allowed;
+ color: $color-text1-1;
+ }
+ &.locked {
+ color: red !important;
+ }
+ &:hover {
+ background-color: $color-brand1-1;
+ color: $color-brand1-6;
+ &:before {
+ content: attr(data-tooltip);
+ display: block;
+ height: auto;
+ width: auto;
+ position: absolute;
+ left: 50%;
+ transform: translate(-50%, 0);
+ bottom: -35px;
+ line-height: 18px;
+ font-size: 12px;
+ white-space: nowrap;
+ padding: 6px 8px;
+ border-radius: 4px;
+ background: rgba(0, 0, 0, 0.75);
+ color: #fff;
+ z-index: 100;
+ }
+ &:after {
+ content: '';
+ display: block;
+ position: absolute;
+ left: 50%;
+ transform: translate(-50%, 0);
+ bottom: -5px;
+ border: 5px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.75);
+ opacity: 1;
+ visibility: visible;
+ z-index: 100;
+ }
+ }
+ i.next-icon {
+ &:before {
+ font-size: 17px;
+ }
+ margin-right: 0;
+ line-height: 18px;
+ }
+ span {
+ display: block;
+ margin: 0px -5px 0;
+ line-height: 16px;
+ text-align: center;
+ font-size: 12px;
+ transform: scale(0.8);
+ }
+}
diff --git a/packages/editor/src/skeleton/components/TopIcon/index.tsx b/packages/editor/src/skeleton/components/TopIcon/index.tsx
new file mode 100644
index 000000000..64d27a6d7
--- /dev/null
+++ b/packages/editor/src/skeleton/components/TopIcon/index.tsx
@@ -0,0 +1,60 @@
+import React, { PureComponent } from 'react';
+import classNames from 'classnames';
+import { Icon, Button } from '@alifd/next';
+
+import './index.scss';
+
+export interface TopIconProps {
+ active?: boolean;
+ className?: string;
+ disabled?: boolean;
+ icon: string;
+ id?: string;
+ 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: '',
+ disabled: false,
+ icon: '',
+ id: '',
+ locked: false,
+ onClick: () => {},
+ showTitle: false,
+ style: {},
+ title: ''
+ };
+
+ render() {
+ const { active, disabled, icon, locked, title, className, id, style, showTitle, onClick } = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/packages/editor/src/skeleton/components/TopPlugin/index.scss b/packages/editor/src/skeleton/components/TopPlugin/index.scss
new file mode 100644
index 000000000..4bdd7b8d2
--- /dev/null
+++ b/packages/editor/src/skeleton/components/TopPlugin/index.scss
@@ -0,0 +1,2 @@
+.lowcode-top-addon {
+}
diff --git a/packages/editor/src/skeleton/components/TopPlugin/index.tsx b/packages/editor/src/skeleton/components/TopPlugin/index.tsx
new file mode 100644
index 000000000..6c4f280d0
--- /dev/null
+++ b/packages/editor/src/skeleton/components/TopPlugin/index.tsx
@@ -0,0 +1,191 @@
+import React, { PureComponent, Fragment } from 'react';
+
+import TopIcon from '../TopIcon';
+import { Balloon, Badge, Dialog } from '@alifd/next';
+
+import './index.scss';
+import { PluginConfig, PluginClass } from '../../../framework/definitions';
+import Editor from '../../../framework/editor';
+
+export interface TopPluginProps {
+ active?: boolean;
+ config: PluginConfig;
+ disabled?: boolean;
+ editor: Editor;
+ locked?: boolean;
+ marked?: boolean;
+ onClick?: () => void;
+ pluginClass: PluginClass;
+}
+
+export interface TopPluginState {
+ dialogVisible: boolean;
+}
+
+export default class TopPlugin extends PureComponent {
+ static displayName = 'LowcodeTopPlugin';
+
+ static defaultProps = {
+ active: false,
+ config: {},
+ disabled: false,
+ marked: false,
+ locked: false,
+ onClick: () => {}
+ };
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ dialogVisible: false
+ };
+ }
+
+ componentDidMount() {
+ const { config, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ if (editor && pluginKey) {
+ editor.on(`${pluginKey}.dialog.show`, this.handleShow);
+ editor.on(`${pluginKey}.dialog.close`, this.handleClose);
+ }
+ }
+
+ componentWillUnmount() {
+ const { config, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ if (editor && pluginKey) {
+ editor.off(`${pluginKey}.dialog.show`, this.handleShow);
+ editor.off(`${pluginKey}.dialog.close`, this.handleClose);
+ }
+ }
+
+ handleShow = () => {
+ const { disabled, config, onClick, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ if (disabled || !pluginKey) return;
+ //考虑到弹窗情况,延时发送消息
+ setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0);
+ this.handleOpen();
+ onClick && onClick();
+ };
+
+ handleClose = () => {
+ const { config, editor } = this.props;
+ const pluginKey = config && config.pluginKey;
+ const plugin = editor.plugins && editor.plugins[pluginKey];
+ if (plugin) {
+ plugin.close().then(() => {
+ this.setState({
+ dialogVisible: false
+ });
+ });
+ }
+ };
+
+ handleOpen = () => {
+ // todo dialog类型的插件初始时拿不动插件实例
+ this.setState({
+ dialogVisible: true
+ });
+ };
+
+ renderIcon = clickCallback => {
+ const { active, disabled, marked, locked, config, onClick, editor } = this.props;
+ const { pluginKey, props } = config || {};
+ const { icon, title } = props || {};
+ const node = (
+ {
+ if (disabled) return;
+ //考虑到弹窗情况,延时发送消息
+ setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0);
+ clickCallback && clickCallback();
+ onClick && onClick();
+ }}
+ />
+ );
+ return marked ? {node} : node;
+ };
+
+ render() {
+ const { active, marked, locked, disabled, config, editor, pluginClass: Comp } = this.props;
+ const { pluginKey, pluginProps, props, type } = config || {};
+ const { onClick, title } = props || {};
+ const { dialogVisible } = this.state;
+ if (!pluginKey || !type) return null;
+ const node =
+ (Comp && (
+ {
+ onClick && onClick.call(null, editor);
+ }}
+ {...pluginProps}
+ />
+ )) ||
+ null;
+
+ switch (type) {
+ case 'LinkIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ })}
+
+ );
+ case 'Icon':
+ return this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ });
+ case 'DialogIcon':
+ return (
+
+ {this.renderIcon(() => {
+ onClick && onClick.call(null, editor);
+ this.handleOpen();
+ })}
+
+
+ );
+ case 'BalloonIcon':
+ return (
+ {
+ onClick && onClick.call(null, editor);
+ })}
+ triggerType={['click', 'hover']}
+ {...props.balloonProps}
+ >
+ {node}
+
+ );
+ case 'Custom':
+ return marked ? {node} : node;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/packages/editor/src/skeleton/config/skeleton.ts b/packages/editor/src/skeleton/config/skeleton.ts
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/skeleton/config/skeleton.ts
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/skeleton/config/utils.ts b/packages/editor/src/skeleton/config/utils.ts
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/skeleton/config/utils.ts
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/skeleton/global.scss b/packages/editor/src/skeleton/global.scss
new file mode 100644
index 000000000..514f5b463
--- /dev/null
+++ b/packages/editor/src/skeleton/global.scss
@@ -0,0 +1,32 @@
+body {
+ font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma, Arial, PingFang SC-Light, Microsoft YaHei;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
+ * {
+ box-sizing: border-box;
+ }
+}
+.next-loading {
+ .next-loading-wrap {
+ height: 100%;
+ }
+}
+.lowcode-editor {
+ .lowcode-main-content {
+ position: absolute;
+ top: 48px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ background-color: #d8d8d8;
+ }
+ .lowcode-center-area {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 10px;
+ overflow: auto;
+ }
+}
diff --git a/packages/editor/src/skeleton/index.tsx b/packages/editor/src/skeleton/index.tsx
new file mode 100644
index 000000000..b721c36bb
--- /dev/null
+++ b/packages/editor/src/skeleton/index.tsx
@@ -0,0 +1,131 @@
+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 { comboEditorConfig, parseSearch } from '../framework/utils';
+
+import defaultConfig from './config/skeleton';
+import skeletonUtils from './config/utils';
+
+import TopArea from './layouts/TopArea';
+import LeftArea from './layouts/LeftArea';
+import CenterArea from './layouts/CenterArea';
+import RightArea from './layouts/RightArea';
+
+import './global.scss';
+
+let renderIdx = 0;
+
+export interface SkeletonProps {
+ components: PluginComponents;
+ config: EditorConfig;
+ history: object;
+ location: object;
+ match: object;
+ utils: Utils;
+}
+
+export interface SkeletonState {
+ initReady: boolean;
+ skeletonKey: string;
+ __hasError?: boolean;
+}
+
+export default class Skeleton extends PureComponent {
+ static displayName = 'LowcodeEditorSkeleton';
+
+ static getDerivedStateFromError() {
+ return {
+ __hasError: true
+ };
+ }
+
+ private editor: Editor;
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ initReady: false,
+ skeletonKey: `skeleton${renderIdx}`
+ };
+
+ this.init();
+ }
+
+ componentWillUnmount() {
+ this.editor && this.editor.destroy();
+ }
+
+ componentDidCatch(err) {
+ console.error(err);
+ }
+
+ init = (isReset: boolean = false): void => {
+ if (this.editor) {
+ this.editor.destroy();
+ }
+ const { utils, config, components } = this.props;
+ const editor = (this.editor = new Editor(comboEditorConfig(defaultConfig, config), components, {
+ ...skeletonUtils,
+ ...utils
+ }));
+ window.__ctx = {
+ editor,
+ appHelper: editor
+ };
+ editor.once('editor.reset', () => {
+ this.setState({
+ initReady: false
+ });
+ editor.emit('editor.beforeReset');
+ this.init(true);
+ });
+
+ this.editor.init().then(() => {
+ this.setState(
+ {
+ initReady: true,
+ //刷新IDE时生成新的skeletonKey保证插件生命周期重新执行
+ skeletonKey: isReset ? `skeleton${++renderIdx}` : this.state.skeletonKey
+ },
+ () => {
+ editor.emit('editor.ready');
+ isReset && editor.emit('ide.afterReset');
+ }
+ );
+ });
+ };
+
+ render() {
+ const { initReady, skeletonKey, __hasError } = this.state;
+ const { location, history, match } = this.props;
+ if (__hasError || !this.editor) {
+ return 'error';
+ }
+
+ location.query = parseSearch(location.search);
+ this.editor.set('location', location);
+ this.editor.set('history', history);
+ this.editor.set('match', match);
+
+ return (
+
+
+
+
+
+ );
+ }
+}
diff --git a/packages/editor/src/skeleton/layouts/CenterArea/index.scss b/packages/editor/src/skeleton/layouts/CenterArea/index.scss
new file mode 100644
index 000000000..b2584ed2b
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/CenterArea/index.scss
@@ -0,0 +1,3 @@
+.lowcode-center-area {
+ padding: 12px;
+}
diff --git a/packages/editor/src/skeleton/layouts/CenterArea/index.tsx b/packages/editor/src/skeleton/layouts/CenterArea/index.tsx
new file mode 100644
index 000000000..1389a00e6
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/CenterArea/index.tsx
@@ -0,0 +1,48 @@
+import React, { PureComponent } from 'react';
+
+import Editor from '../../../framework/editor';
+import './index.scss';
+import AreaManager from '../../../framework/areaManager';
+
+export interface CenterAreaProps {
+ editor: Editor;
+}
+
+export default class CenterArea extends PureComponent {
+ static displayName = 'LowcodeCenterArea';
+
+ private editor: Editor;
+ private areaManager: AreaManager;
+
+ constructor(props) {
+ super(props);
+ this.editor = props.editor;
+ this.areaManager = new AreaManager(this.editor, 'centerArea');
+ }
+
+ componentDidMount() {
+ this.editor.on('skeleton.update', this.handleSkeletonUpdate);
+ }
+ componentWillUnmount() {
+ this.editor.off('skeleton.update', this.handleSkeletonUpdate);
+ }
+
+ handleSkeletonUpdate = (): void => {
+ // 当前区域插件状态改变是更新区域
+ if (this.areaManager.isPluginStatusUpdate()) {
+ this.forceUpdate();
+ }
+ };
+
+ render() {
+ const visiblePluginList = this.areaManager.getVisiblePluginList();
+ return (
+
+ {visiblePluginList.map(item => {
+ 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
new file mode 100644
index 000000000..dac1b6b0a
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/LeftArea/index.scss
@@ -0,0 +1,21 @@
+.lowcode-left-area-nav {
+ width: 48px;
+ height: 100%;
+ background: #ffffff;
+ border-right: 1px solid #e8ebee;
+ position: relative;
+ .top-area {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ background: #ffffff;
+ max-height: 100%;
+ }
+ .bottom-area {
+ position: absolute;
+ bottom: 20px;
+ width: 100%;
+ background: #ffffff;
+ max-height: calc(100% - 20px);
+ }
+}
diff --git a/packages/editor/src/skeleton/layouts/LeftArea/index.tsx b/packages/editor/src/skeleton/layouts/LeftArea/index.tsx
new file mode 100644
index 000000000..7f4684546
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/LeftArea/index.tsx
@@ -0,0 +1,7 @@
+import Nav from './nav';
+import Panel from './panel';
+
+export default {
+ Nav,
+ Panel
+};
diff --git a/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx b/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx
new file mode 100644
index 000000000..a5ed5cd8f
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx
@@ -0,0 +1,172 @@
+import React, { PureComponent } from 'react';
+import LeftPlugin from '../../components/LeftPlugin';
+import './index.scss';
+import Editor from '../../../framework/editor';
+import { PluginConfig } from '../../../framework/definitions';
+import AreaManager from '../../../framework/areaManager';
+
+export interface LeftAreaNavProps {
+ editor: Editor;
+}
+
+export interface LeftAreaNavState {
+ activeKey: string;
+}
+
+export default class LeftAreaNav extends PureComponent {
+ static displayName = 'LowcodeLeftAreaNav';
+
+ private editor: Editor;
+ private areaManager: AreaManager;
+ private cacheActiveKey: string;
+
+ constructor(props) {
+ super(props);
+ this.editor = props.editor;
+ this.areaManager = new AreaManager(this.editor, 'leftArea');
+
+ this.state = {
+ activeKey: 'none'
+ };
+ this.cacheActiveKey = 'none';
+ }
+
+ componentDidMount() {
+ this.editor.on('skeleton.update', this.handleSkeletonUpdate);
+ this.editor.on('leftNav.change', this.handlePluginChange);
+ const visiblePanelPluginList = this.areaManager.getVisiblePluginList().filter(item => item.type === 'IconPanel');
+ const defaultKey = (visiblePanelPluginList[0] && visiblePanelPluginList[0].pluginKey) || 'componentAttr';
+ this.handlePluginChange(defaultKey);
+ }
+ componentWillUnmount() {
+ this.editor.off('skeleton.update', this.handleSkeletonUpdate);
+ this.editor.off('leftNav.change', this.handlePluginChange);
+ }
+
+ handleSkeletonUpdate = (): void => {
+ // 当前区域插件状态改变是更新区域
+ if (this.areaManager.isPluginStatusUpdate()) {
+ this.forceUpdate();
+ }
+ };
+
+ handlePluginChange = (key: string): void => {
+ const { activeKey } = this.state;
+ const plugins = this.editor.plugins;
+ const prePlugin = plugins[activeKey];
+ const nextPlugin = plugins[key];
+ if (activeKey === 'none') {
+ if (nextPlugin) {
+ nextPlugin.open().then(() => {
+ this.updateActiveKey(key);
+ });
+ }
+ } else if (activeKey === key) {
+ if (prePlugin) {
+ prePlugin.close().then(() => {
+ this.updateActiveKey('none');
+ });
+ }
+ } else {
+ // 先关后开
+ if (prePlugin) {
+ prePlugin.close().then(() => {
+ if (nextPlugin) {
+ nextPlugin.open().then(() => {
+ 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');
+ });
+ }
+ }
+ };
+
+ handlePluginClick = (item: PluginConfig): void => {
+ if (item.type === 'PanelIcon') {
+ this.handlePluginChange(item.pluginKey);
+ }
+ };
+
+ updateActiveKey = (key: string): void => {
+ 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 => {
+ 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}
+ />
+ );
+ });
+ };
+
+ render() {
+ const { activeKey } = this.state;
+ const topList: Array = [];
+ const bottomList: Array = [];
+ const visiblePluginList = this.areaManager.getVisiblePluginList();
+ visiblePluginList.forEach(item => {
+ const align = item.props && item.props.align === 'bottom' ? 'bottom' : 'top';
+ if (align === 'bottom') {
+ bottomList.push(item);
+ } else {
+ topList.push(item);
+ }
+ });
+
+ return (
+
+
{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
new file mode 100644
index 000000000..8c03247fd
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx
@@ -0,0 +1,70 @@
+import React, { PureComponent, Fragment } from 'react';
+import Panel from '../../components/Panel';
+import './index.scss';
+import Editor from '../../../framework/editor';
+import AreaManager from '../../../framework/areaManager';
+
+export interface LeftAreaPanelProps {
+ editor: Editor;
+}
+
+export interface LeftAreaPanelState {
+ activeKey: string;
+}
+
+export default class LeftAreaPanel extends PureComponent {
+ static displayName = 'LowcodeLeftAreaPanel';
+
+ private editor: Editor;
+ private areaManager: AreaManager;
+
+ constructor(props) {
+ super(props);
+ this.editor = props.editor;
+ this.areaManager = new AreaManager(this.editor, 'leftArea');
+
+ this.state = {
+ activeKey: 'none'
+ };
+ }
+
+ componentDidMount() {
+ this.editor.on('skeleton.update', this.handleSkeletonUpdate);
+ this.editor.on('leftPanel.show', this.handlePluginChange);
+ }
+ componentWillUnmount() {
+ this.editor.off('skeleton.update', this.handleSkeletonUpdate);
+ this.editor.off('leftPanel.show', this.handlePluginChange);
+ }
+
+ handleSkeletonUpdate = (): void => {
+ // 当前区域插件状态改变是更新区域
+ if (this.areaManager.isPluginStatusUpdate('PanelIcon')) {
+ this.forceUpdate();
+ }
+ };
+
+ handlePluginChange = (key: string): void => {
+ this.setState({
+ activeKey: key
+ });
+ };
+
+ render() {
+ const { activeKey } = this.state;
+ const list = this.areaManager.getVisiblePluginList('PanelIcon');
+
+ return (
+
+ {list.map((item, idx) => {
+ 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
new file mode 100644
index 000000000..fd05f5644
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/RightArea/index.scss
@@ -0,0 +1,52 @@
+.lowcode-right-area {
+ width: 300px;
+ 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;
+ }
+ }
+
+ //tab定义
+ .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;
+ }
+ }
+}
diff --git a/packages/editor/src/skeleton/layouts/RightArea/index.tsx b/packages/editor/src/skeleton/layouts/RightArea/index.tsx
new file mode 100644
index 000000000..5b51d02fb
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/RightArea/index.tsx
@@ -0,0 +1,159 @@
+import React, { PureComponent } from 'react';
+import { Tab, Badge, Icon } from '@alifd/next';
+import './index.scss';
+import Editor from '../../../framework/editor';
+import AreaManager from '../../../framework/areaManager';
+import { PluginConfig } from '../../../framework/definitions';
+
+export interface RightAreaProps {
+ editor: Editor;
+}
+
+export interface RightAreaState {
+ activeKey: string;
+}
+
+export default class RightArea extends PureComponent {
+ static displayName = 'LowcodeRightArea';
+
+ private editor: Editor;
+ private areaManager: AreaManager;
+
+ constructor(props) {
+ super(props);
+ this.editor = props.editor;
+ this.areaManager = new AreaManager(this.editor, 'rightArea');
+ this.state = {
+ activeKey: ''
+ };
+ }
+
+ componentDidMount() {
+ this.editor.on('skeleton.update', this.handleSkeletonUpdate);
+ this.editor.on('rightNav.change', this.handlePluginChange);
+ const visiblePluginList = this.areaManager.getVisiblePluginList();
+ const defaultKey = (visiblePluginList[0] && visiblePluginList[0].pluginKey) || 'componentAttr';
+ this.handlePluginChange(defaultKey, true);
+ }
+ componentWillUnmount() {
+ this.editor.off('skeleton.update', this.handleSkeletonUpdate);
+ this.editor.off('rightNav.change', this.handlePluginChange);
+ }
+
+ handleSkeletonUpdate = (): void => {
+ // 当前区域插件状态改变是更新区域
+ if (this.areaManager.isPluginStatusUpdate()) {
+ const pluginStatus = this.editor.pluginStatus;
+ const activeKey = this.state.activeKey;
+ if (pluginStatus[activeKey] && pluginStatus[activeKey].visible) {
+ this.forceUpdate();
+ } else {
+ const currentPlugin = this.editor.plugins[activeKey];
+ if (currentPlugin) {
+ currentPlugin.close().then(() => {
+ this.setState(
+ {
+ activeKey: ''
+ },
+ () => {
+ const visiblePluginList = this.areaManager.getVisiblePluginList();
+ const firstPlugin = visiblePluginList && visiblePluginList[0];
+ if (firstPlugin) {
+ this.handlePluginChange(firstPlugin.pluginKey);
+ }
+ }
+ );
+ });
+ }
+ }
+ }
+ };
+
+ handlePluginChange = (key: string, isinit?: boolean): void => {
+ const activeKey = this.state.activeKey;
+ const plugins = this.editor.plugins || {};
+ const openPlugin = () => {
+ if (!plugins[key]) {
+ console.error(`plugin ${key} has not regist in the editor`);
+ return;
+ }
+ plugins[key].open().then(() => {
+ this.editor.set('rightNav', key);
+ this.setState({
+ activeKey: key
+ });
+ });
+ };
+ if (key === activeKey && !isinit) return;
+ if (activeKey && plugins[activeKey]) {
+ plugins[activeKey].close().then(() => {
+ openPlugin();
+ });
+ } else {
+ openPlugin();
+ }
+ };
+
+ renderTabTitle = (config: PluginConfig): React.ReactElement => {
+ const { icon, title } = config.props || {};
+ const pluginStatus = this.editor.pluginStatus[config.pluginKey];
+ const { marked, disabled, locked } = pluginStatus;
+ const active = this.state.activeKey === config.pluginKey;
+
+ const renderTitle = (): React.ReactElement => (
+
+ {!!icon && (
+
+ )}
+ {title}
+
+ );
+ if (marked) {
+ return {renderTitle()};
+ }
+ return renderTitle();
+ };
+
+ render() {
+ const visiblePluginList = this.areaManager.getVisiblePluginList();
+ return (
+
+
+ {visiblePluginList.map((item, idx) => {
+ const Comp = this.editor.components[item.pluginKey];
+ return (
+
+
+
+ );
+ })}
+
+
+ );
+ }
+}
diff --git a/packages/editor/src/skeleton/layouts/TopArea/index.scss b/packages/editor/src/skeleton/layouts/TopArea/index.scss
new file mode 100644
index 000000000..98af961da
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/TopArea/index.scss
@@ -0,0 +1,26 @@
+.lowcode-top-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 48px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #e8ebee;
+ user-select: none;
+ .divider {
+ max-width: 0;
+ margin: 8px 12px;
+ height: 30px;
+ border-right: 1px solid rgba(191, 191, 191, 0.3);
+ }
+ .next-col {
+ text-align: center;
+ }
+ .right-area {
+ position: absolute;
+ right: 12px;
+ top: 0;
+ height: 100%;
+ background: #ffffff;
+ }
+}
diff --git a/packages/editor/src/skeleton/layouts/TopArea/index.tsx b/packages/editor/src/skeleton/layouts/TopArea/index.tsx
new file mode 100644
index 000000000..df8828a9d
--- /dev/null
+++ b/packages/editor/src/skeleton/layouts/TopArea/index.tsx
@@ -0,0 +1,89 @@
+import React, { PureComponent } from 'react';
+import { Grid } from '@alifd/next';
+import TopPlugin from '../../components/TopPlugin';
+import './index.scss';
+import Editor from '../../../framework/index';
+import { PluginConfig } from '../../../framework/definitions';
+import AreaManager from '../../../framework/areaManager';
+
+const { Row, Col } = Grid;
+
+export interface TopAreaProps {
+ editor: Editor;
+}
+
+export default class TopArea extends PureComponent {
+ static displayName = 'LowcodeTopArea';
+
+ private areaManager: AreaManager;
+ private editor: Editor;
+
+ constructor(props) {
+ super(props);
+ this.editor = props.editor;
+ this.areaManager = new AreaManager(props.editor, 'topArea');
+ }
+
+ componentDidMount() {
+ this.editor.on('skeleton.update', this.handleSkeletonUpdate);
+ }
+ componentWillUnmount() {
+ this.editor.off('skeleton.update', this.handleSkeletonUpdate);
+ }
+
+ handleSkeletonUpdate = (): void => {
+ // 当前区域插件状态改变是更新区域
+ if (this.areaManager.isPluginStatusUpdate()) {
+ this.forceUpdate();
+ }
+ };
+
+ renderPluginList = (list: Array = []): Array => {
+ return list.map((item, idx) => {
+ const isDivider = item.type === 'Divider';
+ return (
+
+ {!isDivider && (
+
+ )}
+
+ );
+ });
+ };
+
+ render() {
+ const leftList: Array = [];
+ const rightList: Array = [];
+ const visiblePluginList = this.areaManager.getVisiblePluginList();
+ visiblePluginList.forEach(item => {
+ const align = item.props && item.props.align === 'right' ? 'right' : 'left';
+ // 分隔符不允许相邻
+ if (item.type === 'Divider') {
+ const currList = align === 'right' ? rightList : leftList;
+ if (currList.length === 0 || currList[currList.length - 1].type === 'Divider') return;
+ }
+ if (align === 'right') {
+ rightList.push(item);
+ } else {
+ leftList.push(item);
+ }
+ });
+ return (
+
+
+ {this.renderPluginList(leftList)}
+
+
+ {this.renderPluginList(rightList)}
+
+
+ );
+ }
+}
diff --git a/packages/editor/src/skeleton/locale/en-US.js b/packages/editor/src/skeleton/locale/en-US.js
new file mode 100644
index 000000000..936701e33
--- /dev/null
+++ b/packages/editor/src/skeleton/locale/en-US.js
@@ -0,0 +1,10 @@
+export default {
+ loading: 'loading...',
+ rejectRedirect: 'Redirect is not allowed',
+ expand: 'Unfold',
+ fold: 'Fold',
+ pageNotExist: 'The current Page not exist',
+ enterFromAppCenter: 'Please enter from the app center',
+ noPermission: 'Sorry, you do not have the develop permission',
+ getPermission: 'Please connect the app owners {owners} to get the permission'
+};
diff --git a/packages/editor/src/skeleton/locale/ja-JP.js b/packages/editor/src/skeleton/locale/ja-JP.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/skeleton/locale/ja-JP.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/skeleton/locale/zh-CN.js b/packages/editor/src/skeleton/locale/zh-CN.js
new file mode 100644
index 000000000..efe4ea898
--- /dev/null
+++ b/packages/editor/src/skeleton/locale/zh-CN.js
@@ -0,0 +1,10 @@
+export default {
+ loading: '加载中...',
+ rejectRedirect: '开发中,已阻止发生跳转',
+ expand: '展开',
+ fold: '收起',
+ pageNotExist: '当前访问地址不存在',
+ enterFromAppCenter: '请从应用中心入口重新进入',
+ noPermission: '抱歉,您暂无开发权限',
+ getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限'
+};
diff --git a/packages/editor/src/skeleton/locale/zh-TW.js b/packages/editor/src/skeleton/locale/zh-TW.js
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/packages/editor/src/skeleton/locale/zh-TW.js
@@ -0,0 +1 @@
+export default {};
diff --git a/packages/editor/src/skeleton/skeleton.tsx b/packages/editor/src/skeleton/skeleton.tsx
new file mode 100644
index 000000000..7fdec576f
--- /dev/null
+++ b/packages/editor/src/skeleton/skeleton.tsx
@@ -0,0 +1,138 @@
+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 { comboEditorConfig, parseSearch } from '../framework/utils';
+
+import defaultConfig from './config/skeleton';
+import skeletonUtils from './config/utils';
+
+import TopArea from './layouts/TopArea';
+import LeftArea from './layouts/LeftArea';
+import CenterArea from './layouts/CenterArea';
+import RightArea from './layouts/RightArea';
+
+import './global.scss';
+
+let renderIdx = 0;
+
+export interface SkeletonProps {
+ components: PluginComponents;
+ config: EditorConfig;
+ utils: Utils;
+}
+
+export interface SkeletonState {
+ initReady: boolean;
+ skeletonKey: string;
+ __hasError?: boolean;
+}
+
+export default class Skeleton extends PureComponent {
+ static displayName = 'LowcodeEditorSkeleton';
+
+ static getDerivedStateFromError() {
+ return {
+ __hasError: true
+ };
+ }
+
+ private editor: Editor;
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ initReady: false,
+ skeletonKey: `skeleton${renderIdx}`
+ };
+
+ this.init();
+ }
+
+ componentWillUnmount() {
+ this.editor && this.editor.destroy();
+ }
+
+ componentDidCatch(err) {
+ console.error(err);
+ }
+
+ init = (isReset: boolean = false): void => {
+ if (this.editor) {
+ this.editor.destroy();
+ }
+ const { utils, config, components } = this.props;
+ debugger;
+ const editor = (this.editor = new Editor(comboEditorConfig(defaultConfig, config), components, {
+ ...skeletonUtils,
+ ...utils
+ }));
+ window.__ctx = {
+ editor,
+ appHelper: editor
+ };
+ editor.once('editor.reset', () => {
+ this.setState({
+ initReady: false
+ });
+ editor.emit('editor.beforeReset');
+ this.init(true);
+ });
+
+ this.editor.init().then(() => {
+ this.setState(
+ {
+ initReady: true,
+ //刷新IDE时生成新的skeletonKey保证插件生命周期重新执行
+ skeletonKey: isReset ? `skeleton${++renderIdx}` : this.state.skeletonKey
+ },
+ () => {
+ editor.emit('editor.ready');
+ isReset && editor.emit('ide.afterReset');
+ }
+ );
+ });
+ };
+
+ render() {
+ const { initReady, skeletonKey, __hasError } = this.state;
+ if (__hasError || !this.editor) {
+ return 'error';
+ }
+
+ return (
+
+ {
+ const { location, history, match } = props;
+ location.query = parseSearch(location.search);
+ this.editor.set('location', location);
+ this.editor.set('history', history);
+ this.editor.set('match', match);
+ console.log('&&&&&&&&&&');
+ return (
+
+
+
+
+
+ );
+ }}
+ />
+
+ );
+ }
+}
diff --git a/packages/editor/tests/index.js b/packages/editor/tests/index.js
new file mode 100644
index 000000000..346e384d2
--- /dev/null
+++ b/packages/editor/tests/index.js
@@ -0,0 +1 @@
+// test file
diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json
new file mode 100644
index 000000000..3f5e62810
--- /dev/null
+++ b/packages/editor/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compileOnSave": false,
+ "buildOnSave": false,
+ "compilerOptions": {
+ "baseUrl": ".",
+ "outDir": "build",
+ "module": "esnext",
+ "target": "es6",
+ "jsx": "react",
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "lib": ["es6", "dom"],
+ "sourceMap": true,
+ "allowJs": true,
+ "rootDir": "src",
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": false,
+ "importHelpers": true,
+ "strictNullChecks": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "noUnusedLocals": true,
+ "skipLibCheck": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src/*"],
+ "exclude": ["node_modules", "build", "public"]
+}