🔀 merge from feature/mobile-header-dropdown-menu
This commit is contained in:
commit
c019fbcf4c
8 changed files with 85 additions and 80 deletions
|
@ -13,8 +13,9 @@ const DropdownWrapper = styled.ul`
|
||||||
color: ${prop('primaryTextColor')};
|
color: ${prop('primaryTextColor')};
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: ${remSize(64)};
|
right: ${props => (props.right ? 0 : 'initial')};
|
||||||
right: ${remSize(16)};
|
left: ${props => (props.left ? 0 : 'initial')};
|
||||||
|
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: ${remSize(180)};
|
width: ${remSize(180)};
|
||||||
|
@ -56,8 +57,8 @@ const DropdownWrapper = styled.ul`
|
||||||
// TODO: Add Icon to the left of the items in the menu
|
// TODO: Add Icon to the left of the items in the menu
|
||||||
// const MaybeIcon = (Element, label) => Element && <Element aria-label={label} />;
|
// const MaybeIcon = (Element, label) => Element && <Element aria-label={label} />;
|
||||||
|
|
||||||
const Dropdown = ({ items }) => (
|
const Dropdown = ({ items, right, left }) => (
|
||||||
<DropdownWrapper>
|
<DropdownWrapper right={right} left={left}>
|
||||||
{/* className="nav__items-left" */}
|
{/* className="nav__items-left" */}
|
||||||
{items && items.map(({ title, icon, href }) => (
|
{items && items.map(({ title, icon, href }) => (
|
||||||
<li key={`nav-${title && title.toLowerCase()}`}>
|
<li key={`nav-${title && title.toLowerCase()}`}>
|
||||||
|
@ -72,6 +73,8 @@ const Dropdown = ({ items }) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
Dropdown.propTypes = {
|
Dropdown.propTypes = {
|
||||||
|
right: PropTypes.bool,
|
||||||
|
left: PropTypes.bool,
|
||||||
items: PropTypes.arrayOf(PropTypes.shape({
|
items: PropTypes.arrayOf(PropTypes.shape({
|
||||||
action: PropTypes.func,
|
action: PropTypes.func,
|
||||||
icon: PropTypes.func,
|
icon: PropTypes.func,
|
||||||
|
@ -81,6 +84,8 @@ Dropdown.propTypes = {
|
||||||
|
|
||||||
Dropdown.defaultProps = {
|
Dropdown.defaultProps = {
|
||||||
items: [],
|
items: [],
|
||||||
|
right: false,
|
||||||
|
left: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dropdown;
|
export default Dropdown;
|
||||||
|
|
|
@ -1,45 +1,15 @@
|
||||||
import React, { useRef, useEffect } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import Dropdown from './Dropdown';
|
|
||||||
|
|
||||||
import { PreferencesIcon } from '../common/icons';
|
|
||||||
|
|
||||||
const OverlayManager = ({ overlay, hideOverlay }) => {
|
const OverlayManager = ({ overlay, hideOverlay }) => {
|
||||||
const ref = useRef({});
|
// const [visible, trigger, setRef] = useModalBehavior();
|
||||||
|
|
||||||
const handleClickOutside = ({ target }) => {
|
|
||||||
if (ref && ref.current && !ref.current.contains(target)) {
|
|
||||||
hideOverlay();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
|
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
}, [ref]);
|
|
||||||
|
|
||||||
const headerNavOptions = [
|
|
||||||
{
|
|
||||||
icon: PreferencesIcon,
|
|
||||||
title: 'Preferences',
|
|
||||||
href: '/mobile/preferences',
|
|
||||||
},
|
|
||||||
{ icon: PreferencesIcon, title: 'Examples', href: '/mobile/examples' },
|
|
||||||
{
|
|
||||||
icon: PreferencesIcon,
|
|
||||||
title: 'Original Editor',
|
|
||||||
href: '/mobile/preferences',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const jsx = (
|
const jsx = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div ref={(r) => { ref.current = r; }} >
|
{/* <div ref={setRef} >
|
||||||
{overlay === 'dropdown' && <Dropdown items={headerNavOptions} />}
|
{visible && <Dropdown items={headerNavOptions} />}
|
||||||
</div>
|
</div> */}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,9 @@ const HeaderDiv = styled.div`
|
||||||
|
|
||||||
const IconContainer = styled.div`
|
const IconContainer = styled.div`
|
||||||
margin-left: ${props => (props.leftButton ? remSize(32) : remSize(4))};
|
margin-left: ${props => (props.leftButton ? remSize(32) : remSize(4))};
|
||||||
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: horizontal;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,20 +23,13 @@ class Searchbar extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchEnter = (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
this.searchChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
searchChange = () => {
|
searchChange = () => {
|
||||||
if (this.state.searchValue.trim().length === 0) return;
|
|
||||||
this.props.setSearchTerm(this.state.searchValue.trim());
|
this.props.setSearchTerm(this.state.searchValue.trim());
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSearchChange = (e) => {
|
handleSearchChange = (e) => {
|
||||||
this.setState({ searchValue: e.target.value }, () => {
|
this.setState({ searchValue: e.target.value }, () => {
|
||||||
this.throttledSearchChange(this.state.searchValue);
|
this.throttledSearchChange(this.state.searchValue.trim());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +46,6 @@ class Searchbar extends React.Component {
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
placeholder={this.props.searchLabel}
|
placeholder={this.props.searchLabel}
|
||||||
onChange={this.handleSearchChange}
|
onChange={this.handleSearchChange}
|
||||||
onKeyUp={this.handleSearchEnter}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="searchbar__clear-button"
|
className="searchbar__clear-button"
|
||||||
|
|
|
@ -29,8 +29,11 @@ import Footer from '../../../components/mobile/Footer';
|
||||||
import IDEWrapper from '../../../components/mobile/IDEWrapper';
|
import IDEWrapper from '../../../components/mobile/IDEWrapper';
|
||||||
import Console from '../components/Console';
|
import Console from '../components/Console';
|
||||||
import { remSize } from '../../../theme';
|
import { remSize } from '../../../theme';
|
||||||
import OverlayManager from '../../../components/OverlayManager';
|
// import OverlayManager from '../../../components/OverlayManager';
|
||||||
import ActionStrip from '../../../components/mobile/ActionStrip';
|
import ActionStrip from '../../../components/mobile/ActionStrip';
|
||||||
|
import { useAsModal } from '../../../utils/custom-hooks';
|
||||||
|
import { PreferencesIcon } from '../../../common/icons';
|
||||||
|
import Dropdown from '../../../components/Dropdown';
|
||||||
|
|
||||||
const isUserOwner = ({ project, user }) =>
|
const isUserOwner = ({ project, user }) =>
|
||||||
project.owner && project.owner.id === user.id;
|
project.owner && project.owner.id === user.id;
|
||||||
|
@ -39,6 +42,16 @@ const Expander = styled.div`
|
||||||
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
|
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const NavItem = styled.li`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const headerNavOptions = [
|
||||||
|
{ icon: PreferencesIcon, title: 'Preferences', href: '/mobile/preferences', },
|
||||||
|
{ icon: PreferencesIcon, title: 'Examples', href: '/mobile/examples' },
|
||||||
|
{ icon: PreferencesIcon, title: 'Original Editor', href: '/', },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
const MobileIDEView = (props) => {
|
const MobileIDEView = (props) => {
|
||||||
const {
|
const {
|
||||||
|
@ -67,11 +80,9 @@ const MobileIDEView = (props) => {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [tmController, setTmController] = useState(null); // eslint-disable-line
|
const [tmController, setTmController] = useState(null); // eslint-disable-line
|
||||||
const [overlayName, setOverlay] = useState(null); // eslint-disable-line
|
|
||||||
|
|
||||||
// TODO: Move this to OverlayController (?)
|
|
||||||
const hideOverlay = () => setOverlay(null);
|
const [triggerNavDropdown, NavDropDown] = useAsModal(<Dropdown right items={headerNavOptions} />);
|
||||||
// const overlayRef = useRef({});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen fullscreen>
|
<Screen fullscreen>
|
||||||
|
@ -86,19 +97,17 @@ const MobileIDEView = (props) => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IconButton
|
<NavItem>
|
||||||
onClick={() => setOverlay('dropdown')}
|
<IconButton
|
||||||
icon={MoreIcon}
|
onClick={triggerNavDropdown}
|
||||||
aria-label="Options"
|
icon={MoreIcon}
|
||||||
/>
|
aria-label="Options"
|
||||||
<IconButton
|
/>
|
||||||
to="/mobile/preview"
|
<NavDropDown />
|
||||||
onClick={() => {
|
</NavItem>
|
||||||
startSketch();
|
<li>
|
||||||
}}
|
<IconButton to="/mobile/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
|
||||||
icon={PlayIcon}
|
</li>
|
||||||
aria-label="Run sketch"
|
|
||||||
/>
|
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<IDEWrapper>
|
<IDEWrapper>
|
||||||
|
@ -116,9 +125,7 @@ const MobileIDEView = (props) => {
|
||||||
editorOptionsVisible={ide.editorOptionsVisible}
|
editorOptionsVisible={ide.editorOptionsVisible}
|
||||||
showEditorOptions={showEditorOptions}
|
showEditorOptions={showEditorOptions}
|
||||||
closeEditorOptions={closeEditorOptions}
|
closeEditorOptions={closeEditorOptions}
|
||||||
showKeyboardShortcutModal={showKeyboardShortcutModal}
|
showKeyboard={ide.isPlaying}
|
||||||
setUnsavedChanges={setUnsavedChanges}
|
|
||||||
isPlaying={ide.isPlaying}
|
|
||||||
theme={preferences.theme}
|
theme={preferences.theme}
|
||||||
startRefreshSketch={startRefreshSketch}
|
startRefreshSketch={startRefreshSketch}
|
||||||
stopSketch={stopSketch}
|
stopSketch={stopSketch}
|
||||||
|
@ -146,12 +153,6 @@ const MobileIDEView = (props) => {
|
||||||
)}
|
)}
|
||||||
<ActionStrip />
|
<ActionStrip />
|
||||||
</Footer>
|
</Footer>
|
||||||
|
|
||||||
<OverlayManager
|
|
||||||
// ref={overlayRef}
|
|
||||||
overlay={overlayName}
|
|
||||||
hideOverlay={hideOverlay}
|
|
||||||
/>
|
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect, useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Header from '../../components/mobile/Header';
|
import Header from '../../components/mobile/Header';
|
||||||
import IconButton from '../../components/mobile/IconButton';
|
import IconButton from '../../components/mobile/IconButton';
|
||||||
|
@ -21,7 +20,7 @@ import { remSize } from '../../theme';
|
||||||
import Footer from '../../components/mobile/Footer';
|
import Footer from '../../components/mobile/Footer';
|
||||||
import Content from './MobileViewContent';
|
import Content from './MobileViewContent';
|
||||||
|
|
||||||
const MobileSketchView = (props) => {
|
const MobileSketchView = () => {
|
||||||
const { files, ide, preferences } = useSelector(state => state);
|
const { files, ide, preferences } = useSelector(state => state);
|
||||||
|
|
||||||
const htmlFile = useSelector(state => getHTMLFile(state.files));
|
const htmlFile = useSelector(state => getHTMLFile(state.files));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useMemo, useState } from 'react';
|
||||||
|
|
||||||
export const noop = () => {};
|
export const noop = () => {};
|
||||||
|
|
||||||
|
@ -13,3 +13,39 @@ export const useDidUpdate = (callback, deps) => {
|
||||||
}
|
}
|
||||||
}, deps);
|
}, deps);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Usage: const ref = useModalBehavior(() => setSomeState(false))
|
||||||
|
// place this ref on a component
|
||||||
|
export const useModalBehavior = (hideOverlay) => {
|
||||||
|
const ref = useRef({});
|
||||||
|
|
||||||
|
// Return values
|
||||||
|
const setRef = (r) => { ref.current = r; };
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
const trigger = () => setVisible(true);
|
||||||
|
|
||||||
|
const hide = () => setVisible(false);
|
||||||
|
|
||||||
|
|
||||||
|
const handleClickOutside = ({ target }) => {
|
||||||
|
if (ref && ref.current && !ref.current.contains(target)) {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return [visible, trigger, setRef];
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This is HOC, not a hook. Where do we put it?
|
||||||
|
export const useAsModal = (component) => {
|
||||||
|
const [visible, trigger, setRef] = useModalBehavior();
|
||||||
|
|
||||||
|
const wrapper = () => (<div ref={setRef}> {visible && component} </div>); // eslint-disable-line
|
||||||
|
|
||||||
|
return [trigger, wrapper];
|
||||||
|
};
|
||||||
|
|
|
@ -25,7 +25,7 @@ _Note_: The installation steps assume you are using a Unix-like shell. If you ar
|
||||||
* For Mac OSX with [homebrew](http://brew.sh/): `brew tap mongodb/brew` then `brew install mongodb-community` and finally start the server with `brew services start mongodb-community` or you can visit the installation guide here [Installation Guide For MacOS](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/)
|
* For Mac OSX with [homebrew](http://brew.sh/): `brew tap mongodb/brew` then `brew install mongodb-community` and finally start the server with `brew services start mongodb-community` or you can visit the installation guide here [Installation Guide For MacOS](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/)
|
||||||
* For Windows and Linux: [MongoDB Installation](https://docs.mongodb.com/manual/installation/)
|
* For Windows and Linux: [MongoDB Installation](https://docs.mongodb.com/manual/installation/)
|
||||||
7. `$ cp .env.example .env`
|
7. `$ cp .env.example .env`
|
||||||
8. (Optional) Update `.env` with necessary keys to enable certain app behavoirs, i.e. add Github ID and Github Secret if you want to be able to log in with Github.
|
8. (Optional) Update `.env` with necessary keys to enable certain app behaviors, i.e. add Github ID and Github Secret if you want to be able to log in with Github.
|
||||||
9. `$ npm run fetch-examples` - this downloads the example sketches into a user called 'p5'
|
9. `$ npm run fetch-examples` - this downloads the example sketches into a user called 'p5'
|
||||||
10. `$ npm start`
|
10. `$ npm start`
|
||||||
11. Navigate to [http://localhost:8000](http://localhost:8000) in your browser
|
11. Navigate to [http://localhost:8000](http://localhost:8000) in your browser
|
||||||
|
|
Loading…
Reference in a new issue