Skip to content

Commit d39295c

Browse files
authored
Merge pull request #354 from xcube-dev/forman-remember_last_sidebar_split_pos
Remember last sidebar split position
2 parents 4ec3fa2 + e5be813 commit d39295c

File tree

8 files changed

+283
-385
lines changed

8 files changed

+283
-385
lines changed

src/actions/controlActions.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,21 @@ export function setLayerMenuOpen(layerMenuOpen: boolean): SetLayerMenuOpen {
510510

511511
////////////////////////////////////////////////////////////////////////////////
512512

513+
export const SET_SIDEBAR_POSITION = "SET_SIDEBAR_POSITION";
514+
515+
export interface SetSidebarPosition {
516+
type: typeof SET_SIDEBAR_POSITION;
517+
sidebarPosition: number;
518+
}
519+
520+
export function setSidebarPosition(
521+
sidebarPosition: number,
522+
): SetSidebarPosition {
523+
return { type: SET_SIDEBAR_POSITION, sidebarPosition };
524+
}
525+
526+
////////////////////////////////////////////////////////////////////////////////
527+
513528
export const SET_SIDEBAR_OPEN = "SET_SIDEBAR_OPEN";
514529

515530
export interface SetSidebarOpen {
@@ -803,6 +818,7 @@ export type ControlAction =
803818
| OpenDialog
804819
| CloseDialog
805820
| SetLayerMenuOpen
821+
| SetSidebarPosition
806822
| SetSidebarOpen
807823
| SetSidebarPanelId
808824
| SetVolumeRenderMode

src/components/MapSplitter.tsx

+10-46
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
* SOFTWARE.
2323
*/
2424

25-
import React, { useEffect, useRef } from "react";
26-
import makeStyles from "@mui/styles/makeStyles";
25+
import { useEffect, useRef } from "react";
26+
import Box from "@mui/material/Box";
27+
2728
import { isNumber } from "@/util/types";
29+
import { makeStyles } from "@/util/styles";
30+
import useMouseDrag from "@/hooks/useMouseDrag";
2831

29-
const useStyles = makeStyles({
32+
const styles = makeStyles({
3033
splitter: {
3134
position: "absolute",
3235
top: 0,
@@ -43,44 +46,6 @@ const useStyles = makeStyles({
4346

4447
type Point = [number, number];
4548

46-
function useMouseDrag(onMouseDrag: (delta: Point) => void) {
47-
const lastPosition = useRef<Point | null>(null);
48-
49-
const handleMouseMove = useRef((event: MouseEvent) => {
50-
if (event.buttons === 1 && lastPosition.current !== null) {
51-
event.preventDefault();
52-
const { screenX, screenY } = event;
53-
const [lastScreenX, lastScreenY] = lastPosition.current;
54-
const delta: Point = [screenX - lastScreenX, screenY - lastScreenY];
55-
lastPosition.current = [screenX, screenY];
56-
onMouseDrag(delta);
57-
}
58-
});
59-
60-
// Return value
61-
const startDrag = useRef((event: React.MouseEvent) => {
62-
if (event.buttons === 1) {
63-
event.preventDefault();
64-
document.body.addEventListener("mousemove", handleMouseMove.current);
65-
document.body.addEventListener("mouseup", endDrag.current);
66-
document.body.addEventListener("onmouseleave", endDrag.current);
67-
lastPosition.current = [event.screenX, event.screenY];
68-
}
69-
});
70-
71-
const endDrag = useRef((event: Event) => {
72-
if (lastPosition.current !== null) {
73-
event.preventDefault();
74-
lastPosition.current = null;
75-
document.body.removeEventListener("mousemove", handleMouseMove.current);
76-
document.body.removeEventListener("mouseup", endDrag.current);
77-
document.body.removeEventListener("onmouseleave", endDrag.current);
78-
}
79-
});
80-
81-
return startDrag.current;
82-
}
83-
8449
interface MapSplitterProps {
8550
hidden?: boolean;
8651
position?: number;
@@ -92,14 +57,13 @@ export default function MapSplitter({
9257
position,
9358
onPositionChange,
9459
}: MapSplitterProps) {
95-
const classes = useStyles();
9660
const divRef = useRef<HTMLDivElement | null>(null);
9761
const handleDrag = useRef(([deltaX, _]: Point) => {
9862
if (divRef.current !== null) {
9963
onPositionChange(divRef.current.offsetLeft + deltaX);
10064
}
10165
});
102-
const startDrag = useMouseDrag(handleDrag.current);
66+
const handleMouseDown = useMouseDrag(handleDrag.current);
10367

10468
useEffect(() => {
10569
if (
@@ -119,12 +83,12 @@ export default function MapSplitter({
11983
}
12084

12185
return (
122-
<div
86+
<Box
12387
id={"MapSplitter"}
12488
ref={divRef}
125-
className={classes.splitter}
89+
sx={styles.splitter}
12690
style={{ left: isNumber(position) ? position : "50%" }}
127-
onMouseDown={startDrag}
91+
onMouseDown={handleMouseDown}
12892
/>
12993
);
13094
}

src/components/SplitPane.tsx

+67-111
Original file line numberDiff line numberDiff line change
@@ -22,132 +22,88 @@
2222
* SOFTWARE.
2323
*/
2424

25-
import * as React from "react";
25+
import React, { CSSProperties, PropsWithChildren, useRef } from "react";
26+
27+
import { isNumber } from "@/util/types";
2628
import Splitter, { SplitDir } from "./Splitter";
27-
import { Theme } from "@mui/material/styles";
28-
import { WithStyles } from "@mui/styles";
29-
import createStyles from "@mui/styles/createStyles";
30-
import withStyles from "@mui/styles/withStyles";
31-
import classNames from "classnames";
3229

3330
// noinspection JSUnusedLocalSymbols
34-
const styles = (_theme: Theme) =>
35-
createStyles({
36-
hor: {
37-
display: "flex",
38-
flexFlow: "row nowrap",
39-
flex: "auto", // same as "flex: 1 1 auto;"
40-
},
41-
ver: {
42-
// width: "100%",
43-
height: "100%",
44-
display: "flex",
45-
flexFlow: "column nowrap",
46-
flex: "auto", // same as "flex: 1 1 auto;"
47-
},
48-
childHor: {
49-
flex: "none",
50-
},
51-
childVer: {
52-
flex: "none",
53-
},
54-
});
31+
const styles: Record<string, CSSProperties> = {
32+
hor: {
33+
display: "flex",
34+
flexFlow: "row nowrap",
35+
flex: "auto", // same as "flex: 1 1 auto;"
36+
},
37+
ver: {
38+
// width: "100%",
39+
height: "100%",
40+
display: "flex",
41+
flexFlow: "column nowrap",
42+
flex: "auto", // same as "flex: 1 1 auto;"
43+
},
44+
childHor: {
45+
flex: "none",
46+
},
47+
childVer: {
48+
flex: "none",
49+
},
50+
};
5551

56-
export interface ISplitPaneProps extends WithStyles<typeof styles> {
52+
export interface SplitPaneProps {
5753
dir: SplitDir;
58-
initialSize?: number;
59-
onChange?: (newSize: number, oldSize: number) => void;
60-
style?: React.CSSProperties;
61-
child1Style?: React.CSSProperties;
62-
child2Style?: React.CSSProperties;
63-
className?: string;
64-
child1ClassName?: string;
65-
child2ClassName?: string;
66-
children: [React.ReactNode, React.ReactNode];
67-
}
68-
69-
export interface ISplitPaneState {
70-
size: number;
54+
splitPosition: number;
55+
setSplitPosition: (splitPosition: number) => void;
56+
style?: CSSProperties;
57+
child1Style?: CSSProperties;
58+
child2Style?: CSSProperties;
59+
children: React.ReactNode[];
7160
}
7261

7362
/**
7463
* A simple SplitPane component which must have exactly two child elements.
75-
*
76-
* Properties:
77-
* - dir: the split direction, either "hor" or "ver"
78-
* - initialSize: the initial width ("hor") or height ("ver") of the first child's container
7964
*/
80-
class _SplitPane extends React.PureComponent<ISplitPaneProps, ISplitPaneState> {
81-
constructor(props: ISplitPaneProps) {
82-
super(props);
83-
this.handleSplitDelta = this.handleSplitDelta.bind(this);
84-
this.state = { size: props.initialSize || 50 };
85-
}
65+
export default function SplitPane({
66+
dir,
67+
splitPosition,
68+
setSplitPosition,
69+
children,
70+
style,
71+
child1Style,
72+
child2Style,
73+
}: PropsWithChildren<SplitPaneProps>) {
74+
const child1Ref = useRef<HTMLDivElement | null>(null);
8675

87-
private handleSplitDelta(delta: number) {
88-
this.setState((state: ISplitPaneState) => {
89-
const oldSize = state.size;
90-
const newSize = oldSize + delta;
91-
if (this.props.onChange) {
92-
this.props.onChange(newSize, oldSize);
93-
}
94-
return { size: newSize };
95-
});
76+
if (!children || !Array.isArray(children) || children.length !== 2) {
77+
return null;
9678
}
9779

98-
render() {
99-
const children = this.props.children as React.ReactNode;
100-
if (!children || !Array.isArray(children)) {
101-
return children;
102-
}
103-
if (children.length === 1) {
104-
return children[0];
105-
}
106-
if (children.length > 2) {
107-
throw new Error("SplitPane expects not more than two children");
108-
}
109-
let className;
110-
let childClassName;
111-
let child1Style;
112-
let child2Style;
113-
if (this.props.dir === "hor") {
114-
const width1 = this.state.size;
115-
className = this.props.classes.hor;
116-
childClassName = this.props.classes.childVer;
117-
child1Style = { width: width1, ...this.props.child1Style };
118-
child2Style = this.props.child2Style;
119-
} else {
120-
const height1 = this.state.size;
121-
className = this.props.classes.ver;
122-
childClassName = this.props.classes.childVer;
123-
child1Style = { height: height1, ...this.props.child1Style };
124-
child2Style = this.props.child2Style;
80+
const childStyle = dir === "hor" ? styles.childHor : styles.childVer;
81+
82+
const child1SizeStyle =
83+
dir === "hor" ? { width: splitPosition } : { height: splitPosition };
84+
85+
const handleSplitChange = (delta: number) => {
86+
if (child1Ref.current && isNumber(child1Ref.current.clientWidth)) {
87+
setSplitPosition(child1Ref.current.clientWidth + delta);
12588
}
126-
return (
89+
};
90+
91+
return (
92+
<div
93+
id="SplitPane"
94+
style={{ ...style, ...(dir === "hor" ? styles.hor : styles.ver) }}
95+
>
12796
<div
128-
id="SplitPane"
129-
className={classNames(className, this.props.className)}
130-
style={this.props.style}
97+
ref={child1Ref}
98+
id="SplitPane-Child-1"
99+
style={{ ...childStyle, ...child1Style, ...child1SizeStyle }}
131100
>
132-
<div
133-
id="SplitPane-Child-1"
134-
className={classNames(childClassName, this.props.child1ClassName)}
135-
style={child1Style}
136-
>
137-
{children[0]}
138-
</div>
139-
<Splitter dir={this.props.dir} onChange={this.handleSplitDelta} />
140-
<div
141-
id="SplitPane-Child-2"
142-
className={classNames(childClassName, this.props.child2ClassName)}
143-
style={child2Style}
144-
>
145-
{children[1]}
146-
</div>
101+
{children[0]}
147102
</div>
148-
);
149-
}
103+
<Splitter dir={dir} onChange={handleSplitChange} />
104+
<div id="SplitPane-Child-2" style={{ ...childStyle, ...child2Style }}>
105+
{children[1]}
106+
</div>
107+
</div>
108+
);
150109
}
151-
152-
const SplitPane = withStyles(styles)(_SplitPane);
153-
export default SplitPane;

0 commit comments

Comments
 (0)