1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

cleanup Draggable into ResizingAnchor

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2020-09-28 17:47:46 -04:00
parent 34e141e517
commit 44d9e732b1
10 changed files with 210 additions and 178 deletions

View File

@ -14,10 +14,6 @@
left: 0; left: 0;
bottom: 0; bottom: 0;
z-index: 100; z-index: 100;
> .resizer {
display: none;
}
} }
} }
@ -65,23 +61,6 @@
} }
} }
.resizer {
$height: 12px;
position: absolute;
top: -$height / 2;
left: 0;
right: 0;
bottom: 100%;
height: $height;
cursor: row-resize;
z-index: 10;
&.disabled {
pointer-events: none;
}
}
.AceEditor { .AceEditor {
border: none; border: none;
} }

View File

@ -28,7 +28,11 @@ export class DockStore {
protected storage = createStorage("dock", {}); // keep settings in localStorage protected storage = createStorage("dock", {}); // keep settings in localStorage
public defaultTabId = this.initialTabs[0].id; public defaultTabId = this.initialTabs[0].id;
public minHeight = 100; private _minHeight = 100;
set minHeight(val: number) {
this._minHeight = val
}
@observable isOpen = false; @observable isOpen = false;
@observable fullSize = false; @observable fullSize = false;
@ -176,8 +180,8 @@ export class DockStore {
} }
@action @action
setHeight(height: number) { setHeight(height?: number) {
this.height = Math.max(0, Math.min(height, this.maxHeight)); this.height = Math.max(0, Math.min(height || this._minHeight, this.maxHeight));
} }
@action @action

View File

@ -4,7 +4,7 @@ import React, { Fragment } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { autobind, cssNames, prevDefault } from "../../utils"; import { autobind, cssNames, prevDefault } from "../../utils";
import { Draggable, DraggableState } from "../draggable"; import { ResizingAnchor, ResizeEventData, ResizeDirection, ResizeSide } from "../resizing-anchor";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Tabs } from "../tabs/tabs"; import { Tabs } from "../tabs/tabs";
import { MenuItem } from "../menu"; import { MenuItem } from "../menu";
@ -30,16 +30,17 @@ interface Props {
@observer @observer
export class Dock extends React.Component<Props> { export class Dock extends React.Component<Props> {
onResizeStart = () => { onResizeStart = () => {
const { isOpen, open, setHeight, minHeight } = dockStore; const { isOpen, open, setHeight } = dockStore;
if (!isOpen) { if (!isOpen) {
open(); open();
setHeight(minHeight); setHeight();
} }
} }
onResize = ({ offsetY }: DraggableState) => { onResize = ({ movementY }: ResizeEventData) => {
const { isOpen, close, height, setHeight, minHeight, defaultHeight } = dockStore; const { isOpen, close, height, setHeight, minHeight, defaultHeight } = dockStore;
const newHeight = height + offsetY; console.log(height, movementY)
const newHeight = height + movementY;
if (height > newHeight && newHeight < minHeight) { if (height > newHeight && newHeight < minHeight) {
setHeight(defaultHeight); setHeight(defaultHeight);
close(); close();
@ -71,13 +72,13 @@ export class Dock extends React.Component<Props> {
@autobind() @autobind()
renderTab(tab: IDockTab) { renderTab(tab: IDockTab) {
if (isTerminalTab(tab)) { if (isTerminalTab(tab)) {
return <TerminalTab value={tab}/> return <TerminalTab value={tab} />
} }
if (isCreateResourceTab(tab) || isEditResourceTab(tab)) { if (isCreateResourceTab(tab) || isEditResourceTab(tab)) {
return <DockTab value={tab} icon="edit"/> return <DockTab value={tab} icon="edit" />
} }
if (isInstallChartTab(tab) || isUpgradeChartTab(tab)) { if (isInstallChartTab(tab) || isUpgradeChartTab(tab)) {
return <DockTab value={tab} icon={<Icon svg="install"/>}/> return <DockTab value={tab} icon={<Icon svg="install" />} />
} }
} }
@ -86,11 +87,11 @@ export class Dock extends React.Component<Props> {
if (!isOpen || !tab) return; if (!isOpen || !tab) return;
return ( return (
<div className="tab-content" style={{ flexBasis: height }}> <div className="tab-content" style={{ flexBasis: height }}>
{isCreateResourceTab(tab) && <CreateResource tab={tab}/>} {isCreateResourceTab(tab) && <CreateResource tab={tab} />}
{isEditResourceTab(tab) && <EditResource tab={tab}/>} {isEditResourceTab(tab) && <EditResource tab={tab} />}
{isInstallChartTab(tab) && <InstallChart tab={tab}/>} {isInstallChartTab(tab) && <InstallChart tab={tab} />}
{isUpgradeChartTab(tab) && <UpgradeChart tab={tab}/>} {isUpgradeChartTab(tab) && <UpgradeChart tab={tab} />}
{isTerminalTab(tab) && <TerminalWindow tab={tab}/>} {isTerminalTab(tab) && <TerminalWindow tab={tab} />}
</div> </div>
) )
} }
@ -104,11 +105,11 @@ export class Dock extends React.Component<Props> {
onKeyDown={this.onKeydown} onKeyDown={this.onKeydown}
tabIndex={-1} tabIndex={-1}
> >
<Draggable <ResizingAnchor
className={cssNames("resizer", { disabled: !hasTabs() })} disabled={!hasTabs()}
horizontal={false} direction={ResizeDirection.VERTICAL}
onStart={this.onResizeStart} onStart={this.onResizeStart}
onEnter={this.onResize} onDrag={this.onResize}
/> />
<div className="tabs-container flex align-center" onDoubleClick={prevDefault(toggle)}> <div className="tabs-container flex align-center" onDoubleClick={prevDefault(toggle)}>
<Tabs <Tabs
@ -121,11 +122,11 @@ export class Dock extends React.Component<Props> {
<div className="dock-menu box grow"> <div className="dock-menu box grow">
<MenuActions usePortal triggerIcon={{ material: "add", className: "new-dock-tab", tooltip: <Trans>New tab</Trans> }} closeOnScroll={false}> <MenuActions usePortal triggerIcon={{ material: "add", className: "new-dock-tab", tooltip: <Trans>New tab</Trans> }} closeOnScroll={false}>
<MenuItem className="create-terminal-tab" onClick={() => createTerminalTab()}> <MenuItem className="create-terminal-tab" onClick={() => createTerminalTab()}>
<Icon small svg="terminal" size={15}/> <Icon small svg="terminal" size={15} />
<Trans>Terminal session</Trans> <Trans>Terminal session</Trans>
</MenuItem> </MenuItem>
<MenuItem className="create-resource-tab" onClick={() => createResourceTab()}> <MenuItem className="create-resource-tab" onClick={() => createResourceTab()}>
<Icon small material="create"/> <Icon small material="create" />
<Trans>Create resource</Trans> <Trans>Create resource</Trans>
</MenuItem> </MenuItem>
</MenuActions> </MenuActions>
@ -133,7 +134,7 @@ export class Dock extends React.Component<Props> {
{hasTabs() && ( {hasTabs() && (
<> <>
<Icon <Icon
material={fullSize ? "fullscreen_exit": "fullscreen"} material={fullSize ? "fullscreen_exit" : "fullscreen"}
tooltip={fullSize ? <Trans>Exit full size mode</Trans> : <Trans>Fit to window</Trans>} tooltip={fullSize ? <Trans>Exit full size mode</Trans> : <Trans>Fit to window</Trans>}
onClick={toggleFillSize} onClick={toggleFillSize}
/> />

View File

@ -1,5 +0,0 @@
body.dragging {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}

View File

@ -1,119 +0,0 @@
import "./draggable.scss";
import React from "react";
import { cssNames, IClassName, noop } from "../../utils";
import throttle from "lodash/throttle";
export interface DraggableEventHandler {
(state: DraggableState): void;
}
interface Props {
className?: IClassName;
vertical?: boolean;
horizontal?: boolean;
onStart?: DraggableEventHandler;
onEnter?: DraggableEventHandler;
onEnd?: DraggableEventHandler;
}
export interface DraggableState {
inited?: boolean;
changed?: boolean;
initX?: number;
initY?: number;
pageX?: number;
pageY?: number;
offsetX?: number;
offsetY?: number;
}
const initState: DraggableState = {
inited: false,
changed: false,
offsetX: 0,
offsetY: 0,
};
export class Draggable extends React.PureComponent<Props, DraggableState> {
public state = initState;
static IS_DRAGGING = "dragging"
static defaultProps: Props = {
vertical: true,
horizontal: true,
onStart: noop,
onEnter: noop,
onEnd: noop,
};
constructor(props: Props) {
super(props);
document.addEventListener("mousemove", this.onDrag);
document.addEventListener("mouseup", this.onDragEnd);
}
componentWillUnmount() {
document.removeEventListener("mousemove", this.onDrag);
document.removeEventListener("mouseup", this.onDragEnd);
}
onDragInit = (evt: React.MouseEvent<any>) => {
document.body.classList.add(Draggable.IS_DRAGGING);
const { pageX, pageY } = evt;
this.setState({
inited: true,
initX: pageX,
initY: pageY,
pageX: pageX,
pageY: pageY,
})
}
onDrag = throttle((evt: MouseEvent) => {
const { vertical, horizontal, onEnter, onStart } = this.props;
const { inited, pageX, pageY } = this.state;
const offsetX = pageX - evt.pageX;
const offsetY = pageY - evt.pageY;
let changed = false;
if (horizontal && offsetX !== 0) changed = true;
if (vertical && offsetY !== 0) changed = true;
if (inited && changed) {
const start = !this.state.changed;
const state = Object.assign({}, this.state, {
changed: true,
pageX: evt.pageX,
pageY: evt.pageY,
offsetX: offsetX,
offsetY: offsetY,
});
if (start) onStart(state);
this.setState(state, () => onEnter(state));
}
}, 100)
onDragEnd = (evt: MouseEvent) => {
const { pageX, pageY } = evt;
const { inited, changed, initX, initY } = this.state;
if (inited) {
document.body.classList.remove(Draggable.IS_DRAGGING);
this.setState(initState, () => {
if (!changed) return;
const state = Object.assign({}, this.state, {
offsetX: initX - pageX,
offsetY: initY - pageY,
});
this.props.onEnd(state);
});
}
}
render() {
const { className, children } = this.props;
return (
<div className={cssNames("Draggable", className)} onMouseDown={this.onDragInit}>
{children}
</div>
);
}
}

View File

@ -1 +0,0 @@
export * from './draggable'

View File

@ -0,0 +1 @@
export * from './resizing-anchor'

View File

@ -0,0 +1,46 @@
body.resizing {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.ResizingAnchor {
$prominentDimention: 12px;
position: absolute;
z-index: 10;
&.disabled {
display: none;
}
&.vertical {
left: 0;
right: 0;
cursor: row-resize;
height: $prominentDimention;
&.leading {
top: -$prominentDimention / 2;
}
&.trailing {
bottom: -$prominentDimention / 2;
}
}
&.horizontal {
top: 0;
bottom: 0;
cursor: col-resize;
width: $prominentDimention;
&.leading {
left: -$prominentDimention / 2;
}
&.trailing {
right: -$prominentDimention / 2;
}
}
}

View File

@ -0,0 +1,126 @@
import "./resizing-anchor.scss";
import React from "react";
import { cssNames, noop } from "../../utils";
import { action, computed, observable } from "mobx";
import _ from "lodash"
export enum ResizeDirection {
HORIZONTAL = "horizontal",
VERTICAL = "vertical",
}
/**
* ResizeSide is for customizing where the area should be rendered.
* That location is determined in conjunction with the `ResizeDirection` using the following table:
*
* +----------+------------+----------+
* | | HORIZONTAL | VERTICAL |
* +----------+------------+----------+
* | LEADING | left | top |
* +----------+------------+----------+
* | TRAILING | right | bottom |
* +----------+------------+----------+
*/
export enum ResizeSide {
LEADING = "leading",
TRAILING = "trailing",
}
interface Props {
direction: ResizeDirection;
disabled?: boolean;
placement?: ResizeSide;
onStart?: () => void;
onDrag?: (data: ResizeEventData) => void;
onEnd?: () => void;
}
export interface ResizeEventData {
initX: number;
initY: number;
pageX: number;
pageY: number;
movementX: number;
movementY: number;
}
const defaultProps: Partial<Props> = {
onStart: noop,
onDrag: noop,
onEnd: noop,
disabled: false,
placement: ResizeSide.LEADING,
}
export class ResizingAnchor extends React.PureComponent<Props> {
@observable startingPosition?: { initX: number, initY: number };
@observable lastEvent?: MouseEvent
@computed get firstDragEvent() {
return this.lastEvent == null
}
static defaultProps = defaultProps
static IS_RESIZING = "resizing"
constructor(props: Props) {
super(props);
}
componentWillUnmount() {
document.removeEventListener("mousemove", this.onDrag);
document.removeEventListener("mouseup", this.onDragEnd);
}
@action
onDragInit = ({ pageX, pageY, buttons }: React.MouseEvent<any>) => {
if (buttons !== 1) {
return
}
document.addEventListener("mousemove", this.onDrag)
document.addEventListener("mouseup", this.onDragEnd)
document.body.classList.add(ResizingAnchor.IS_RESIZING)
this.startingPosition = {
initX: pageX,
initY: pageY
}
this.lastEvent = undefined
}
calculateMovement = (event: MouseEvent) => {
return {
movementX: this.lastEvent.pageX - event.pageX,
movementY: this.lastEvent.pageY - event.pageY
}
}
onDrag = _.throttle((event: MouseEvent) => {
const { onDrag, onStart } = this.props
const { initX, initY } = this.startingPosition
const { pageX, pageY } = event
if (this.firstDragEvent) onStart()
const { movementX, movementY } = this.firstDragEvent ? event : this.calculateMovement(event)
onDrag({ movementX, movementY, pageX, pageY, initY, initX })
this.lastEvent = event
}, 100)
@action
onDragEnd = (event: MouseEvent) => {
const { onEnd } = this.props;
onEnd()
document.removeEventListener("mousemove", this.onDrag);
document.removeEventListener("mouseup", this.onDragEnd);
document.body.classList.remove(ResizingAnchor.IS_RESIZING)
}
render() {
const { disabled, direction, placement } = this.props
return <div className={cssNames("ResizingAnchor", direction, placement, { disabled })} onMouseDown={this.onDragInit} />
}
}

View File

@ -8181,17 +8181,17 @@ mobx-observable-history@^1.0.3:
history "^4.10.1" history "^4.10.1"
mobx "^5.15.4" mobx "^5.15.4"
mobx-react-lite@2: mobx-react-lite@>=2.2.0:
version "2.0.7" version "2.2.2"
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-2.0.7.tgz#1bfb3b4272668e288047cf0c7940b14e91cba284" resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-2.2.2.tgz#87c217dc72b4e47b22493daf155daf3759f868a6"
integrity sha512-YKAh2gThC6WooPnVZCoC+rV1bODAKFwkhxikzgH18wpBjkgTkkR9Sb0IesQAH5QrAEH/JQVmy47jcpQkf2Au3Q== integrity sha512-2SlXALHIkyUPDsV4VTKVR9DW7K3Ksh1aaIv3NrNJygTbhXe2A9GrcKHZ2ovIiOp/BXilOcTYemfHHZubP431dg==
mobx-react@^6.2.2: mobx-react@^6.2.2:
version "6.2.2" version "6.3.0"
resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-6.2.2.tgz#45e8e7c4894cac8399bba0a91060d7cfb8ea084b" resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-6.3.0.tgz#7d11799f988bbdadc49e725081993b18baa20329"
integrity sha512-Us6V4ng/iKIRJ8pWxdbdysC6bnS53ZKLKlVGBqzHx6J+gYPYbOotWvhHZnzh/W5mhpYXxlXif4kL2cxoWJOplQ== integrity sha512-C14yya2nqEBRSEiJjPkhoWJLlV8pcCX3m2JRV7w1KivwANJqipoiPx9UMH4pm6QNMbqDdvJqoyl+LqNu9AhvEQ==
dependencies: dependencies:
mobx-react-lite "2" mobx-react-lite ">=2.2.0"
mobx@^5.15.4: mobx@^5.15.4:
version "5.15.4" version "5.15.4"