diff --git a/.env.example b/.env.example index 3a070317..a1db1ecb 100644 --- a/.env.example +++ b/.env.example @@ -26,3 +26,4 @@ S3_BUCKET_URL_BASE= SESSION_SECRET=whatever_you_want_this_to_be_it_only_matters_for_production UI_ACCESS_TOKEN_ENABLED=false UPLOAD_LIMIT=250000000 +MOBILE_ENABLED=true diff --git a/client/components/mobile/Header.jsx b/client/components/mobile/Header.jsx index ded0b3be..cb6702ed 100644 --- a/client/components/mobile/Header.jsx +++ b/client/components/mobile/Header.jsx @@ -3,16 +3,22 @@ import styled from 'styled-components'; import PropTypes from 'prop-types'; import { prop, remSize } from '../../theme'; -const background = transparent => prop(transparent ? 'backgroundColor' : 'MobilePanel.default.background'); -const textColor = prop('primaryTextColor'); + +const background = ({ transparent, inverted }) => prop(transparent === true + ? 'backgroundColor' + : `MobilePanel.default.${inverted === true ? 'foreground' : 'background'}`); + +const textColor = ({ transparent, inverted }) => prop((transparent === false && inverted === true) + ? 'MobilePanel.default.background' + : 'primaryTextColor'); const HeaderDiv = styled.div` position: fixed; width: 100%; - background: ${props => background(props.transparent === true)}; + background: ${props => background(props)}; color: ${textColor}; - padding: ${remSize(12)}; + padding: ${props => remSize(props.slim === true ? 2 : 12)}; padding-left: ${remSize(16)}; padding-right: ${remSize(16)}; z-index: 1; @@ -25,8 +31,10 @@ const HeaderDiv = styled.div` svg { max-height: ${remSize(32)}; - padding: ${remSize(4)} + padding: ${remSize(4)}; } + + & svg path { fill: ${textColor} !important; } `; const IconContainer = styled.div` @@ -48,9 +56,10 @@ const TitleContainer = styled.div` `; const Header = ({ - title, subtitle, leftButton, children, transparent + title, subtitle, leftButton, children, + transparent, inverted, slim }) => ( - + {leftButton} {title &&

{title}

} @@ -67,7 +76,9 @@ Header.propTypes = { subtitle: PropTypes.string, leftButton: PropTypes.element, children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]), - transparent: PropTypes.bool + transparent: PropTypes.bool, + inverted: PropTypes.bool, + slim: PropTypes.bool, }; Header.defaultProps = { @@ -75,7 +86,9 @@ Header.defaultProps = { subtitle: null, leftButton: null, children: [], - transparent: false + transparent: false, + inverted: false, + slim: false }; export default Header; diff --git a/client/components/mobile/Tab.jsx b/client/components/mobile/Tab.jsx new file mode 100644 index 00000000..20a158b4 --- /dev/null +++ b/client/components/mobile/Tab.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Link } from 'react-router'; +import { prop, remSize } from '../../theme'; + +export default styled(Link)` + box-sizing: border-box; + + + background: transparent; + /* border-top: ${remSize(4)} solid ${props => prop(props.selected ? 'colors.p5jsPink' : 'MobilePanel.default.background')}; */ + border-top: ${remSize(4)} solid ${props => (props.selected ? prop('TabHighlight') : 'transparent')}; + + color: ${prop('primaryTextColor')}; + + padding: ${remSize(8)} ${remSize(16)}; + width: 30%; +`; diff --git a/client/components/mobile/TabSwitcher.jsx b/client/components/mobile/TabSwitcher.jsx new file mode 100644 index 00000000..15a3bb0e --- /dev/null +++ b/client/components/mobile/TabSwitcher.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import styled from 'styled-components'; + +import { prop, remSize } from '../../theme'; + +export default styled.div` + display: flex; + justify-content: space-between; + + h3 { text-align: center; width: 100%; } + border-top: 1px solid ${prop('Separator')}; + + background: ${props => prop('backgroundColor')}; +`; + diff --git a/client/modules/IDE/actions/collections.js b/client/modules/IDE/actions/collections.js index 3aa954ac..1d2a8e2b 100644 --- a/client/modules/IDE/actions/collections.js +++ b/client/modules/IDE/actions/collections.js @@ -17,6 +17,7 @@ export function getCollections(username) { } else { url = '/collections'; } + console.log(url); apiClient.get(url) .then((response) => { dispatch({ diff --git a/client/modules/IDE/components/CollectionList/CollectionList.jsx b/client/modules/IDE/components/CollectionList/CollectionList.jsx index 251f3e3c..1007ca97 100644 --- a/client/modules/IDE/components/CollectionList/CollectionList.jsx +++ b/client/modules/IDE/components/CollectionList/CollectionList.jsx @@ -21,6 +21,7 @@ import CollectionListRow from './CollectionListRow'; import ArrowUpIcon from '../../../../images/sort-arrow-up.svg'; import ArrowDownIcon from '../../../../images/sort-arrow-down.svg'; + class CollectionList extends React.Component { constructor(props) { super(props); @@ -127,6 +128,7 @@ class CollectionList extends React.Component { render() { const username = this.props.username !== undefined ? this.props.username : this.props.user.username; + const { mobile } = this.props; return (
@@ -141,15 +143,16 @@ class CollectionList extends React.Component { {this._renderFieldHeader('name', 'Name')} - {this._renderFieldHeader('createdAt', 'Date Created')} - {this._renderFieldHeader('updatedAt', 'Date Updated')} - {this._renderFieldHeader('numItems', '# sketches')} + {this._renderFieldHeader('createdAt', `${mobile ? '' : 'Date '}Created`)} + {this._renderFieldHeader('updatedAt', `${mobile ? '' : 'Date '}Updated`)} + {this._renderFieldHeader('numItems', mobile ? 'Sketches' : '# sketches')} {this.props.collections.map(collection => ( format(new Date(date), 'MMM D, YYYY'); + class CollectionListRowBase extends React.Component { static projectInCollection(project, collection) { return collection.items.find(item => item.project.id === project.id) != null; @@ -199,7 +201,7 @@ class CollectionListRowBase extends React.Component { } render() { - const { collection } = this.props; + const { collection, mobile } = this.props; return ( - {format(new Date(collection.createdAt), 'MMM D, YYYY')} - {format(new Date(collection.updatedAt), 'MMM D, YYYY')} - {(collection.items || []).length} + {mobile && 'Created: '}{format(new Date(collection.createdAt), 'MMM D, YYYY')} + {mobile && 'Updated: '}{formatDateCell(collection.updatedAt)} + {mobile && '# sketches: '}{(collection.items || []).length} {this.renderActions()} @@ -245,6 +247,11 @@ CollectionListRowBase.propTypes = { deleteCollection: PropTypes.func.isRequired, editCollection: PropTypes.func.isRequired, onAddSketches: PropTypes.func.isRequired, + mobile: PropTypes.bool, +}; + +CollectionListRowBase.defaultProps = { + mobile: false, }; function mapDispatchToPropsSketchListRow(dispatch) { diff --git a/client/modules/IDE/components/EditableInput.jsx b/client/modules/IDE/components/EditableInput.jsx index c10f3d70..64f38222 100644 --- a/client/modules/IDE/components/EditableInput.jsx +++ b/client/modules/IDE/components/EditableInput.jsx @@ -5,13 +5,20 @@ import EditIcon from '../../../images/pencil.svg'; // TODO I think this needs a description prop so that it's accessible function EditableInput({ - validate, value, emptyPlaceholder, InputComponent, inputProps, onChange + validate, + value, + emptyPlaceholder, + InputComponent, + inputProps, + onChange, }) { const [isEditing, setIsEditing] = React.useState(false); const [currentValue, setCurrentValue] = React.useState(value || ''); const displayValue = currentValue || emptyPlaceholder; const hasValue = currentValue !== ''; - const classes = `editable-input editable-input--${isEditing ? 'is-editing' : 'is-not-editing'} editable-input--${hasValue ? 'has-value' : 'has-placeholder'}`; + const classes = `editable-input editable-input--${ + isEditing ? 'is-editing' : 'is-not-editing' + } editable-input--${hasValue ? 'has-value' : 'has-placeholder'}`; const inputRef = React.createRef(); React.useEffect(() => { @@ -54,7 +61,11 @@ function EditableInput({ aria-label={`Edit ${displayValue} value`} > {displayValue} -