feat: add pane drag, use config "enableDrag:true"

This commit is contained in:
暁仙 2021-02-25 19:52:39 +08:00 committed by 力皓
parent 3bcf34926d
commit 2cb24a41c0
12 changed files with 371 additions and 94 deletions

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { observer } from '@ali/lowcode-editor-core';
import { observer, globalContext } from '@ali/lowcode-editor-core';
import { BuiltinSimulatorHost, BuiltinSimulatorProps } from './host';
import { BemTools } from './bem-tools';
import { Project } from '../project';
@ -73,14 +73,43 @@ class Canvas extends Component<{ host: BuiltinSimulatorHost }> {
@observer
class Content extends Component<{ host: BuiltinSimulatorHost }> {
state = {
disabledEvents: false,
};
private dispose?: () => void;
componentDidMount() {
const editor = globalContext.get('editor');
const onEnableEvents = (type: boolean) => {
this.setState({
disabledEvents: type,
});
};
editor.on('designer.builtinSimulator.disabledEvents', onEnableEvents);
this.dispose = () => {
editor.removeListener('designer.builtinSimulator.disabledEvents', onEnableEvents);
};
}
componentWillUnmount() {
this.dispose?.();
}
render() {
const sim = this.props.host;
const { disabledEvents } = this.state;
const { viewport } = sim;
const frameStyle = {
const frameStyle: any = {
transform: `scale(${viewport.scale})`,
height: viewport.contentHeight,
width: viewport.contentWidth,
};
if (disabledEvents) {
frameStyle.pointerEvents = 'none';
}
return (
<div className="lc-simulator-content">

View File

@ -0,0 +1,15 @@
.lc-draggable-line-vertical {
position: absolute;
width: 4px;
height: 100%;
background-color: transparent;
cursor: col-resize;
}
.lc-draggable-line-horizontal {
position: absolute;
width: 100%;
height: 4px;
background-color: transparent;
cursor: row-resize;
}

View File

@ -0,0 +1,146 @@
import { Component } from 'react';
import classNames from 'classnames';
import './index.less';
export interface DraggableLineProps {
onDrag: (l: number, e: any) => any;
onDragStart?: () => any;
onDragEnd?: () => any;
position?: 'right' | 'left' | 'top';
className?: string;
maxIncrement?: number;
maxDecrement?: number;
}
export default class DraggableLine extends Component<DraggableLineProps> {
static displayName = 'DraggableLine';
static defaultProps = {
onDrag() {},
position: 'right',
className: '',
maxIncrement: 100,
maxDecrement: 0,
};
private startDrag: boolean;
private canDrag: boolean;
private offset: number;
private currentOffset: number;
private offEvent: any;
private offDragEvent: any;
private startOffset: any;
private shell: HTMLElement | null = null;
constructor(props: DraggableLineProps) {
super(props);
this.startDrag = false;
this.canDrag = false;
this.offset = 0;
this.currentOffset = 0;
}
componentDidMount() {
this.offEvent = this.initEvent();
}
componentWillUnmount() {
if (this.offEvent) {
this.offEvent();
}
}
onSelectStart(e: any) {
if (this.startDrag) {
e.preventDefault();
}
}
onStartMove(e: any) {
const { onDragStart } = this.props;
if (!this.startDrag) {
onDragStart && onDragStart();
}
this.startDrag = true;
this.canDrag = true;
this.currentOffset = 0;
this.offDragEvent = this.initDragEvent();
this.startOffset = this.getClientPosition(e);
}
onEndMove() {
const { onDragEnd } = this.props;
if (this.startDrag) {
if (this.offDragEvent) {
this.offDragEvent();
}
this.startDrag = false;
this.offset = this.currentOffset;
}
onDragEnd && onDragEnd();
}
onDrag(e: any) {
const { position, onDrag, maxIncrement = 100, maxDecrement = 0 } = this.props;
if (this.startDrag) {
if (position === 'left' || position === 'top') {
this.currentOffset = this.offset + this.startOffset - this.getClientPosition(e);
} else {
this.currentOffset = this.offset + this.getClientPosition(e) - this.startOffset;
}
if (this.currentOffset < -maxDecrement) {
this.currentOffset = -maxDecrement;
} else if (this.currentOffset > maxIncrement) {
this.currentOffset = maxIncrement;
}
onDrag(this.currentOffset, e);
}
}
getClientPosition(e: any) {
const { position } = this.props;
return position === 'left' || position === 'right' ? e.clientX : e.clientY;
}
initEvent() {
const selectStart = this.onSelectStart.bind(this);
document.addEventListener('selectstart', selectStart);
return () => document.removeEventListener('selectstart', selectStart);
}
initDragEvent() {
const onDrag = this.onDrag.bind(this);
const onEndMove = this.onEndMove.bind(this);
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', onEndMove);
return () => {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', onEndMove);
};
}
getParent() {
return this.shell?.parentElement;
}
render() {
const { className = '', position } = this.props;
return (
<div
ref={(ref) => { this.shell = ref; }}
className={classNames(
position === 'left' || position === 'right'
? 'lc-draggable-line-vertical'
: 'lc-draggable-line-horizontal',
{
[className]: !!className,
},
)}
onMouseDown={(e) => this.onStartMove(e)}
/>
);
}
}

View File

@ -5,10 +5,24 @@
&.hidden {
display: none;
}
}
.lc-widget-disabled {
pointer-events: none;
opacity: 0.4;
}
}
.lc-draggable-line-vertical {
position: absolute;
width: 4px;
height: 100%;
top: 0;
background-color: transparent;
cursor: col-resize;
right: -2px;
z-index: 99;
}
.lc-engine-slate-draggable-line-right {
right: -2px;
}

View File

@ -9,6 +9,8 @@ import WidgetContainer from '../../widget/widget-container';
import Panel from '../../widget/panel';
import { IWidget } from '../../widget/widget';
import { SkeletonEvents } from '../../skeleton';
import DraggableLine from '../draggable-line';
import PanelOperationRow from './panel-operation-row';
import './index.less';
@ -84,6 +86,75 @@ export class PanelDockView extends Component<DockProps & { dock: PanelDock }> {
export class DialogDockView extends Component {}
export class DraggableLineView extends Component<{ panel: Panel }> {
private shell: any;
private defaultWidth: number;
private getDefaultWidth() {
const configWidth = this.props.panel?.config.props?.width;
if (configWidth) {
return configWidth;
}
if (this.defaultWidth) {
return this.defaultWidth;
}
const containerRef = this.shell?.getParent();
if (containerRef) {
this.defaultWidth = containerRef.offsetWidth;
return this.defaultWidth;
}
return 300;
}
onDrag(value: number) {
const defaultWidth = this.getDefaultWidth();
const width = defaultWidth + value;
const containerRef = this.shell?.getParent();
if (containerRef) {
containerRef.style.width = `${width}px`;
}
// 抛出事件,对于有些需要 panel 插件随着 度变化进行再次渲染的由panel插件内部监听事件实现
const editor = globalContext.get(Editor);
editor?.emit('dockpane.drag', width);
}
onDragChange(type: 'start' | 'end') {
const editor = globalContext.get(Editor);
editor?.emit('dockpane.dragchange', type);
// builtinSimulator 屏蔽掉 鼠标事件
editor?.emit('designer.builtinSimulator.disabledEvents', type === 'start');
}
render() {
// left fixed 下不允许改变宽度
// 默认 关闭,通过配置开启
const enableDrag = this.props.panel.config.props?.enableDrag;
const isRightArea = this.props.panel.config?.area === 'rightArea';
if (isRightArea || !enableDrag || this.props.panel?.parent.name === 'leftFixedArea') {
return null;
}
return (
<DraggableLine
ref={(ref) => {
this.shell = ref;
}}
position="right"
className="lc-engine-slate-draggable-line-right"
onDrag={(e) => this.onDrag(e)}
onDragStart={() => this.onDragChange('start')}
onDragEnd={() => this.onDragChange('end')}
maxIncrement={500}
maxDecrement={0}
// TODO: 优化
// maxIncrement={dock.getMaxWidth() - this.cachedSize.width}
// maxDecrement={this.cachedSize.width - dock.getWidth()}
/>
);
}
}
@observer
export class TitledPanelView extends Component<{ panel: Panel; area?: string }> {
shouldComponentUpdate() {
@ -131,15 +202,22 @@ export class TitledPanelView extends Component<{ panel: Panel; area?: string }>
})}
id={panelName}
>
<PanelOperationRow panel={panel} />
<PanelTitle panel={panel} />
<div className="lc-panel-body">{panel.body}</div>
<DraggableLineView panel={panel} />
</div>
);
}
}
@observer
export class PanelView extends Component<{ panel: Panel; area?: string }> {
export class PanelView extends Component<{
panel: Panel;
area?: string;
hideOperationRow?: boolean;
hideDragLine?: boolean;
}> {
shouldComponentUpdate() {
return false;
}
@ -172,7 +250,7 @@ export class PanelView extends Component<{ panel: Panel; area?: string }> {
}
render() {
const { panel, area } = this.props;
const { panel, area, hideOperationRow, hideDragLine } = this.props;
if (!panel.inited) {
return null;
}
@ -189,7 +267,9 @@ export class PanelView extends Component<{ panel: Panel; area?: string }> {
})}
id={panelName}
>
{!hideOperationRow && <PanelOperationRow panel={panel} />}
{panel.body}
{!hideDragLine && <DraggableLineView panel={panel} />}
</div>
);
}
@ -203,7 +283,7 @@ export class TabsPanelView extends Component<{ container: WidgetContainer<Panel>
const contents: ReactElement[] = [];
container.items.forEach((item: any) => {
titles.push(<PanelTitle key={item.id} panel={item} className="lc-tab-title" />);
contents.push(<PanelView key={item.id} panel={item} />);
contents.push(<PanelView key={item.id} panel={item} hideOperationRow hideDragLine />);
});
return (
@ -302,11 +382,7 @@ export class WidgetView extends Component<{ widget: IWidget }> {
return null;
}
if (widget.disabled) {
return (
<div className="lc-widget-disabled">
{widget.body}
</div>
);
return <div className="lc-widget-disabled">{widget.body}</div>;
}
return widget.body;
}

View File

@ -0,0 +1,67 @@
import { Component, Fragment } from 'react';
import { Button, Icon } from '@alifd/next';
import { IconFix } from '../../icons/fix';
import { IconFloat } from '../../icons/float';
import Panel from '../../widget/panel';
export default class PanelOperationRow extends Component<{ panel: Panel }> {
// fix or float
setDisplay() {
const { panel } = this.props;
const current = panel;
if (!current) {
return;
}
if (panel?.parent?.name === 'leftFloatArea') {
panel.skeleton.leftFloatArea.remove(current);
panel.skeleton.leftFixedArea.add(current);
panel.skeleton.leftFixedArea.container.active(current);
} else {
panel.skeleton.leftFixedArea.remove(current);
panel.skeleton.leftFloatArea.add(current);
panel.skeleton.leftFloatArea.container.active(current);
}
}
render() {
const { panel } = this.props;
const isRightArea = this.props.panel.config?.area === 'rightArea';
if (isRightArea) {
return null;
}
// can be set fixed by default
let canSetFixed = true;
if (panel?.config.props?.canSetFixed === false) {
canSetFixed = false;
}
const hideTitleBar = panel?.config.props?.hideTitleBar;
const areaName = panel?.parent?.name;
const area = panel.skeleton[areaName];
return (
<Fragment>
{!hideTitleBar && (
<Fragment>
{canSetFixed && (
// eslint-disable-next-line react/jsx-no-bind
<Button text className="lc-pane-icon-fix" onClick={this.setDisplay.bind(this)}>
{areaName === 'leftFloatArea' ? <IconFloat /> : <IconFix />}
</Button>
)}
<Button
text
className="lc-pane-icon-close"
onClick={() => {
area && area.setVisible(false);
}}
>
<Icon type="close" />
</Button>
</Fragment>
)}
</Fragment>
);
}
}

View File

@ -1,12 +1,10 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@ali/lowcode-editor-core';
import { Button, Icon } from '@alifd/next';
import Area from '../area';
import { PanelConfig } from '../types';
import Panel from '../widget/panel';
import { Designer } from '@ali/lowcode-designer';
import { IconFloat } from '../icons/float';
@observer
export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> {
@ -19,21 +17,9 @@ export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, P
this.props.area.skeleton.editor.get(Designer)?.touchOffsetObserver();
}
// 取消固定
setFloat() {
const { area } = this.props;
const { current } = area;
if (!current) {
return;
}
area.skeleton.leftFixedArea.remove(current);
area.skeleton.leftFloatArea.add(current);
area.skeleton.leftFloatArea.container.active(current);
}
render() {
const { area } = this.props;
const hideTitleBar = area.current?.config.props?.hideTitleBar;
const width = area.current?.config.props?.width;
const style = width
? {
@ -48,26 +34,6 @@ export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, P
})}
style={style}
>
{!hideTitleBar && (
<Fragment>
<Button
text
className="lc-pane-icon-float"
onClick={this.setFloat.bind(this)}
>
<IconFloat />
</Button>
<Button
text
className="lc-pane-icon-close"
onClick={() => {
area.setVisible(false);
}}
>
<Icon type="close" />
</Button>
</Fragment>
)}
<Contents area={area} />
</div>
);

View File

@ -1,13 +1,12 @@
import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer, Focusable, focusTracker } from '@ali/lowcode-editor-core';
import { Button, Icon } from '@alifd/next';
import { IconFix } from '../icons/fix';
import Area from '../area';
import Panel from '../widget/panel';
@observer
export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }> {
shouldComponentUpdate() {
return false;
}
@ -22,6 +21,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
const { area } = this.props;
const triggerClose = () => area.setVisible(false);
area.skeleton.editor.on('designer.dragstart', triggerClose);
this.dispose = () => {
area.skeleton.editor.removeListener('designer.dragstart', triggerClose);
};
@ -95,29 +95,10 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
this.dispose?.();
}
// 固定
setFixed() {
const { area } = this.props;
const { current } = area;
if (!current) {
return;
}
area.skeleton.leftFloatArea.remove(current);
area.skeleton.leftFixedArea.add(current);
area.skeleton.leftFixedArea.container.active(current);
}
render() {
const { area } = this.props;
const width = area.current?.config.props?.width;
// can be set fixed by default
let canSetFixed = true;
if (area.current?.config.props?.canSetFixed === false) {
canSetFixed = false;
}
const hideTitleBar = area.current?.config.props?.hideTitleBar;
const style = width ? {
width,
} : undefined;
@ -129,32 +110,6 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
})}
style={style}
>
{
!hideTitleBar && (
<Fragment>
{
canSetFixed && (
<Button
text
className="lc-pane-icon-fix"
onClick={this.setFixed.bind(this)}
>
<IconFix />
</Button>
)
}
<Button
text
className="lc-pane-icon-close"
onClick={() => {
area.setVisible(false);
}}
>
<Icon type="close" />
</Button>
</Fragment>
)
}
<Contents area={area} />
</div>
);

View File

@ -49,6 +49,8 @@ body {
width: 100%;
height: 100%;
position: relative;
background-color: #fff;
background-color: var(--color-pane-background);
&.hidden {
display: none;
}
@ -122,6 +124,9 @@ body {
.lc-panel {
height: 100%;
width: 100%;
position: relative;
background-color: #fff;
background-color: var(--color-pane-background);
// overflow: auto;
&.hidden {
display: none;

View File

@ -103,6 +103,7 @@ export interface PanelProps {
onInit?: (widget: IWidget) => any;
onDestroy?: () => any;
shortcut?: string; // 只有在特定位置,可触发 toggle show
enableDrag?: boolean; // 是否开启通过 drag 调整 宽度
}
export interface PanelDockConfig extends IDockBaseConfig {

View File

@ -23,6 +23,7 @@ registerDefaults();
const editor = new Editor();
globalContext.register(editor, Editor);
globalContext.register(editor, 'editor');
const skeleton = new Skeleton(editor);
editor.set(Skeleton, skeleton);

View File

@ -49,6 +49,7 @@ export interface OldPaneConfig {
fullScreen?: boolean; // todo
canSetFixed?: boolean; // 是否可以设置固定模式
defaultFixed?: boolean; // 是否默认固定
enableDrag?: boolean;
}
function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: string } {
@ -83,6 +84,7 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
isAction,
canSetFixed,
defaultFixed,
enableDrag,
} = config;
if (menu) {
newConfig.props.title = menu;
@ -99,6 +101,7 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
height,
maxHeight,
canSetFixed,
enableDrag,
};
if (defaultFixed) {
@ -135,7 +138,6 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
}
newConfig.props.onInit = init;
newConfig.props.onDestroy = destroy;
return newConfig;
}