daily tag

This commit is contained in:
下羊 2020-03-10 10:21:01 +08:00
parent baa6adb199
commit 8eb0851e4c
18 changed files with 445 additions and 180 deletions

View File

@ -8,7 +8,7 @@ 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';
import PluginFactory from '../framework/pluginFactory';
export default {
topBalloonIcon: PluginFactory(topBalloonIcon),

View File

@ -159,8 +159,7 @@ export default {
pluginKey: 'rightPanel1',
type: 'Panel',
props: {
title: 'panel1',
icon: 'dengpao'
title: '样式'
},
config: {
package: '@ali/iceluna-addon-2',
@ -172,7 +171,7 @@ export default {
pluginKey: 'rightPanel2',
type: 'Panel',
props: {
title: 'panel2',
title: '属性',
icon: 'dengpao'
},
config: {
@ -185,8 +184,7 @@ export default {
pluginKey: 'rightPanel3',
type: 'Panel',
props: {
title: 'panel3',
icon: 'dengpao'
title: '事件'
},
config: {
package: '@ali/iceluna-addon-2',
@ -198,8 +196,7 @@ export default {
pluginKey: 'rightPanel4',
type: 'Panel',
props: {
title: 'panel4',
icon: 'dengpao'
title: '数据'
},
config: {
package: '@ali/iceluna-addon-2',

View File

@ -0,0 +1,31 @@
import Editor from './index';
import { PluginConfig, PluginStatus } from './definitions';
import { clone, deepEqual } from './utils';
export default class AreaManager {
private pluginStatus: PluginStatus;
private config: Array<PluginConfig>;
constructor(private editor: Editor, private area: string) {
this.config = (editor && editor.config && editor.config.plugins && editor.config.plugins[this.area]) || [];
this.pluginStatus = clone(editor.pluginStatus);
}
isPluginStatusUpdate(): boolean {
const { pluginStatus } = this.editor;
const isUpdate = this.config.some(
item => !deepEqual(pluginStatus[item.pluginKey], this.pluginStatus[item.pluginKey])
);
this.pluginStatus = clone(pluginStatus);
return isUpdate;
}
getVisiblePluginList(): Array<PluginConfig> {
return this.config.filter(item => {
return !this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible;
});
}
getPluginConfig(): Array<PluginConfig> {
return this.config;
}
}

View File

@ -125,5 +125,6 @@ export interface PluginStatus {
disabled: boolean;
visible: boolean;
marked: boolean;
locked: boolean;
};
}

View File

@ -1,5 +1,5 @@
import Editor from './editor';
export { default as PluginFactory } from './plugin';
export { default as PluginFactory } from './pluginFactory';
export { default as EditorContext } from './context';
import * as editorUtils from './utils';

View File

@ -14,7 +14,7 @@ export interface InjectedPluginProps {
i18n?: I18nFunction;
}
export default function plugin(
export default function pluginFactory(
Comp: React.ComponentType<PluginProps & InjectedPluginProps>
): React.ComponentType<PluginProps> {
class LowcodePlugin extends PureComponent<PluginProps> {
@ -31,7 +31,6 @@ export default function plugin(
constructor(props, context) {
super(props, context);
if (isEmpty(props.config) || !props.config.pluginKey) {
console.warn('lowcode editor plugin has wrong config');
return;
@ -41,8 +40,10 @@ export default function plugin(
this.editor = editor;
this.i18n = generateI18n(locale, messages);
this.pluginKey = props.config.pluginKey;
editor.plugins = editor.plugins || {};
editor.plugins[this.pluginKey] = this;
editor.set('plugins', {
...editor.plugins,
[this.pluginKey]: this
});
}
componentWillUnmount() {
@ -52,6 +53,14 @@ export default function plugin(
}
}
open = () => {
return this.ref && this.ref.open && this.ref.open();
};
close = () => {
return this.ref && this.ref.close && this.ref.close();
};
render() {
const { config } = this.props;
return <Comp ref={this.ref} i18n={this.i18n} editor={this.editor} config={config} {...config.pluginProps} />;

View File

@ -1,10 +1,24 @@
import IntlMessageFormat from 'intl-messageformat';
import keymaster from 'keymaster';
import _isEmpty from 'lodash/isEmpty';
import { EditorConfig, LocaleType, I18nMessages, I18nFunction, ShortCutsConfig } from './definitions';
import Editor from './editor';
import _pick from 'lodash/pick';
import _deepEqual from 'lodash/isEqualWith';
import _clone from 'lodash/cloneDeep';
import _isEmpty from 'lodash/isEmpty';
import _throttle from 'lodash/throttle';
import _debounce from 'lodash/debounce';
export const pick = _pick;
export const deepEqual = _deepEqual;
export const clone = _clone;
export const isEmpty = _isEmpty;
export const throttle = _throttle;
export const debounce = _debounce;
import _serialize from 'serialize-javascript';
export const serialize = _serialize;
const ENV = {
TBE: 'TBE',

View File

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

View File

@ -14,6 +14,41 @@
&.locked {
color: red !important;
}
&:hover {
background-color: $color-brand1-1;
color: $color-brand1-6;
&:before {
content: attr(data-tooltip);
display: block;
height: auto;
width: auto;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
bottom: -35px;
line-height: 18px;
font-size: 12px;
white-space: nowrap;
padding: 6px 8px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.75);
color: #fff;
z-index: 100;
}
&:after {
content: '';
display: block;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
bottom: -5px;
border: 5px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.75);
opacity: 1;
visibility: visible;
z-index: 100;
}
}
i.next-icon {
&:before {
font-size: 17px;

View File

@ -45,6 +45,7 @@ export default class TopIcon extends PureComponent<TopIconProps> {
disabled,
locked
})}
data-tooltip={title}
id={id}
style={style}
onClick={disabled ? undefined : onClick}

View File

@ -22,6 +22,9 @@ let renderIdx = 0;
export interface SkeletonProps {
components: PluginComponents;
config: EditorConfig;
history: object;
location: object;
match: object;
utils: Utils;
}
@ -99,38 +102,30 @@ export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState
render() {
const { initReady, skeletonKey, __hasError } = this.state;
const { location, history, match } = this.props;
if (__hasError || !this.editor) {
return 'error';
}
location.query = parseSearch(location.search);
this.editor.set('location', location);
this.editor.set('history', history);
this.editor.set('match', match);
return (
<Router>
<Route
path="/*"
component={props => {
const { location, history, match } = props;
location.query = parseSearch(location.search);
this.editor.set('location', location);
this.editor.set('history', history);
this.editor.set('match', match);
return (
<ConfigProvider>
<Loading tip="Loading" size="large" visible={!initReady} shape="fusion-reactor" fullScreen>
<div className="lowcode-editor" key={skeletonKey}>
<TopArea editor={this.editor} />
<div className="lowcode-main-content">
<LeftArea.Nav editor={this.editor} />
<LeftArea.Panel editor={this.editor} />
<CenterArea editor={this.editor} />
<RightArea editor={this.editor} />
</div>
</div>
</Loading>
</ConfigProvider>
);
}}
/>
</Router>
<ConfigProvider>
<Loading tip="Loading" size="large" visible={!initReady} shape="fusion-reactor" fullScreen>
<div className="lowcode-editor" key={skeletonKey}>
<TopArea editor={this.editor} />
<div className="lowcode-main-content">
<LeftArea.Nav editor={this.editor} />
<LeftArea.Panel editor={this.editor} />
<CenterArea editor={this.editor} />
<RightArea editor={this.editor} />
</div>
</div>
</Loading>
</ConfigProvider>
);
}
}

View File

@ -3,6 +3,7 @@ import React, { PureComponent } from 'react';
import Editor from '../../../framework/editor';
import { PluginConfig } from '../../../framework/definitions';
import './index.scss';
import AreaManager from '../../../framework/areaManager';
export interface CenterAreaProps {
editor: Editor;
@ -12,18 +13,33 @@ export default class CenterArea extends PureComponent<CenterAreaProps> {
static displayName = 'LowcodeCenterArea';
private editor: Editor;
private config: Array<PluginConfig>;
private areaManager: AreaManager;
constructor(props) {
super(props);
this.editor = props.editor;
this.config = (this.editor.config && this.editor.config.plugins && this.editor.config.plugins.centerArea) || [];
this.areaManager = new AreaManager(this.editor, 'centerArea');
}
componentDidMount() {
this.editor.on('skeleton.update', this.handleSkeletonUpdate);
}
componentWillUnmount() {
this.editor.off('skeleton.update', this.handleSkeletonUpdate);
}
handleSkeletonUpdate = (): void => {
// 当前区域插件状态改变是更新区域
if (this.areaManager.isPluginStatusUpdate()) {
this.forceUpdate();
}
};
render() {
const visiblePluginList = this.areaManager.getVisiblePluginList();
return (
<div className="lowcode-center-area">
{this.config.map(item => {
{visiblePluginList.map(item => {
const Comp = this.editor.components[item.pluginKey];
return <Comp editor={this.editor} config={item} {...item.pluginProps} />;
})}

View File

@ -3,6 +3,7 @@ import LeftPlugin from '../../components/LeftPlugin';
import './index.scss';
import Editor from '../../../framework/editor';
import { PluginConfig } from '../../../framework/definitions';
import AreaManager from '../../../framework/areaManager';
export interface LeftAreaNavProps {
editor: Editor;
@ -12,15 +13,29 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps> {
static displayName = 'LowcodeLeftAreaNav';
private editor: Editor;
private config: Array<PluginConfig>;
private areaManager: AreaManager;
constructor(props) {
super(props);
this.editor = props.editor;
this.config = (this.editor.config.plugins && this.editor.config.plugins.leftArea) || [];
this.areaManager = new AreaManager(this.editor, 'leftArea');
}
handlePluginClick = item => {};
componentDidMount() {
this.editor.on('skeleton.update', this.handleSkeletonUpdate);
}
componentWillUnmount() {
this.editor.off('skeleton.update', this.handleSkeletonUpdate);
}
handleSkeletonUpdate = (): void => {
// 当前区域插件状态改变是更新区域
if (this.areaManager.isPluginStatusUpdate()) {
this.forceUpdate();
}
};
handlePluginClick = (item: PluginConfig): void => {};
renderPluginList = (list: Array<PluginConfig> = []): Array<React.ReactElement> => {
return list.map((item, idx) => {
@ -39,7 +54,8 @@ export default class LeftAreaNav extends PureComponent<LeftAreaNavProps> {
render() {
const topList: Array<PluginConfig> = [];
const bottomList: Array<PluginConfig> = [];
this.config.forEach(item => {
const visiblePluginList = this.areaManager.getVisiblePluginList();
visiblePluginList.forEach(item => {
const align = item.props && item.props.align === 'bottom' ? 'bottom' : 'top';
if (align === 'bottom') {
bottomList.push(item);

View File

@ -49,109 +49,4 @@
overflow-y: auto;
}
}
//组件
.select-comp {
padding: 10px 16px;
line-height: 16px;
color: #989a9c;
& > span {
font-size: 12px;
line-height: 16px;
font-weight: 400;
}
& > .btn-wrap,
& > .next-btn {
width: auto;
margin: 0 5px;
float: right;
}
}
.unselected {
padding: 60px 0;
text-align: center;
}
//右侧属性面板样式调整;
.offset-56 {
padding-left: 56px;
margin-bottom: 16px;
overflow: hidden;
}
.fixedSpan.next-form-item {
& > .next-form-item-label {
width: 56px;
flex: none;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
& > .next-form-item-control {
padding-right: 24px;
}
}
.fixedSpan.next-form-item,
.offset-56 .next-form-item {
display: flex;
& > .next-form-item-control {
width: auto;
flex: 1;
max-width: none;
.next-input,
.next-select,
.next-radio-group,
.next-number-picker,
.luna-reactnode-btn,
.luna-monaco-button button,
.luna-object-button button {
width: 100%;
}
.next-number-picker {
width: 100%;
.next-after {
padding-right: 5px;
}
}
.next-radio-group {
display: flex;
label {
flex: 1;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
}
.topSpan.next-form-item {
margin-bottom: 4px;
& > .next-form-item-control {
padding-right: 24px;
.next-input,
.next-select,
.next-radio-group,
.next-number-picker,
.luna-reactnode-btn,
.luna-monaco-button button,
.luna-object-button button {
width: 100%;
}
.next-number-picker {
width: 100%;
.next-after {
padding-right: 5px;
}
}
.next-radio-group {
display: flex;
label {
flex: 1;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
}
}

View File

@ -1,7 +1,9 @@
import React, { PureComponent } from 'react';
import { Tab } from '@alifd/next';
import { Tab, Badge, Icon } from '@alifd/next';
import './index.scss';
import Editor from '../../../framework/editor';
import { transformToPromise } from '../../../framework/utils';
import AreaManager from '../../../framework/areaManager';
import { PluginConfig } from '../../../framework/definitions';
export interface RightAreaProps {
@ -16,24 +18,117 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
static displayName = 'LowcodeRightArea';
private editor: Editor;
private config: Array<PluginConfig>;
private areaManager: AreaManager;
constructor(props) {
super(props);
this.editor = props.editor;
this.config = (this.editor.config.plugins && this.editor.config.plugins.rightArea) || [];
this.areaManager = new AreaManager(this.editor, 'rightArea');
this.state = {
activeKey: 'rightPanel1'
activeKey: ''
};
}
handleTabChange = (key: string): void => {
this.setState({
activeKey: key
});
componentDidMount() {
this.editor.on('skeleton.update', this.handleSkeletonUpdate);
this.editor.on('rightNav.change', this.handlePluginChange);
const visiblePluginList = this.areaManager.getVisiblePluginList();
const defaultKey = (visiblePluginList[0] && visiblePluginList[0].pluginKey) || '';
this.handlePluginChange(defaultKey, true);
}
componentWillUnmount() {
this.editor.off('skeleton.update', this.handleSkeletonUpdate);
this.editor.off('rightNav.change', this.handlePluginChange);
}
handleSkeletonUpdate = (): void => {
// 当前区域插件状态改变是更新区域
if (this.areaManager.isPluginStatusUpdate()) {
const pluginStatus = this.editor.pluginStatus;
const activeKey = this.state.activeKey;
if (pluginStatus[activeKey] && pluginStatus[activeKey].visible) {
this.forceUpdate();
} else {
const currentPlugin = this.editor.plugins[activeKey];
if (currentPlugin) {
transformToPromise(currentPlugin.close()).then(() => {
this.setState(
{
activeKey: ''
},
() => {
const visiblePluginList = this.areaManager.getVisiblePluginList();
const firstPlugin = visiblePluginList && visiblePluginList[0];
if (firstPlugin) {
this.handlePluginChange(firstPlugin.pluginKey);
}
}
);
});
}
}
}
};
handlePluginChange = (key: string, isinit?: boolean): void => {
const activeKey = this.state.activeKey;
const plugins = this.editor.plugins || {};
const openPlugin = () => {
if (!plugins[key]) {
console.error(`plugin ${key} has not regist in the editor`);
return;
}
transformToPromise(plugins[key].open()).then(() => {
this.editor.set('rightNav', key);
this.setState({
activeKey: key
});
});
};
if (key === activeKey && !isinit) return;
if (activeKey && plugins[activeKey]) {
transformToPromise(plugins[activeKey].close()).then(() => {
openPlugin();
});
} else {
openPlugin();
}
};
renderTabTitle = (config: PluginConfig): React.ReactElement => {
const { icon, title } = config.props || {};
const pluginStatus = this.editor.pluginStatus[config.pluginKey];
const { marked, disabled, locked } = pluginStatus;
const active = this.state.activeKey === config.pluginKey;
const renderTitle = (): React.ReactElement => (
<div
className={`right-addon-title ${active ? 'active' : ''} ${locked ? 'locked' : ''} ${
disabled ? 'disabled' : ''
}`}
>
{!!icon && (
<Icon
type={icon}
style={{
marginRight: 2,
fontSize: '14px',
lineHeight: '14px',
verticalAlign: 'top'
}}
/>
)}
{title}
</div>
);
if (marked) {
return <Badge dot>{renderTitle()}</Badge>;
}
return renderTitle();
};
render() {
const visiblePluginList = this.areaManager.getVisiblePluginList();
return (
<div className="lowcode-right-area">
<Tab
@ -44,13 +139,17 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
}}
activeKey={this.state.activeKey}
lazyLoad={false}
onChange={this.handleTabChange}
onChange={this.handlePluginChange}
>
{this.config.map((item, idx) => {
{visiblePluginList.map((item, idx) => {
const Comp = this.editor.components[item.pluginKey];
return (
<Tab.Item key={item.pluginKey} title={item.props.title}>
<Comp editor={this.editor} config={item.config} {...item.pluginProps} />
<Tab.Item
key={item.pluginKey}
title={this.renderTabTitle(item)}
disabled={this.editor.pluginStatus[item.pluginKey].disabled}
>
<Comp editor={this.editor} config={item} {...item.pluginProps} />
</Tab.Item>
);
})}

View File

@ -6,7 +6,6 @@
height: 48px;
background-color: #ffffff;
border-bottom: 1px solid #e8ebee;
overflow: hidden;
user-select: none;
.divider {
max-width: 0;

View File

@ -4,6 +4,7 @@ import TopPlugin from '../../components/TopPlugin';
import './index.scss';
import Editor from '../../../framework/index';
import { PluginConfig } from '../../../framework/definitions';
import AreaManager from '../../../framework/areaManager';
const { Row, Col } = Grid;
@ -14,19 +15,28 @@ export interface TopAreaProps {
export default class TopArea extends PureComponent<TopAreaProps> {
static displayName = 'LowcodeTopArea';
private areaManager: AreaManager;
private editor: Editor;
private config: Array<PluginConfig>;
constructor(props) {
super(props);
this.editor = props.editor;
this.config = (this.editor.config.plugins && this.editor.config.plugins.topArea) || [];
this.areaManager = new AreaManager(props.editor, 'topArea');
}
componentDidMount() {}
componentWillUnmount() {}
componentDidMount() {
this.editor.on('skeleton.update', this.handleSkeletonUpdate);
}
componentWillUnmount() {
this.editor.off('skeleton.update', this.handleSkeletonUpdate);
}
handlePluginStatusChange = () => {};
handleSkeletonUpdate = (): void => {
// 当前区域插件状态改变是更新区域
if (this.areaManager.isPluginStatusUpdate()) {
this.forceUpdate();
}
};
renderPluginList = (list: Array<PluginConfig> = []): Array<React.ReactElement> => {
return list.map((item, idx) => {
@ -49,10 +59,10 @@ export default class TopArea extends PureComponent<TopAreaProps> {
};
render() {
if (!this.config) return null;
const leftList: Array<PluginConfig> = [];
const rightList: Array<PluginConfig> = [];
this.config.forEach(item => {
const visiblePluginList = this.areaManager.getVisiblePluginList();
visiblePluginList.forEach(item => {
const align = item.props && item.props.align === 'right' ? 'right' : 'left';
// 分隔符不允许相邻
if (item.type === 'Divider') {

View File

@ -0,0 +1,138 @@
import React, { PureComponent } from 'react';
import { HashRouter as Router, Route } from 'react-router-dom';
import { Loading, ConfigProvider } from '@alifd/next';
import Editor from '../framework/editor';
import { EditorConfig, Utils, PluginComponents } from '../framework/definitions';
import { comboEditorConfig, parseSearch } from '../framework/utils';
import defaultConfig from './config/skeleton';
import skeletonUtils from './config/utils';
import TopArea from './layouts/TopArea';
import LeftArea from './layouts/LeftArea';
import CenterArea from './layouts/CenterArea';
import RightArea from './layouts/RightArea';
import './global.scss';
let renderIdx = 0;
export interface SkeletonProps {
components: PluginComponents;
config: EditorConfig;
utils: Utils;
}
export interface SkeletonState {
initReady: boolean;
skeletonKey: string;
__hasError?: boolean;
}
export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState> {
static displayName = 'LowcodeEditorSkeleton';
static getDerivedStateFromError() {
return {
__hasError: true
};
}
private editor: Editor;
constructor(props) {
super(props);
this.state = {
initReady: false,
skeletonKey: `skeleton${renderIdx}`
};
this.init();
}
componentWillUnmount() {
this.editor && this.editor.destroy();
}
componentDidCatch(err) {
console.error(err);
}
init = (isReset: boolean = false): void => {
if (this.editor) {
this.editor.destroy();
}
const { utils, config, components } = this.props;
debugger;
const editor = (this.editor = new Editor(comboEditorConfig(defaultConfig, config), components, {
...skeletonUtils,
...utils
}));
window.__ctx = {
editor,
appHelper: editor
};
editor.once('editor.reset', () => {
this.setState({
initReady: false
});
editor.emit('editor.beforeReset');
this.init(true);
});
this.editor.init().then(() => {
this.setState(
{
initReady: true,
//刷新IDE时生成新的skeletonKey保证插件生命周期重新执行
skeletonKey: isReset ? `skeleton${++renderIdx}` : this.state.skeletonKey
},
() => {
editor.emit('editor.ready');
isReset && editor.emit('ide.afterReset');
}
);
});
};
render() {
const { initReady, skeletonKey, __hasError } = this.state;
if (__hasError || !this.editor) {
return 'error';
}
return (
<Router>
<Route
path="/*"
component={props => {
const { location, history, match } = props;
location.query = parseSearch(location.search);
this.editor.set('location', location);
this.editor.set('history', history);
this.editor.set('match', match);
console.log('&&&&&&&&&&');
return (
<ConfigProvider>
<Loading tip="Loading" size="large" visible={!initReady} shape="fusion-reactor" fullScreen>
<div className="lowcode-editor" key={skeletonKey}>
<TopArea editor={this.editor} />
<div className="lowcode-main-content">
<LeftArea.Nav editor={this.editor} />
<LeftArea.Panel editor={this.editor} />
<CenterArea editor={this.editor} />
<RightArea editor={this.editor} />
</div>
</div>
</Loading>
</ConfigProvider>
);
}}
/>
</Router>
);
}
}