daily tag

This commit is contained in:
下羊 2020-03-08 16:32:25 +08:00
parent 9e26b961eb
commit c9d139bf30
91 changed files with 4182 additions and 580 deletions

View File

@ -38,16 +38,16 @@
"@types/lodash": "^4.14.149", "@types/lodash": "^4.14.149",
"@types/react": "^16.9.13", "@types/react": "^16.9.13",
"@types/react-dom": "^16.9.4", "@types/react-dom": "^16.9.4",
"build-plugin-component": "^0.2.0", "build-plugin-component": "^0.2.7-1",
"build-plugin-fusion": "^0.1.0", "build-plugin-fusion": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0", "build-plugin-moment-locales": "^0.1.0",
"eslint": "^6.0.1", "eslint": "^6.0.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"react": "^16.3.0", "react": "^16.8.0",
"react-dom": "^16.3.0" "react-dom": "^16.8.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.3.0", "react": "^16.8.0",
"@alifd/next": "1.x" "@alifd/next": "1.x"
}, },
"license": "MIT", "license": "MIT",

View File

@ -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<PluginConfig>
};
export interface PluginConfig {
pluginKey: string,
type: string,
props: object,
config: NpmConfig,
pluginProps: object
};
export type HooksConfig = Array<HookConfig>;
export interface HookConfig {
};

View File

@ -10,6 +10,48 @@ export interface pluginProps {
messages: object messages: object
} }
export default function plugin(Comp) {
class Plugin extends PureComponent<pluginProps> {
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 <Comp i18n={this.i18n} editor={this.editor} config={config} {...config.pluginProps}/>
}
}
return Plugin;
}
export class Plugin extends PureComponent<pluginProps> { export class Plugin extends PureComponent<pluginProps> {

View File

@ -213,5 +213,30 @@ export function transformToPromise(input) {
} }
export function comboEditorConfig(defaultConfig, customConfig) { 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()
};
} }

View File

@ -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;
}

View File

@ -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 };

View File

@ -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;
}
}
}

View File

@ -1,6 +1,30 @@
/// <reference types="react" /> import { PureComponent } from 'react';
export interface Props { import './index.scss';
name: string; 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;
} }
declare const Greeting: ({ name }: Props) => JSX.Element;
export default Greeting;

View File

@ -1,14 +1,76 @@
import React from 'react'; 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 Greeting = function Greeting(_ref) { var TopIcon = /*#__PURE__*/function (_PureComponent) {
var name = _ref.name; _inheritsLoose(TopIcon, _PureComponent);
return React.createElement("div", {
style: { function TopIcon() {
textAlign: 'center', return _PureComponent.apply(this, arguments) || this;
fontSize: '40px', }
fontWeight: 'bold'
} var _proto = TopIcon.prototype;
}, "Hello, ", name);
_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 = {
export default Greeting; active: false,
className: '',
disabled: false,
icon: '',
id: '',
locked: false,
onClick: function onClick() {},
showTitle: false,
style: {},
title: ''
};
export { TopIcon as default };

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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 };

View File

@ -0,0 +1,2 @@
.lowcode-top-addon {
}

View File

@ -1,3 +1,13 @@
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 {
.next-loading-wrap { .next-loading-wrap {
height: 100%; height: 100%;
@ -6,17 +16,17 @@
.lowcode-editor { .lowcode-editor {
.lowcode-main-content { .lowcode-main-content {
position: absolute; position: absolute;
top: 54px; top: 48px;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
display: flex; display: flex;
background-color: #d8d8d8;
} }
.lowcode-center-area { .lowcode-center-area {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: #f7f7f7;
padding: 10px; padding: 10px;
overflow: auto; overflow: auto;
} }

View File

@ -1,5 +1,4 @@
/// <reference types="react" /> import { PureComponent } from 'react';
import { PureComponent } from 'react-dom';
import './global.scss'; import './global.scss';
export default class Skeleton extends PureComponent { export default class Skeleton extends PureComponent {
static displayName: string; static displayName: string;

View File

@ -1,15 +1,29 @@
import _ConfigProvider from "@alifd/next/es/config-provider"; import _ConfigProvider from "@alifd/next/es/config-provider";
import _Loading from "@alifd/next/es/loading"; import _Loading from "@alifd/next/es/loading";
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
import React, { PureComponent } from 'react-dom'; // import Editor from '@ali/lowcode-engine-editor'; 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'; import './global.scss';
var Skeleton = /*#__PURE__*/function (_PureComponent) { var Skeleton = /*#__PURE__*/function (_PureComponent) {
_inheritsLoose(Skeleton, _PureComponent); _inheritsLoose(Skeleton, _PureComponent);
function Skeleton(props) { function Skeleton(props) {
return _PureComponent.call(this, props) || this; // this.editor = new Editor(props.config, props.utils); 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; var _proto = Skeleton.prototype;
@ -23,17 +37,30 @@ var Skeleton = /*#__PURE__*/function (_PureComponent) {
location = _this$props.location, location = _this$props.location,
history = _this$props.history, history = _this$props.history,
messages = _this$props.messages; messages = _this$props.messages;
return React.createElement(_ConfigProvider, { this.editor.location = location;
locale: messages[appHelper.locale] this.editor.history = history;
}, React.createElement(_Loading, { this.editor.messages = messages;
tip: this.i18n('loading'), return React.createElement(_ConfigProvider, null, React.createElement(_Loading, {
tip: "Loading",
size: "large", size: "large",
visible: loading || !initReady, visible: false,
shape: "fusion-reactor", shape: "fusion-reactor",
fullScreen: true fullScreen: true
}, React.createElement("div", { }, React.createElement("div", {
className: "lowcode-editor" 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; return Skeleton;

View File

@ -0,0 +1,3 @@
.lowcode-center-area {
padding: 12px;
}

View File

@ -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);
}
}

View File

@ -13,7 +13,7 @@ var LeftAreaPanel = /*#__PURE__*/function (_PureComponent) {
_proto.render = function render() { _proto.render = function render() {
return React.createElement("div", { return React.createElement("div", {
className: "lowcode-left-area" className: "lowcode-left-area-panel"
}); });
}; };

View File

@ -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;
}
}
}
}
}

View File

@ -3,5 +3,9 @@ import './index.scss';
export default class TopArea extends PureComponent { export default class TopArea extends PureComponent {
static displayName: string; static displayName: string;
constructor(props: any); constructor(props: any);
componentDidMount(): void;
componentWillUnmount(): void;
handlePluginStatusChange: () => void;
renderPluginList: (list?: any[]) => JSX.Element[];
render(): JSX.Element; render(): JSX.Element;
} }

View File

@ -1,20 +1,79 @@
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
import _Grid from "@alifd/next/es/grid";
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import TopPlugin from '../../components/TopPlugin';
import './index.scss'; import './index.scss';
var Row = _Grid.Row,
Col = _Grid.Col;
var TopArea = /*#__PURE__*/function (_PureComponent) { var TopArea = /*#__PURE__*/function (_PureComponent) {
_inheritsLoose(TopArea, _PureComponent); _inheritsLoose(TopArea, _PureComponent);
function TopArea(props) { function TopArea(props) {
return _PureComponent.call(this, props) || this; 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; var _proto = TopArea.prototype;
_proto.componentDidMount = function componentDidMount() {};
_proto.componentWillUnmount = function componentWillUnmount() {};
_proto.render = function render() { _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", { return React.createElement("div", {
className: "lowcode-top-area" className: "lowcode-top-area"
}); }, React.createElement("div", {
className: "left-area"
}, this.renderPluginList(leftList)), React.createElement("div", {
classname: "right-area"
}, this.renderPluginList(rightList)));
}; };
return TopArea; return TopArea;

View File

@ -0,0 +1,5 @@
.lowcode-top-area {
height: 48px;
background-color: #ffffff;
border-bottom: 1px solid #e8ebee;
}

View File

@ -1,2 +1,8 @@
import '@alifd/next/es/config-provider/style'; import '@alifd/next/es/config-provider/style';
import '@alifd/next/es/loading/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';

View File

@ -2,6 +2,26 @@
"name": "@ali/lowcode-engine-skeleton", "name": "@ali/lowcode-engine-skeleton",
"version": "0.0.1", "version": "0.0.1",
"description": "alibaba lowcode editor skeleton", "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", "author": "xiayang.xy",
"dependencies": { "dependencies": {
"@alifd/next": "^1.x", "@alifd/next": "^1.x",
@ -21,7 +41,7 @@
"@types/lodash": "^4.14.149", "@types/lodash": "^4.14.149",
"@types/react": "^16.9.13", "@types/react": "^16.9.13",
"@types/react-dom": "^16.9.4", "@types/react-dom": "^16.9.4",
"build-plugin-component": "^0.2.0", "build-plugin-component": "^0.2.7-1",
"build-plugin-fusion": "^0.1.0", "build-plugin-fusion": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0", "build-plugin-moment-locales": "^0.1.0",
"eslint": "^6.0.1", "eslint": "^6.0.1",
@ -29,23 +49,6 @@
"react": "^16.8.0", "react": "^16.8.0",
"react-dom": "^16.8.0" "react-dom": "^16.8.0"
}, },
"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}\" "
},
"engines": {
"node": ">=8.0.0"
},
"iceworks": {
"type": "react",
"adapter": "adapter-react-v3"
},
"ideMode": {
"name": "ice-react"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/ice-lab/react-materials/tree/master/scaffolds/ice-ts" "url": "https://github.com/ice-lab/react-materials/tree/master/scaffolds/ice-ts"

View File

@ -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;
}
}
}

View File

@ -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 (
<div
className={classNames('luna-left-addon', addonKey, {
active,
disabled,
locked,
})}
data-tooltip={title}
onClick={() => {
if (disabled) return;
//考虑到弹窗情况,延时发送消息
clickCallback && clickCallback();
onClick && onClick();
}}
>
{dotted ? (
<Badge dot>
<Icon type={icon} size="small" />
</Badge>
) : (
<Icon type={icon} size="small" />
)}
</div>
);
};
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 && (
<AddonComp
active={active}
locked={locked}
disabled={disabled}
config={config}
onClick={() => {
onClick && onClick.call(null, appHelper);
}}
{...localeProps}
{...(addonProps || {})}
/>
)) ||
null;
switch (type) {
case 'LinkIcon':
return (
<a {...(props.linkProps || {})}>
{this.renderIcon(() => {
onClick && onClick.call(null, appHelper);
})}
</a>
);
case 'Icon':
return this.renderIcon(() => {
onClick && onClick.call(null, appHelper);
});
case 'DialogIcon':
return (
<Fragment>
{this.renderIcon(() => {
onClick && onClick.call(null, appHelper);
this.handleOpen();
})}
<Dialog
onOk={() => {
appHelper.emit(`${addonKey}.dialog.onOk`);
this.handleClose();
}}
onCancel={this.handleClose}
onClose={this.handleClose}
title={title}
{...(props.dialogProps || {})}
visible={dialogVisible}
>
{node}
</Dialog>
</Fragment>
);
case 'BalloonIcon':
return (
<Balloon
trigger={this.renderIcon(() => {
onClick && onClick.call(null, appHelper);
})}
align="r"
triggerType={['click', 'hover']}
{...(props.balloonProps || {})}
>
{node}
</Balloon>
);
case 'PanelIcon':
return this.renderIcon(() => {
onClick && onClick.call(null, appHelper);
this.handleOpen();
});
case 'Custom':
return dotted ? <Badge dot>{node}</Badge> : node;
default:
return null;
}
}
}

View File

@ -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);
}
}

View File

@ -1,15 +1,68 @@
import React from 'react'; import React, { PureComponent } from 'react';
export interface Props { import PropTypes from 'prop-types';
name: string; 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 (
<Button
type="normal"
size="large"
text={true}
className={classNames('lowcode-top-btn', className, {
active,
disabled,
locked,
})}
id={id}
style={style}
onClick={disabled ? null : onClick}
>
<div>
<Icon size="large" type={icon} />
{showTitle && <span>{title}</span>}
</div>
</Button>
);
}
} }
const Greeting = ({ name }: Props) => {
return (
<div style={{ textAlign: 'center', fontSize: '40px', fontWeight: 'bold' }}>
Hello, {name}
</div>
);
};
export default Greeting;

View File

@ -0,0 +1,2 @@
.lowcode-top-addon {
}

View File

@ -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 = (
<TopIcon
className={`lowcode-top-addon ${pluginKey}`}
active={active}
disabled={disabled}
locked={locked}
icon={icon}
title={title}
onClick={() => {
if (disabled) return;
//考虑到弹窗情况,延时发送消息
setTimeout(
() => this.appHelper.emit(`${pluginKey}.addon.activate`),
0,
);
clickCallback && clickCallback();
onClick && onClick();
}}
/>
);
return dotted ? <Badge dot>{node}</Badge> : 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 = <Comp
active={active}
locked={locked}
disabled={disabled}
config={config}
onClick={() => {
onClick && onClick.call(null, editor);
}}
{...pluginProps}
/>;
switch (type) {
case 'LinkIcon':
return (
<a {...props.linkProps}>
{this.renderIcon(() => {
onClick && onClick.call(null, editor);
})}
</a>
);
case 'Icon':
return this.renderIcon(() => {
onClick && onClick.call(null, editor);
});
case 'DialogIcon':
return (
<Fragment>
{this.renderIcon(() => {
onClick && onClick.call(null, editor);
this.handleOpen();
})}
<Dialog
onOk={() => {
editor.emit(`${pluginKey}.dialog.onOk`);
this.handleClose();
}}
onCancel={this.handleClose}
onClose={this.handleClose}
title={title}
{...props.dialogProps}
visible={dialogVisible}
>
{node}
</Dialog>
</Fragment>
);
case 'BalloonIcon':
return (
<Balloon
trigger={this.renderIcon(() => {
onClick && onClick.call(null, editor);
})}
triggerType={['click', 'hover']}
{...props.balloonProps}
>
{node}
</Balloon>
);
case 'Custom':
return dotted ? <Badge dot>{node}</Badge> : node;
default:
return null;
}
}
}

View File

@ -13,7 +13,7 @@ const routerConfig = [
{ {
path: '/', path: '/',
redirect: '/dashboard', redirect: '/dashboard',
} },
], ],
}, },
]; ];

View File

@ -1,3 +1,13 @@
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 {
.next-loading-wrap { .next-loading-wrap {
height: 100%; height: 100%;
@ -6,17 +16,17 @@
.lowcode-editor { .lowcode-editor {
.lowcode-main-content { .lowcode-main-content {
position: absolute; position: absolute;
top: 54px; top: 48px;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
display: flex; display: flex;
background-color: #d8d8d8;
} }
.lowcode-center-area { .lowcode-center-area {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: #f7f7f7;
padding: 10px; padding: 10px;
overflow: auto; overflow: auto;
} }

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react-dom'; import React, { PureComponent } from 'react';
// import Editor from '@ali/lowcode-engine-editor'; // import Editor from '@ali/lowcode-engine-editor';
import { Loading, ConfigProvider } from '@alifd/next'; import { Loading, ConfigProvider } from '@alifd/next';
@ -17,6 +17,12 @@ export default class Skeleton extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
// this.editor = new Editor(props.config, props.utils); // this.editor = new Editor(props.config, props.utils);
this.editor = {
on: () => {},
off: () => {},
config: props.config,
pluginComponents: props.pluginComponents
};
} }
componentWillUnmount() { componentWillUnmount() {
@ -26,24 +32,26 @@ export default class Skeleton extends PureComponent {
render() { render() {
const { location, history, messages } = this.props; const { location, history, messages } = this.props;
this.editor.location = location;
this.editor.history = history;
this.editor.messages = messages;
return ( return (
<ConfigProvider locale={messages[appHelper.locale]}> <ConfigProvider>
<Loading <Loading
tip={this.i18n('loading')} tip="Loading"
size="large" size="large"
visible={loading || !initReady} visible={false}
shape="fusion-reactor" shape="fusion-reactor"
fullScreen fullScreen
> >
<div className="lowcode-editor"> <div className="lowcode-editor">
{/* <TopArea/> <TopArea editor={this.editor}/>
<div className="lowcode-main-content"> <div className="lowcode-main-content">
<LeftArea.Nav/> <LeftArea.Nav editor={this.editor}/>
<LeftArea.Panel/> <LeftArea.Panel editor={this.editor}/>
<CenterArea/> <CenterArea editor={this.editor}/>
<RightArea/> <RightArea editor={this.editor}/>
</div> */} </div>
</div> </div>
</Loading> </Loading>
</ConfigProvider> </ConfigProvider>

View File

@ -0,0 +1,3 @@
.lowcode-center-area {
padding: 12px;
}

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react'; import React, { PureComponent } from 'react';
import './index.scss'; import './index.scss';
@ -10,8 +10,6 @@ export default class CenterArea extends PureComponent {
} }
render() { render() {
return ( return <div className="lowcode-center-area"></div>;
<div className="lowcode-center-area" />
);
} }
} }

View File

@ -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);
}
}

View File

@ -3,5 +3,5 @@ import Panel from './panel';
export default { export default {
Nav, Nav,
Panel Panel,
}; };

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react'; import React, { PureComponent } from 'react';
import './index.scss'; import './index.scss';
@ -10,8 +10,6 @@ export default class LeftAreaPanel extends PureComponent {
} }
render() { render() {
return ( return <div className="lowcode-left-area-nav" />;
<div className="lowcode-left-area-nav"/>
);
} }
} }

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react'; import React, { PureComponent } from 'react';
import './index.scss'; import './index.scss';
@ -10,8 +10,6 @@ export default class LeftAreaPanel extends PureComponent {
} }
render() { render() {
return ( return <div className="lowcode-left-area-panel" />;
<div className="lowcode-left-area"/>
);
} }
} }

View File

@ -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;
}
}
}
}
}

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react'; import React, { PureComponent } from 'react';
import './index.scss'; import './index.scss';
@ -10,8 +10,6 @@ export default class RightArea extends PureComponent {
} }
render() { render() {
return ( return <div className="lowcode-right-area" />;
<div className="lowcode-right-area"/>
);
} }
} }

View File

@ -0,0 +1,5 @@
.lowcode-top-area {
height: 48px;
background-color: #ffffff;
border-bottom: 1px solid #e8ebee;
}

View File

@ -1,17 +1,79 @@
import React, {PureComponent} from 'react'; import React, { PureComponent } from 'react';
import { Grid } from '@alifd/next';
import TopPlugin from '../../components/TopPlugin';
import './index.scss'; import './index.scss';
const { Row, Col } = Grid;
export default class TopArea extends PureComponent { export default class TopArea extends PureComponent {
static displayName = 'lowcodeTopArea'; static displayName = 'lowcodeTopArea';
constructor(props) { constructor(props) {
super(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 (
<Col
className={isDivider ? 'divider' : ''}
key={isDivider ? idx : item.pluginKey}
style={{
width: (item.props && item.props.width) || 40,
flex: 'none',
}}
>
{!isDivider && (
<TopPlugin
config={item}
pluginClass={this.editor.pluginComponents[item.pluginKey]}
status={this.editor.pluginStatus[item.pluginKey]}
/>
)}
</Col>
);
});
};
render() { 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 ( return (
<div className="lowcode-top-area"/> <div className="lowcode-top-area">
<div className="left-area">{this.renderPluginList(leftList)}</div>
<div classname="right-area">{this.renderPluginList(rightList)}</div>
</div>
); );
} }
} }

View File

@ -6,5 +6,5 @@ export default {
pageNotExist: 'The current Page not exist', pageNotExist: 'The current Page not exist',
enterFromAppCenter: 'Please enter from the app center', enterFromAppCenter: 'Please enter from the app center',
noPermission: 'Sorry, you do not have the develop permission', noPermission: 'Sorry, you do not have the develop permission',
getPermission: 'Please connect the app owners {owners} to get the permission' getPermission: 'Please connect the app owners {owners} to get the permission',
}; };

View File

@ -6,5 +6,5 @@ export default {
pageNotExist: '当前访问地址不存在', pageNotExist: '当前访问地址不存在',
enterFromAppCenter: '请从应用中心入口重新进入', enterFromAppCenter: '请从应用中心入口重新进入',
noPermission: '抱歉,您暂无开发权限', noPermission: '抱歉,您暂无开发权限',
getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限' getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限',
}; };

View File

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

View File

@ -4,9 +4,11 @@
"description": "低代码编辑器", "description": "低代码编辑器",
"dependencies": { "dependencies": {
"@alifd/next": "^1.x", "@alifd/next": "^1.x",
"@alife/dpl-iceluna": "^2.3.2",
"@icedesign/theme": "^1.x", "@icedesign/theme": "^1.x",
"@types/react": "^16.8.3", "@types/react": "^16.8.3",
"@types/react-dom": "^16.8.2", "@types/react-dom": "^16.8.2",
"keymaster": "^1.6.2",
"moment": "^2.23.0", "moment": "^2.23.0",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"react": "^16.4.1", "react": "^16.4.1",
@ -20,6 +22,7 @@
"ice-plugin-fusion": "^0.1.4", "ice-plugin-fusion": "^0.1.4",
"ice-plugin-moment-locales": "^0.1.0", "ice-plugin-moment-locales": "^0.1.0",
"ice-scripts": "^2.0.0", "ice-scripts": "^2.0.0",
"prettier": "^1.19.1",
"stylelint": "^10.1.0" "stylelint": "^10.1.0"
}, },
"scripts": { "scripts": {
@ -27,7 +30,8 @@
"build": "ice-scripts build", "build": "ice-scripts build",
"lint": "npm run eslint && npm run stylelint", "lint": "npm run eslint && npm run stylelint",
"eslint": "eslint --cache --ext .js,.jsx ./", "eslint": "eslint --cache --ext .js,.jsx ./",
"stylelint": "stylelint ./**/*.scss" "stylelint": "stylelint ./**/*.scss",
"prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,ejs,less,css,scss,json}\" "
}, },
"engines": { "engines": {
"node": ">=8.0.0" "node": ">=8.0.0"

View File

@ -1,3 +1,24 @@
import topBalloonIcon from '@ali/iceluna-addon-2';
import topDialogIcon from '@ali/iceluna-addon-2';
import leftPanelIcon 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/plugin';
export default { export default {
}; topBalloonIcon: PluginFactory(topBalloonIcon),
topDialogIcon: PluginFactory(topDialogIcon),
leftPanelIcon: PluginFactory(leftPanelIcon),
leftBalloonIcon:PluginFactory(leftBalloonIcon),
leftDialogIcon:PluginFactory(leftDialogIcon),
rightPanel1:PluginFactory(rightPanel1),
rightPanel2:PluginFactory(rightPanel2),
rightPanel3: PluginFactory(rightPanel3),
rightPanel4: PluginFactory(rightPanel4),
};

View File

@ -1,3 +1,3 @@
export default { export default {
"namespace": "page" namespace: 'page',
} };

View File

@ -1 +1 @@
export default {}; export default {};

View File

@ -6,5 +6,5 @@ export default {
'en-US': en_us, 'en-US': en_us,
'zh-CN': zh_cn, 'zh-CN': zh_cn,
'zh-TW': zh_tw, 'zh-TW': zh_tw,
'ja-JP': ja_jp 'ja-JP': ja_jp,
}; };

View File

@ -1 +1 @@
export default {}; export default {};

View File

@ -1 +1 @@
export default {}; export default {};

View File

@ -1 +1 @@
export default {}; export default {};

View File

@ -1,435 +1,215 @@
export default { export default {
"skeleton": { version: '^1.0.2',
"config": { theme: {
"package": "@ali/lowcode-skeleton", dpl: {
"version": "0.0.1" package: '@alife/dpl-iceluna',
version: '^2.3.0',
}, },
scss: '',
}, },
"theme": { constants: {
"fusion": { namespace: 'page',
"package": "@alife/dpl-iceluna",
"version": "^2.3.0"
},
"scss": ""
}, },
"constants": { utils: [],
"namespace": "page" plugins: {
topArea: [
{
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: '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: '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: 'panel1',
icon: 'dengpao',
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0',
},
pluginProps: {},
},
{
pluginKey: 'rightPanel2',
type: 'Panel',
props: {
title: 'panel2',
icon: 'dengpao',
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0',
},
pluginProps: {},
},
{
pluginKey: 'rightPanel3',
type: 'Panel',
props: {
title: 'panel3',
icon: 'dengpao',
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0',
},
pluginProps: {},
},
{
pluginKey: 'rightPanel4',
type: 'Panel',
props: {
title: 'panel4',
icon: 'dengpao',
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0',
},
pluginProps: {},
},
],
centerArea: [],
}, },
"utils": [], hooks: [],
"plugins": { shortCuts: [],
"topArea": [{ };
"pluginKey": "logo",
"type": "Custom",
"props": {
"width": 110,
"align": "left"
},
"config": {
"package": "@ali/iceluna-addon-logo",
"version": "^1.0.2"
},
"pluginProps": {}
}, {
"pluginKey": "divider",
"type": "Divider",
"props": {
"align": "left"
}
}, {
"pluginKey": "pageList",
"type": "Custom",
"props": {
"align": "left",
"width": 360
},
"config": {
"package": "@ali/iceluna-addon-page-list",
"version": "^1.0.11"
},
"pluginProps": {}
}, {
"pluginKey": "partner",
"type": "Custom",
"props": {
"align": "right",
"width": 200
},
"config": {
"package": "@ali/iceluna-addon-partner",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "divider",
"type": "Divider",
"props": {
"align": "right"
}
}, {
"pluginKey": "designMode",
"type": "Custom",
"props": {
"align": "right",
"width": 144
},
"config": {
"package": "@ali/iceluna-addon-design-mode",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "divider",
"type": "Divider",
"props": {
"align": "right"
}
}, {
"pluginKey": "undoRedo",
"type": "Custom",
"props": {
"align": "right",
"width": 88
},
"config": {
"package": "@ali/iceluna-addon-undo-redo",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "d2c",
"type": "Custom",
"props": {
"align": "right",
"width": 44
},
"config": {
"package": "@ali/iceluna-addon-d2c",
"version": "^1.0.1"
},
"pluginProps": {}
}, {
"pluginKey": "history",
"type": "DialogIcon",
"props": {
"align": "right",
"icon": "lishijilu1",
"title": "历史",
"dialogProps": {
"title": "历史记录",
"footer": false,
"shouldUpdatePosition": true
}
},
"config": {
"package": "@ali/iceluna-addon-history",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "refresh",
"type": "Icon",
"props": {
"align": "right",
"icon": "shuaxin",
"title": "刷新",
"onClick": function(appHelper) {
appHelper.emit('ide.reset');
}
}
}, {
"pluginKey": "divider",
"type": "Divider",
"props": {
"align": "right"
}
}, {
"pluginKey": "save",
"type": "Custom",
"props": {
"align": "right",
"width": 86
},
"config": {
"package": "@ali/iceluna-addon-save",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "preview",
"type": "Custom",
"props": {
"align": "right",
"width": 86
},
"config": {
"package": "@ali/iceluna-addon-preview",
"version": "^1.0.1"
},
"pluginProps": {}
}, {
"pluginKey": "publish",
"type": "Custom",
"props": {
"align": "right",
"width": 104
},
"config": {
"package": "@ali/iceluna-addon-publish",
"version": "^1.0.1"
},
"pluginProps": {}
}],
"leftArea": [{
"pluginKey": "componentTree",
"type": "PanelIcon",
"props": {
"align": "top",
"icon": "shuxingkongjian",
"title": "组件树",
"panelProps": {
"minWidth": 100,
"maxWidth": 500
}
},
"config": {
"package": "@ali/iceluna-addon-component-tree",
"version": "^1.0.5"
},
"pluginProps": {}
}, {
"pluginKey": "componentList",
"type": "PanelIcon",
"props": {
"align": "top",
"icon": "zujianku",
"title": "组件库"
},
"config": {
"package": "@ali/iceluna-addon-component-list",
"version": "^1.0.4"
},
"pluginProps": {}
}, {
"pluginKey": "blockList",
"type": "PanelIcon",
"props": {
"align": "top",
"icon": "jihe",
"title": "区块库"
},
"config": {
"package": "@ali/iceluna-addon-block-list",
"version": "^1.0.2"
},
"pluginProps": {}
}, {
"pluginKey": "schema",
"type": "PanelIcon",
"props": {
"align": "bottom",
"icon": "ceshi",
"title": "schema 源码开发",
"panelProps": {
"defaultWidth": 480
}
},
"config": {
"package": "@ali/iceluna-addon-schema",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "style",
"type": "PanelIcon",
"props": {
"align": "bottom",
"icon": "SCSS",
"title": "scss 全局样式设置",
"panelProps": {
"defaultWidth": 480
}
},
"config": {
"package": "@ali/iceluna-addon-style",
"version": "^1.0.2"
},
"pluginProps": {}
}, {
"pluginKey": "utils",
"type": "PanelIcon",
"props": {
"align": "bottom",
"icon": "funcsgaiban",
"title": "utils 全局公共函数设置",
"panelProps": {
"defaultWidth": 540
}
},
"config": {
"package": "@ali/iceluna-addon-utils",
"version": "^1.0.7"
},
"pluginProps": {}
}, {
"pluginKey": "constants",
"type": "PanelIcon",
"props": {
"align": "bottom",
"icon": "constgaiban",
"title": "constants 全局常量设置",
"panelProps": {
"defaultWidth": 480
}
},
"config": {
"package": "@ali/iceluna-addon-constants",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "package",
"type": "PanelIcon",
"props": {
"align": "bottom",
"icon": "packagegaiban",
"title": "package.json 应用设置",
"panelProps": {
"defaultWidth": 480
}
},
"config": {
"package": "@ali/iceluna-addon-package",
"version": "^1.0.2"
},
"pluginProps": {}
}, {
"pluginKey": "canvasSetting",
"type": "PanelIcon",
"props": {
"align": "bottom",
"icon": "huabushezhi",
"title": "canvas 画布配置",
"panelProps": {
"defaultWidth": 300
}
},
"config": {
"package": "@ali/iceluna-addon-canvas-setting",
"version": "^1.0.2"
},
"pluginProps": {}
}, {
"pluginKey": "issue",
"type": "LinkIcon",
"props": {
"align": "bottom",
"icon": "chongzi",
"title": "issue 问题反馈",
"linkProps": {
"href": "//work.aone.alibaba-inc.com/project/860698/issue/new",
"target": "blank"
}
}
}, {
"pluginKey": "document",
"type": "LinkIcon",
"props": {
"align": "bottom",
"icon": "wendangzhongxin",
"title": "docs 文档中心",
"linkProps": {
"href": "https://iceluna.alibaba-inc.com/#/document",
"target": "blank"
}
}
}],
"rightArea": [{
"pluginKey": "componentStyle",
"props": {
"title": "样式"
},
"config": {
"package": "@ali/iceluna-addon-component-style",
"version": "^1.0.8"
}
}, {
"pluginKey": "componentAttr",
"props": {
"title": "属性"
},
"config": {
"package": "@ali/iceluna-addon-component-attr",
"version": "^1.0.3"
},
"pluginProps": {}
}, {
"pluginKey": "componentEvent",
"props": {
"title": "事件"
},
"config": {
"package": "@ali/iceluna-addon-component-event",
"version": "^1.0.4"
},
"pluginProps": {}
}, {
"pluginKey": "componentData",
"props": {
"title": "数据"
},
"config": {
"package": "@ali/iceluna-addon-component-data",
"version": "^1.0.3"
},
"pluginProps": {}
}],
"centerArea": [{
"pluginKey": "canvas",
"config": {
"package": "@ali/iceluna-addon-canvas",
"version": "^1.0.8"
}
}, {
"pluginKey": "guide",
"config": {
"package": "@ali/iceluna-addon-guide",
"version": "^1.0.1"
}
}]
},
"hooks": [{
"message": "wsHelper.result.updateInfo",
"type": "on",
"handler": function(appHelper, data) {
const pageInfo = appHelper.pageInfo;
if (data && data.code > 0 && pageInfo) {
const {
clientLocks,
entityLocks,
entityUsers,
entityPubInfo
} = data.data;
if (JSON.stringify(clientLocks || {}) !== JSON.stringify(appHelper.clientLocks || {})) {
clientLocks.schema = clientLocks[pageInfo.id];
appHelper.set('clientLocks', clientLocks);
appHelper.emit('wsHelper.update.clientLocks', clientLocks);
}
if (JSON.stringify(entityLocks || {}) !== JSON.stringify(appHelper.entityLocks || {})) {
entityLocks.schema = entityLocks[pageInfo.id];
appHelper.set('entityLocks', entityLocks);
appHelper.emit('wsHelper.update.entityLocks', entityLocks);
}
if (JSON.stringify(entityUsers || {}) !== JSON.stringify(appHelper.entityUsers || {})) {
appHelper.set('entityUsers', entityUsers);
appHelper.emit('wsHelper.update.entityUsers', entityUsers);
}
if (JSON.stringify(entityPubInfo || {}) !== JSON.stringify(appHelper.entityPubInfo || {})) {
appHelper.set('entityPubInfo', entityPubInfo);
appHelper.emit('wsHelper.update.entityPubInfo', entityPubInfo);
}
}
}
}],
"shortCuts": [],
"lifeCycles": {}
};

View File

@ -1,3 +1 @@
export default { export default {};
};

View File

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

View File

@ -0,0 +1,41 @@
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<PluginConfig>;
}
export interface PluginConfig {
pluginKey: string;
type: string;
props: object;
config: NpmConfig;
pluginProps: object;
}
export type HooksConfig = Array<HookConfig>;
export interface HookConfig {}

View File

@ -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 default class Editor extends EventEmitter {
static getInstance = () => {
if (!instance) {
instance = new Editor();
}
return instance;
};
constructor(config, utils, components) {
super();
instance = this;
this.config = config;
this.utils = utils;
this.components = components;
this.init();
}
init() {
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');
})
.catch(err => {
console.error(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<string>, lisenter: function): void {
if (!Array.isArray(events)) return;
events.forEach(event => this.on(event, lisenter));
}
batchOnce(events: Array<string>, lisenter: function): void {
if (!Array.isArray(events)) return;
events.forEach(event => this.once(event, lisenter));
}
batchOff(events: Array<string>, 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.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.components[plugin.pluginKey];
// 判断如果编辑器插件有init静态方法则在此执行init方法
if (pluginClass && pluginClass.init) {
pluginClass.init(this);
}
});
});
return res;
}
}

View File

@ -0,0 +1,3 @@
import Editor from './editor';
export default Editor;

View File

@ -0,0 +1,54 @@
import React, { PureComponent, creatRef} from 'react';
import EditorContext from './context';
import { isEmpty, generateI18n, goldlog } from './utils';
export default function plugin(Comp) {
class LowcodePlugin extends PureComponent {
static displayName = 'LowcodeEditorPlugin';
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;
}
this.ref = React.createRef();
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 (
<Comp
ref={this.ref}
i18n={this.i18n}
editor={this.editor}
config={config}
{...config.pluginProps}
/>
);
}
}
LowcodePlugin.init = Comp.init;
return LowcodePlugin;
}

View File

@ -0,0 +1,320 @@
import IntlMessageFormat from 'intl-messageformat';
import keymaster from 'keymaster';
import _isEmpty from 'lodash/isEmpty';
export const isEmpty = _isEmpty;
/**
*
* @param {*} locale zh-CNen-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<string> = [];
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 = editor.utils && editor.utils.ideMessage;
//复制
if (!document.copyListener) {
document.copyListener = e => {
if (!keyboardFilter(e) || editor.isCopying) return;
const schema =
editor.schemaHelper &&
editor.schemaHelper.schemaMap[editor.activeKey];
if (!schema || !isSchema(schema)) return;
editor.isCopying = true;
const schemaStr = serialize(transformSchemaToPure(schema), {
unsafe: true,
});
setClipboardData(schemaStr)
.then(() => {
ideMessage &&
ideMessage(
'success',
'当前内容已复制到剪贴板请使用快捷键Command+v进行粘贴',
);
editor.emit('schema.copy', schemaStr, schema);
editor.isCopying = false;
})
.catch(errMsg => {
ideMessage && ideMessage('error', errMsg);
editor.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) || editor.isPasting) return;
const schemaHelper = editor.schemaHelper;
let targetKey = editor.activeKey;
let direction = 'after';
const topKey =
schemaHelper.schema &&
schemaHelper.schema.__ctx &&
schemaHelper.schema.__ctx.lunaKey;
if (!targetKey || topKey === targetKey) {
const schemaHelper = editor.schemaHelper;
const topKey =
schemaHelper.schema &&
schemaHelper.schema.__ctx &&
schemaHelper.schema.__ctx.lunaKey;
if (!topKey) return;
targetKey = topKey;
direction = 'in';
}
editor.isPasting = true;
const schema = parseObj(text);
if (!isSchema(schema)) {
editor.emit('illegalSchema.paste', text);
// ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!');
console.warn('paste schema illegal');
editor.isPasting = false;
return;
}
editor.emit('material.add', {
schema,
targetKey,
direction,
});
editor.isPasting = false;
editor.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, editor, 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 transformArrayToMap(arr, key, overwrite = true) {
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;
}
export function parseSearch(search) {
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 = {}, customConfig = {}) {
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,
};
}

View File

@ -1,8 +1,8 @@
body { body {
font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma, Arial, PingFang SC-Light, font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma,
Microsoft YaHei; Arial, PingFang SC-Light, Microsoft YaHei;
font-size: 12px; font-size: 12px;
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
} }

View File

@ -1,10 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { HashRouter as Router, Route } from 'react-router-dom'; // import Skeleton from '@ali/lowcode-engine-skeleton';
import Skeleton from '@ali/lowcode-engine-skeleton'; import Skeleton from './skeleton';
import config from './config/skeleton'; import config from './config/skeleton';
import components from './config/components'; import components from './config/components';
import componentsMap from './config/componentsMap';
import utils from './config/utils'; import utils from './config/utils';
import constants from './config/constants'; import constants from './config/constants';
import messages from './config/locale'; import messages from './config/locale';
@ -22,21 +21,12 @@ if (!ICE_CONTAINER) {
} }
ReactDOM.render( ReactDOM.render(
<Router> <Skeleton
<Route {...(config.skeleton && config.skeleton.props)}
path="/*" config={config}
component={props => { utils={utils}
return ( constants={constants}
<Skeleton components={components}
{...props} />,
{...(config.skeleton && config.skeleton.props)} ICE_CONTAINER,
config={config} );
utils={utils}
messages={messages}
constants={constants}
pluginComponents={components}
/>
);
}}
/>
</Router>, ICE_CONTAINER);

View File

@ -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;
}
}
}

View File

@ -0,0 +1,205 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Balloon, Dialog, Icon, Badge } from '@alife/next';
import './index.scss';
export default class LeftPlugin extends PureComponent {
static displayName = 'lowcodeLeftPlugin';
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 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,
editor,
} = this.props;
const { pluginKey, props } = config || {};
const { icon, title } = props || {};
return (
<div
className={classNames('lowcode-left-plugin', pluginKey, {
active,
disabled,
locked,
})}
data-tooltip={title}
onClick={() => {
if (disabled) return;
//考虑到弹窗情况,延时发送消息
clickCallback && clickCallback();
onClick && onClick();
}}
>
{dotted ? (
<Badge dot>
<Icon type={icon} size="small" />
</Badge>
) : (
<Icon type={icon} size="small" />
)}
</div>
);
};
render() {
const {
dotted,
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 && (
<Comp
editor={editor}
active={active}
locked={locked}
disabled={disabled}
config={config}
onClick={() => {
onClick && onClick.call(null, editor);
}}
{...pluginProps}
/>
)) ||
null;
switch (type) {
case 'LinkIcon':
return (
<a {...(props.linkProps || {})}>
{this.renderIcon(() => {
onClick && onClick.call(null, editor);
})}
</a>
);
case 'Icon':
return this.renderIcon(() => {
onClick && onClick.call(null, editor);
});
case 'DialogIcon':
return (
<Fragment>
{this.renderIcon(() => {
onClick && onClick.call(null, editor);
this.handleOpen();
})}
<Dialog
onOk={() => {
editor.emit(`${pluginKey}.dialog.onOk`);
this.handleClose();
}}
onCancel={this.handleClose}
onClose={this.handleClose}
title={title}
{...(props.dialogProps || {})}
visible={dialogVisible}
>
{node}
</Dialog>
</Fragment>
);
case 'BalloonIcon':
return (
<Balloon
trigger={this.renderIcon(() => {
onClick && onClick.call(null, editor);
})}
align="r"
triggerType={['click', 'hover']}
{...(props.balloonProps || {})}
>
{node}
</Balloon>
);
case 'PanelIcon':
return this.renderIcon(() => {
onClick && onClick.call(null, editor);
this.handleOpen();
});
case 'Custom':
return dotted ? <Badge dot>{node}</Badge> : node;
default:
return null;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,23 @@
import React, { PureComponent } from 'react';
import './index.scss';
export default class Panel extends PureComponent {
static displayName = 'Panel';
constructor(props) {
super(props);
}
render() {
return (
<div
className="lowcode-panel"
style={{
width: 240,
}}
>
{this.props.children}
</div>
);
}
}

View File

@ -0,0 +1,32 @@
.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;
}
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);
}
}

View File

@ -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 (
<Button
type="normal"
size="large"
text={true}
className={classNames('lowcode-top-btn', className, {
active,
disabled,
locked,
})}
id={id}
style={style}
onClick={disabled ? null : onClick}
>
<div>
<Icon size="large" type={icon} />
{showTitle && <span>{title}</span>}
</div>
</Button>
);
}
}

View File

@ -0,0 +1,2 @@
.lowcode-top-addon {
}

View File

@ -0,0 +1,192 @@
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 && pluginKey) {
// appHelper.on(`${pluginKey}.dialog.show`, this.handleShow);
// appHelper.on(`${pluginKey}.dialog.close`, this.handleClose);
// }
}
componentWillUnmount() {
// const { config } = this.props;
// const pluginKey = config && config.pluginKey;
// const appHelper = this.appHelper;
// if (appHelper && pluginKey) {
// appHelper.off(`${pluginKey}.dialog.show`, this.handleShow);
// appHelper.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 pluginKey = this.props.config && this.props.config.pluginKey;
// const currentAddon =
// this.appHelper.addons && this.appHelper.addons[pluginKey];
// 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,
editor,
} = this.props;
const { pluginKey, props } = config || {};
const { icon, title } = props || {};
const node = (
<TopIcon
className={`lowcode-top-addon ${pluginKey}`}
active={active}
disabled={disabled}
locked={locked}
icon={icon}
title={title}
onClick={() => {
if (disabled) return;
//考虑到弹窗情况,延时发送消息
setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0);
clickCallback && clickCallback();
onClick && onClick();
}}
/>
);
return dotted ? <Badge dot>{node}</Badge> : 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) return null;
const node =
(Comp && (
<Comp
editor={editor}
active={active}
locked={locked}
disabled={disabled}
config={config}
onClick={() => {
onClick && onClick.call(null, editor);
}}
{...pluginProps}
/>
)) ||
null;
switch (type) {
case 'LinkIcon':
return (
<a {...props.linkProps}>
{this.renderIcon(() => {
onClick && onClick.call(null, editor);
})}
</a>
);
case 'Icon':
return this.renderIcon(() => {
onClick && onClick.call(null, editor);
});
case 'DialogIcon':
return (
<Fragment>
{this.renderIcon(() => {
onClick && onClick.call(null, editor);
this.handleOpen();
})}
<Dialog
onOk={() => {
editor.emit(`${pluginKey}.dialog.onOk`);
this.handleClose();
}}
onCancel={this.handleClose}
onClose={this.handleClose}
title={title}
{...props.dialogProps}
visible={dialogVisible}
>
{node}
</Dialog>
</Fragment>
);
case 'BalloonIcon':
return (
<Balloon
trigger={this.renderIcon(() => {
onClick && onClick.call(null, editor);
})}
triggerType={['click', 'hover']}
{...props.balloonProps}
>
{node}
</Balloon>
);
case 'Custom':
return dotted ? <Badge dot>{node}</Badge> : node;
default:
return null;
}
}
}

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1 @@
export default {};

View File

@ -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;
}
}

View File

@ -0,0 +1,129 @@
import React, { PureComponent } from 'react';
import { HashRouter as Router, Route } from 'react-router-dom';
import Editor from '../framework/editor';
import { comboEditorConfig, parseSearch } from '../framework/utils';
import { Loading, ConfigProvider } from '@alifd/next';
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 default class Skeleton extends PureComponent {
static displayName = 'LowcodeEditorSkeleton';
static getDerivedStateFromError() {
return {
__hasError: true,
};
}
constructor(props) {
super(props);
this.state = {
initReady: false,
skeletonKey: `skeleton${renderIdx}`,
};
this.init();
}
componentWillUnmount() {
this.editor && this.editor.destroy();
this.editor = null;
}
componentDidCatch(err) {
console.error(err);
}
init = (isReset = false) => {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
const { utils, config, components } = this.props;
const editor = (this.editor = new Editor(
comboEditorConfig(defaultConfig, config),
{ ...skeletonUtils, ...utils },
components,
));
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) {
return 'error';
}
return (
<Router>
<Route
path="/*"
component={props => {
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);
return (
<ConfigProvider>
<Loading
tip="Loading"
size="large"
visible={!initReady}
shape="fusion-reactor"
fullScreen
>
<div className="lowcode-editor" key={skeletonKey}>
<TopArea editor={this.editor} />
<div className="lowcode-main-content">
<LeftArea.Nav editor={this.editor} />
<LeftArea.Panel editor={this.editor} />
<CenterArea editor={this.editor} />
<RightArea editor={this.editor} />
</div>
</div>
</Loading>
</ConfigProvider>
);
}}
/>
</Router>
);
}
}

View File

@ -0,0 +1,3 @@
.lowcode-center-area {
padding: 12px;
}

View File

@ -0,0 +1,33 @@
import React, { PureComponent } from 'react';
import './index.scss';
export default class CenterArea extends PureComponent {
static displayName = 'lowcodeCenterArea';
constructor(props) {
super(props);
this.editor = props.editor;
this.config = this.editor.config && this.editor.config.plugins && this.editor.config.plugins.centerArea || [];
}
render() {
const list = this.config.filter(item => {
return true;
});
return (
<div className="lowcode-center-area">
{list.map(item => {
const Comp = this.editor.components[item.pluginKey];
return (
<Comp
editor={this.editor}
config={item}
{...item.pluginProps}
/>
)
})}
</div>
);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,7 @@
import Nav from './nav';
import Panel from './panel';
export default {
Nav,
Panel,
};

View File

@ -0,0 +1,51 @@
import React, { PureComponent } from 'react';
import LeftPlugin from '../../components/LeftPlugin';
import './index.scss';
export default class LeftAreaPanel extends PureComponent {
static displayName = 'lowcodeLeftAreaNav';
constructor(props) {
super(props);
this.editor = props.editor;
this.config =
this.editor.config.plugins && this.editor.config.plugins.leftArea;
}
handlePluginClick = item => {};
renderPluginList = (list = []) => {
return list.map((item, idx) => {
return (
<LeftPlugin
key={item.pluginKey}
config={item}
editor={this.editor}
pluginClass={this.editor.components[item.pluginKey]}
onClick={() => this.handlePluginClick(item)}
/>
);
});
};
render() {
const topList = [];
const bottomList = [];
this.config.forEach(item => {
const align =
item.props && item.props.align === 'bottom' ? 'bottom' : 'top';
if (align === 'bottom') {
bottomList.push(item);
} else {
topList.push(item);
}
});
return (
<div className="lowcode-left-area-nav">
<div className="bottom-area">{this.renderPluginList(bottomList)}</div>
<div className="top-area">{this.renderPluginList(topList)}</div>
</div>
);
}
}

View File

@ -0,0 +1,39 @@
import React, { PureComponent, Fragment } from 'react';
import Panel from '../../components/Panel';
import './index.scss';
export default class LeftAreaPanel extends PureComponent {
static displayName = 'lowcodeLeftAreaPanel';
constructor(props) {
super(props);
this.editor = props.editor;
this.config =
this.editor.config.plugins && this.editor.config.plugins.leftArea;
this.state = {
activeKey: 'leftPanelIcon',
};
}
render() {
const list = this.config.filter(item => {
return item.type === 'PanelIcon';
});
return (
<Fragment>
{list.map((item, idx) => {
const Comp = this.editor.components[item.pluginKey];
return (
<Panel
key={item.pluginKey}
visible={item.pluginKey === this.state.activeKey}
>
<Comp editor={this.editor} config={item} />
</Panel>
);
})}
</Fragment>
);
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -0,0 +1,53 @@
import React, { PureComponent } from 'react';
import { Tab } from '@alifd/next';
import './index.scss';
export default class RightArea extends PureComponent {
static displayName = 'lowcodeRightArea';
constructor(props) {
super(props);
this.editor = props.editor;
this.state = {
activeKey: 'rightPanel1',
};
}
handleTabChange = key => {
this.setState({
activeKey: key,
});
};
render() {
const list =
(this.editor &&
this.editor.config &&
this.editor.config.plugins &&
this.editor.config.plugins.rightArea) ||
[];
return (
<div className="lowcode-right-area">
<Tab
shape="wrapped"
className="right-tabs"
style={{
height: '100%',
}}
activeKey={this.state.activeKey}
lazyLoad={false}
onChange={this.handleTabChange}
>
{list.map((item, idx) => {
const Comp = this.editor.components[item.pluginKey];
return (
<Tab.Item key={item.pluginKey} title={item.props.title}>
<Comp editor={this.editor} config={item.config} {...item.pluginProps} />
</Tab.Item>
);
})}
</Tab>
</div>
);
}
}

View File

@ -0,0 +1,27 @@
.lowcode-top-area {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 48px;
background-color: #ffffff;
border-bottom: 1px solid #e8ebee;
overflow: hidden;
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;
}
}

View File

@ -0,0 +1,80 @@
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 (
<Col
className={isDivider ? 'divider' : ''}
key={isDivider ? idx : item.pluginKey}
style={{
width: (item.props && item.props.width) || 40,
flex: 'none',
}}
>
{!isDivider && (
<TopPlugin
config={item}
pluginClass={this.editor.components[item.pluginKey]}
editor={this.editor}
/>
)}
</Col>
);
});
};
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 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 (
<div className="lowcode-top-area">
<div className="left-area">
<Row>{this.renderPluginList(leftList)}</Row>
</div>
<div className="right-area">
<Row justify="end">{this.renderPluginList(rightList)}</Row>
</div>
</div>
);
}
}

View File

@ -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',
};

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1,10 @@
export default {
loading: '加载中...',
rejectRedirect: '开发中,已阻止发生跳转',
expand: '展开',
fold: '收起',
pageNotExist: '当前访问地址不存在',
enterFromAppCenter: '请从应用中心入口重新进入',
noPermission: '抱歉,您暂无开发权限',
getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限',
};

View File

@ -0,0 +1 @@
export default {};