Skip to content

Commit fdf60aa

Browse files
authored
Merge pull request #1539 from ghalestrilo/feature/mobile-files-tab
Implement Mobile version of Files tab / sidebar
2 parents 127660a + 9ced702 commit fdf60aa

17 files changed

+274
-38
lines changed

client/common/icons.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import More from '../images/more.svg';
1616
import Code from '../images/code.svg';
1717
import Terminal from '../images/terminal.svg';
1818

19+
import Folder from '../images/folder-padded.svg';
20+
21+
import CircleTerminal from '../images/circle-terminal.svg';
22+
import CircleFolder from '../images/circle-folder.svg';
23+
import CircleInfo from '../images/circle-info.svg';
24+
1925

2026
// HOC that adds the right web accessibility props
2127
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
@@ -81,3 +87,9 @@ export const PlayIcon = withLabel(Play);
8187
export const MoreIcon = withLabel(More);
8288
export const TerminalIcon = withLabel(Terminal);
8389
export const CodeIcon = withLabel(Code);
90+
91+
export const FolderIcon = withLabel(Folder);
92+
93+
export const CircleTerminalIcon = withLabel(CircleTerminal);
94+
export const CircleFolderIcon = withLabel(CircleFolder);
95+
export const CircleInfoIcon = withLabel(CircleInfo);

client/components/Dropdown.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const DropdownWrapper = styled.ul`
2525
display: flex;
2626
flex-direction: column;
2727
height: auto;
28-
z-index: 9999;
28+
z-index: 2;
2929
border-radius: ${remSize(6)};
3030
3131
& li:first-child { border-radius: ${remSize(5)} ${remSize(5)} 0 0; }
Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,66 @@
11
import React from 'react';
22
import styled from 'styled-components';
3+
import PropTypes from 'prop-types';
34
import { bindActionCreators } from 'redux';
45
import { useDispatch, useSelector } from 'react-redux';
5-
import { remSize } from '../../theme';
6+
import { remSize, prop } from '../../theme';
67
import IconButton from './IconButton';
7-
import { TerminalIcon } from '../../common/icons';
8+
import { TerminalIcon, FolderIcon } from '../../common/icons';
89
import * as IDEActions from '../../modules/IDE/actions/ide';
910

10-
const BottomBarContent = styled.h2`
11+
const BottomBarContent = styled.div`
1112
padding: ${remSize(8)};
12-
13+
display: flex;
14+
1315
svg {
1416
max-height: ${remSize(32)};
17+
18+
}
19+
20+
path { fill: ${prop('primaryTextColor')} !important }
21+
22+
.inverted {
23+
path { fill: ${prop('backgroundColor')} !important }
24+
rect { fill: ${prop('primaryTextColor')} !important }
1525
}
1626
`;
1727

18-
export default () => {
28+
// Maybe this component shouldn't be connected, and instead just receive the `actions` prop
29+
const ActionStrip = ({ toggleExplorer }) => {
1930
const { expandConsole, collapseConsole } = bindActionCreators(IDEActions, useDispatch());
2031
const { consoleIsExpanded } = useSelector(state => state.ide);
2132

22-
const actions = [{ icon: TerminalIcon, aria: 'Say Something', action: consoleIsExpanded ? collapseConsole : expandConsole }];
33+
const actions = [
34+
{
35+
icon: TerminalIcon, inverted: true, aria: 'Open terminal console', action: consoleIsExpanded ? collapseConsole : expandConsole
36+
},
37+
{ icon: FolderIcon, aria: 'Open files explorer', action: toggleExplorer }
38+
];
2339

2440
return (
2541
<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-
/>))}
42+
{actions.map(({
43+
icon, aria, action, inverted
44+
}) =>
45+
(
46+
<IconButton
47+
inverted={inverted}
48+
className={inverted && 'inverted'}
49+
icon={icon}
50+
aria-label={aria}
51+
key={`bottom-bar-${aria}`}
52+
onClick={() => action()}
53+
/>))}
3354
</BottomBarContent>
3455
);
3556
};
57+
58+
ActionStrip.propTypes = {
59+
toggleExplorer: PropTypes.func
60+
};
61+
62+
ActionStrip.defaultProps = {
63+
toggleExplorer: () => {}
64+
};
65+
66+
export default ActionStrip;

client/components/mobile/Explorer.jsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import PropTypes from 'prop-types';
4+
import Sidebar from './Sidebar';
5+
import ConnectedFileNode from '../../modules/IDE/components/FileNode';
6+
7+
8+
const Explorer = ({ id, canEdit, onPressClose }) => (
9+
<Sidebar title="Files" onPressClose={onPressClose}>
10+
<ConnectedFileNode id={id} canEdit={canEdit} onClickFile={() => onPressClose()} />
11+
</Sidebar>
12+
);
13+
14+
Explorer.propTypes = {
15+
id: PropTypes.number.isRequired,
16+
onPressClose: PropTypes.func,
17+
canEdit: PropTypes.bool
18+
};
19+
Explorer.defaultProps = {
20+
canEdit: false,
21+
onPressClose: () => {}
22+
};
23+
24+
export default Explorer;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import styled from 'styled-components';
4+
import { remSize, prop } from '../../theme';
5+
import Button from '../../common/Button';
6+
import IconButton from './IconButton';
7+
8+
const FloatingContainer = styled.div`
9+
position: fixed;
10+
right: ${remSize(16)};
11+
top: ${remSize(80)};
12+
13+
text-align: right;
14+
z-index: 3;
15+
16+
svg { width: ${remSize(32)}; };
17+
svg > path { fill: ${prop('Button.default.background')} !important };
18+
`;
19+
20+
const FloatingNav = ({ items }) => (
21+
<FloatingContainer>
22+
{ items.map(({ icon, onPress }) =>
23+
(
24+
<IconButton
25+
onClick={onPress}
26+
icon={icon}
27+
/>
28+
))}
29+
</FloatingContainer>
30+
);
31+
32+
FloatingNav.propTypes = {
33+
items: PropTypes.arrayOf(PropTypes.shape({
34+
icon: PropTypes.element,
35+
onPress: PropTypes.func
36+
}))
37+
};
38+
39+
FloatingNav.defaultProps = {
40+
items: []
41+
};
42+
43+
export default FloatingNav;

client/components/mobile/Header.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const textColor = ({ transparent, inverted }) => prop((transparent === false &&
1414

1515

1616
const HeaderDiv = styled.div`
17-
position: fixed;
17+
${props => props.fixed && 'position: fixed;'}
1818
width: 100%;
1919
background: ${props => background(props)};
2020
color: ${textColor};
@@ -57,9 +57,9 @@ const TitleContainer = styled.div`
5757

5858
const Header = ({
5959
title, subtitle, leftButton, children,
60-
transparent, inverted, slim
60+
transparent, inverted, slim, fixed
6161
}) => (
62-
<HeaderDiv transparent={transparent} slim={slim} inverted={inverted}>
62+
<HeaderDiv transparent={transparent} slim={slim} inverted={inverted} fixed={fixed}>
6363
{leftButton}
6464
<TitleContainer padded={subtitle === null}>
6565
{title && <h2>{title}</h2>}
@@ -79,6 +79,7 @@ Header.propTypes = {
7979
transparent: PropTypes.bool,
8080
inverted: PropTypes.bool,
8181
slim: PropTypes.bool,
82+
fixed: PropTypes.bool,
8283
};
8384

8485
Header.defaultProps = {
@@ -88,7 +89,8 @@ Header.defaultProps = {
8889
children: [],
8990
transparent: false,
9091
inverted: false,
91-
slim: false
92+
slim: false,
93+
fixed: true
9294
};
9395

9496
export default Header;

client/components/mobile/IDEWrapper.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import React from 'react';
22
import styled from 'styled-components';
33
import { remSize } from '../../theme';
44

5+
// Applies padding to top and bottom so editor content is always visible
6+
57
export default styled.div`
68
z-index: 0;
79
margin-top: ${remSize(16)};
10+
.CodeMirror-sizer > * { padding-bottom: ${remSize(320)}; };
811
`;

client/components/mobile/Sidebar.jsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Link } from 'react-router';
4+
import styled from 'styled-components';
5+
import { remSize, prop, common } from '../../theme';
6+
import Header from './Header';
7+
import IconButton from './IconButton';
8+
import { ExitIcon } from '../../common/icons';
9+
10+
11+
const SidebarWrapper = styled.div`
12+
height: 100%;
13+
width: ${remSize(180)};
14+
15+
position: fixed;
16+
z-index: 2;
17+
left: 0;
18+
19+
background: white;
20+
box-shadow: 0 6px 6px 0 rgba(0,0,0,0.10);
21+
`;
22+
23+
const Sidebar = ({ title, onPressClose, children }) => (
24+
<SidebarWrapper>
25+
{title &&
26+
<Header slim title={title} fixed={false}>
27+
<IconButton onClick={onPressClose} icon={ExitIcon} aria-label="Return to ide view" />
28+
</Header>}
29+
{children}
30+
</SidebarWrapper>
31+
);
32+
33+
Sidebar.propTypes = {
34+
title: PropTypes.string,
35+
onPressClose: PropTypes.func,
36+
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
37+
};
38+
39+
Sidebar.defaultProps = {
40+
title: null,
41+
children: [],
42+
onPressClose: () => {}
43+
};
44+
45+
46+
export default Sidebar;

client/components/useAsModal.jsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
import React from 'react';
2+
import styled from 'styled-components';
23
import { useModalBehavior } from '../utils/custom-hooks';
34

4-
export default (component) => {
5-
const [visible, trigger, setRef] = useModalBehavior();
5+
const BackgroundOverlay = styled.div`
6+
position: fixed;
7+
z-index: 2;
8+
width: 100% !important;
9+
height: 100% !important;
10+
11+
background: black;
12+
opacity: 0.3;
13+
`;
614

7-
const wrapper = () => <div ref={setRef}> {visible && component} </div>; // eslint-disable-line
15+
export default (Element, hasOverlay = false) => {
16+
const [visible, toggle, setRef] = useModalBehavior();
817

9-
return [trigger, wrapper];
18+
const wrapper = () => (visible &&
19+
<div>
20+
{hasOverlay && <BackgroundOverlay />}
21+
<div ref={setRef}>
22+
{ (typeof (Element) === 'function')
23+
? Element(toggle)
24+
: Element}
25+
</div>
26+
</div>);
27+
28+
return [toggle, wrapper];
1029
};

client/images/circle-folder.svg

Lines changed: 5 additions & 0 deletions
Loading

client/images/circle-info.svg

Lines changed: 4 additions & 0 deletions
Loading

client/images/circle-terminal.svg

Lines changed: 6 additions & 0 deletions
Loading

client/images/folder-padded.svg

Lines changed: 4 additions & 0 deletions
Loading

client/modules/App/App.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ class App extends React.Component {
1919

2020
componentWillReceiveProps(nextProps) {
2121
const locationWillChange = nextProps.location !== this.props.location;
22-
const shouldSkipRemembering = nextProps.location.state && nextProps.location.state.skipSavingPath === true;
22+
const shouldSkipRemembering =
23+
nextProps.location.state &&
24+
nextProps.location.state.skipSavingPath === true;
2325

2426
if (locationWillChange && !shouldSkipRemembering) {
2527
this.props.setPreviousPath(this.props.location.pathname);

client/modules/IDE/components/FileNode.jsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,15 @@ export class FileNode extends React.Component {
108108
handleFileClick = (event) => {
109109
event.stopPropagation();
110110
const { isDeleting } = this.state;
111-
const { id, setSelectedFile, name } = this.props;
111+
const {
112+
id, setSelectedFile, name, onClickFile
113+
} = this.props;
112114
if (name !== 'root' && !isDeleting) {
113115
setSelectedFile(id);
114116
}
117+
118+
// debugger; // eslint-disable-line
119+
if (onClickFile) { onClickFile(); }
115120
}
116121

117122
handleFileNameChange = (event) => {
@@ -214,7 +219,7 @@ export class FileNode extends React.Component {
214219

215220
renderChild = childId => (
216221
<li key={childId}>
217-
<ConnectedFileNode id={childId} parentId={this.props.id} canEdit={this.props.canEdit} />
222+
<ConnectedFileNode id={childId} parentId={this.props.id} canEdit={this.props.canEdit} onClickFile={this.props.onClickFile} />
218223
</li>
219224
)
220225

@@ -233,7 +238,7 @@ export class FileNode extends React.Component {
233238
const isRoot = this.props.name === 'root';
234239

235240
return (
236-
<div className={itemClass}>
241+
<div className={itemClass} >
237242
{ !isRoot &&
238243
<div className="file-item__content" onContextMenu={this.toggleFileOptions}>
239244
<span className="file-item__spacer"></span>
@@ -382,10 +387,12 @@ FileNode.propTypes = {
382387
hideFolderChildren: PropTypes.func.isRequired,
383388
canEdit: PropTypes.bool.isRequired,
384389
openUploadFileModal: PropTypes.func.isRequired,
385-
authenticated: PropTypes.bool.isRequired
390+
authenticated: PropTypes.bool.isRequired,
391+
onClickFile: PropTypes.func
386392
};
387393

388394
FileNode.defaultProps = {
395+
onClickFile: null,
389396
parentId: '0',
390397
isSelectedFile: false,
391398
isFolderClosed: false,

0 commit comments

Comments
 (0)