Skip to content

Commit 7dbcde4

Browse files
authored
Merge pull request #1507 from ghalestrilo/chore/refactor-mobile-hooks
Refactor mobile components to react hooks
2 parents ee02a3e + ccbe96d commit 7dbcde4

File tree

14 files changed

+248
-445
lines changed

14 files changed

+248
-445
lines changed

client/common/icons.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import Exit from '../images/exit.svg';
1212
import DropdownArrow from '../images/down-filled-triangle.svg';
1313
import Preferences from '../images/preferences.svg';
1414
import Play from '../images/triangle-arrow-right.svg';
15+
import Code from '../images/code.svg';
16+
import Terminal from '../images/terminal.svg';
17+
1518

1619
// HOC that adds the right web accessibility props
1720
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
@@ -74,3 +77,4 @@ export const ExitIcon = withLabel(Exit);
7477
export const DropdownArrowIcon = withLabel(DropdownArrow);
7578
export const PreferencesIcon = withLabel(Preferences);
7679
export const PlayIcon = withLabel(Play);
80+
export const TerminalIcon = withLabel(Terminal);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import { bindActionCreators } from 'redux';
4+
import { useDispatch, useSelector } from 'react-redux';
5+
import { remSize } from '../../theme';
6+
import IconButton from './IconButton';
7+
import { TerminalIcon } from '../../common/icons';
8+
import * as IDEActions from '../../modules/IDE/actions/ide';
9+
10+
const BottomBarContent = styled.h2`
11+
padding: ${remSize(8)};
12+
13+
svg {
14+
max-height: ${remSize(32)};
15+
}
16+
`;
17+
18+
export default () => {
19+
const { expandConsole, collapseConsole } = bindActionCreators(IDEActions, useDispatch());
20+
const { consoleIsExpanded } = useSelector(state => state.ide);
21+
22+
const actions = [{ icon: TerminalIcon, aria: 'Say Something', action: consoleIsExpanded ? collapseConsole : expandConsole }];
23+
24+
return (
25+
<BottomBarContent>
26+
{actions.map(({ icon, aria, action }) =>
27+
(<IconButton
28+
icon={icon}
29+
aria-label={aria}
30+
key={`bottom-bar-${aria}`}
31+
onClick={() => action()}
32+
/>))}
33+
</BottomBarContent>
34+
);
35+
};

client/components/mobile/Footer.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import styled from 'styled-components';
3-
import { prop } from '../../theme';
3+
import { prop, grays } from '../../theme';
44

55

66
const background = prop('MobilePanel.default.background');
@@ -12,4 +12,6 @@ export default styled.div`
1212
bottom: 0;
1313
background: ${background};
1414
color: ${textColor};
15+
16+
& > * + * { border-top: dashed 1px ${prop('Separator')} }
1517
`;

client/components/mobile/Header.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import styled from 'styled-components';
33
import PropTypes from 'prop-types';
44
import { prop, remSize } from '../../theme';
55

6-
const background = prop('MobilePanel.default.background');
6+
const background = transparent => prop(transparent ? 'backgroundColor' : 'MobilePanel.default.background');
77
const textColor = prop('primaryTextColor');
88

99

1010
const HeaderDiv = styled.div`
1111
position: fixed;
1212
width: 100%;
13-
background: ${props => (props.transparent ? 'transparent' : background)};
13+
background: ${props => background(props.transparent === true)};
1414
color: ${textColor};
1515
padding: ${remSize(12)};
1616
padding-left: ${remSize(16)};
@@ -23,7 +23,6 @@ const HeaderDiv = styled.div`
2323
justify-content: flex-start;
2424
align-items: center;
2525
26-
// TODO:
2726
svg {
2827
max-height: ${remSize(32)};
2928
padding: ${remSize(4)}

client/images/terminal.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 103 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import PropTypes from 'prop-types';
2-
import React from 'react';
3-
import { useSelector, useDispatch, connect } from 'react-redux';
1+
import React, { useRef } from 'react';
2+
3+
import { bindActionCreators } from 'redux';
4+
5+
import { useSelector, useDispatch } from 'react-redux';
46
import classNames from 'classnames';
57
import { Console as ConsoleFeed } from 'console-feed';
68
import {
@@ -25,172 +27,118 @@ import DownArrowIcon from '../../../images/down-arrow.svg';
2527

2628
import * as IDEActions from '../../IDE/actions/ide';
2729
import * as ConsoleActions from '../../IDE/actions/console';
30+
import { useDidUpdate } from '../../../utils/custom-hooks';
2831

29-
class ConsoleComponent extends React.Component {
30-
componentDidUpdate(prevProps) {
31-
this.consoleMessages.scrollTop = this.consoleMessages.scrollHeight;
32-
if (this.props.theme !== prevProps.theme) {
33-
this.props.clearConsole();
34-
this.props.dispatchConsoleEvent(this.props.consoleEvents);
35-
}
36-
37-
if (this.props.fontSize !== prevProps.fontSize) {
38-
this.props.clearConsole();
39-
this.props.dispatchConsoleEvent(this.props.consoleEvents);
40-
}
41-
}
42-
43-
getConsoleFeedStyle(theme, times) {
44-
const style = {};
45-
const CONSOLE_FEED_LIGHT_ICONS = {
46-
LOG_WARN_ICON: `url(${warnLightUrl})`,
47-
LOG_ERROR_ICON: `url(${errorLightUrl})`,
48-
LOG_DEBUG_ICON: `url(${debugLightUrl})`,
49-
LOG_INFO_ICON: `url(${infoLightUrl})`
50-
};
51-
const CONSOLE_FEED_DARK_ICONS = {
52-
LOG_WARN_ICON: `url(${warnDarkUrl})`,
53-
LOG_ERROR_ICON: `url(${errorDarkUrl})`,
54-
LOG_DEBUG_ICON: `url(${debugDarkUrl})`,
55-
LOG_INFO_ICON: `url(${infoDarkUrl})`
56-
};
57-
const CONSOLE_FEED_CONTRAST_ICONS = {
58-
LOG_WARN_ICON: `url(${warnContrastUrl})`,
59-
LOG_ERROR_ICON: `url(${errorContrastUrl})`,
60-
LOG_DEBUG_ICON: `url(${debugContrastUrl})`,
61-
LOG_INFO_ICON: `url(${infoContrastUrl})`
62-
};
63-
const CONSOLE_FEED_SIZES = {
64-
TREENODE_LINE_HEIGHT: 1.2,
65-
BASE_FONT_SIZE: this.props.fontSize,
66-
ARROW_FONT_SIZE: this.props.fontSize,
67-
LOG_ICON_WIDTH: this.props.fontSize,
68-
LOG_ICON_HEIGHT: 1.45 * this.props.fontSize,
69-
};
32+
const getConsoleFeedStyle = (theme, times, fontSize) => {
33+
const style = {};
34+
const CONSOLE_FEED_LIGHT_ICONS = {
35+
LOG_WARN_ICON: `url(${warnLightUrl})`,
36+
LOG_ERROR_ICON: `url(${errorLightUrl})`,
37+
LOG_DEBUG_ICON: `url(${debugLightUrl})`,
38+
LOG_INFO_ICON: `url(${infoLightUrl})`
39+
};
40+
const CONSOLE_FEED_DARK_ICONS = {
41+
LOG_WARN_ICON: `url(${warnDarkUrl})`,
42+
LOG_ERROR_ICON: `url(${errorDarkUrl})`,
43+
LOG_DEBUG_ICON: `url(${debugDarkUrl})`,
44+
LOG_INFO_ICON: `url(${infoDarkUrl})`
45+
};
46+
const CONSOLE_FEED_CONTRAST_ICONS = {
47+
LOG_WARN_ICON: `url(${warnContrastUrl})`,
48+
LOG_ERROR_ICON: `url(${errorContrastUrl})`,
49+
LOG_DEBUG_ICON: `url(${debugContrastUrl})`,
50+
LOG_INFO_ICON: `url(${infoContrastUrl})`
51+
};
52+
const CONSOLE_FEED_SIZES = {
53+
TREENODE_LINE_HEIGHT: 1.2,
54+
BASE_FONT_SIZE: fontSize,
55+
ARROW_FONT_SIZE: fontSize,
56+
LOG_ICON_WIDTH: fontSize,
57+
LOG_ICON_HEIGHT: 1.45 * fontSize,
58+
};
7059

71-
if (times > 1) {
72-
Object.assign(style, CONSOLE_FEED_WITHOUT_ICONS);
73-
}
74-
switch (theme) {
75-
case 'light':
76-
return Object.assign(CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_LIGHT_ICONS, CONSOLE_FEED_SIZES, style);
77-
case 'dark':
78-
return Object.assign(CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_DARK_ICONS, CONSOLE_FEED_SIZES, style);
79-
case 'contrast':
80-
return Object.assign(CONSOLE_FEED_CONTRAST_STYLES, CONSOLE_FEED_CONTRAST_ICONS, CONSOLE_FEED_SIZES, style);
81-
default:
82-
return '';
83-
}
60+
if (times > 1) {
61+
Object.assign(style, CONSOLE_FEED_WITHOUT_ICONS);
8462
}
85-
86-
render() {
87-
const consoleClass = classNames({
88-
'preview-console': true,
89-
'preview-console--collapsed': !this.props.isExpanded
90-
});
91-
92-
return (
93-
<section className={consoleClass} >
94-
<header className="preview-console__header">
95-
<h2 className="preview-console__header-title">Console</h2>
96-
<div className="preview-console__header-buttons">
97-
<button className="preview-console__clear" onClick={this.props.clearConsole} aria-label="Clear console">
98-
Clear
99-
</button>
100-
<button
101-
className="preview-console__collapse"
102-
onClick={this.props.collapseConsole}
103-
aria-label="Close console"
104-
>
105-
<DownArrowIcon focusable="false" aria-hidden="true" />
106-
</button>
107-
<button className="preview-console__expand" onClick={this.props.expandConsole} aria-label="Open console" >
108-
<UpArrowIcon focusable="false" aria-hidden="true" />
109-
</button>
110-
</div>
111-
</header>
112-
<div ref={(element) => { this.consoleMessages = element; }} className="preview-console__messages">
113-
{this.props.consoleEvents.map((consoleEvent) => {
114-
const { method, times } = consoleEvent;
115-
const { theme } = this.props;
116-
return (
117-
<div key={consoleEvent.id} className={`preview-console__message preview-console__message--${method}`}>
118-
{ times > 1 &&
119-
<div
120-
className="preview-console__logged-times"
121-
style={{ fontSize: this.props.fontSize, borderRadius: this.props.fontSize / 2 }}
122-
>
123-
{times}
124-
</div>
125-
}
126-
<ConsoleFeed
127-
styles={this.getConsoleFeedStyle(theme, times)}
128-
logs={[consoleEvent]}
129-
/>
130-
</div>
131-
);
132-
})}
133-
</div>
134-
</section>
135-
);
63+
switch (theme) {
64+
case 'light':
65+
return Object.assign(CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_LIGHT_ICONS, CONSOLE_FEED_SIZES, style);
66+
case 'dark':
67+
return Object.assign(CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_DARK_ICONS, CONSOLE_FEED_SIZES, style);
68+
case 'contrast':
69+
return Object.assign(CONSOLE_FEED_CONTRAST_STYLES, CONSOLE_FEED_CONTRAST_ICONS, CONSOLE_FEED_SIZES, style);
70+
default:
71+
return '';
13672
}
137-
}
138-
139-
ConsoleComponent.propTypes = {
140-
consoleEvents: PropTypes.arrayOf(PropTypes.shape({
141-
method: PropTypes.string.isRequired,
142-
args: PropTypes.arrayOf(PropTypes.string)
143-
})),
144-
isExpanded: PropTypes.bool.isRequired,
145-
collapseConsole: PropTypes.func.isRequired,
146-
expandConsole: PropTypes.func.isRequired,
147-
clearConsole: PropTypes.func.isRequired,
148-
dispatchConsoleEvent: PropTypes.func.isRequired,
149-
theme: PropTypes.string.isRequired,
150-
fontSize: PropTypes.number.isRequired
151-
};
152-
153-
ConsoleComponent.defaultProps = {
154-
consoleEvents: []
15573
};
15674

157-
// TODO: Use Hooks implementation. Requires react-redux 7.1.0
158-
/*
15975
const Console = () => {
16076
const consoleEvents = useSelector(state => state.console);
161-
const { consoleIsExpanded } = useSelector(state => state.ide);
77+
const isExpanded = useSelector(state => state.ide.consoleIsExpanded);
16278
const { theme, fontSize } = useSelector(state => state.preferences);
16379

164-
const dispatch = useDispatch();
80+
const {
81+
collapseConsole, expandConsole, clearConsole, dispatchConsoleEvent
82+
} = bindActionCreators({ ...IDEActions, ...ConsoleActions }, useDispatch());
83+
84+
useDidUpdate(() => {
85+
clearConsole();
86+
dispatchConsoleEvent(consoleEvents);
87+
}, [theme, fontSize]);
88+
89+
const cm = useRef({});
90+
91+
useDidUpdate(() => { cm.current.scrollTop = cm.current.scrollHeight; });
92+
93+
const consoleClass = classNames({
94+
'preview-console': true,
95+
'preview-console--collapsed': !isExpanded
96+
});
16597

16698
return (
167-
<ConsoleComponent
168-
consoleEvents={consoleEvents}
169-
isExpanded={consoleIsExpanded}
170-
theme={theme}
171-
fontSize={fontSize}
172-
collapseConsole={() => dispatch({})}
173-
expandConsole={() => dispatch({})}
174-
clearConsole={() => dispatch({})}
175-
dispatchConsoleEvent={() => dispatch({})}
176-
/>
99+
<section className={consoleClass} >
100+
<header className="preview-console__header">
101+
<h2 className="preview-console__header-title">Console</h2>
102+
<div className="preview-console__header-buttons">
103+
<button className="preview-console__clear" onClick={clearConsole} aria-label="Clear console">
104+
Clear
105+
</button>
106+
<button
107+
className="preview-console__collapse"
108+
onClick={collapseConsole}
109+
aria-label="Close console"
110+
>
111+
<DownArrowIcon focusable="false" aria-hidden="true" />
112+
</button>
113+
<button className="preview-console__expand" onClick={expandConsole} aria-label="Open console" >
114+
<UpArrowIcon focusable="false" aria-hidden="true" />
115+
</button>
116+
</div>
117+
</header>
118+
<div ref={cm} className="preview-console__messages">
119+
{consoleEvents.map((consoleEvent) => {
120+
const { method, times } = consoleEvent;
121+
return (
122+
<div key={consoleEvent.id} className={`preview-console__message preview-console__message--${method}`}>
123+
{ times > 1 &&
124+
<div
125+
className="preview-console__logged-times"
126+
style={{ fontSize, borderRadius: fontSize / 2 }}
127+
>
128+
{times}
129+
</div>
130+
}
131+
<ConsoleFeed
132+
styles={getConsoleFeedStyle(theme, times, fontSize)}
133+
logs={[consoleEvent]}
134+
/>
135+
</div>
136+
);
137+
})}
138+
</div>
139+
</section>
177140
);
178141
};
179-
*/
180142

181-
const Console = connect(
182-
state => ({
183-
consoleEvents: state.console,
184-
isExpanded: state.ide.consoleIsExpanded,
185-
theme: state.preferences.theme,
186-
fontSize: state.preferences.fontSize
187-
}),
188-
dispatch => ({
189-
collapseConsole: () => dispatch(IDEActions.collapseConsole()),
190-
expandConsole: () => dispatch(IDEActions.expandConsole()),
191-
clearConsole: () => dispatch(ConsoleActions.clearConsole()),
192-
dispatchConsoleEvent: msgs => dispatch(ConsoleActions.dispatchConsoleEvent(msgs)),
193-
})
194-
)(ConsoleComponent);
195143

196144
export default Console;

0 commit comments

Comments
 (0)