Skip to content

Commit af801b7

Browse files
committed
Rewrite split editor to avoid losing browser state
1 parent 7472bcf commit af801b7

File tree

4 files changed

+84
-111
lines changed

4 files changed

+84
-111
lines changed

ui/frontend/Playground.module.css

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@
55
padding-bottom: 1em;
66
}
77

8-
.-parent {
8+
.-resizeableArea {
99
composes: -autoSize from './shared.module.css';
1010
display: grid;
1111
}
1212

13-
$plainPrimaryDimension: 1fr auto;
13+
.resizeableAreaRowOutputUnfocused {
14+
composes: -resizeableArea;
15+
grid-template-rows: 1fr auto;
16+
}
17+
18+
.resizeableAreaRowOutputFocused {
19+
composes: -resizeableArea;
20+
grid-template-rows: 1fr 12px 1fr;
21+
}
1422

15-
.plainRows {
16-
composes: -parent;
17-
grid-template-rows: $plainPrimaryDimension;
23+
.resizeableAreaColumnOutputUnfocused {
24+
composes: -resizeableArea;
25+
grid-template-columns: 1fr auto;
1826
}
1927

20-
.plainColumns {
21-
composes: -parent;
22-
grid-template-columns: $plainPrimaryDimension;
28+
.resizeableAreaColumnOutputFocused {
29+
composes: -resizeableArea;
30+
grid-template-columns: 1fr 12px 1fr;
2331
}
2432

2533
.-gutter {
@@ -28,15 +36,6 @@ $plainPrimaryDimension: 1fr auto;
2836
justify-content: center;
2937
}
3038

31-
$splitPrimaryDimension: 1fr 12px 1fr;
32-
$splitSecondaryDimension: 1fr;
33-
34-
.splitRows {
35-
composes: -parent;
36-
grid-template-columns: $splitSecondaryDimension;
37-
grid-template-rows: $splitPrimaryDimension;
38-
}
39-
4039
.splitRowsGutter {
4140
composes: -gutter;
4241
cursor: row-resize;
@@ -47,12 +46,6 @@ $splitSecondaryDimension: 1fr;
4746
transform: rotate(90deg);
4847
}
4948

50-
.splitColumns {
51-
composes: -parent;
52-
grid-template-columns: $splitPrimaryDimension;
53-
grid-template-rows: $splitSecondaryDimension;
54-
}
55-
5649
.splitColumnsGutter {
5750
composes: -gutter;
5851
cursor: col-resize;

ui/frontend/Playground.tsx

Lines changed: 66 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { useCallback } from 'react';
1+
import React, { useCallback, useEffect, useRef } from 'react';
22
import { useDispatch, useSelector } from 'react-redux';
3-
import Split from 'react-split-grid';
3+
import Split from 'split-grid';
44

55
import Editor from './Editor';
66
import Header from './Header';
@@ -12,103 +12,91 @@ import * as actions from './actions';
1212

1313
import styles from './Playground.module.css';
1414

15-
const NoOutput: React.SFC = () => (
16-
<div className={styles.editor}><Editor /></div>
17-
);
18-
19-
const PlainRows: React.SFC = () => (
20-
<div className={styles.plainRows}>
21-
<div className={styles.editor}><Editor /></div>
22-
<div className={styles.output}><Output /></div>
23-
</div>
24-
);
25-
26-
const PlainColumns: React.SFC = () => (
27-
<div className={styles.plainColumns}>
28-
<div className={styles.editor}><Editor /></div>
29-
<div className={styles.output}><Output /></div>
30-
</div>
31-
);
32-
33-
interface SplitProps {
34-
resizeComplete: () => void;
15+
const TRACK_OPTION_NAME = {
16+
[Orientation.Horizontal]: 'rowGutters',
17+
[Orientation.Vertical]: 'columnGutters',
3518
}
3619

37-
const SplitRows: React.SFC<SplitProps> = ({ resizeComplete }) => (
38-
<Split
39-
minSize={100}
40-
onDragEnd={resizeComplete}
41-
render={({
42-
getGridProps,
43-
getGutterProps,
44-
}) => (
45-
<div className={styles.splitRows} {...getGridProps()}>
46-
<div className={styles.editor}><Editor /></div>
47-
<div className={styles.splitRowsGutter} {...getGutterProps('row', 1)}>
48-
<span className={styles.splitRowsGutterHandle}></span>
49-
</div>
50-
<div className={styles.output}><Output /></div>
51-
</div>
52-
)} />
53-
)
54-
55-
const SplitColumns: React.SFC<SplitProps> = ({ resizeComplete }) => (
56-
<Split
57-
minSize={100}
58-
onDragEnd={resizeComplete}
59-
render={({
60-
getGridProps,
61-
getGutterProps,
62-
}) => (
63-
<div className={styles.splitColumns} {...getGridProps()}>
64-
<div className={styles.editor}><Editor /></div>
65-
<div className={styles.splitColumnsGutter} {...getGutterProps('column', 1)}></div>
66-
<div className={styles.output}><Output /></div>
67-
</div>
68-
)} />
69-
)
20+
const FOCUSED_GRID_STYLE = {
21+
[Orientation.Horizontal]: styles.resizeableAreaRowOutputFocused,
22+
[Orientation.Vertical]: styles.resizeableAreaColumnOutputFocused,
23+
}
7024

71-
const ORIENTATION_PLAIN_MAP = {
72-
[Orientation.Horizontal]: PlainRows,
73-
[Orientation.Vertical]: PlainColumns,
25+
const UNFOCUSED_GRID_STYLE = {
26+
[Orientation.Horizontal]: styles.resizeableAreaRowOutputUnfocused,
27+
[Orientation.Vertical]: styles.resizeableAreaColumnOutputUnfocused,
7428
}
7529

76-
const ORIENTATION_SPLIT_MAP = {
77-
[Orientation.Horizontal]: SplitRows,
78-
[Orientation.Vertical]: SplitColumns,
30+
const HANDLE_STYLES = {
31+
[Orientation.Horizontal]: [styles.splitRowsGutter, styles.splitRowsGutterHandle],
32+
[Orientation.Vertical]: [styles.splitColumnsGutter, ''],
7933
}
8034

81-
const Playground: React.SFC = () => {
82-
const showNotifications = useSelector(selectors.anyNotificationsToShowSelector);
35+
// We drop down to lower-level split-grid code and use some hooks
36+
// because we want to reduce the number of times that the Editor
37+
// component is remounted. Each time it's remounted, we see a flicker and
38+
// lose state (like undo history).
39+
const ResizableArea: React.SFC = () => {
8340
const somethingToShow = useSelector(selectors.getSomethingToShow);
8441
const isFocused = useSelector(selectors.isOutputFocused);
8542
const orientation = useSelector(selectors.orientation);
8643

8744
const dispatch = useDispatch();
8845
const resizeComplete = useCallback(() => dispatch(actions.splitRatioChanged()), [dispatch]);
8946

90-
let Foo;
91-
if (!somethingToShow) {
92-
Foo = NoOutput;
93-
} else {
94-
if (isFocused) {
95-
Foo = ORIENTATION_SPLIT_MAP[orientation];
96-
} else {
97-
Foo = ORIENTATION_PLAIN_MAP[orientation];
98-
}
99-
}
47+
const grid = useRef(null);
48+
const dragHandle = useRef(null);
49+
50+
// Reset styles left on the grid from split-grid when we change orientation or focus.
51+
useEffect(() => {
52+
grid.current.style['grid-template-columns'] = null;
53+
grid.current.style['grid-template-rows'] = null;
54+
55+
resizeComplete();
56+
}, [orientation, isFocused, resizeComplete])
57+
58+
useEffect(() => {
59+
const split = Split({
60+
minSize: 100,
61+
[TRACK_OPTION_NAME[orientation]]: [{
62+
track: 1,
63+
element: dragHandle.current,
64+
}],
65+
onDragEnd: resizeComplete,
66+
});
67+
68+
return () => split.destroy();
69+
}, [orientation, isFocused, somethingToShow, resizeComplete])
70+
71+
const gridStyles = isFocused ? FOCUSED_GRID_STYLE : UNFOCUSED_GRID_STYLE;
72+
const gridStyle = gridStyles[orientation];
73+
const [handleOuterStyle, handleInnerStyle] = HANDLE_STYLES[orientation];
74+
75+
return (
76+
<div ref={grid} className={gridStyle}>
77+
<div className={styles.editor}><Editor /></div>
78+
{ isFocused &&
79+
<div ref={dragHandle} className={handleOuterStyle}>
80+
<span className={handleInnerStyle}></span>
81+
</div>
82+
}
83+
{ somethingToShow && <div className={styles.output}><Output /></div>}
84+
</div>
85+
);
86+
};
87+
88+
const Playground: React.SFC = () => {
89+
const showNotifications = useSelector(selectors.anyNotificationsToShowSelector);
10090

10191
return (
10292
<>
10393
<div className={styles.container}>
104-
<div>
105-
<Header />
106-
</div>
107-
<Foo resizeComplete={resizeComplete} />
94+
<Header />
95+
<ResizableArea />
10896
</div>
10997
{ showNotifications && <Notifications />}
11098
</>
11199
);
112-
};
100+
}
113101

114102
export default Playground;

ui/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
"react-prism": "^4.0.0",
2222
"react-redux": "^7.0.0",
2323
"react-shadow": "^19.0.2",
24-
"react-split-grid": "^1.0.3",
2524
"redux": "^4.0.0",
2625
"redux-thunk": "^2.1.0",
2726
"regenerator-runtime": "^0.13.2",
2827
"reselect": "^4.0.0",
2928
"route-parser": "^0.0.5",
29+
"split-grid": "^1.0.9",
3030
"url": "^0.11.0"
3131
},
3232
"devDependencies": {

ui/frontend/yarn.lock

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5893,7 +5893,7 @@ prompts@^2.0.1:
58935893
kleur "^3.0.3"
58945894
sisteransi "^1.0.5"
58955895

5896-
prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.7.2:
5896+
prop-types@^15.5.8, prop-types@^15.7.2:
58975897
version "15.7.2"
58985898
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
58995899
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -6036,14 +6036,6 @@ react-shadow@^19.0.2:
60366036
humps "^2.0.1"
60376037
react-use "^15.3.3"
60386038

6039-
react-split-grid@^1.0.3:
6040-
version "1.0.3"
6041-
resolved "https://registry.yarnpkg.com/react-split-grid/-/react-split-grid-1.0.3.tgz#f3b7e8a7aee6085870521c7b697b4b9147c46edf"
6042-
integrity sha512-iE7R7Ne6KOIstKH4hROG3pwPod3LGJ4TB1P2jmPY2/oYI9+Jw8K7Rj0z7C2ZkfDLgMK2vTc+vpYOkFzr/+eaIg==
6043-
dependencies:
6044-
prop-types "^15.5.7"
6045-
split-grid "^1.0.9"
6046-
60476039
react-universal-interface@^0.6.2:
60486040
version "0.6.2"
60496041
resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"

0 commit comments

Comments
 (0)