Skip to content

Commit 38823dc

Browse files
alexpelanoutoftime
authored andcommitted
Make Preview component collapsible and handle completely collapsed right column (#1702)
* WIP: making the preview hideable and expanding Workspace column logic * Fix bug with preview not getting hidden and lint * share constants * Don't force show the errors until user is done typing and it's definitely invalid * render the preview but hidden if it's collapsed so we can still capture runtime errors and the console works while preview is collapsed. * Don't show collapsed RHS components when there are errors
1 parent 5591cbf commit 38823dc

File tree

8 files changed

+155
-42
lines changed

8 files changed

+155
-42
lines changed

src/components/Preview.jsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {
2+
faChevronDown,
23
faExternalLinkAlt,
34
faSyncAlt,
45
} from '@fortawesome/free-solid-svg-icons';
56
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
6-
import get from 'lodash-es/get';
7+
import classnames from 'classnames';
8+
import partial from 'lodash-es/partial';
79
import PropTypes from 'prop-types';
810
import ImmutablePropTypes from 'react-immutable-proptypes';
911
import React from 'react';
@@ -13,13 +15,17 @@ import PreviewFrame from './PreviewFrame';
1315
export default function Preview({
1416
compiledProjects,
1517
consoleEntries,
18+
currentProjectKey,
19+
isOpen,
1620
showingErrors,
21+
title,
1722
onConsoleError,
1823
onConsoleLog,
1924
onConsoleValue,
2025
onPopOutProject,
2126
onRefreshClick,
2227
onRuntimeError,
28+
onToggleVisible,
2329
}) {
2430
if (showingErrors) {
2531
return null;
@@ -38,11 +44,14 @@ export default function Preview({
3844
/>
3945
));
4046

41-
const mostRecentCompiledProject = compiledProjects.last();
42-
const title = get(mostRecentCompiledProject, 'title', '');
43-
4447
return (
45-
<div className="preview output__item">
48+
<div
49+
className={classnames(
50+
'preview',
51+
'output__item',
52+
{u__hidden: !isOpen},
53+
)}
54+
>
4655
<div className="preview__title-bar">
4756
<span className="preview__button preview__button_pop-out">
4857
<FontAwesomeIcon
@@ -51,6 +60,12 @@ export default function Preview({
5160
/>
5261
</span>
5362
{title}
63+
<span className="preview__button preview__button_toggle-visibility">
64+
<FontAwesomeIcon
65+
icon={faChevronDown}
66+
onClick={partial(onToggleVisible, currentProjectKey)}
67+
/>
68+
</span>
5469
<span className="preview__button preview__button_reset">
5570
<FontAwesomeIcon icon={faSyncAlt} onClick={onRefreshClick} />
5671
</span>
@@ -63,11 +78,15 @@ export default function Preview({
6378
Preview.propTypes = {
6479
compiledProjects: ImmutablePropTypes.iterable.isRequired,
6580
consoleEntries: ImmutablePropTypes.iterable.isRequired,
81+
currentProjectKey: PropTypes.string.isRequired,
82+
isOpen: PropTypes.bool.isRequired,
6683
showingErrors: PropTypes.bool.isRequired,
84+
title: PropTypes.string.isRequired,
6785
onConsoleError: PropTypes.func.isRequired,
6886
onConsoleLog: PropTypes.func.isRequired,
6987
onConsoleValue: PropTypes.func.isRequired,
7088
onPopOutProject: PropTypes.func.isRequired,
7189
onRefreshClick: PropTypes.func.isRequired,
7290
onRuntimeError: PropTypes.func.isRequired,
91+
onToggleVisible: PropTypes.func.isRequired,
7392
};

src/components/Workspace.jsx

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import includes from 'lodash-es/includes';
1010
import isNull from 'lodash-es/isNull';
1111
import get from 'lodash-es/get';
1212
import partial from 'lodash-es/partial';
13+
import some from 'lodash-es/some';
1314
import {t} from 'i18next';
1415
import classnames from 'classnames';
1516

1617
import prefix from '../services/inlineStylePrefixer';
1718
import {getQueryParameters, setQueryParameters} from '../util/queryParams';
1819
import {LANGUAGES} from '../util/editor';
20+
import {RIGHT_COLUMN_COMPONENTS} from '../util/ui';
1921
import {dehydrateProject, rehydrateProject} from '../clients/localStorage';
2022

2123
import {isPristineProject} from '../util/projectUtils';
@@ -163,30 +165,42 @@ export default class Workspace extends React.Component {
163165
_renderHiddenRightColumnComponents() {
164166
const {
165167
currentProject,
168+
hasErrors,
166169
onComponentToggle,
167-
shouldShowCollapsedConsole,
170+
title,
168171
} = this.props;
169-
const rightColumnComponents = ['console'];
170-
return rightColumnComponents.
172+
// Errors take over the whole right side of the screen
173+
if (hasErrors) {
174+
return null;
175+
}
176+
return RIGHT_COLUMN_COMPONENTS.
171177
filter(component => (
172178
includes(currentProject.hiddenUIComponents, component)
173179
)).
174180
map((component) => {
175181
switch (component) {
176182
case 'console':
177-
if (shouldShowCollapsedConsole) {
178-
return (
179-
<CollapsedComponent
180-
component="console"
181-
isRightJustified={false}
182-
key="console"
183-
projectKey={currentProject.projectKey}
184-
text={t('workspace.components.console')}
185-
onComponentUnhide={onComponentToggle}
186-
/>
187-
);
188-
}
189-
return null;
183+
return (
184+
<CollapsedComponent
185+
component="console"
186+
isRightJustified={false}
187+
key="console"
188+
projectKey={currentProject.projectKey}
189+
text={t('workspace.components.console')}
190+
onComponentUnhide={onComponentToggle}
191+
/>
192+
);
193+
case 'preview':
194+
return (
195+
<CollapsedComponent
196+
component="preview"
197+
isRightJustified={false}
198+
key="preview"
199+
projectKey={currentProject.projectKey}
200+
text={title}
201+
onComponentUnhide={onComponentToggle}
202+
/>
203+
);
190204
default:
191205
return null;
192206
}
@@ -197,6 +211,29 @@ export default class Workspace extends React.Component {
197211
return this.props.hiddenLanguages.length !== LANGUAGES.length;
198212
}
199213

214+
_shouldRenderRightColumn() {
215+
const {
216+
currentProject,
217+
shouldRenderOutput,
218+
} = this.props;
219+
return shouldRenderOutput || some(RIGHT_COLUMN_COMPONENTS,
220+
component => (
221+
!includes(currentProject.hiddenUIComponents, component)
222+
));
223+
}
224+
225+
_isEverythingHidden() {
226+
return !this._shouldRenderLeftColumn() && !this._shouldRenderRightColumn();
227+
}
228+
229+
_renderEverythingHidden() {
230+
return (
231+
<div className="environment__column">
232+
{this._renderHiddenRightColumnComponents()}
233+
{this._renderHiddenLanguages()}
234+
</div>
235+
);
236+
}
200237
_renderEnvironment() {
201238
const {
202239
currentProject,
@@ -208,6 +245,7 @@ export default class Workspace extends React.Component {
208245
onResizableFlexDividerDrag,
209246
onStartDragColumnDivider,
210247
onStopDragColumnDivider,
248+
shouldRenderOutput,
211249
} = this.props;
212250
if (isNull(currentProject)) {
213251
return <PopThrobber message={t('workspace.loading')} />;
@@ -218,7 +256,7 @@ export default class Workspace extends React.Component {
218256
return (
219257
<div className="environment">
220258
{this._shouldRenderLeftColumn() &&
221-
<React.Fragment>
259+
<>
222260
<div
223261
className="environment__column"
224262
ref={_handleEditorsRef}
@@ -227,6 +265,8 @@ export default class Workspace extends React.Component {
227265
<div className="environment__column-contents">
228266
<div className="environment__column-contents-inner">
229267
<EditorsColumn />
268+
{!this._shouldRenderRightColumn() &&
269+
this._renderHiddenRightColumnComponents()}
230270
{this._renderHiddenLanguages()}
231271
</div>
232272
</div>
@@ -246,25 +286,30 @@ export default class Workspace extends React.Component {
246286
)}
247287
/>
248288
</DraggableCore>
249-
</React.Fragment>
289+
</>
250290
}
251-
<div
252-
className="environment__column"
253-
ref={_handleOutputRef}
254-
style={prefix({
255-
flexGrow: resizableFlexGrow.get(1),
256-
pointerEvents: ignorePointerEvents ? 'none' : 'all',
257-
})}
258-
>
259-
<div className="environment__column-contents">
260-
<div className="environment__column-contents-inner">
261-
<Output />
262-
{this._renderHiddenRightColumnComponents()}
263-
{!this._shouldRenderLeftColumn() &&
264-
this._renderHiddenLanguages()}
291+
{this._shouldRenderRightColumn() &&
292+
<>
293+
<div
294+
className="environment__column"
295+
ref={_handleOutputRef}
296+
style={prefix({
297+
flexGrow: resizableFlexGrow.get(1),
298+
pointerEvents: ignorePointerEvents ? 'none' : 'all',
299+
})}
300+
>
301+
<div className="environment__column-contents">
302+
<div className="environment__column-contents-inner">
303+
{shouldRenderOutput && <Output />}
304+
{this._renderHiddenRightColumnComponents()}
305+
{!this._shouldRenderLeftColumn() &&
306+
this._renderHiddenLanguages()}
307+
</div>
308+
</div>
265309
</div>
266-
</div>
267-
</div>
310+
</>
311+
}
312+
{this._isEverythingHidden() && this._renderEverythingHidden()}
268313
</div>
269314
);
270315
}
@@ -290,14 +335,16 @@ export default class Workspace extends React.Component {
290335

291336
Workspace.propTypes = {
292337
currentProject: PropTypes.object,
338+
hasErrors: PropTypes.bool.isRequired,
293339
hiddenLanguages: PropTypes.array.isRequired,
294340
isAnyTopBarMenuOpen: PropTypes.bool.isRequired,
295341
isDraggingColumnDivider: PropTypes.bool.isRequired,
296342
isEditingInstructions: PropTypes.bool.isRequired,
297343
isFlexResizingSupported: PropTypes.bool.isRequired,
298344
resizableFlexGrow: ImmutablePropTypes.list.isRequired,
299345
resizableFlexRefs: PropTypes.array.isRequired,
300-
shouldShowCollapsedConsole: PropTypes.bool.isRequired,
346+
shouldRenderOutput: PropTypes.bool.isRequired,
347+
title: PropTypes.string.isRequired,
301348
onApplicationLoaded: PropTypes.func.isRequired,
302349
onClickInstructionsEditButton: PropTypes.func.isRequired,
303350
onComponentToggle: PropTypes.func.isRequired,

src/containers/Preview.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import {
1010
consoleValueProduced,
1111
popOutProject,
1212
refreshPreview,
13+
toggleComponent,
1314
} from '../actions';
1415
import {
1516
getCompiledProjects,
1617
getConsoleHistory,
18+
getCurrentProjectKey,
19+
getCurrentProjectPreviewTitle,
20+
getHiddenUIComponents,
1721
isCurrentProjectSyntacticallyValid,
1822
isUserTyping,
1923
} from '../selectors';
@@ -22,10 +26,13 @@ function mapStateToProps(state) {
2226
return {
2327
compiledProjects: getCompiledProjects(state),
2428
consoleEntries: getConsoleHistory(state),
29+
currentProjectKey: getCurrentProjectKey(state),
30+
isOpen: !getHiddenUIComponents(state).includes('preview'),
2531
showingErrors: (
2632
!isUserTyping(state) &&
2733
!isCurrentProjectSyntacticallyValid(state)
2834
),
35+
title: getCurrentProjectPreviewTitle(state),
2936
};
3037
}
3138

@@ -74,6 +81,10 @@ function mapDispatchToProps(dispatch) {
7481
onRuntimeError(error) {
7582
dispatch(addRuntimeError('javascript', error));
7683
},
84+
85+
onToggleVisible(projectKey) {
86+
dispatch(toggleComponent(projectKey, 'preview'));
87+
},
7788
};
7889
}
7990

src/containers/Workspace.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import {connect} from 'react-redux';
2+
import every from 'lodash-es/every';
23

34
import Workspace from '../components/Workspace';
45
import {
56
getCurrentProject,
7+
isCurrentlyValidating,
68
isDraggingColumnDivider,
79
isEditingInstructions,
10+
getCurrentProjectPreviewTitle,
811
getHiddenAndVisibleLanguages,
12+
getHiddenUIComponents,
913
getOpenTopBarMenu,
1014
isCurrentProjectSyntacticallyValid,
15+
isUserTyping,
1116
} from '../selectors';
1217
import {
1318
toggleComponent,
@@ -17,16 +22,30 @@ import {
1722
startEditingInstructions,
1823
} from '../actions';
1924
import resizableFlex from '../higherOrderComponents/resizableFlex';
25+
import {RIGHT_COLUMN_COMPONENTS} from '../util/ui';
2026

2127
function mapStateToProps(state) {
2228
const {hiddenLanguages} = getHiddenAndVisibleLanguages(state);
29+
const isCurrentProjectValid = isCurrentProjectSyntacticallyValid(state);
30+
const isCurrentProjectValidating = isCurrentlyValidating(state);
31+
const hiddenUIComponents = getHiddenUIComponents(state);
32+
const areAllRightColumnComponentsCollapsed = every(
33+
RIGHT_COLUMN_COMPONENTS,
34+
component => hiddenUIComponents.includes(component),
35+
);
36+
const shouldRenderOutput = !areAllRightColumnComponentsCollapsed ||
37+
(!isCurrentProjectValid &&
38+
!isCurrentProjectValidating &&
39+
!isUserTyping(state));
2340
return {
2441
currentProject: getCurrentProject(state),
42+
hasErrors: !isCurrentProjectValid,
2543
isAnyTopBarMenuOpen: Boolean(getOpenTopBarMenu(state)),
2644
isDraggingColumnDivider: isDraggingColumnDivider(state),
2745
isEditingInstructions: isEditingInstructions(state),
2846
hiddenLanguages,
29-
shouldShowCollapsedConsole: isCurrentProjectSyntacticallyValid(state),
47+
shouldRenderOutput,
48+
title: getCurrentProjectPreviewTitle(state),
3049
};
3150
}
3251

src/css/application.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,8 @@ body {
761761
float: left;
762762
}
763763

764-
.preview__button_reset {
764+
.preview__button_reset,
765+
.preview__button_toggle-visibility {
765766
float: right;
766767
}
767768

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {createSelector} from 'reselect';
2+
import get from 'lodash-es/get';
3+
4+
import getCompiledProjects from './getCompiledProjects';
5+
6+
export default createSelector(
7+
[getCompiledProjects],
8+
(compiledProjects) => {
9+
const mostRecentCompiledProject = compiledProjects.last();
10+
const title = get(mostRecentCompiledProject, 'title', '');
11+
return title;
12+
},
13+
);

0 commit comments

Comments
 (0)