diff --git a/packages/editor-framework/package.json b/packages/editor-framework/package.json index 2b11f2c53..3d1465113 100644 --- a/packages/editor-framework/package.json +++ b/packages/editor-framework/package.json @@ -38,16 +38,16 @@ "@types/lodash": "^4.14.149", "@types/react": "^16.9.13", "@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-moment-locales": "^0.1.0", "eslint": "^6.0.1", "prettier": "^1.19.1", - "react": "^16.3.0", - "react-dom": "^16.3.0" + "react": "^16.8.0", + "react-dom": "^16.8.0" }, "peerDependencies": { - "react": "^16.3.0", + "react": "^16.8.0", "@alifd/next": "1.x" }, "license": "MIT", diff --git a/packages/editor-framework/src/definitions.ts b/packages/editor-framework/src/definitions.ts new file mode 100644 index 000000000..87dd5dca2 --- /dev/null +++ b/packages/editor-framework/src/definitions.ts @@ -0,0 +1,48 @@ + +export interface EditorConfig { + +}; + +export interface NpmConfig { + version: string, + package: string, + main?: string, + exportName?: string, + subName?: string, + destructuring?: boolean +}; + +export interface SkeletonConfig { + config: NpmConfig, + props?: object, + handler?: (EditorConfig) => EditorConfig +}; + +export interface FusionTheme { + package: string, + version: string +}; + +export interface ThemeConfig { + fusion?: FusionTheme +} + +export interface PluginsConfig { + [key]: Array +}; + +export interface PluginConfig { + pluginKey: string, + type: string, + props: object, + config: NpmConfig, + pluginProps: object +}; + +export type HooksConfig = Array; + +export interface HookConfig { + +}; + + diff --git a/packages/editor-framework/src/plugin.ts b/packages/editor-framework/src/plugin.ts index e42aa73cc..33d81cc48 100644 --- a/packages/editor-framework/src/plugin.ts +++ b/packages/editor-framework/src/plugin.ts @@ -10,6 +10,48 @@ export interface pluginProps { messages: object } +export default function plugin(Comp) { + + class Plugin extends PureComponent { + static displayName = 'lowcode-editor-plugin'; + static defaultProps = { + config: {} + }; + static contextType = EditorContext; + constructor(props, context) { + super(props, context); + if (isEmpty(props.config) || !props.config.pluginKey) { + console.warn('lowcode editor plugin has wrong config'); + return; + } + + const { locale, messages, editor } = props; + // 注册插件 + this.editor = editor; + this.i18n = generateI18n(locale, messages); + this.pluginKey = props.config.pluginKey; + editor.plugins = editor.plugins || {}; + editor.plugins[this.pluginKey] = this; + } + + componentWillUnmount() { + // 销毁插件 + if (this.editor && this.editor.plugins) { + delete this.editor.plugins[this.pluginKey]; + } + } + + render() { + const { + config + } = this.props; + return + } + } + + return Plugin; +} + export class Plugin extends PureComponent { diff --git a/packages/editor-framework/src/utils.ts b/packages/editor-framework/src/utils.ts index d09c4929c..d1c5eaf2b 100644 --- a/packages/editor-framework/src/utils.ts +++ b/packages/editor-framework/src/utils.ts @@ -213,5 +213,30 @@ export function transformToPromise(input) { } export function comboEditorConfig(defaultConfig, customConfig) { - + const { ideConfig = {}, utils = {} } = this.props; + const comboShortCuts = () => { + const defaultShortCuts = defaultIdeConfig.shortCuts; + const shortCuts = ideConfig.shortCuts || []; + const configMap = skeletonUtils.transformArrayToMap(defaultShortCuts, 'keyboard'); + (shortCuts || []).forEach(item => { + configMap[item.keyboard] = item; + }); + return Object.keys(configMap).map(key => configMap[key]); + }; + return { + ...ideConfig, + utils: { + ...skeletonUtils, + ...utils + }, + constants: { + ...defaultIdeConfig.constants, + ...ideConfig.constants + }, + extensions: { + ...defaultIdeConfig.extensions, + ...ideConfig.extensions + }, + shortCuts: comboShortCuts() + }; } \ No newline at end of file diff --git a/packages/editor-skeleton/es/components/LeftPlugin/index.d.ts b/packages/editor-skeleton/es/components/LeftPlugin/index.d.ts new file mode 100644 index 000000000..312fc40aa --- /dev/null +++ b/packages/editor-skeleton/es/components/LeftPlugin/index.d.ts @@ -0,0 +1,30 @@ +import { PureComponent } from 'react'; +import './index.scss'; +export default class LeftAddon extends PureComponent { + static displayName: string; + static propTypes: { + active: any; + config: any; + disabled: any; + dotted: any; + locked: any; + onClick: any; + }; + static defaultProps: { + active: boolean; + config: {}; + disabled: boolean; + dotted: boolean; + locked: boolean; + onClick: () => void; + }; + static contextType: any; + constructor(props: any, context: any); + componentDidMount(): void; + componentWillUnmount(): void; + handleClose: () => void; + handleOpen: () => void; + handleShow: () => void; + renderIcon: (clickCallback: any) => JSX.Element; + render(): JSX.Element; +} diff --git a/packages/editor-skeleton/es/components/LeftPlugin/index.js b/packages/editor-skeleton/es/components/LeftPlugin/index.js index e69de29bb..517104928 100644 --- a/packages/editor-skeleton/es/components/LeftPlugin/index.js +++ b/packages/editor-skeleton/es/components/LeftPlugin/index.js @@ -0,0 +1,259 @@ +import _extends from "@babel/runtime/helpers/extends"; +import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; +import React, { PureComponent, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import AppContext from '@ali/iceluna-sdk/lib/context/appContext'; +import { Balloon, Dialog, Icon, Badge } from '@alife/next'; +import './index.scss'; + +var LeftAddon = /*#__PURE__*/function (_PureComponent) { + _inheritsLoose(LeftAddon, _PureComponent); + + function LeftAddon(_props, context) { + var _this; + + _this = _PureComponent.call(this, _props, context) || this; + + _this.handleClose = function () { + var addonKey = _this.props.config && _this.props.config.addonKey; + var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey]; + + if (currentAddon) { + _this.utils.transformToPromise(currentAddon.close()).then(function () { + _this.setState({ + dialogVisible: false + }); + }); + } + }; + + _this.handleOpen = function () { + // todo 对话框类型的插件初始时拿不到插件实例 + _this.setState({ + dialogVisible: true + }); + }; + + _this.handleShow = function () { + var _this$props = _this.props, + disabled = _this$props.disabled, + config = _this$props.config, + onClick = _this$props.onClick; + var addonKey = config && config.addonKey; + if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息 + + setTimeout(function () { + return _this.appHelper.emit(addonKey + ".addon.activate"); + }, 0); + + _this.handleOpen(); + + onClick && onClick(); + }; + + _this.renderIcon = function (clickCallback) { + var _this$props2 = _this.props, + active = _this$props2.active, + disabled = _this$props2.disabled, + dotted = _this$props2.dotted, + locked = _this$props2.locked, + _onClick = _this$props2.onClick, + config = _this$props2.config; + + var _ref = config || {}, + addonKey = _ref.addonKey, + props = _ref.props; + + var _ref2 = props || {}, + icon = _ref2.icon, + title = _ref2.title; + + return React.createElement("div", { + className: classNames('luna-left-addon', addonKey, { + active: active, + disabled: disabled, + locked: locked + }), + "data-tooltip": title, + onClick: function onClick() { + if (disabled) return; //考虑到弹窗情况,延时发送消息 + + clickCallback && clickCallback(); + _onClick && _onClick(); + } + }, dotted ? React.createElement(Badge, { + dot: true + }, React.createElement(Icon, { + type: icon, + size: "small" + })) : React.createElement(Icon, { + type: icon, + size: "small" + })); + }; + + _this.state = { + dialogVisible: false + }; + _this.appHelper = context.appHelper; + _this.utils = _this.appHelper.utils; + _this.constants = _this.appHelper.constants; + return _this; + } + + var _proto = LeftAddon.prototype; + + _proto.componentDidMount = function componentDidMount() { + var config = this.props.config; + var addonKey = config && config.addonKey; + var appHelper = this.appHelper; + + if (appHelper && addonKey) { + appHelper.on(addonKey + ".dialog.show", this.handleShow); + appHelper.on(addonKey + ".dialog.close", this.handleClose); + } + }; + + _proto.componentWillUnmount = function componentWillUnmount() { + var config = this.props.config; + var appHelper = this.appHelper; + var addonKey = config && config.addonKey; + + if (appHelper && addonKey) { + appHelper.off(addonKey + ".dialog.show", this.handleShow); + appHelper.off(addonKey + ".dialog.close", this.handleClose); + } + }; + + _proto.render = function render() { + var _this2 = this; + + var _this$props3 = this.props, + dotted = _this$props3.dotted, + locked = _this$props3.locked, + active = _this$props3.active, + disabled = _this$props3.disabled, + config = _this$props3.config; + + var _ref3 = config || {}, + addonKey = _ref3.addonKey, + props = _ref3.props, + type = _ref3.type, + addonProps = _ref3.addonProps; + + var _ref4 = props || {}, + _onClick2 = _ref4.onClick, + title = _ref4.title; + + var dialogVisible = this.state.dialogVisible; + var _this$context = this.context, + appHelper = _this$context.appHelper, + components = _this$context.components; + if (!addonKey || !type || !props) return null; + var componentName = appHelper.utils.generateAddonCompName(addonKey); + var localeProps = {}; + var locale = appHelper.locale, + messages = appHelper.messages; + + if (locale) { + localeProps.locale = locale; + } + + if (messages && messages[componentName]) { + localeProps.messages = messages[componentName]; + } + + var AddonComp = components && components[componentName]; + var node = AddonComp && React.createElement(AddonComp, _extends({ + active: active, + locked: locked, + disabled: disabled, + config: config, + onClick: function onClick() { + _onClick2 && _onClick2.call(null, appHelper); + } + }, localeProps, addonProps || {})) || null; + + switch (type) { + case 'LinkIcon': + return React.createElement("a", props.linkProps || {}, this.renderIcon(function () { + _onClick2 && _onClick2.call(null, appHelper); + })); + + case 'Icon': + return this.renderIcon(function () { + _onClick2 && _onClick2.call(null, appHelper); + }); + + case 'DialogIcon': + return React.createElement(Fragment, null, this.renderIcon(function () { + _onClick2 && _onClick2.call(null, appHelper); + + _this2.handleOpen(); + }), React.createElement(Dialog, _extends({ + onOk: function onOk() { + appHelper.emit(addonKey + ".dialog.onOk"); + + _this2.handleClose(); + }, + onCancel: this.handleClose, + onClose: this.handleClose, + title: title + }, props.dialogProps || {}, { + visible: dialogVisible + }), node)); + + case 'BalloonIcon': + return React.createElement(Balloon, _extends({ + trigger: this.renderIcon(function () { + _onClick2 && _onClick2.call(null, appHelper); + }), + align: "r", + triggerType: ['click', 'hover'] + }, props.balloonProps || {}), node); + + case 'PanelIcon': + return this.renderIcon(function () { + _onClick2 && _onClick2.call(null, appHelper); + + _this2.handleOpen(); + }); + + case 'Custom': + return dotted ? React.createElement(Badge, { + dot: true + }, node) : node; + + default: + return null; + } + }; + + return LeftAddon; +}(PureComponent); + +LeftAddon.displayName = 'LunaLeftAddon'; +LeftAddon.propTypes = { + active: PropTypes.bool, + config: PropTypes.shape({ + addonKey: PropTypes.string, + addonProps: PropTypes.object, + props: PropTypes.object, + type: PropTypes.oneOf(['DialogIcon', 'BalloonIcon', 'PanelIcon', 'LinkIcon', 'Icon', 'Custom']) + }), + disabled: PropTypes.bool, + dotted: PropTypes.bool, + locked: PropTypes.bool, + onClick: PropTypes.func +}; +LeftAddon.defaultProps = { + active: false, + config: {}, + disabled: false, + dotted: false, + locked: false, + onClick: function onClick() {} +}; +LeftAddon.contextType = AppContext; +export { LeftAddon as default }; \ No newline at end of file diff --git a/packages/editor-skeleton/es/components/LeftPlugin/index.scss b/packages/editor-skeleton/es/components/LeftPlugin/index.scss index e69de29bb..9c6922129 100644 --- a/packages/editor-skeleton/es/components/LeftPlugin/index.scss +++ b/packages/editor-skeleton/es/components/LeftPlugin/index.scss @@ -0,0 +1,59 @@ +.luna-left-addon { + font-size: 16px; + text-align: center; + line-height: 36px; + height: 36px; + position: relative; + cursor: pointer; + transition: all 0.3s ease; + color: #777; + &.collapse { + height: 40px; + color: #8c8c8c; + border-bottom: 1px solid #bfbfbf; + } + &.locked { + color: red !important; + } + &.active { + color: #fff !important; + background-color: $color-brand1-9 !important; + &.disabled { + color: #fff; + background-color: $color-fill1-7; + } + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + &:hover { + background-color: $color-brand1-1; + color: $color-brand1-6; + &:before { + content: attr(data-tooltip); + display: block; + position: absolute; + left: 50px; + top: 5px; + line-height: 18px; + font-size: 12px; + white-space: nowrap; + padding: 6px 8px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.75); + color: #fff; + z-index: 100; + } + &:after { + content: ''; + display: block; + position: absolute; + left: 40px; + top: 15px; + border: 5px solid transparent; + border-right-color: rgba(0, 0, 0, 0.75); + z-index: 100; + } + } +} diff --git a/packages/editor-skeleton/es/components/TopIcon/index.d.ts b/packages/editor-skeleton/es/components/TopIcon/index.d.ts index 0e1aea7cb..2bb6854b4 100644 --- a/packages/editor-skeleton/es/components/TopIcon/index.d.ts +++ b/packages/editor-skeleton/es/components/TopIcon/index.d.ts @@ -1,6 +1,30 @@ -/// -export interface Props { - name: string; +import { PureComponent } from 'react'; +import './index.scss'; +export default class TopIcon extends PureComponent { + static displayName: string; + static propTypes: { + active: any; + className: any; + disabled: any; + icon: any; + id: any; + locked: any; + onClick: any; + showTitle: any; + style: any; + title: any; + }; + static defaultProps: { + active: boolean; + className: string; + disabled: boolean; + icon: string; + id: string; + locked: boolean; + onClick: () => void; + showTitle: boolean; + style: {}; + title: string; + }; + render(): JSX.Element; } -declare const Greeting: ({ name }: Props) => JSX.Element; -export default Greeting; diff --git a/packages/editor-skeleton/es/components/TopIcon/index.js b/packages/editor-skeleton/es/components/TopIcon/index.js index 3a62c613d..bfa50a96b 100644 --- a/packages/editor-skeleton/es/components/TopIcon/index.js +++ b/packages/editor-skeleton/es/components/TopIcon/index.js @@ -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 name = _ref.name; - return React.createElement("div", { - style: { - textAlign: 'center', - fontSize: '40px', - fontWeight: 'bold' - } - }, "Hello, ", name); +var TopIcon = /*#__PURE__*/function (_PureComponent) { + _inheritsLoose(TopIcon, _PureComponent); + + function TopIcon() { + return _PureComponent.apply(this, arguments) || this; + } + + var _proto = TopIcon.prototype; + + _proto.render = function render() { + var _this$props = this.props, + active = _this$props.active, + disabled = _this$props.disabled, + icon = _this$props.icon, + locked = _this$props.locked, + title = _this$props.title, + className = _this$props.className, + id = _this$props.id, + style = _this$props.style, + showTitle = _this$props.showTitle, + onClick = _this$props.onClick; + return React.createElement(_Button, { + type: "normal", + size: "large", + text: true, + className: classNames('lowcode-top-btn', className, { + active: active, + disabled: disabled, + locked: locked + }), + id: id, + style: style, + onClick: disabled ? null : onClick + }, React.createElement("div", null, React.createElement(_Icon, { + size: "large", + type: icon + }), showTitle && React.createElement("span", null, title))); + }; + + return TopIcon; +}(PureComponent); + +TopIcon.displayName = 'TopIcon'; +TopIcon.propTypes = { + active: PropTypes.bool, + className: PropTypes.string, + disabled: PropTypes.bool, + icon: PropTypes.string, + id: PropTypes.string, + locked: PropTypes.bool, + onClick: PropTypes.func, + showTitle: PropTypes.bool, + style: PropTypes.object, + title: PropTypes.string }; - -export default Greeting; \ No newline at end of file +TopIcon.defaultProps = { + active: false, + className: '', + disabled: false, + icon: '', + id: '', + locked: false, + onClick: function onClick() {}, + showTitle: false, + style: {}, + title: '' +}; +export { TopIcon as default }; \ No newline at end of file diff --git a/packages/editor-skeleton/es/components/TopIcon/index.scss b/packages/editor-skeleton/es/components/TopIcon/index.scss new file mode 100644 index 000000000..1cb3bdfdf --- /dev/null +++ b/packages/editor-skeleton/es/components/TopIcon/index.scss @@ -0,0 +1,32 @@ +.next-btn.next-large.lowcode-top-btn { + width: 44px; + height: 44px; + padding: 0; + margin: 4px -2px; + text-align: center; + border-radius: 8px; + border: 1px solid transparent; + color: #777; + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + &.locked { + color: red !important; + } + i.next-icon { + &:before { + font-size: 17px; + } + margin-right: 0; + line-height: 18px; + } + span { + display: block; + margin: 0px -5px 0; + line-height: 16px; + text-align: center; + font-size: 12px; + transform: scale(0.8); + } +} diff --git a/packages/editor-skeleton/es/components/TopPlugin/index.d.ts b/packages/editor-skeleton/es/components/TopPlugin/index.d.ts new file mode 100644 index 000000000..dc09c377e --- /dev/null +++ b/packages/editor-skeleton/es/components/TopPlugin/index.d.ts @@ -0,0 +1,21 @@ +import { PureComponent } from 'react'; +import './index.scss'; +export default class TopPlugin extends PureComponent { + static displayName: string; + static defaultProps: { + active: boolean; + config: {}; + disabled: boolean; + dotted: boolean; + locked: boolean; + onClick: () => void; + }; + constructor(props: any, context: any); + componentDidMount(): void; + componentWillUnmount(): void; + handleShow: () => void; + handleClose: () => void; + handleOpen: () => void; + renderIcon: (clickCallback: any) => JSX.Element; + render(): JSX.Element; +} diff --git a/packages/editor-skeleton/es/components/TopPlugin/index.js b/packages/editor-skeleton/es/components/TopPlugin/index.js index e69de29bb..e881bb6c1 100644 --- a/packages/editor-skeleton/es/components/TopPlugin/index.js +++ b/packages/editor-skeleton/es/components/TopPlugin/index.js @@ -0,0 +1,213 @@ +import _Balloon from "@alifd/next/es/balloon"; +import _Dialog from "@alifd/next/es/dialog"; +import _extends from "@babel/runtime/helpers/extends"; +import _Badge from "@alifd/next/es/badge"; +import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; +import React, { PureComponent, Fragment } from 'react'; +import TopIcon from '../TopIcon'; +import './index.scss'; + +var TopPlugin = /*#__PURE__*/function (_PureComponent) { + _inheritsLoose(TopPlugin, _PureComponent); + + function TopPlugin(_props, context) { + var _this; + + _this = _PureComponent.call(this, _props, context) || this; + + _this.handleShow = function () { + var _this$props = _this.props, + disabled = _this$props.disabled, + config = _this$props.config, + onClick = _this$props.onClick; + var addonKey = config && config.addonKey; + if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息 + + setTimeout(function () { + return _this.appHelper.emit(addonKey + ".addon.activate"); + }, 0); + + _this.handleOpen(); + + onClick && onClick(); + }; + + _this.handleClose = function () { + var addonKey = _this.props.config && _this.props.config.addonKey; + var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey]; + + if (currentAddon) { + _this.utils.transformToPromise(currentAddon.close()).then(function () { + _this.setState({ + dialogVisible: false + }); + }); + } + }; + + _this.handleOpen = function () { + // todo dialog类型的插件初始时拿不动插件实例 + _this.setState({ + dialogVisible: true + }); + }; + + _this.renderIcon = function (clickCallback) { + var _this$props2 = _this.props, + active = _this$props2.active, + disabled = _this$props2.disabled, + dotted = _this$props2.dotted, + locked = _this$props2.locked, + config = _this$props2.config, + _onClick = _this$props2.onClick; + + var _ref = config || {}, + pluginKey = _ref.pluginKey, + props = _ref.props; + + var _ref2 = props || {}, + icon = _ref2.icon, + title = _ref2.title; + + var node = React.createElement(TopIcon, { + className: "lowcode-top-addon " + pluginKey, + active: active, + disabled: disabled, + locked: locked, + icon: icon, + title: title, + onClick: function onClick() { + if (disabled) return; //考虑到弹窗情况,延时发送消息 + + setTimeout(function () { + return _this.appHelper.emit(pluginKey + ".addon.activate"); + }, 0); + clickCallback && clickCallback(); + _onClick && _onClick(); + } + }); + return dotted ? React.createElement(_Badge, { + dot: true + }, node) : node; + }; + + _this.state = { + dialogVisible: false + }; + return _this; + } + + var _proto = TopPlugin.prototype; + + _proto.componentDidMount = function componentDidMount() { + var config = this.props.config; + var pluginKey = config && config.pluginKey; // const appHelper = this.appHelper; + // if (appHelper && addonKey) { + // appHelper.on(`${addonKey}.dialog.show`, this.handleShow); + // appHelper.on(`${addonKey}.dialog.close`, this.handleClose); + // } + }; + + _proto.componentWillUnmount = function componentWillUnmount() {// const { config } = this.props; + // const addonKey = config && config.addonKey; + // const appHelper = this.appHelper; + // if (appHelper && addonKey) { + // appHelper.off(`${addonKey}.dialog.show`, this.handleShow); + // appHelper.off(`${addonKey}.dialog.close`, this.handleClose); + // } + }; + + _proto.render = function render() { + var _this2 = this; + + var _this$props3 = this.props, + active = _this$props3.active, + dotted = _this$props3.dotted, + locked = _this$props3.locked, + disabled = _this$props3.disabled, + config = _this$props3.config, + editor = _this$props3.editor, + Comp = _this$props3.pluginClass; + + var _ref3 = config || {}, + pluginKey = _ref3.pluginKey, + pluginProps = _ref3.pluginProps, + props = _ref3.props, + type = _ref3.type; + + var _ref4 = props || {}, + _onClick2 = _ref4.onClick, + title = _ref4.title; + + var dialogVisible = this.state.dialogVisible; + if (!pluginKey || !type || !Comp) return null; + var node = React.createElement(Comp, _extends({ + active: active, + locked: locked, + disabled: disabled, + config: config, + onClick: function onClick() { + _onClick2 && _onClick2.call(null, editor); + } + }, pluginProps)); + + switch (type) { + case 'LinkIcon': + return React.createElement("a", props.linkProps, this.renderIcon(function () { + _onClick2 && _onClick2.call(null, editor); + })); + + case 'Icon': + return this.renderIcon(function () { + _onClick2 && _onClick2.call(null, editor); + }); + + case 'DialogIcon': + return React.createElement(Fragment, null, this.renderIcon(function () { + _onClick2 && _onClick2.call(null, editor); + + _this2.handleOpen(); + }), React.createElement(_Dialog, _extends({ + onOk: function onOk() { + editor.emit(pluginKey + ".dialog.onOk"); + + _this2.handleClose(); + }, + onCancel: this.handleClose, + onClose: this.handleClose, + title: title + }, props.dialogProps, { + visible: dialogVisible + }), node)); + + case 'BalloonIcon': + return React.createElement(_Balloon, _extends({ + trigger: this.renderIcon(function () { + _onClick2 && _onClick2.call(null, editor); + }), + triggerType: ['click', 'hover'] + }, props.balloonProps), node); + + case 'Custom': + return dotted ? React.createElement(_Badge, { + dot: true + }, node) : node; + + default: + return null; + } + }; + + return TopPlugin; +}(PureComponent); + +TopPlugin.displayName = 'lowcodeTopPlugin'; +TopPlugin.defaultProps = { + active: false, + config: {}, + disabled: false, + dotted: false, + locked: false, + onClick: function onClick() {} +}; +export { TopPlugin as default }; \ No newline at end of file diff --git a/packages/editor-skeleton/es/components/TopPlugin/index.scss b/packages/editor-skeleton/es/components/TopPlugin/index.scss index e69de29bb..4bdd7b8d2 100644 --- a/packages/editor-skeleton/es/components/TopPlugin/index.scss +++ b/packages/editor-skeleton/es/components/TopPlugin/index.scss @@ -0,0 +1,2 @@ +.lowcode-top-addon { +} diff --git a/packages/editor-skeleton/es/global.scss b/packages/editor-skeleton/es/global.scss index 68689a592..0a710b895 100644 --- a/packages/editor-skeleton/es/global.scss +++ b/packages/editor-skeleton/es/global.scss @@ -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-wrap { height: 100%; @@ -6,17 +16,17 @@ .lowcode-editor { .lowcode-main-content { position: absolute; - top: 54px; + top: 48px; left: 0; right: 0; bottom: 0; display: flex; + background-color: #d8d8d8; } .lowcode-center-area { flex: 1; display: flex; flex-direction: column; - background: #f7f7f7; padding: 10px; overflow: auto; } diff --git a/packages/editor-skeleton/es/index.d.ts b/packages/editor-skeleton/es/index.d.ts index f07a622f4..468afa29c 100644 --- a/packages/editor-skeleton/es/index.d.ts +++ b/packages/editor-skeleton/es/index.d.ts @@ -1,5 +1,4 @@ -/// -import { PureComponent } from 'react-dom'; +import { PureComponent } from 'react'; import './global.scss'; export default class Skeleton extends PureComponent { static displayName: string; diff --git a/packages/editor-skeleton/es/index.js b/packages/editor-skeleton/es/index.js index 7031d8ee9..f875f6604 100644 --- a/packages/editor-skeleton/es/index.js +++ b/packages/editor-skeleton/es/index.js @@ -1,15 +1,29 @@ import _ConfigProvider from "@alifd/next/es/config-provider"; import _Loading from "@alifd/next/es/loading"; import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; -import React, { PureComponent } from 'react-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'; var Skeleton = /*#__PURE__*/function (_PureComponent) { _inheritsLoose(Skeleton, _PureComponent); 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; @@ -23,17 +37,30 @@ var Skeleton = /*#__PURE__*/function (_PureComponent) { location = _this$props.location, history = _this$props.history, messages = _this$props.messages; - return React.createElement(_ConfigProvider, { - locale: messages[appHelper.locale] - }, React.createElement(_Loading, { - tip: this.i18n('loading'), + this.editor.location = location; + this.editor.history = history; + this.editor.messages = messages; + return React.createElement(_ConfigProvider, null, React.createElement(_Loading, { + tip: "Loading", size: "large", - visible: loading || !initReady, + visible: false, shape: "fusion-reactor", fullScreen: true }, React.createElement("div", { className: "lowcode-editor" - }))); + }, React.createElement(TopArea, { + editor: this.editor + }), React.createElement("div", { + className: "lowcode-main-content" + }, React.createElement(LeftArea.Nav, { + editor: this.editor + }), React.createElement(LeftArea.Panel, { + editor: this.editor + }), React.createElement(CenterArea, { + editor: this.editor + }), React.createElement(RightArea, { + editor: this.editor + }))))); }; return Skeleton; diff --git a/packages/editor-skeleton/es/layouts/CenterArea/index.scss b/packages/editor-skeleton/es/layouts/CenterArea/index.scss index e69de29bb..b2584ed2b 100644 --- a/packages/editor-skeleton/es/layouts/CenterArea/index.scss +++ b/packages/editor-skeleton/es/layouts/CenterArea/index.scss @@ -0,0 +1,3 @@ +.lowcode-center-area { + padding: 12px; +} diff --git a/packages/editor-skeleton/es/layouts/LeftArea/index.scss b/packages/editor-skeleton/es/layouts/LeftArea/index.scss index e69de29bb..dac1b6b0a 100644 --- a/packages/editor-skeleton/es/layouts/LeftArea/index.scss +++ b/packages/editor-skeleton/es/layouts/LeftArea/index.scss @@ -0,0 +1,21 @@ +.lowcode-left-area-nav { + width: 48px; + height: 100%; + background: #ffffff; + border-right: 1px solid #e8ebee; + position: relative; + .top-area { + position: absolute; + top: 0; + width: 100%; + background: #ffffff; + max-height: 100%; + } + .bottom-area { + position: absolute; + bottom: 20px; + width: 100%; + background: #ffffff; + max-height: calc(100% - 20px); + } +} diff --git a/packages/editor-skeleton/es/layouts/LeftArea/panel.js b/packages/editor-skeleton/es/layouts/LeftArea/panel.js index 0120423d8..332529ce2 100644 --- a/packages/editor-skeleton/es/layouts/LeftArea/panel.js +++ b/packages/editor-skeleton/es/layouts/LeftArea/panel.js @@ -13,7 +13,7 @@ var LeftAreaPanel = /*#__PURE__*/function (_PureComponent) { _proto.render = function render() { return React.createElement("div", { - className: "lowcode-left-area" + className: "lowcode-left-area-panel" }); }; diff --git a/packages/editor-skeleton/es/layouts/RightArea/index.scss b/packages/editor-skeleton/es/layouts/RightArea/index.scss index e69de29bb..120ef4f11 100644 --- a/packages/editor-skeleton/es/layouts/RightArea/index.scss +++ b/packages/editor-skeleton/es/layouts/RightArea/index.scss @@ -0,0 +1,157 @@ +.lowcode-right-area { + width: 300px; + height: 100%; + background-color: #ffffff; + border-left: 1px solid #e8ebee; + .right-plugin-title { + &.locked { + color: red !important; + } + &.active { + color: $color-brand1-9 !important; + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + } + + //tab定义 + .next-tabs-wrapped.right-tabs { + display: flex; + flex-direction: column; + margin-top: -1px; + .next-tabs-bar { + z-index: 1; + } + .next-tabs-nav { + display: block; + .next-tabs-tab { + &:first-child { + border-left: none; + } + font-size: 14px; + text-align: center; + border-right: none !important; + margin-right: 0 !important; + width: 25%; + &.active { + background: none; + border-bottom-color: #f7f7f7 !important; + } + } + } + } + .next-tabs-content { + flex: 1; + .next-tabs-tabpane.active { + height: 100%; + overflow-y: auto; + } + } + //组件 + .select-comp { + padding: 10px 16px; + line-height: 16px; + color: #989a9c; + & > span { + font-size: 12px; + line-height: 16px; + font-weight: 400; + } + & > .btn-wrap, + & > .next-btn { + width: auto; + margin: 0 5px; + float: right; + } + } + + .unselected { + padding: 60px 0; + text-align: center; + } + //右侧属性面板样式调整; + .offset-56 { + padding-left: 56px; + margin-bottom: 16px; + overflow: hidden; + } + .fixedSpan.next-form-item { + & > .next-form-item-label { + width: 56px; + flex: none; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + & > .next-form-item-control { + padding-right: 24px; + } + } + .fixedSpan.next-form-item, + .offset-56 .next-form-item { + display: flex; + & > .next-form-item-control { + width: auto; + flex: 1; + max-width: none; + .next-input, + .next-select, + .next-radio-group, + .next-number-picker, + .luna-reactnode-btn, + .luna-monaco-button button, + .luna-object-button button { + width: 100%; + } + .next-number-picker { + width: 100%; + .next-after { + padding-right: 5px; + } + } + .next-radio-group { + display: flex; + label { + flex: 1; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + } + .topSpan.next-form-item { + margin-bottom: 4px; + & > .next-form-item-control { + padding-right: 24px; + .next-input, + .next-select, + .next-radio-group, + .next-number-picker, + .luna-reactnode-btn, + .luna-monaco-button button, + .luna-object-button button { + width: 100%; + } + .next-number-picker { + width: 100%; + .next-after { + padding-right: 5px; + } + } + .next-radio-group { + display: flex; + label { + flex: 1; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + } +} diff --git a/packages/editor-skeleton/es/layouts/TopArea/index.d.ts b/packages/editor-skeleton/es/layouts/TopArea/index.d.ts index 716bd94a8..e800a1e74 100644 --- a/packages/editor-skeleton/es/layouts/TopArea/index.d.ts +++ b/packages/editor-skeleton/es/layouts/TopArea/index.d.ts @@ -3,5 +3,9 @@ import './index.scss'; export default class TopArea extends PureComponent { static displayName: string; constructor(props: any); + componentDidMount(): void; + componentWillUnmount(): void; + handlePluginStatusChange: () => void; + renderPluginList: (list?: any[]) => JSX.Element[]; render(): JSX.Element; } diff --git a/packages/editor-skeleton/es/layouts/TopArea/index.js b/packages/editor-skeleton/es/layouts/TopArea/index.js index 39f2b2b23..c82eefc61 100644 --- a/packages/editor-skeleton/es/layouts/TopArea/index.js +++ b/packages/editor-skeleton/es/layouts/TopArea/index.js @@ -1,20 +1,79 @@ import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; +import _Grid from "@alifd/next/es/grid"; import React, { PureComponent } from 'react'; +import TopPlugin from '../../components/TopPlugin'; import './index.scss'; +var Row = _Grid.Row, + Col = _Grid.Col; var TopArea = /*#__PURE__*/function (_PureComponent) { _inheritsLoose(TopArea, _PureComponent); function TopArea(props) { - 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; + _proto.componentDidMount = function componentDidMount() {}; + + _proto.componentWillUnmount = function componentWillUnmount() {}; + _proto.render = function render() { + if (!this.config) return null; + var leftList = []; + var rightList = []; + this.config.forEach(function (item) { + var align = item.props && item.props.align === 'right' ? 'right' : 'left'; // 分隔符不允许相邻 + + if (item.type === 'Divider') { + var currentList = align === 'right' ? rightList : leftList; + if (currList.length === 0 || currList[currList.length - 1].type === 'Divider') return; + } + + if (align === 'right') { + rightList.push(item); + } else { + leftList.push(item); + } + }); return React.createElement("div", { className: "lowcode-top-area" - }); + }, React.createElement("div", { + className: "left-area" + }, this.renderPluginList(leftList)), React.createElement("div", { + classname: "right-area" + }, this.renderPluginList(rightList))); }; return TopArea; diff --git a/packages/editor-skeleton/es/layouts/TopArea/index.scss b/packages/editor-skeleton/es/layouts/TopArea/index.scss index e69de29bb..ca8bbd825 100644 --- a/packages/editor-skeleton/es/layouts/TopArea/index.scss +++ b/packages/editor-skeleton/es/layouts/TopArea/index.scss @@ -0,0 +1,5 @@ +.lowcode-top-area { + height: 48px; + background-color: #ffffff; + border-bottom: 1px solid #e8ebee; +} diff --git a/packages/editor-skeleton/es/style.js b/packages/editor-skeleton/es/style.js index ba2702ef4..7f81d0018 100644 --- a/packages/editor-skeleton/es/style.js +++ b/packages/editor-skeleton/es/style.js @@ -1,2 +1,8 @@ import '@alifd/next/es/config-provider/style'; -import '@alifd/next/es/loading/style'; \ No newline at end of file +import '@alifd/next/es/loading/style'; +import '@alifd/next/es/grid/style'; +import '@alifd/next/es/balloon/style'; +import '@alifd/next/es/dialog/style'; +import '@alifd/next/es/badge/style'; +import '@alifd/next/es/button/style'; +import '@alifd/next/es/icon/style'; \ No newline at end of file diff --git a/packages/editor-skeleton/package.json b/packages/editor-skeleton/package.json index 9067b4544..1320d2903 100644 --- a/packages/editor-skeleton/package.json +++ b/packages/editor-skeleton/package.json @@ -2,6 +2,26 @@ "name": "@ali/lowcode-engine-skeleton", "version": "0.0.1", "description": "alibaba lowcode editor skeleton", + "files": [ + "demo/", + "es/", + "lib/", + "build/" + ], + "main": "lib/index.tsx", + "module": "es/index.js", + "stylePath": "style.js", + "scripts": { + "start": "build-scripts start", + "build": "build-scripts build --skip-demo", + "prepublishOnly": "npm run prettier && npm run build", + "lint": "eslint --cache --ext .js,.jsx ./", + "prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,ejs,less,css,scss,json}\" " + }, + "keywords": [ + "lowcode", + "editor" + ], "author": "xiayang.xy", "dependencies": { "@alifd/next": "^1.x", @@ -21,7 +41,7 @@ "@types/lodash": "^4.14.149", "@types/react": "^16.9.13", "@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-moment-locales": "^0.1.0", "eslint": "^6.0.1", @@ -29,23 +49,6 @@ "react": "^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": { "type": "git", "url": "https://github.com/ice-lab/react-materials/tree/master/scaffolds/ice-ts" diff --git a/packages/editor-skeleton/src/components/LeftPlugin/index.scss b/packages/editor-skeleton/src/components/LeftPlugin/index.scss index e69de29bb..9c6922129 100644 --- a/packages/editor-skeleton/src/components/LeftPlugin/index.scss +++ b/packages/editor-skeleton/src/components/LeftPlugin/index.scss @@ -0,0 +1,59 @@ +.luna-left-addon { + font-size: 16px; + text-align: center; + line-height: 36px; + height: 36px; + position: relative; + cursor: pointer; + transition: all 0.3s ease; + color: #777; + &.collapse { + height: 40px; + color: #8c8c8c; + border-bottom: 1px solid #bfbfbf; + } + &.locked { + color: red !important; + } + &.active { + color: #fff !important; + background-color: $color-brand1-9 !important; + &.disabled { + color: #fff; + background-color: $color-fill1-7; + } + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + &:hover { + background-color: $color-brand1-1; + color: $color-brand1-6; + &:before { + content: attr(data-tooltip); + display: block; + position: absolute; + left: 50px; + top: 5px; + line-height: 18px; + font-size: 12px; + white-space: nowrap; + padding: 6px 8px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.75); + color: #fff; + z-index: 100; + } + &:after { + content: ''; + display: block; + position: absolute; + left: 40px; + top: 15px; + border: 5px solid transparent; + border-right-color: rgba(0, 0, 0, 0.75); + z-index: 100; + } + } +} diff --git a/packages/editor-skeleton/src/components/LeftPlugin/index.tsx b/packages/editor-skeleton/src/components/LeftPlugin/index.tsx index e69de29bb..d9a637a21 100644 --- a/packages/editor-skeleton/src/components/LeftPlugin/index.tsx +++ b/packages/editor-skeleton/src/components/LeftPlugin/index.tsx @@ -0,0 +1,223 @@ +import React, { PureComponent, Fragment } from 'react'; + +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import AppContext from '@ali/iceluna-sdk/lib/context/appContext'; +import { Balloon, Dialog, Icon, Badge } from '@alife/next'; + +import './index.scss'; +export default class LeftAddon extends PureComponent { + static displayName = 'LunaLeftAddon'; + static propTypes = { + active: PropTypes.bool, + config: PropTypes.shape({ + addonKey: PropTypes.string, + addonProps: PropTypes.object, + props: PropTypes.object, + type: PropTypes.oneOf([ + 'DialogIcon', + 'BalloonIcon', + 'PanelIcon', + 'LinkIcon', + 'Icon', + 'Custom', + ]), + }), + disabled: PropTypes.bool, + dotted: PropTypes.bool, + locked: PropTypes.bool, + onClick: PropTypes.func, + }; + static defaultProps = { + active: false, + config: {}, + disabled: false, + dotted: false, + locked: false, + onClick: () => {}, + }; + static contextType = AppContext; + + constructor(props, context) { + super(props, context); + this.state = { + dialogVisible: false, + }; + this.appHelper = context.appHelper; + this.utils = this.appHelper.utils; + this.constants = this.appHelper.constants; + } + + componentDidMount() { + const { config } = this.props; + const addonKey = config && config.addonKey; + const appHelper = this.appHelper; + if (appHelper && addonKey) { + appHelper.on(`${addonKey}.dialog.show`, this.handleShow); + appHelper.on(`${addonKey}.dialog.close`, this.handleClose); + } + } + + componentWillUnmount() { + const { config } = this.props; + const appHelper = this.appHelper; + const addonKey = config && config.addonKey; + if (appHelper && addonKey) { + appHelper.off(`${addonKey}.dialog.show`, this.handleShow); + appHelper.off(`${addonKey}.dialog.close`, this.handleClose); + } + } + + handleClose = () => { + const addonKey = this.props.config && this.props.config.addonKey; + const currentAddon = + this.appHelper.addons && this.appHelper.addons[addonKey]; + if (currentAddon) { + this.utils.transformToPromise(currentAddon.close()).then(() => { + this.setState({ + dialogVisible: false, + }); + }); + } + }; + + handleOpen = () => { + // todo 对话框类型的插件初始时拿不到插件实例 + this.setState({ + dialogVisible: true, + }); + }; + + handleShow = () => { + const { disabled, config, onClick } = this.props; + const addonKey = config && config.addonKey; + if (disabled || !addonKey) return; + //考虑到弹窗情况,延时发送消息 + setTimeout(() => this.appHelper.emit(`${addonKey}.addon.activate`), 0); + this.handleOpen(); + onClick && onClick(); + }; + + renderIcon = clickCallback => { + const { active, disabled, dotted, locked, onClick, config } = this.props; + const { addonKey, props } = config || {}; + const { icon, title } = props || {}; + return ( +
{ + if (disabled) return; + //考虑到弹窗情况,延时发送消息 + clickCallback && clickCallback(); + onClick && onClick(); + }} + > + {dotted ? ( + + + + ) : ( + + )} +
+ ); + }; + + render() { + const { dotted, locked, active, disabled, config } = this.props; + const { addonKey, props, type, addonProps } = config || {}; + const { onClick, title } = props || {}; + const { dialogVisible } = this.state; + const { appHelper, components } = this.context; + if (!addonKey || !type || !props) return null; + const componentName = appHelper.utils.generateAddonCompName(addonKey); + const localeProps = {}; + const { locale, messages } = appHelper; + if (locale) { + localeProps.locale = locale; + } + if (messages && messages[componentName]) { + localeProps.messages = messages[componentName]; + } + const AddonComp = components && components[componentName]; + const node = + (AddonComp && ( + { + onClick && onClick.call(null, appHelper); + }} + {...localeProps} + {...(addonProps || {})} + /> + )) || + null; + + switch (type) { + case 'LinkIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, appHelper); + })} + + ); + case 'Icon': + return this.renderIcon(() => { + onClick && onClick.call(null, appHelper); + }); + case 'DialogIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, appHelper); + this.handleOpen(); + })} + { + appHelper.emit(`${addonKey}.dialog.onOk`); + this.handleClose(); + }} + onCancel={this.handleClose} + onClose={this.handleClose} + title={title} + {...(props.dialogProps || {})} + visible={dialogVisible} + > + {node} + + + ); + case 'BalloonIcon': + return ( + { + onClick && onClick.call(null, appHelper); + })} + align="r" + triggerType={['click', 'hover']} + {...(props.balloonProps || {})} + > + {node} + + ); + case 'PanelIcon': + return this.renderIcon(() => { + onClick && onClick.call(null, appHelper); + this.handleOpen(); + }); + case 'Custom': + return dotted ? {node} : node; + default: + return null; + } + } +} diff --git a/packages/editor-skeleton/src/components/TopIcon/index.scss b/packages/editor-skeleton/src/components/TopIcon/index.scss new file mode 100644 index 000000000..1cb3bdfdf --- /dev/null +++ b/packages/editor-skeleton/src/components/TopIcon/index.scss @@ -0,0 +1,32 @@ +.next-btn.next-large.lowcode-top-btn { + width: 44px; + height: 44px; + padding: 0; + margin: 4px -2px; + text-align: center; + border-radius: 8px; + border: 1px solid transparent; + color: #777; + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + &.locked { + color: red !important; + } + i.next-icon { + &:before { + font-size: 17px; + } + margin-right: 0; + line-height: 18px; + } + span { + display: block; + margin: 0px -5px 0; + line-height: 16px; + text-align: center; + font-size: 12px; + transform: scale(0.8); + } +} diff --git a/packages/editor-skeleton/src/components/TopIcon/index.tsx b/packages/editor-skeleton/src/components/TopIcon/index.tsx index b9369ed63..8e81c9c3a 100644 --- a/packages/editor-skeleton/src/components/TopIcon/index.tsx +++ b/packages/editor-skeleton/src/components/TopIcon/index.tsx @@ -1,15 +1,68 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; -export interface Props { - name: string; +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 ( + + ); + } } - -const Greeting = ({ name }: Props) => { - return ( -
- Hello, {name} -
- ); -}; - -export default Greeting; diff --git a/packages/editor-skeleton/src/components/TopPlugin/index.scss b/packages/editor-skeleton/src/components/TopPlugin/index.scss index e69de29bb..4bdd7b8d2 100644 --- a/packages/editor-skeleton/src/components/TopPlugin/index.scss +++ b/packages/editor-skeleton/src/components/TopPlugin/index.scss @@ -0,0 +1,2 @@ +.lowcode-top-addon { +} diff --git a/packages/editor-skeleton/src/components/TopPlugin/index.tsx b/packages/editor-skeleton/src/components/TopPlugin/index.tsx index e69de29bb..04f5d0e59 100644 --- a/packages/editor-skeleton/src/components/TopPlugin/index.tsx +++ b/packages/editor-skeleton/src/components/TopPlugin/index.tsx @@ -0,0 +1,174 @@ +import React, { PureComponent, Fragment } from 'react'; + +import PropTypes from 'prop-types'; +import TopIcon from '../TopIcon'; +import { Balloon, Badge, Dialog } from '@alifd/next'; + +import './index.scss'; +export default class TopPlugin extends PureComponent { + static displayName = 'lowcodeTopPlugin'; + + static defaultProps = { + active: false, + config: {}, + disabled: false, + dotted: false, + locked: false, + onClick: () => {}, + }; + + constructor(props, context) { + super(props, context); + this.state = { + dialogVisible: false, + }; + } + + componentDidMount() { + const { config } = this.props; + const pluginKey = config && config.pluginKey; + // const appHelper = this.appHelper; + // if (appHelper && addonKey) { + // appHelper.on(`${addonKey}.dialog.show`, this.handleShow); + // appHelper.on(`${addonKey}.dialog.close`, this.handleClose); + // } + } + + componentWillUnmount() { + // const { config } = this.props; + // const addonKey = config && config.addonKey; + // const appHelper = this.appHelper; + // if (appHelper && addonKey) { + // appHelper.off(`${addonKey}.dialog.show`, this.handleShow); + // appHelper.off(`${addonKey}.dialog.close`, this.handleClose); + // } + } + + handleShow = () => { + const { disabled, config, onClick } = this.props; + const addonKey = config && config.addonKey; + if (disabled || !addonKey) return; + //考虑到弹窗情况,延时发送消息 + setTimeout(() => this.appHelper.emit(`${addonKey}.addon.activate`), 0); + this.handleOpen(); + onClick && onClick(); + }; + + handleClose = () => { + const addonKey = this.props.config && this.props.config.addonKey; + const currentAddon = + this.appHelper.addons && this.appHelper.addons[addonKey]; + if (currentAddon) { + this.utils.transformToPromise(currentAddon.close()).then(() => { + this.setState({ + dialogVisible: false, + }); + }); + } + }; + + handleOpen = () => { + // todo dialog类型的插件初始时拿不动插件实例 + this.setState({ + dialogVisible: true, + }); + }; + + renderIcon = clickCallback => { + const { active, disabled, dotted, locked, config, onClick } = this.props; + const { pluginKey, props } = config || {}; + const { icon, title } = props || {}; + const node = ( + { + if (disabled) return; + //考虑到弹窗情况,延时发送消息 + setTimeout( + () => this.appHelper.emit(`${pluginKey}.addon.activate`), + 0, + ); + clickCallback && clickCallback(); + onClick && onClick(); + }} + /> + ); + return dotted ? {node} : node; + }; + + render() { + const { active, dotted, locked, disabled, config, editor, pluginClass: Comp } = this.props; + const { pluginKey, pluginProps, props, type } = config || {}; + const { onClick, title } = props || {}; + const { dialogVisible } = this.state; + if (!pluginKey || !type || !Comp) return null; + const node = { + onClick && onClick.call(null, editor); + }} + {...pluginProps} + />; + + switch (type) { + case 'LinkIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, editor); + })} + + ); + case 'Icon': + return this.renderIcon(() => { + onClick && onClick.call(null, editor); + }); + case 'DialogIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, editor); + this.handleOpen(); + })} + { + editor.emit(`${pluginKey}.dialog.onOk`); + this.handleClose(); + }} + onCancel={this.handleClose} + onClose={this.handleClose} + title={title} + {...props.dialogProps} + visible={dialogVisible} + > + {node} + + + ); + case 'BalloonIcon': + return ( + { + onClick && onClick.call(null, editor); + })} + triggerType={['click', 'hover']} + {...props.balloonProps} + > + {node} + + ); + case 'Custom': + return dotted ? {node} : node; + default: + return null; + } + } +} diff --git a/packages/editor-skeleton/src/config/skeleton.ts b/packages/editor-skeleton/src/config/skeleton.ts index 5d63e83cc..9e3f6898f 100644 --- a/packages/editor-skeleton/src/config/skeleton.ts +++ b/packages/editor-skeleton/src/config/skeleton.ts @@ -13,7 +13,7 @@ const routerConfig = [ { path: '/', redirect: '/dashboard', - } + }, ], }, ]; diff --git a/packages/editor-skeleton/src/global.scss b/packages/editor-skeleton/src/global.scss index 68689a592..0a710b895 100644 --- a/packages/editor-skeleton/src/global.scss +++ b/packages/editor-skeleton/src/global.scss @@ -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-wrap { height: 100%; @@ -6,17 +16,17 @@ .lowcode-editor { .lowcode-main-content { position: absolute; - top: 54px; + top: 48px; left: 0; right: 0; bottom: 0; display: flex; + background-color: #d8d8d8; } .lowcode-center-area { flex: 1; display: flex; flex-direction: column; - background: #f7f7f7; padding: 10px; overflow: auto; } diff --git a/packages/editor-skeleton/src/index.tsx b/packages/editor-skeleton/src/index.tsx index 2a9643b69..3f1c89522 100644 --- a/packages/editor-skeleton/src/index.tsx +++ b/packages/editor-skeleton/src/index.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react-dom'; +import React, { PureComponent } from 'react'; // import Editor from '@ali/lowcode-engine-editor'; import { Loading, ConfigProvider } from '@alifd/next'; @@ -17,6 +17,12 @@ export default class Skeleton extends PureComponent { constructor(props) { super(props); // this.editor = new Editor(props.config, props.utils); + this.editor = { + on: () => {}, + off: () => {}, + config: props.config, + pluginComponents: props.pluginComponents + }; } componentWillUnmount() { @@ -26,24 +32,26 @@ export default class Skeleton extends PureComponent { render() { const { location, history, messages } = this.props; - + this.editor.location = location; + this.editor.history = history; + this.editor.messages = messages; return ( - +
- {/* +
- - - - -
*/} + + + + +
diff --git a/packages/editor-skeleton/src/layouts/CenterArea/index.scss b/packages/editor-skeleton/src/layouts/CenterArea/index.scss index e69de29bb..b2584ed2b 100644 --- a/packages/editor-skeleton/src/layouts/CenterArea/index.scss +++ b/packages/editor-skeleton/src/layouts/CenterArea/index.scss @@ -0,0 +1,3 @@ +.lowcode-center-area { + padding: 12px; +} diff --git a/packages/editor-skeleton/src/layouts/CenterArea/index.tsx b/packages/editor-skeleton/src/layouts/CenterArea/index.tsx index 36263969c..dc2b38d25 100644 --- a/packages/editor-skeleton/src/layouts/CenterArea/index.tsx +++ b/packages/editor-skeleton/src/layouts/CenterArea/index.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react'; +import React, { PureComponent } from 'react'; import './index.scss'; @@ -10,8 +10,6 @@ export default class CenterArea extends PureComponent { } render() { - return ( -
- ); + return
; } -} \ No newline at end of file +} diff --git a/packages/editor-skeleton/src/layouts/LeftArea/index.scss b/packages/editor-skeleton/src/layouts/LeftArea/index.scss index e69de29bb..dac1b6b0a 100644 --- a/packages/editor-skeleton/src/layouts/LeftArea/index.scss +++ b/packages/editor-skeleton/src/layouts/LeftArea/index.scss @@ -0,0 +1,21 @@ +.lowcode-left-area-nav { + width: 48px; + height: 100%; + background: #ffffff; + border-right: 1px solid #e8ebee; + position: relative; + .top-area { + position: absolute; + top: 0; + width: 100%; + background: #ffffff; + max-height: 100%; + } + .bottom-area { + position: absolute; + bottom: 20px; + width: 100%; + background: #ffffff; + max-height: calc(100% - 20px); + } +} diff --git a/packages/editor-skeleton/src/layouts/LeftArea/index.tsx b/packages/editor-skeleton/src/layouts/LeftArea/index.tsx index f77a9b830..805a5e014 100644 --- a/packages/editor-skeleton/src/layouts/LeftArea/index.tsx +++ b/packages/editor-skeleton/src/layouts/LeftArea/index.tsx @@ -3,5 +3,5 @@ import Panel from './panel'; export default { Nav, - Panel -}; \ No newline at end of file + Panel, +}; diff --git a/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx b/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx index 5c1fbea92..6c58c12ef 100644 --- a/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx +++ b/packages/editor-skeleton/src/layouts/LeftArea/nav.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react'; +import React, { PureComponent } from 'react'; import './index.scss'; @@ -10,8 +10,6 @@ export default class LeftAreaPanel extends PureComponent { } render() { - return ( -
- ); + return
; } -} \ No newline at end of file +} diff --git a/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx b/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx index c378ad929..ab41860fb 100644 --- a/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx +++ b/packages/editor-skeleton/src/layouts/LeftArea/panel.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react'; +import React, { PureComponent } from 'react'; import './index.scss'; @@ -10,8 +10,6 @@ export default class LeftAreaPanel extends PureComponent { } render() { - return ( -
- ); + return
; } -} \ No newline at end of file +} diff --git a/packages/editor-skeleton/src/layouts/RightArea/index.scss b/packages/editor-skeleton/src/layouts/RightArea/index.scss index e69de29bb..120ef4f11 100644 --- a/packages/editor-skeleton/src/layouts/RightArea/index.scss +++ b/packages/editor-skeleton/src/layouts/RightArea/index.scss @@ -0,0 +1,157 @@ +.lowcode-right-area { + width: 300px; + height: 100%; + background-color: #ffffff; + border-left: 1px solid #e8ebee; + .right-plugin-title { + &.locked { + color: red !important; + } + &.active { + color: $color-brand1-9 !important; + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + } + + //tab定义 + .next-tabs-wrapped.right-tabs { + display: flex; + flex-direction: column; + margin-top: -1px; + .next-tabs-bar { + z-index: 1; + } + .next-tabs-nav { + display: block; + .next-tabs-tab { + &:first-child { + border-left: none; + } + font-size: 14px; + text-align: center; + border-right: none !important; + margin-right: 0 !important; + width: 25%; + &.active { + background: none; + border-bottom-color: #f7f7f7 !important; + } + } + } + } + .next-tabs-content { + flex: 1; + .next-tabs-tabpane.active { + height: 100%; + overflow-y: auto; + } + } + //组件 + .select-comp { + padding: 10px 16px; + line-height: 16px; + color: #989a9c; + & > span { + font-size: 12px; + line-height: 16px; + font-weight: 400; + } + & > .btn-wrap, + & > .next-btn { + width: auto; + margin: 0 5px; + float: right; + } + } + + .unselected { + padding: 60px 0; + text-align: center; + } + //右侧属性面板样式调整; + .offset-56 { + padding-left: 56px; + margin-bottom: 16px; + overflow: hidden; + } + .fixedSpan.next-form-item { + & > .next-form-item-label { + width: 56px; + flex: none; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + & > .next-form-item-control { + padding-right: 24px; + } + } + .fixedSpan.next-form-item, + .offset-56 .next-form-item { + display: flex; + & > .next-form-item-control { + width: auto; + flex: 1; + max-width: none; + .next-input, + .next-select, + .next-radio-group, + .next-number-picker, + .luna-reactnode-btn, + .luna-monaco-button button, + .luna-object-button button { + width: 100%; + } + .next-number-picker { + width: 100%; + .next-after { + padding-right: 5px; + } + } + .next-radio-group { + display: flex; + label { + flex: 1; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + } + .topSpan.next-form-item { + margin-bottom: 4px; + & > .next-form-item-control { + padding-right: 24px; + .next-input, + .next-select, + .next-radio-group, + .next-number-picker, + .luna-reactnode-btn, + .luna-monaco-button button, + .luna-object-button button { + width: 100%; + } + .next-number-picker { + width: 100%; + .next-after { + padding-right: 5px; + } + } + .next-radio-group { + display: flex; + label { + flex: 1; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + } +} diff --git a/packages/editor-skeleton/src/layouts/RightArea/index.tsx b/packages/editor-skeleton/src/layouts/RightArea/index.tsx index 449a273e5..31273a4e2 100644 --- a/packages/editor-skeleton/src/layouts/RightArea/index.tsx +++ b/packages/editor-skeleton/src/layouts/RightArea/index.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react'; +import React, { PureComponent } from 'react'; import './index.scss'; @@ -10,8 +10,6 @@ export default class RightArea extends PureComponent { } render() { - return ( -
- ); + return
; } -} \ No newline at end of file +} diff --git a/packages/editor-skeleton/src/layouts/TopArea/index.scss b/packages/editor-skeleton/src/layouts/TopArea/index.scss index e69de29bb..ca8bbd825 100644 --- a/packages/editor-skeleton/src/layouts/TopArea/index.scss +++ b/packages/editor-skeleton/src/layouts/TopArea/index.scss @@ -0,0 +1,5 @@ +.lowcode-top-area { + height: 48px; + background-color: #ffffff; + border-bottom: 1px solid #e8ebee; +} diff --git a/packages/editor-skeleton/src/layouts/TopArea/index.tsx b/packages/editor-skeleton/src/layouts/TopArea/index.tsx index 3bc7e3562..c2f60a637 100644 --- a/packages/editor-skeleton/src/layouts/TopArea/index.tsx +++ b/packages/editor-skeleton/src/layouts/TopArea/index.tsx @@ -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'; +const { Row, Col } = Grid; + export default class TopArea extends PureComponent { static displayName = 'lowcodeTopArea'; constructor(props) { super(props); + this.editor = props.editor; + this.config = this.editor.config.plugins && this.editor.config.plugins.topArea; } + componentDidMount() { + } + componentWillUnmount() { + } + + handlePluginStatusChange = () => {}; + + renderPluginList = (list = []) => { + return list.map((item, idx) => { + const isDivider = item.type === 'Divider'; + + return ( + + {!isDivider && ( + + )} + + ); + }); + }; + render() { + if (!this.config) return null; + const leftList = []; + const rightList = []; + this.config.forEach(item => { + const align = + item.props && item.props.align === 'right' ? 'right' : 'left'; + // 分隔符不允许相邻 + if (item.type === 'Divider') { + const currentList = align === 'right' ? rightList : leftList; + if ( + currList.length === 0 || + currList[currList.length - 1].type === 'Divider' + ) + return; + } + if (align === 'right') { + rightList.push(item); + } else { + leftList.push(item); + } + }); + return ( -
+
+
{this.renderPluginList(leftList)}
+
{this.renderPluginList(rightList)}
+
); } -} \ No newline at end of file +} diff --git a/packages/editor-skeleton/src/locale/en-US.js b/packages/editor-skeleton/src/locale/en-US.js index 936701e33..36e3b219c 100644 --- a/packages/editor-skeleton/src/locale/en-US.js +++ b/packages/editor-skeleton/src/locale/en-US.js @@ -6,5 +6,5 @@ export default { 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' + getPermission: 'Please connect the app owners {owners} to get the permission', }; diff --git a/packages/editor-skeleton/src/locale/zh-CN.js b/packages/editor-skeleton/src/locale/zh-CN.js index efe4ea898..2d5229d2c 100644 --- a/packages/editor-skeleton/src/locale/zh-CN.js +++ b/packages/editor-skeleton/src/locale/zh-CN.js @@ -6,5 +6,5 @@ export default { pageNotExist: '当前访问地址不存在', enterFromAppCenter: '请从应用中心入口重新进入', noPermission: '抱歉,您暂无开发权限', - getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限' + getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限', }; diff --git a/packages/editor/ice.config.js b/packages/editor/ice.config.js index f905f88e8..b40ce581a 100644 --- a/packages/editor/ice.config.js +++ b/packages/editor/ice.config.js @@ -8,7 +8,7 @@ module.exports = { }, plugins: [ ['ice-plugin-fusion', { - themePackage: '@icedesign/theme', + themePackage: '@alife/dpl-iceluna', }], ['ice-plugin-moment-locales', { locales: ['zh-cn'], diff --git a/packages/editor/package.json b/packages/editor/package.json index 5f31e94e0..df3f52b9c 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -4,9 +4,11 @@ "description": "低代码编辑器", "dependencies": { "@alifd/next": "^1.x", + "@alife/dpl-iceluna": "^2.3.2", "@icedesign/theme": "^1.x", "@types/react": "^16.8.3", "@types/react-dom": "^16.8.2", + "keymaster": "^1.6.2", "moment": "^2.23.0", "prop-types": "^15.5.8", "react": "^16.4.1", @@ -20,6 +22,7 @@ "ice-plugin-fusion": "^0.1.4", "ice-plugin-moment-locales": "^0.1.0", "ice-scripts": "^2.0.0", + "prettier": "^1.19.1", "stylelint": "^10.1.0" }, "scripts": { @@ -27,7 +30,8 @@ "build": "ice-scripts build", "lint": "npm run eslint && npm run stylelint", "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": { "node": ">=8.0.0" diff --git a/packages/editor/src/config/components.js b/packages/editor/src/config/components.js index 96acc32a7..901075ab6 100644 --- a/packages/editor/src/config/components.js +++ b/packages/editor/src/config/components.js @@ -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 { -}; \ No newline at end of file + 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), +}; diff --git a/packages/editor/src/config/constants.js b/packages/editor/src/config/constants.js index 1c713fc4e..d20e24f41 100644 --- a/packages/editor/src/config/constants.js +++ b/packages/editor/src/config/constants.js @@ -1,3 +1,3 @@ export default { - "namespace": "page" -} \ No newline at end of file + namespace: 'page', +}; diff --git a/packages/editor/src/config/locale/en-US.js b/packages/editor/src/config/locale/en-US.js index 7c645e42f..ff8b4c563 100644 --- a/packages/editor/src/config/locale/en-US.js +++ b/packages/editor/src/config/locale/en-US.js @@ -1 +1 @@ -export default {}; \ No newline at end of file +export default {}; diff --git a/packages/editor/src/config/locale/index.js b/packages/editor/src/config/locale/index.js index eaa4da099..5bc4373a4 100644 --- a/packages/editor/src/config/locale/index.js +++ b/packages/editor/src/config/locale/index.js @@ -6,5 +6,5 @@ export default { 'en-US': en_us, 'zh-CN': zh_cn, 'zh-TW': zh_tw, - 'ja-JP': ja_jp -}; \ No newline at end of file + 'ja-JP': ja_jp, +}; diff --git a/packages/editor/src/config/locale/ja-JP.js b/packages/editor/src/config/locale/ja-JP.js index 7c645e42f..ff8b4c563 100644 --- a/packages/editor/src/config/locale/ja-JP.js +++ b/packages/editor/src/config/locale/ja-JP.js @@ -1 +1 @@ -export default {}; \ No newline at end of file +export default {}; diff --git a/packages/editor/src/config/locale/zh-CN.js b/packages/editor/src/config/locale/zh-CN.js index 7c645e42f..ff8b4c563 100644 --- a/packages/editor/src/config/locale/zh-CN.js +++ b/packages/editor/src/config/locale/zh-CN.js @@ -1 +1 @@ -export default {}; \ No newline at end of file +export default {}; diff --git a/packages/editor/src/config/locale/zh-TW.js b/packages/editor/src/config/locale/zh-TW.js index 7c645e42f..ff8b4c563 100644 --- a/packages/editor/src/config/locale/zh-TW.js +++ b/packages/editor/src/config/locale/zh-TW.js @@ -1 +1 @@ -export default {}; \ No newline at end of file +export default {}; diff --git a/packages/editor/src/config/skeleton.js b/packages/editor/src/config/skeleton.js index 3b3841521..87d035f4a 100644 --- a/packages/editor/src/config/skeleton.js +++ b/packages/editor/src/config/skeleton.js @@ -1,435 +1,215 @@ export default { - "skeleton": { - "config": { - "package": "@ali/lowcode-skeleton", - "version": "0.0.1" + version: '^1.0.2', + theme: { + dpl: { + package: '@alife/dpl-iceluna', + version: '^2.3.0', }, + scss: '', }, - "theme": { - "fusion": { - "package": "@alife/dpl-iceluna", - "version": "^2.3.0" - }, - "scss": "" + constants: { + namespace: 'page', }, - "constants": { - "namespace": "page" + utils: [], + 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": [], - "plugins": { - "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": {} -}; \ No newline at end of file + hooks: [], + shortCuts: [], +}; diff --git a/packages/editor/src/config/utils.js b/packages/editor/src/config/utils.js index dd651ca49..ff8b4c563 100644 --- a/packages/editor/src/config/utils.js +++ b/packages/editor/src/config/utils.js @@ -1,3 +1 @@ -export default { - -}; \ No newline at end of file +export default {}; diff --git a/packages/editor/src/framework/context.ts b/packages/editor/src/framework/context.ts new file mode 100644 index 000000000..78d3ce177 --- /dev/null +++ b/packages/editor/src/framework/context.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; +const context = createContext({}); +export default context; diff --git a/packages/editor/src/framework/definitions.ts b/packages/editor/src/framework/definitions.ts new file mode 100644 index 000000000..73655b11e --- /dev/null +++ b/packages/editor/src/framework/definitions.ts @@ -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; +} + +export interface PluginConfig { + pluginKey: string; + type: string; + props: object; + config: NpmConfig; + pluginProps: object; +} + +export type HooksConfig = Array; + +export interface HookConfig {} diff --git a/packages/editor/src/framework/editor.ts b/packages/editor/src/framework/editor.ts new file mode 100644 index 000000000..78f1209ab --- /dev/null +++ b/packages/editor/src/framework/editor.ts @@ -0,0 +1,186 @@ +import EventEmitter from 'events'; +import Debug from 'debug'; +import store from 'store'; + +import { + unRegistShortCuts, + registShortCuts, + transformToPromise, + generateI18n, +} from './utils'; + +// 根据url参数设置debug选项 +const res = /_?debug=(.*?)(&|$)/.exec(location.search); +if (res && res[1]) { + window.__isDebug = true; + store.storage.write('debug', res[1] === 'true' ? '*' : res[1]); +} else { + window.__isDebug = false; + store.remove('debug'); +} + +//重要,用于矫正画布执行new Function的window对象上下文 +window.__newFunc = funContext => { + return new Function(funContext); +}; + +//关闭浏览器前提醒,只有产生过交互才会生效 +window.onbeforeunload = function(e) { + e = e || window.event; + // 本地调试不生效 + if (location.href.indexOf('localhost') > 0) return; + var msg = '您确定要离开此页面吗?'; + e.cancelBubble = true; + e.returnValue = msg; + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + return msg; +}; + +let instance = null; +const debug = Debug('editor'); +EventEmitter.defaultMaxListeners = 100; + + +export 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, lisenter: function): void { + if (!Array.isArray(events)) return; + events.forEach(event => this.on(event, lisenter)); + } + + batchOnce(events: Array, lisenter: function): void { + if (!Array.isArray(events)) return; + events.forEach(event => this.once(event, lisenter)); + } + + batchOff(events: Array, lisenter: function): void { + if (!Array.isArray(events)) return; + events.forEach(event => this.off(event, lisenter)); + } + + //销毁hooks中的消息监听 + private destroyHooks(hooks = []) { + hooks.forEach((item, idx) => { + if (typeof this.__hooksFuncs[idx] === 'function') { + this.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; + } +} diff --git a/packages/editor/src/framework/index.ts b/packages/editor/src/framework/index.ts new file mode 100644 index 000000000..8b82edf2e --- /dev/null +++ b/packages/editor/src/framework/index.ts @@ -0,0 +1,3 @@ +import Editor from './editor'; + +export default Editor; diff --git a/packages/editor/src/framework/plugin.js b/packages/editor/src/framework/plugin.js new file mode 100644 index 000000000..43bf5ef30 --- /dev/null +++ b/packages/editor/src/framework/plugin.js @@ -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 ( + + ); + } + } + + LowcodePlugin.init = Comp.init; + + return LowcodePlugin; +} diff --git a/packages/editor/src/framework/utils.ts b/packages/editor/src/framework/utils.ts new file mode 100644 index 000000000..59a32d270 --- /dev/null +++ b/packages/editor/src/framework/utils.ts @@ -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-CN、en-US + * @param {*} messages 国际化语言包 + */ +export function generateI18n(locale = 'zh-CN', messages = {}) { + return (key, values = {}) => { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +} + +/** + * 序列化参数 + * @param {*} obj 参数 + */ +export function serializeParams(obj: object): string { + if (typeof obj !== 'object') return ''; + + const res: Array = []; + Object.entries(obj).forEach(([key, val]) => { + if (val === null || val === undefined || val === '') return; + if (typeof val === 'object') { + res.push( + `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}`, + ); + } else { + res.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`); + } + }); + return res.join('&'); +} + +/** + * 黄金令箭埋点 + * @param {String} gmKey 为黄金令箭业务类型 + * @param {Object} params 参数 + * @param {String} logKey 属性串 + */ +export function goldlog(gmKey, params = {}, logKey = 'other') { + const sendIDEMessage = window.sendIDEMessage || window.parent.sendIDEMessage; + const goKey = serializeParams({ + sdkVersion: pkg.version, + env: getEnv(), + ...params, + }); + if (sendIDEMessage) { + sendIDEMessage({ + action: 'goldlog', + data: { + logKey: `/iceluna.core.${logKey}`, + gmKey, + goKey, + }, + }); + } + window.goldlog && + window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST'); +} + +/** + * 获取当前编辑器环境 + */ +export function getEnv() { + const userAgent = navigator.userAgent; + const isVscode = /Electron\//.test(userAgent); + if (isVscode) return ENV.VSCODE; + const isTheia = window.is_theia === true; + if (isTheia) return ENV.WEBIDE; + return ENV.WEB; +} + +// 注册快捷键 +export function registShortCuts(config, editor) { + const keyboardFilter = (keymaster.filter = event => { + let eTarget = event.target || event.srcElement; + let tagName = eTarget.tagName; + let isInput = !!( + tagName == 'INPUT' || + tagName == 'SELECT' || + tagName == 'TEXTAREA' + ); + let isContenteditable = !!eTarget.getAttribute('contenteditable'); + if (isInput || isContenteditable) { + if (event.metaKey === true && [70, 83].includes(event.keyCode)) + event.preventDefault(); //禁止触发chrome原生的页面保存或查找 + return false; + } else { + return true; + } + }); + + const ideMessage = 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, + }; +} diff --git a/packages/editor/src/global.scss b/packages/editor/src/global.scss index e827d91b7..9b8705bdc 100644 --- a/packages/editor/src/global.scss +++ b/packages/editor/src/global.scss @@ -1,8 +1,8 @@ body { - font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma, Arial, PingFang SC-Light, - Microsoft YaHei; + font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma, + Arial, PingFang SC-Light, Microsoft YaHei; font-size: 12px; * { box-sizing: border-box; } -} \ No newline at end of file +} diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index f4b8bc855..43efc5e8e 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -1,10 +1,9 @@ +import React from 'react'; 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 components from './config/components'; -import componentsMap from './config/componentsMap'; import utils from './config/utils'; import constants from './config/constants'; import messages from './config/locale'; @@ -22,21 +21,12 @@ if (!ICE_CONTAINER) { } ReactDOM.render( - - { - return ( - - ); - }} - /> - , ICE_CONTAINER); + , + ICE_CONTAINER, +); diff --git a/packages/editor/src/skeleton/components/LeftPlugin/index.scss b/packages/editor/src/skeleton/components/LeftPlugin/index.scss new file mode 100644 index 000000000..06b6ef63a --- /dev/null +++ b/packages/editor/src/skeleton/components/LeftPlugin/index.scss @@ -0,0 +1,59 @@ +.lowcode-left-plugin { + font-size: 16px; + text-align: center; + line-height: 36px; + height: 36px; + position: relative; + cursor: pointer; + transition: all 0.3s ease; + color: #777; + &.collapse { + height: 40px; + color: #8c8c8c; + border-bottom: 1px solid #bfbfbf; + } + &.locked { + color: red !important; + } + &.active { + color: #fff !important; + background-color: $color-brand1-9 !important; + &.disabled { + color: #fff; + background-color: $color-fill1-7; + } + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + &:hover { + background-color: $color-brand1-1; + color: $color-brand1-6; + &:before { + content: attr(data-tooltip); + display: block; + position: absolute; + left: 50px; + top: 5px; + line-height: 18px; + font-size: 12px; + white-space: nowrap; + padding: 6px 8px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.75); + color: #fff; + z-index: 100; + } + &:after { + content: ''; + display: block; + position: absolute; + left: 40px; + top: 15px; + border: 5px solid transparent; + border-right-color: rgba(0, 0, 0, 0.75); + z-index: 100; + } + } +} diff --git a/packages/editor/src/skeleton/components/LeftPlugin/index.tsx b/packages/editor/src/skeleton/components/LeftPlugin/index.tsx new file mode 100644 index 000000000..e32a4dcc0 --- /dev/null +++ b/packages/editor/src/skeleton/components/LeftPlugin/index.tsx @@ -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 ( +
{ + if (disabled) return; + //考虑到弹窗情况,延时发送消息 + clickCallback && clickCallback(); + onClick && onClick(); + }} + > + {dotted ? ( + + + + ) : ( + + )} +
+ ); + }; + + 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 && ( + { + onClick && onClick.call(null, editor); + }} + {...pluginProps} + /> + )) || + null; + + switch (type) { + case 'LinkIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, editor); + })} + + ); + case 'Icon': + return this.renderIcon(() => { + onClick && onClick.call(null, editor); + }); + case 'DialogIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, editor); + this.handleOpen(); + })} + { + editor.emit(`${pluginKey}.dialog.onOk`); + this.handleClose(); + }} + onCancel={this.handleClose} + onClose={this.handleClose} + title={title} + {...(props.dialogProps || {})} + visible={dialogVisible} + > + {node} + + + ); + case 'BalloonIcon': + return ( + { + onClick && onClick.call(null, editor); + })} + align="r" + triggerType={['click', 'hover']} + {...(props.balloonProps || {})} + > + {node} + + ); + case 'PanelIcon': + return this.renderIcon(() => { + onClick && onClick.call(null, editor); + this.handleOpen(); + }); + case 'Custom': + return dotted ? {node} : node; + default: + return null; + } + } +} diff --git a/packages/editor/src/skeleton/components/Panel/index.scss b/packages/editor/src/skeleton/components/Panel/index.scss new file mode 100644 index 000000000..cd3211ab4 --- /dev/null +++ b/packages/editor/src/skeleton/components/Panel/index.scss @@ -0,0 +1,52 @@ +.lowcode-panel { + user-select: none; + overflow: hidden; + position: relative; + background: #ffffff; + transition: width 0.3s ease; + transform: translate3d(0, 0, 0); + height: 100%; + &.visible { + border-right: 1px solid #bfbfbf; + } + .drag-area { + display: none; + } + &.floatable { + position: absolute; + top: 0; + bottom: 0; + z-index: 999; + } + &.draggable { + .drag-area { + display: block; + width: 10px; + position: absolute; + top: 0; + bottom: 0; + cursor: col-resize; + z-index: 9999; + } + &.left { + .drag-area { + right: 0; + } + } + &.right { + .drag-area { + left: 0; + } + } + } + &.left { + &.floatable { + left: 44px; + } + } + &.right { + &.floatable { + right: 44px; + } + } +} diff --git a/packages/editor/src/skeleton/components/Panel/index.tsx b/packages/editor/src/skeleton/components/Panel/index.tsx new file mode 100644 index 000000000..12b44c51b --- /dev/null +++ b/packages/editor/src/skeleton/components/Panel/index.tsx @@ -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 ( +
+ {this.props.children} +
+ ); + } +} diff --git a/packages/editor/src/skeleton/components/TopIcon/index.scss b/packages/editor/src/skeleton/components/TopIcon/index.scss new file mode 100644 index 000000000..a75e4cbd5 --- /dev/null +++ b/packages/editor/src/skeleton/components/TopIcon/index.scss @@ -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); + } +} diff --git a/packages/editor/src/skeleton/components/TopIcon/index.tsx b/packages/editor/src/skeleton/components/TopIcon/index.tsx new file mode 100644 index 000000000..8e81c9c3a --- /dev/null +++ b/packages/editor/src/skeleton/components/TopIcon/index.tsx @@ -0,0 +1,68 @@ +import React, { PureComponent } from 'react'; + +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { Icon, Button } from '@alifd/next'; +import './index.scss'; +export default class TopIcon extends PureComponent { + static displayName = 'TopIcon'; + static propTypes = { + active: PropTypes.bool, + className: PropTypes.string, + disabled: PropTypes.bool, + icon: PropTypes.string, + id: PropTypes.string, + locked: PropTypes.bool, + onClick: PropTypes.func, + showTitle: PropTypes.bool, + style: PropTypes.object, + title: PropTypes.string, + }; + static defaultProps = { + active: false, + className: '', + disabled: false, + icon: '', + id: '', + locked: false, + onClick: () => {}, + showTitle: false, + style: {}, + title: '', + }; + + render() { + const { + active, + disabled, + icon, + locked, + title, + className, + id, + style, + showTitle, + onClick, + } = this.props; + return ( + + ); + } +} diff --git a/packages/editor/src/skeleton/components/TopPlugin/index.scss b/packages/editor/src/skeleton/components/TopPlugin/index.scss new file mode 100644 index 000000000..4bdd7b8d2 --- /dev/null +++ b/packages/editor/src/skeleton/components/TopPlugin/index.scss @@ -0,0 +1,2 @@ +.lowcode-top-addon { +} diff --git a/packages/editor/src/skeleton/components/TopPlugin/index.tsx b/packages/editor/src/skeleton/components/TopPlugin/index.tsx new file mode 100644 index 000000000..720efe425 --- /dev/null +++ b/packages/editor/src/skeleton/components/TopPlugin/index.tsx @@ -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 = ( + { + if (disabled) return; + //考虑到弹窗情况,延时发送消息 + setTimeout(() => editor.emit(`${pluginKey}.addon.activate`), 0); + clickCallback && clickCallback(); + onClick && onClick(); + }} + /> + ); + return dotted ? {node} : node; + }; + + render() { + const { + active, + dotted, + locked, + disabled, + config, + editor, + pluginClass: Comp, + } = this.props; + const { pluginKey, pluginProps, props, type } = config || {}; + const { onClick, title } = props || {}; + const { dialogVisible } = this.state; + if (!pluginKey || !type) return null; + const node = + (Comp && ( + { + onClick && onClick.call(null, editor); + }} + {...pluginProps} + /> + )) || + null; + + switch (type) { + case 'LinkIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, editor); + })} + + ); + case 'Icon': + return this.renderIcon(() => { + onClick && onClick.call(null, editor); + }); + case 'DialogIcon': + return ( + + {this.renderIcon(() => { + onClick && onClick.call(null, editor); + this.handleOpen(); + })} + { + editor.emit(`${pluginKey}.dialog.onOk`); + this.handleClose(); + }} + onCancel={this.handleClose} + onClose={this.handleClose} + title={title} + {...props.dialogProps} + visible={dialogVisible} + > + {node} + + + ); + case 'BalloonIcon': + return ( + { + onClick && onClick.call(null, editor); + })} + triggerType={['click', 'hover']} + {...props.balloonProps} + > + {node} + + ); + case 'Custom': + return dotted ? {node} : node; + default: + return null; + } + } +} diff --git a/packages/editor/src/skeleton/config/skeleton.ts b/packages/editor/src/skeleton/config/skeleton.ts new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/editor/src/skeleton/config/skeleton.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/editor/src/skeleton/config/utils.ts b/packages/editor/src/skeleton/config/utils.ts new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/editor/src/skeleton/config/utils.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/editor/src/skeleton/global.scss b/packages/editor/src/skeleton/global.scss new file mode 100644 index 000000000..0a710b895 --- /dev/null +++ b/packages/editor/src/skeleton/global.scss @@ -0,0 +1,33 @@ +body { + font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma, + Arial, PingFang SC-Light, Microsoft YaHei; + font-size: 12px; + padding: 0; + margin: 0; + * { + box-sizing: border-box; + } +} +.next-loading { + .next-loading-wrap { + height: 100%; + } +} +.lowcode-editor { + .lowcode-main-content { + position: absolute; + top: 48px; + left: 0; + right: 0; + bottom: 0; + display: flex; + background-color: #d8d8d8; + } + .lowcode-center-area { + flex: 1; + display: flex; + flex-direction: column; + padding: 10px; + overflow: auto; + } +} diff --git a/packages/editor/src/skeleton/index.tsx b/packages/editor/src/skeleton/index.tsx new file mode 100644 index 000000000..47ca4ef86 --- /dev/null +++ b/packages/editor/src/skeleton/index.tsx @@ -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 ( + + { + 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 ( + + +
+ +
+ + + + +
+
+
+
+ ); + }} + /> +
+ ); + } +} diff --git a/packages/editor/src/skeleton/layouts/CenterArea/index.scss b/packages/editor/src/skeleton/layouts/CenterArea/index.scss new file mode 100644 index 000000000..b2584ed2b --- /dev/null +++ b/packages/editor/src/skeleton/layouts/CenterArea/index.scss @@ -0,0 +1,3 @@ +.lowcode-center-area { + padding: 12px; +} diff --git a/packages/editor/src/skeleton/layouts/CenterArea/index.tsx b/packages/editor/src/skeleton/layouts/CenterArea/index.tsx new file mode 100644 index 000000000..f1a463caf --- /dev/null +++ b/packages/editor/src/skeleton/layouts/CenterArea/index.tsx @@ -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 ( +
+ {list.map(item => { + const Comp = this.editor.components[item.pluginKey]; + return ( + + ) + })} +
+ ); + } +} diff --git a/packages/editor/src/skeleton/layouts/LeftArea/index.scss b/packages/editor/src/skeleton/layouts/LeftArea/index.scss new file mode 100644 index 000000000..dac1b6b0a --- /dev/null +++ b/packages/editor/src/skeleton/layouts/LeftArea/index.scss @@ -0,0 +1,21 @@ +.lowcode-left-area-nav { + width: 48px; + height: 100%; + background: #ffffff; + border-right: 1px solid #e8ebee; + position: relative; + .top-area { + position: absolute; + top: 0; + width: 100%; + background: #ffffff; + max-height: 100%; + } + .bottom-area { + position: absolute; + bottom: 20px; + width: 100%; + background: #ffffff; + max-height: calc(100% - 20px); + } +} diff --git a/packages/editor/src/skeleton/layouts/LeftArea/index.tsx b/packages/editor/src/skeleton/layouts/LeftArea/index.tsx new file mode 100644 index 000000000..805a5e014 --- /dev/null +++ b/packages/editor/src/skeleton/layouts/LeftArea/index.tsx @@ -0,0 +1,7 @@ +import Nav from './nav'; +import Panel from './panel'; + +export default { + Nav, + Panel, +}; diff --git a/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx b/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx new file mode 100644 index 000000000..84d54f3ce --- /dev/null +++ b/packages/editor/src/skeleton/layouts/LeftArea/nav.tsx @@ -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 ( + 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 ( +
+
{this.renderPluginList(bottomList)}
+
{this.renderPluginList(topList)}
+
+ ); + } +} diff --git a/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx b/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx new file mode 100644 index 000000000..10d8348ad --- /dev/null +++ b/packages/editor/src/skeleton/layouts/LeftArea/panel.tsx @@ -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 ( + + {list.map((item, idx) => { + const Comp = this.editor.components[item.pluginKey]; + return ( + + + + ); + })} + + ); + } +} diff --git a/packages/editor/src/skeleton/layouts/RightArea/index.scss b/packages/editor/src/skeleton/layouts/RightArea/index.scss new file mode 100644 index 000000000..120ef4f11 --- /dev/null +++ b/packages/editor/src/skeleton/layouts/RightArea/index.scss @@ -0,0 +1,157 @@ +.lowcode-right-area { + width: 300px; + height: 100%; + background-color: #ffffff; + border-left: 1px solid #e8ebee; + .right-plugin-title { + &.locked { + color: red !important; + } + &.active { + color: $color-brand1-9 !important; + } + &.disabled { + cursor: not-allowed; + color: $color-text1-1; + } + } + + //tab定义 + .next-tabs-wrapped.right-tabs { + display: flex; + flex-direction: column; + margin-top: -1px; + .next-tabs-bar { + z-index: 1; + } + .next-tabs-nav { + display: block; + .next-tabs-tab { + &:first-child { + border-left: none; + } + font-size: 14px; + text-align: center; + border-right: none !important; + margin-right: 0 !important; + width: 25%; + &.active { + background: none; + border-bottom-color: #f7f7f7 !important; + } + } + } + } + .next-tabs-content { + flex: 1; + .next-tabs-tabpane.active { + height: 100%; + overflow-y: auto; + } + } + //组件 + .select-comp { + padding: 10px 16px; + line-height: 16px; + color: #989a9c; + & > span { + font-size: 12px; + line-height: 16px; + font-weight: 400; + } + & > .btn-wrap, + & > .next-btn { + width: auto; + margin: 0 5px; + float: right; + } + } + + .unselected { + padding: 60px 0; + text-align: center; + } + //右侧属性面板样式调整; + .offset-56 { + padding-left: 56px; + margin-bottom: 16px; + overflow: hidden; + } + .fixedSpan.next-form-item { + & > .next-form-item-label { + width: 56px; + flex: none; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + & > .next-form-item-control { + padding-right: 24px; + } + } + .fixedSpan.next-form-item, + .offset-56 .next-form-item { + display: flex; + & > .next-form-item-control { + width: auto; + flex: 1; + max-width: none; + .next-input, + .next-select, + .next-radio-group, + .next-number-picker, + .luna-reactnode-btn, + .luna-monaco-button button, + .luna-object-button button { + width: 100%; + } + .next-number-picker { + width: 100%; + .next-after { + padding-right: 5px; + } + } + .next-radio-group { + display: flex; + label { + flex: 1; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + } + .topSpan.next-form-item { + margin-bottom: 4px; + & > .next-form-item-control { + padding-right: 24px; + .next-input, + .next-select, + .next-radio-group, + .next-number-picker, + .luna-reactnode-btn, + .luna-monaco-button button, + .luna-object-button button { + width: 100%; + } + .next-number-picker { + width: 100%; + .next-after { + padding-right: 5px; + } + } + .next-radio-group { + display: flex; + label { + flex: 1; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + } +} diff --git a/packages/editor/src/skeleton/layouts/RightArea/index.tsx b/packages/editor/src/skeleton/layouts/RightArea/index.tsx new file mode 100644 index 000000000..0304929c0 --- /dev/null +++ b/packages/editor/src/skeleton/layouts/RightArea/index.tsx @@ -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 ( +
+ + {list.map((item, idx) => { + const Comp = this.editor.components[item.pluginKey]; + return ( + + + + ); + })} + +
+ ); + } +} diff --git a/packages/editor/src/skeleton/layouts/TopArea/index.scss b/packages/editor/src/skeleton/layouts/TopArea/index.scss new file mode 100644 index 000000000..783d48092 --- /dev/null +++ b/packages/editor/src/skeleton/layouts/TopArea/index.scss @@ -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; + } +} diff --git a/packages/editor/src/skeleton/layouts/TopArea/index.tsx b/packages/editor/src/skeleton/layouts/TopArea/index.tsx new file mode 100644 index 000000000..a59ca6990 --- /dev/null +++ b/packages/editor/src/skeleton/layouts/TopArea/index.tsx @@ -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 ( + + {!isDivider && ( + + )} + + ); + }); + }; + + render() { + if (!this.config) return null; + const leftList = []; + const rightList = []; + this.config.forEach(item => { + const align = + item.props && item.props.align === 'right' ? 'right' : 'left'; + // 分隔符不允许相邻 + if (item.type === 'Divider') { + const currList = align === 'right' ? rightList : leftList; + if ( + currList.length === 0 || + currList[currList.length - 1].type === 'Divider' + ) + return; + } + if (align === 'right') { + rightList.push(item); + } else { + leftList.push(item); + } + }); + return ( +
+
+ {this.renderPluginList(leftList)} +
+
+ {this.renderPluginList(rightList)} +
+
+ ); + } +} diff --git a/packages/editor/src/skeleton/locale/en-US.js b/packages/editor/src/skeleton/locale/en-US.js new file mode 100644 index 000000000..36e3b219c --- /dev/null +++ b/packages/editor/src/skeleton/locale/en-US.js @@ -0,0 +1,10 @@ +export default { + loading: 'loading...', + rejectRedirect: 'Redirect is not allowed', + expand: 'Unfold', + fold: 'Fold', + pageNotExist: 'The current Page not exist', + enterFromAppCenter: 'Please enter from the app center', + noPermission: 'Sorry, you do not have the develop permission', + getPermission: 'Please connect the app owners {owners} to get the permission', +}; diff --git a/packages/editor/src/skeleton/locale/ja-JP.js b/packages/editor/src/skeleton/locale/ja-JP.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/editor/src/skeleton/locale/ja-JP.js @@ -0,0 +1 @@ +export default {}; diff --git a/packages/editor/src/skeleton/locale/zh-CN.js b/packages/editor/src/skeleton/locale/zh-CN.js new file mode 100644 index 000000000..2d5229d2c --- /dev/null +++ b/packages/editor/src/skeleton/locale/zh-CN.js @@ -0,0 +1,10 @@ +export default { + loading: '加载中...', + rejectRedirect: '开发中,已阻止发生跳转', + expand: '展开', + fold: '收起', + pageNotExist: '当前访问地址不存在', + enterFromAppCenter: '请从应用中心入口重新进入', + noPermission: '抱歉,您暂无开发权限', + getPermission: '请移步应用中心申请开发权限, 或联系 {owners} 开通权限', +}; diff --git a/packages/editor/src/skeleton/locale/zh-TW.js b/packages/editor/src/skeleton/locale/zh-TW.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/editor/src/skeleton/locale/zh-TW.js @@ -0,0 +1 @@ +export default {};