Skip to content

Commit 37ab1bc

Browse files
authored
allow resizing of left sidebar (#244)
* wip * integrate original sidebar content * ResizableSidebar component * trigger toggleCollapse * remove debugging code * minor refactor. disable text select on mousemove * replace icons with fontawesome icons. fix alignment issues * fix session view width when tabs overflow * prevent index and icon from shifting when resizing * snap effect * minor refactor * apply collapsed mode to sidebar contents * change default width to 240px * backend implementation * fix wrong subcmd * save collapsed state * retore sidebar state on reload/launch * use collapse data form db on first load. use previously saved width on expand. * persist width as well collapse state * various fixes and improvements * bind methods * refactor * more refactor * fix minor bug * fix merge issues * various fixes * refactor * fixes * fix issues * fix all issues * resolve undefind tempWidth * fix toggleCollapsed * use Promise in stopResizing method * use tempCollapsed to for real time toggling between logos * minor method name change * refactor * remove debugging code * fix conflict * fix setting collapsed state via CLI * minor refactor * remove debugging code * create setTempWidthAndTempCollapsed method * handle invalid width set via cli * refactor: setbycli not actually needed * remove unused code
1 parent 40757fa commit 37ab1bc

11 files changed

Lines changed: 471 additions & 129 deletions

File tree

src/app/app.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type OV<V> = mobx.IObservableValue<V>;
3030
@mobxReact.observer
3131
class App extends React.Component<{}, {}> {
3232
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
33+
mainContentRef: React.RefObject<HTMLDivElement> = React.createRef();
3334

3435
constructor(props: any) {
3536
super(props);
@@ -75,15 +76,22 @@ class App extends React.Component<{}, {}> {
7576
let hasClientStop = GlobalModel.getHasClientStop();
7677
let dcWait = this.dcWait.get();
7778
let platform = GlobalModel.getPlatform();
79+
let clientData = GlobalModel.clientData.get();
80+
81+
// Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around.
82+
// Doing it here fixes the flicker cos app is not rendered until clientData is populated.
83+
if (clientData == null) {
84+
return null;
85+
}
7886

7987
if (disconnected || hasClientStop) {
8088
if (!dcWait) {
8189
setTimeout(() => this.updateDcWait(true), 1500);
8290
}
8391
return (
8492
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
85-
<div className="main-content">
86-
<MainSideBar />
93+
<div ref={this.mainContentRef} className="main-content">
94+
<MainSideBar parentRef={this.mainContentRef} clientData={clientData} />
8795
<div className="session-view" />
8896
</div>
8997
<If condition={dcWait}>
@@ -102,8 +110,8 @@ class App extends React.Component<{}, {}> {
102110
}
103111
return (
104112
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
105-
<div className="main-content">
106-
<MainSideBar />
113+
<div ref={this.mainContentRef} className="main-content">
114+
<MainSideBar parentRef={this.mainContentRef} clientData={clientData} />
107115
<ErrorBoundary>
108116
<PluginsView />
109117
<WorkspaceView />

src/app/common/common.tsx

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import ReactMarkdown from "react-markdown";
99
import remarkGfm from "remark-gfm";
1010
import cn from "classnames";
1111
import { If } from "tsx-control-statements/components";
12-
import { RemoteType, StatusIndicatorLevel } from "../../types/types";
12+
import { RemoteType } from "../../types/types";
1313
import ReactDOM from "react-dom";
14-
import { GlobalModel } from "../../model/model";
14+
import { GlobalModel, GlobalCommandRunner } from "../../model/model";
1515
import * as appconst from "../appconst";
1616
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
17+
import { MagicLayout } from "../magiclayout";
1718

1819
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
1920
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
@@ -1265,6 +1266,169 @@ In order to use Wave's advanced features like unified history and persistent ses
12651266
});
12661267
}
12671268

1269+
interface ResizableSidebarProps {
1270+
parentRef: React.RefObject<HTMLElement>;
1271+
position: "left" | "right";
1272+
enableSnap?: boolean;
1273+
className?: string;
1274+
children?: (toggleCollapsed: () => void) => React.ReactNode;
1275+
toggleCollapse?: () => void;
1276+
}
1277+
1278+
@mobxReact.observer
1279+
class ResizableSidebar extends React.Component<ResizableSidebarProps> {
1280+
resizeStartWidth: number = 0;
1281+
startX: number = 0;
1282+
prevDelta: number = 0;
1283+
prevDragDirection: string = null;
1284+
disposeReaction: any;
1285+
1286+
@boundMethod
1287+
startResizing(event: React.MouseEvent<HTMLDivElement>) {
1288+
event.preventDefault();
1289+
1290+
let { parentRef, position } = this.props;
1291+
let parentRect = parentRef.current?.getBoundingClientRect();
1292+
1293+
if (!parentRect) return;
1294+
1295+
if (position === "right") {
1296+
this.startX = parentRect.right - event.clientX;
1297+
} else {
1298+
this.startX = event.clientX - parentRect.left;
1299+
}
1300+
1301+
this.resizeStartWidth = GlobalModel.mainSidebarModel.getWidth();
1302+
document.addEventListener("mousemove", this.onMouseMove);
1303+
document.addEventListener("mouseup", this.stopResizing);
1304+
1305+
document.body.style.cursor = "col-resize";
1306+
mobx.action(() => {
1307+
GlobalModel.mainSidebarModel.isDragging.set(true);
1308+
})();
1309+
}
1310+
1311+
@boundMethod
1312+
onMouseMove(event: MouseEvent) {
1313+
event.preventDefault();
1314+
1315+
let { parentRef, enableSnap, position } = this.props;
1316+
let parentRect = parentRef.current?.getBoundingClientRect();
1317+
let mainSidebarModel = GlobalModel.mainSidebarModel;
1318+
1319+
if (!mainSidebarModel.isDragging.get() || !parentRect) return;
1320+
1321+
let delta, newWidth;
1322+
1323+
if (position === "right") {
1324+
delta = parentRect.right - event.clientX - this.startX;
1325+
} else {
1326+
delta = event.clientX - parentRect.left - this.startX;
1327+
}
1328+
1329+
newWidth = this.resizeStartWidth + delta;
1330+
1331+
if (enableSnap) {
1332+
let minWidth = MagicLayout.MainSidebarMinWidth;
1333+
let snapPoint = minWidth + MagicLayout.MainSidebarSnapThreshold;
1334+
let dragResistance = MagicLayout.MainSidebarDragResistance;
1335+
let dragDirection;
1336+
1337+
if (delta - this.prevDelta > 0) {
1338+
dragDirection = "+";
1339+
} else if (delta - this.prevDelta == 0) {
1340+
if (this.prevDragDirection == "+") {
1341+
dragDirection = "+";
1342+
} else {
1343+
dragDirection = "-";
1344+
}
1345+
} else {
1346+
dragDirection = "-";
1347+
}
1348+
1349+
this.prevDelta = delta;
1350+
this.prevDragDirection = dragDirection;
1351+
1352+
if (newWidth - dragResistance > minWidth && newWidth < snapPoint && dragDirection == "+") {
1353+
newWidth = snapPoint;
1354+
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
1355+
} else if (newWidth + dragResistance < snapPoint && dragDirection == "-") {
1356+
newWidth = minWidth;
1357+
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true);
1358+
} else if (newWidth > snapPoint) {
1359+
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
1360+
}
1361+
} else {
1362+
if (newWidth <= MagicLayout.MainSidebarMinWidth) {
1363+
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true);
1364+
} else {
1365+
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
1366+
}
1367+
}
1368+
}
1369+
1370+
@boundMethod
1371+
stopResizing() {
1372+
let mainSidebarModel = GlobalModel.mainSidebarModel;
1373+
1374+
GlobalCommandRunner.clientSetSidebar(
1375+
mainSidebarModel.tempWidth.get(),
1376+
mainSidebarModel.tempCollapsed.get()
1377+
).finally(() => {
1378+
mobx.action(() => {
1379+
mainSidebarModel.isDragging.set(false);
1380+
})();
1381+
});
1382+
1383+
document.removeEventListener("mousemove", this.onMouseMove);
1384+
document.removeEventListener("mouseup", this.stopResizing);
1385+
document.body.style.cursor = "";
1386+
}
1387+
1388+
@boundMethod
1389+
toggleCollapsed() {
1390+
let mainSidebarModel = GlobalModel.mainSidebarModel;
1391+
1392+
let tempCollapsed = mainSidebarModel.getCollapsed();
1393+
let width = MagicLayout.MainSidebarDefaultWidth;
1394+
let newWidth;
1395+
if (tempCollapsed) {
1396+
newWidth = width;
1397+
} else {
1398+
newWidth = MagicLayout.MainSidebarMinWidth;
1399+
}
1400+
1401+
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, !tempCollapsed);
1402+
GlobalCommandRunner.clientSetSidebar(newWidth, !tempCollapsed);
1403+
}
1404+
1405+
render() {
1406+
let { className, children } = this.props;
1407+
let mainSidebarModel = GlobalModel.mainSidebarModel;
1408+
let width = mainSidebarModel.getWidth();
1409+
let isCollapsed = mainSidebarModel.getCollapsed();
1410+
1411+
return (
1412+
<div className={cn("sidebar", className, { collapsed: isCollapsed })} style={{ width }}>
1413+
<div className="sidebar-content">{children(this.toggleCollapsed)}</div>
1414+
<div
1415+
className="sidebar-handle"
1416+
style={{
1417+
position: "absolute",
1418+
top: 0,
1419+
[this.props.position === "left" ? "right" : "left"]: 0,
1420+
bottom: 0,
1421+
width: "5px",
1422+
cursor: "col-resize",
1423+
}}
1424+
onMouseDown={this.startResizing}
1425+
onDoubleClick={this.toggleCollapsed}
1426+
></div>
1427+
</div>
1428+
);
1429+
}
1430+
}
1431+
12681432
export {
12691433
CmdStrCode,
12701434
Toggle,
@@ -1286,5 +1450,6 @@ export {
12861450
LinkButton,
12871451
Status,
12881452
Modal,
1453+
ResizableSidebar,
12891454
ShowWaveShellInstallPrompt,
12901455
};

src/app/magiclayout.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ let MagicLayout = {
2727
ScreenSidebarWidthPadding: 5,
2828
ScreenSidebarMinWidth: 200,
2929
ScreenSidebarHeaderHeight: 28,
30+
31+
MainSidebarMinWidth: 75,
32+
MainSidebarMaxWidth: 300,
33+
MainSidebarSnapThreshold: 90,
34+
MainSidebarDragResistance: 50,
35+
MainSidebarDefaultWidth: 240,
3036
};
3137

3238
let m = MagicLayout;

src/app/sidebar/sidebar.less

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33

44
.main-sidebar {
55
padding: 0;
6-
min-width: 20rem;
7-
max-width: 20rem;
86
display: flex;
97
flex-direction: column;
108
position: relative;
119
font-size: 12.5px;
1210
line-height: 20px;
1311
backdrop-filter: blur(4px);
12+
z-index: 20;
1413

1514
.title-bar-drag {
1615
-webkit-app-region: drag;
@@ -24,7 +23,6 @@
2423
&.collapsed {
2524
width: 6em;
2625
min-width: 6em;
27-
2826
.arrow-container,
2927
.collapse-button {
3028
transform: rotate(180deg);
@@ -34,7 +32,7 @@
3432
margin-top: 26px;
3533

3634
.top,
37-
.workspaces-item,
35+
.workspaces,
3836
.middle,
3937
.bottom,
4038
.separator {
@@ -50,7 +48,7 @@
5048
justify-content: center;
5149
align-items: center;
5250

53-
.logo-container img {
51+
.logo-container {
5452
width: 45px;
5553
}
5654

@@ -86,10 +84,14 @@
8684
display: flex;
8785
flex-direction: row;
8886

87+
.logo-container {
88+
flex-shrink: 0;
89+
width: 100px;
90+
}
91+
8992
.spacer {
9093
flex-grow: 1;
9194
}
92-
9395
img {
9496
width: 100px;
9597
}
@@ -150,7 +152,6 @@
150152
margin-left: 6px;
151153
border-radius: 4px;
152154
opacity: 1;
153-
transition: opacity 0.1s ease-in-out, visibility 0.1s step-end;
154155
width: inherit;
155156
max-width: inherit;
156157
min-width: inherit;
@@ -177,6 +178,7 @@
177178
float: right;
178179
margin-right: 6px;
179180
letter-spacing: 6px;
181+
margin-left: auto;
180182
}
181183
&:hover {
182184
:not(.disabled) .hotkey {

0 commit comments

Comments
 (0)