Merge branch 'release/0.9.2' into 'release/0.9.2'

主设置面板 entry display 使用 stagebox 的切换模式

RT

See merge request !915532
This commit is contained in:
荣彬 2020-07-30 22:20:17 +08:00
commit 0e65b1ff51
11 changed files with 400 additions and 11 deletions

View File

@ -102,8 +102,8 @@ context.use(HOOKS.VE_SETTING_FIELD_VARIABLE_SETTER, VariableSetter);
const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore'];
async function loadAssets() {
const legaoAssets = await editor.utils.get('./raxAssets.json');
// const legaoAssets = await editor.utils.get('./legao-assets.json');
// const legaoAssets = await editor.utils.get('./raxAssets.json');
const legaoAssets = await editor.utils.get('./legao-assets.json');
const assets = upgradeAssetsBundle(legaoAssets);
@ -145,11 +145,11 @@ async function loadAssets() {
}
async function loadSchema() {
const schema = await editor.utils.get('./rax.json');
// const schema = await editor.utils.get('./schema.json');
// const schema = await editor.utils.get('./rax.json');
const schema = await editor.utils.get('./schema.json');
editor.set('schema', schema);
editor.set('renderEnv', 'rax');
editor.set('clientTypes', ['mobile']);
// editor.set('renderEnv', 'rax');
// editor.set('clientTypes', ['mobile']);
}

View File

@ -26,7 +26,10 @@
"@alifd/next": "^1.20.12",
"classnames": "^2.2.6",
"react": "^16.8.1",
"react-dom": "^16.8.1"
"react-dom": "^16.8.1",
"events": "^3.2.0",
"@ali/ve-icons": "latest",
"@ali/ve-less-variables": "^2.0.0"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.3",

View File

@ -147,8 +147,13 @@ export function createSettingFieldView(item: SettingField | CustomView, field: S
}
}
export type SettingsPaneProps = {
target: SettingTopEntry | SettingField;
usePopup?: boolean;
};
@observer
export class SettingsPane extends Component<{ target: SettingTopEntry | SettingField }> {
export class SettingsPane extends Component<SettingsPaneProps> {
static contextType = SkeletonContext;
shouldComponentUpdate() {
return false;
@ -160,6 +165,8 @@ export class SettingsPane extends Component<{ target: SettingTopEntry | SettingF
private handleClick = (e: MouseEvent) => {
// compatiable vision stageBox
// TODO: optimize these codes
const { usePopup = true } = this.props;
if (!usePopup) return;
const pane = e.currentTarget as HTMLDivElement;
let entry: any;
function getTarget(node: any): any {

View File

@ -4,6 +4,8 @@ import { Title, observer, Editor, obx, globalContext } from '@ali/lowcode-editor
import { Node, isSettingField, SettingField, Designer } from '@ali/lowcode-designer';
import { SettingsMain } from './main';
import { SettingsPane } from './settings-pane';
import { StageBox } from '../stage-box';
import { SkeletonContext } from '../../context';
import { createIcon } from '@ali/lowcode-utils';
@observer
@ -114,7 +116,18 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> {
<div className="lc-settings-main">
{this.renderBreadcrumb()}
<div className="lc-settings-body">
<SettingsPane target={settings} />
<SkeletonContext.Consumer>
{(skeleton) => {
if (skeleton) {
return (
<StageBox skeleton={skeleton} target={settings}>
<SettingsPane target={settings} usePopup={false} />
</StageBox>
);
}
return null;
}}
</SkeletonContext.Consumer>
</div>
</div>
);
@ -127,7 +140,18 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> {
}
return (
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
<SettingsPane target={field} key={field.id} />
<SkeletonContext.Consumer>
{(skeleton) => {
if (skeleton) {
return (
<StageBox skeleton={skeleton} target={field} key={field.id}>
<SettingsPane target={field} key={field.id} usePopup={false} />
</StageBox>
);
}
return null;
}}
</SkeletonContext.Consumer>
</Tab.Item>
);
});

View File

@ -0,0 +1,74 @@
@import "~@ali/ve-less-variables/index.less";
.skeleton-stagebox {
overflow: hidden;
position: relative;
min-height: 50px;
.skeleton-stagebox-stage {
height: auto;
overflow: hidden;
transition: transform 0.2s;
&.skeleton-stagebox-refer {
position: absolute;
top: 0;
left: 0;
right: 0;
height: auto;
}
&.skeleton-stagebox-stageout-left, &.skeleton-stagebox-stagein-right {
transform: translateX(-100%);
}
&.skeleton-stagebox-stageout-right, &.skeleton-stagebox-stagein-left {
transform: translateX(100%);
}
.skeleton-stagebox-stagebacker {
cursor: pointer;
height: 30px;
display: flex;
align-items: center;
background: var(--color-block-background-light, @normal-alpha-9);
justify-content: center;
position: relative;
.skeleton-stagebox-stage-arrow {
position: absolute;
left: 3px;
top: 50%;
transform: translateY(-50%) rotate(90deg);
opacity: 0.6;
}
.skeleton-stagebox-stage-title {
font-weight: bold;
}
&:hover {
background: var(--color-block-background-dark, @normal-alpha-7);
.skeleton-stagebox-stage-arrow {
opacity: 1;
}
}
.skeleton-stagebox-stage-exit {
position: absolute;
right: 3px;
top: 50%;
transform: translateY(-50%);
opacity: 0.6;
}
}
.skeleton-stagebox-stage-content {
overflow: hidden;
box-sizing: border-box;
}
&.skeleton-stagebox-has-backer {
.skeleton-stagebox-stage-content {
padding-top: 30px;
}
}
}
}

View File

@ -0,0 +1,4 @@
import StageBox from './stage-box';
import './index.less';
export { StageBox };

View File

@ -0,0 +1,118 @@
import React, { Component } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-editor-core';
import { SettingTopEntry, SettingField } from '@ali/lowcode-designer';
import StageChain from './stage-chain';
import Stage from './stage';
import { Skeleton } from '../../skeleton';
import { Stage as StageWidget } from '../../widget/stage';
export const StageBoxDefaultProps = {};
export type StageBoxProps = typeof StageBoxDefaultProps & {
stageChain?: StageChain;
className?: string;
children: React.ReactNode;
skeleton: Skeleton;
// @todo to remove
target?: SettingTopEntry | SettingField;
};
type WillDetachMember = () => void;
@observer
export default class StageBox extends Component<StageBoxProps> {
static defaultProps = StageBoxDefaultProps;
static displayName = 'StageBox';
private stageChain: StageChain;
private willDetach: WillDetachMember[] = [];
private shell: HTMLElement | null;
constructor(props: StageBoxProps) {
super(props);
const { stageChain, children, skeleton } = this.props;
if (stageChain) {
this.stageChain = stageChain;
} else {
const stateName = skeleton.createStage({
content: children,
});
this.stageChain = new StageChain(skeleton.getStage(stateName as string) as StageWidget);
}
this.willDetach.push(this.stageChain.onStageChange(() => this.forceUpdate()));
}
componentDidMount() {
const shell = this.shell;
/**
* target
* @param node
* @returns dataset.stageTarget
*/
const getTarget = (node: HTMLElement | null): null | string => {
if (!node || !shell?.contains(node) || (node.nodeName === 'A' && node.getAttribute('href'))) {
return null;
}
const target = node.dataset ? node.dataset.stageTarget : null;
if (target) {
return target;
}
return getTarget(node.parentNode as HTMLElement);
};
const click = (e: MouseEvent) => {
const target = getTarget(e.target as HTMLElement);
if (!target) {
return;
}
if (target === 'stageback') {
this.stageChain.stageBack();
} else {
const { skeleton } = this.props;
this.stageChain.stagePush(skeleton.getStage(target));
}
};
shell?.addEventListener('click', click, false);
this.willDetach.push(() => shell?.removeEventListener('click', click, false));
}
componentWillUnmount() {
if (this.willDetach) {
this.willDetach.forEach((off: () => void) => off());
}
}
render() {
const className = classNames('skeleton-stagebox', this.props.className);
const stage = this.stageChain.getCurrentStage();
const refer = stage?.getRefer();
let contentCurrent = null;
let contentRefer = null;
if (refer) {
contentCurrent = <Stage key={stage.getId()} stage={stage} direction={refer.direction} current />;
contentRefer = <Stage key={refer?.stage?.getId()} stage={refer?.stage} direction={refer.direction} />;
} else {
contentCurrent = <Stage key={stage.getId()} stage={stage} current />;
}
return (
<div
ref={(ref) => {
this.shell = ref;
}}
className={className}
>
{contentRefer}
{contentCurrent}
</div>
);
}
}

View File

@ -0,0 +1,52 @@
import { EventEmitter } from 'events';
import { Stage as StageWidget } from '../../widget/stage';
export default class StageChain {
private emitter: EventEmitter;
private stage: StageWidget;
constructor(stage: StageWidget) {
this.emitter = new EventEmitter();
this.stage = stage;
}
stagePush(stage: StageWidget | null) {
if (!stage) return;
stage.setPrevious(this.stage);
stage.setReferLeft(this.stage);
this.stage = stage;
this.emitter.emit('stagechange');
}
stageBack() {
const stage = this.stage.getPrevious();
if (!stage) return;
stage.setReferRight(this.stage);
this.stage = stage;
this.emitter.emit('stagechange');
}
/**
*
*/
stageBackToRoot() {
while (!this.stage.isRoot) {
const stage = this.stage.getPrevious();
if (!stage) return;
stage.setReferRight(this.stage);
this.stage = stage;
}
this.emitter.emit('stagechange');
}
getCurrentStage() {
return this.stage;
}
onStageChange(func: () => void) {
this.emitter.on('stagechange', func);
return () => {
this.emitter.removeListener('stagechange', func);
};
}
}

View File

@ -0,0 +1,93 @@
// @todo 改成 hooks
import React, { Component } from 'react';
import classNames from 'classnames';
import Icons from '@ali/ve-icons';
import { Stage as StageWidget } from '../../widget/stage';
export const StageDefaultProps = {
current: false,
};
export type StageProps = typeof StageDefaultProps & {
stage?: StageWidget;
current: boolean;
direction?: string;
};
export default class Stage extends Component<StageProps> {
static defaultProps = StageDefaultProps;
private timer: number;
private additionClassName: string | null;
private shell: any;
componentDidMount() {
this.doSkate();
}
componentDidUpdate() {
this.doSkate();
}
componentWillUnmount() {
window.clearTimeout(this.timer);
}
doSkate() {
window.clearTimeout(this.timer);
if (this.additionClassName) {
this.timer = window.setTimeout(() => {
const elem = this.shell;
if (elem) {
if (this.props.current) {
elem.classList.remove(this.additionClassName);
} else {
elem.classList.add(this.additionClassName);
}
this.additionClassName = null;
}
}, 15);
}
}
render() {
const { stage, current, direction } = this.props;
const content = stage?.getContent();
if (current) {
if (direction) {
this.additionClassName = `skeleton-stagebox-stagein-${direction}`;
}
} else if (direction) {
this.additionClassName = `skeleton-stagebox-stageout-${direction}`;
}
const className = classNames(
'skeleton-stagebox-stage',
{
'skeleton-stagebox-refer': !current,
},
this.additionClassName,
);
const stageBacker = stage?.hasBack() ? (
<div className="skeleton-stagebox-stagebacker" data-stage-target="stageback">
<Icons name="arrow" className="skeleton-stagebox-stage-arrow" size="medium" />
<span className="skeleton-stagebox-stage-title">{stage.title}</span>
<Icons name="exit" className="skeleton-stagebox-stage-exit" size="medium" />
</div>
) : null;
return (
<div
ref={(ref) => {
this.shell = ref;
}}
className={className}
>
{stageBacker}
<div className="skeleton-stagebox-stage-content">{content}</div>
</div>
);
}
}

View File

@ -20,7 +20,7 @@ import PanelDock from './widget/panel-dock';
import Dock from './widget/dock';
import { Stage, StageConfig } from './widget/stage';
import { isValidElement } from 'react';
import { isPlainObject } from '@ali/lowcode-utils';
import { isPlainObject, uniqueId } from '@ali/lowcode-utils';
import { Divider } from '@alifd/next';
import { EditorConfig, PluginClassSet } from '@ali/lowcode-types';
@ -248,6 +248,19 @@ export class Skeleton {
return this.panels.get(name);
}
getStage(name: string) {
return this.stages.container.get(name);
}
createStage(config: any) {
const stage = this.add({
name: uniqueId('stage'),
area: 'stages',
...config,
});
return stage?.getName();
}
createContainer(
name: string,
handle: (item: any) => any,

View File

@ -1,3 +1,4 @@
import { uniqueId } from '@ali/lowcode-utils';
import Widget from './widget';
import { Skeleton } from '../skeleton';
import { WidgetConfig } from '../types';