Skip to content

Commit d407d59

Browse files
committed
Allow the editor/output panels to be resized
Closes #302
1 parent f8f0bc1 commit d407d59

File tree

13 files changed

+230
-107
lines changed

13 files changed

+230
-107
lines changed

ui/frontend/AdvancedEditor.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import { connect } from 'react-redux';
3+
import { aceResizeKey } from './selectors';
34

45
import State from './state';
5-
import { CommonEditorProps, Crate, Edition, Focus, PairCharacters, Position, Selection } from './types';
6+
import { AceResizeKey, CommonEditorProps, Crate, Edition, PairCharacters, Position, Selection } from './types';
67

78
type Ace = typeof import('ace-builds');
89
type AceEditor = import('ace-builds').Ace.Editor;
@@ -67,7 +68,7 @@ interface AdvancedEditorProps {
6768
selection: Selection;
6869
theme: string;
6970
crates: Crate[];
70-
focus?: Focus;
71+
resizeKey?: AceResizeKey;
7172
pairCharacters: PairCharacters;
7273
}
7374

@@ -281,9 +282,9 @@ const AdvancedEditor: React.SFC<AdvancedEditorProps> = props => {
281282
// 4. Try to scroll
282283
//
283284
// Ace doesn't know that we changed the visible area and so
284-
// doesn't recalculate. Knowing if the focus changed is enough
285-
// to force such a recalculation.
286-
useEditorProp(editor, props.focus, useCallback((editor, _focus) => {
285+
// doesn't recalculate. We track factors that lead to this case to
286+
// force such a recalculation.
287+
useEditorProp(editor, props.resizeKey, useCallback((editor, _resizeKey) => {
287288
editor.resize();
288289
}, []));
289290

@@ -321,7 +322,7 @@ interface AdvancedEditorAsyncProps {
321322
selection: Selection;
322323
theme: string;
323324
crates: Crate[];
324-
focus?: Focus;
325+
resizeKey?: AceResizeKey;
325326
pairCharacters: PairCharacters;
326327
}
327328

@@ -462,7 +463,7 @@ interface AdvancedEditorAsyncState {
462463
interface PropsFromState {
463464
theme: string;
464465
keybinding?: string;
465-
focus?: Focus;
466+
resizeKey?: AceResizeKey;
466467
autocompleteOnUse: boolean;
467468
pairCharacters: PairCharacters;
468469
}
@@ -474,7 +475,7 @@ const mapStateToProps = (state: State) => {
474475
theme,
475476
pairCharacters,
476477
keybinding: keybinding === 'ace' ? null : keybinding,
477-
focus: state.output.meta.focus,
478+
resizeKey: aceResizeKey(state),
478479
autocompleteOnUse: state.configuration.edition === Edition.Rust2018,
479480
};
480481
};

ui/frontend/Output.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ interface PaneWithCodeProps extends SimplePaneProps {
4444
code?: string;
4545
}
4646

47-
interface OutputProps {
48-
isFocused?: boolean;
49-
}
50-
51-
const Output: React.SFC<OutputProps> = ({ isFocused }) => {
47+
const Output: React.SFC = () => {
5248
const somethingToShow = useSelector(selectors.getSomethingToShow);
5349
const { meta: { focus }, execute, format, clippy, miri, macroExpansion, assembly, llvmIr, mir, hir, wasm, gist } =
5450
useSelector((state: State) => state.output);
@@ -97,7 +93,7 @@ const Output: React.SFC<OutputProps> = ({ isFocused }) => {
9793
}
9894

9995
return (
100-
<div className={`output ${isFocused ? 'output--focused' : ''}`}>
96+
<div className="output">
10197
<div className="output-tabs">
10298
<Tab kind={Focus.Execute} focus={focus}
10399
label="Execution"

ui/frontend/Playground.module.css

Lines changed: 38 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,68 @@
55
padding-bottom: 1em;
66
}
77

8-
.split {
9-
display: flex;
8+
.-autoSize {
9+
width: 100%;
10+
min-width: 0;
1011
height: 100%;
12+
min-height: 0;
1113
}
1214

13-
.splitAutomatic {
14-
composes: split;
15+
.-parent {
16+
composes: -autoSize;
17+
display: grid;
1518
}
1619

17-
@media screen and (max-width: 1600px) {
18-
.splitAutomatic {
19-
flex-direction: column;
20-
}
21-
}
20+
$plainPrimaryDimension: 1fr auto;
2221

23-
@media screen and (min-width: 1600px) {
24-
.splitAutomatic {
25-
flex-direction: row;
26-
}
22+
.plainRows {
23+
composes: -parent;
24+
grid-template-rows: $plainPrimaryDimension;
2725
}
2826

29-
.splitHorizontal {
30-
composes: split;
31-
flex-direction: column;
27+
.plainColumns {
28+
composes: -parent;
29+
grid-template-columns: $plainPrimaryDimension;
3230
}
3331

34-
.splitVertical {
35-
composes: split;
36-
flex-direction: row;
32+
.-gutter {
33+
display: flex;
34+
align-items: center;
35+
justify-content: center;
3736
}
3837

39-
.splitVertical > * {
40-
/* FIXME: remove this, when the output-rules are 'aware' of the
41-
* orientation (this disables the extra margin-top for the spacing
42-
* between editor and output in the horizontal split mode)
43-
*/
44-
margin-top: -0.2em;
38+
$splitPrimaryDimension: 1fr 12px 1fr;
39+
$splitSecondaryDimension: 1fr;
4540

46-
/* for the border of the editor */
47-
margin-bottom: 4px;
48-
49-
/* space between the split */
50-
margin-left: 0.5em;
41+
.splitRows {
42+
composes: -parent;
43+
grid-template-columns: $splitSecondaryDimension;
44+
grid-template-rows: $splitPrimaryDimension;
5145
}
5246

53-
.splitVertical > *:first-child {
54-
/* the first child, i.e. the editor has the border already */
55-
margin-top: 0;
56-
margin-bottom: 0;
57-
margin-left: 0;
47+
.splitRowsGutter {
48+
composes: -gutter;
49+
cursor: row-resize;
50+
transform: rotate(90deg);
5851
}
5952

60-
@media screen and (min-width: 1600px) {
61-
/* automatic vertical */
62-
.splitAutomatic > *:first-child {
63-
/* the first child, i.e. the editor has the border already */
64-
margin-top: 0;
65-
margin-bottom: 0;
66-
margin-left: 0;
67-
}
53+
.splitColumns {
54+
composes: -parent;
55+
grid-template-columns: $splitPrimaryDimension;
56+
grid-template-rows: $splitSecondaryDimension;
6857
}
6958

70-
/* automatic vertical */
71-
@media screen and (min-width: 1600px) {
72-
.splitAutomatic > * {
73-
/* FIXME: remove this, when the output-rules are 'aware' of the
74-
* orientation (this disables the extra margin-top for the spacing
75-
* between editor and output in the horizontal split mode)
76-
*/
77-
margin-top: -0.2em;
78-
79-
/* for the border of the editor */
80-
margin-bottom: 4px;
81-
82-
/* space between the split */
83-
margin-left: 0.5em;
84-
}
59+
.splitColumnsGutter {
60+
composes: -gutter;
61+
cursor: col-resize;
8562
}
8663

8764
.editor {
88-
position: relative;
89-
flex: 1 1 auto;
65+
composes: -autoSize;
9066
border: 4px solid var(--border-color);
9167
border-radius: 4px;
9268
}
9369

94-
.outputFocused {
95-
position: relative;
96-
flex: 1 1 auto;
70+
.output {
71+
composes: -autoSize;
9772
}

ui/frontend/Playground.tsx

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,111 @@
1-
import React from 'react';
2-
import { useSelector } from 'react-redux';
1+
import React, { useCallback } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
3+
import Split from 'react-split-grid';
34

45
import Editor from './Editor';
56
import Header from './Header';
67
import Notifications from './Notifications';
78
import Output from './Output';
89
import * as selectors from './selectors';
9-
import State from './state';
1010
import { Orientation } from './types';
11+
import * as actions from './actions';
1112

1213
import styles from './Playground.module.css';
1314

14-
const ORIENTATION_STYLE_MAP = {
15-
[Orientation.Automatic]: styles.splitAutomatic,
16-
[Orientation.Horizontal]: styles.splitHorizontal,
17-
[Orientation.Vertical]: styles.splitVertical,
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;
35+
}
36+
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)}></div>
48+
<div className={styles.output}><Output /></div>
49+
</div>
50+
)} />
51+
)
52+
53+
const SplitColumns: React.SFC<SplitProps> = ({ resizeComplete }) => (
54+
<Split
55+
minSize={100}
56+
onDragEnd={resizeComplete}
57+
render={({
58+
getGridProps,
59+
getGutterProps,
60+
}) => (
61+
<div className={styles.splitColumns} {...getGridProps()}>
62+
<div className={styles.editor}><Editor /></div>
63+
<div className={styles.splitColumnsGutter} {...getGutterProps('column', 1)}></div>
64+
<div className={styles.output}><Output /></div>
65+
</div>
66+
)} />
67+
)
68+
69+
const ORIENTATION_PLAIN_MAP = {
70+
[Orientation.Horizontal]: PlainRows,
71+
[Orientation.Vertical]: PlainColumns,
72+
}
73+
74+
const ORIENTATION_SPLIT_MAP = {
75+
[Orientation.Horizontal]: SplitRows,
76+
[Orientation.Vertical]: SplitColumns,
1877
}
1978

2079
const Playground: React.SFC = () => {
2180
const showNotifications = useSelector(selectors.anyNotificationsToShowSelector);
22-
const focus = useSelector((state: State) => state.output.meta.focus);
23-
const splitOrientation = useSelector((state: State) => state.configuration.orientation);
81+
const somethingToShow = useSelector(selectors.getSomethingToShow);
82+
const isFocused = useSelector(selectors.isOutputFocused);
83+
const orientation = useSelector(selectors.orientation);
84+
85+
const dispatch = useDispatch();
86+
const resizeComplete = useCallback(() => dispatch(actions.splitRatioChanged()), [dispatch]);
2487

25-
const outputFocused = focus ? styles.outputFocused : '';
26-
const splitClass = ORIENTATION_STYLE_MAP[splitOrientation];
88+
let Foo;
89+
if (!somethingToShow) {
90+
Foo = NoOutput;
91+
} else {
92+
if (isFocused) {
93+
Foo = ORIENTATION_SPLIT_MAP[orientation];
94+
} else {
95+
Foo = ORIENTATION_PLAIN_MAP[orientation];
96+
}
97+
}
2798

2899
return (
29-
<div>
100+
<>
30101
<div className={styles.container}>
31102
<div>
32103
<Header />
33104
</div>
34-
<div className={splitClass}>
35-
<div className={styles.editor}>
36-
<Editor />
37-
</div>
38-
<div className={outputFocused}>
39-
<Output isFocused={!!focus} />
40-
</div>
41-
</div>
105+
<Foo resizeComplete={resizeComplete} />
42106
</div>
43107
{ showNotifications && <Notifications />}
44-
</div>
108+
</>
45109
);
46110
};
47111

ui/frontend/actions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import fetch from 'isomorphic-fetch';
23
import { ThunkAction as ReduxThunkAction } from 'redux-thunk';
34
import url from 'url';
@@ -121,6 +122,8 @@ export enum ActionType {
121122
RequestVersionsLoad = 'REQUEST_VERSIONS_LOAD',
122123
VersionsLoadSucceeded = 'VERSIONS_LOAD_SUCCEEDED',
123124
NotificationSeen = 'NOTIFICATION_SEEN',
125+
BrowserWidthChanged = 'BROWSER_WIDTH_CHANGED',
126+
SplitRatioChanged = 'SPLIT_RATIO_CHANGED',
124127
}
125128

126129
const setPage = (page: Page) =>
@@ -700,6 +703,12 @@ const notificationSeen = (notification: Notification) =>
700703

701704
export const seenRustSurvey2020 = () => notificationSeen(Notification.RustSurvey2020);
702705

706+
export const browserWidthChanged = (isSmall: boolean) =>
707+
createAction(ActionType.BrowserWidthChanged, { isSmall });
708+
709+
export const splitRatioChanged = () =>
710+
createAction(ActionType.SplitRatioChanged);
711+
703712
function parseChannel(s: string): Channel | null {
704713
switch (s) {
705714
case 'stable':
@@ -852,4 +861,6 @@ export type Action =
852861
| ReturnType<typeof requestVersionsLoad>
853862
| ReturnType<typeof receiveVersionsLoadSuccess>
854863
| ReturnType<typeof notificationSeen>
864+
| ReturnType<typeof browserWidthChanged>
865+
| ReturnType<typeof splitRatioChanged>
855866
;

0 commit comments

Comments
 (0)