diff --git a/client/components/mobile/Header.jsx b/client/components/mobile/Header.jsx
index eca2c98e..1f7f7a29 100644
--- a/client/components/mobile/Header.jsx
+++ b/client/components/mobile/Header.jsx
@@ -1,14 +1,16 @@
import React from 'react';
import styled from 'styled-components';
+import PropTypes from 'prop-types';
import { prop, remSize } from '../../theme';
const background = prop('MobilePanel.default.background');
const textColor = prop('primaryTextColor');
-const Header = styled.div`
+
+const HeaderDiv = styled.div`
position: fixed;
width: 100%;
- background: ${background};
+ background: ${props => (props.transparent ? 'transparent' : background)};
color: ${textColor};
padding: ${remSize(12)};
padding-left: ${remSize(16)};
@@ -23,8 +25,56 @@ const Header = styled.div`
// TODO:
svg {
- height: 2rem;
+ max-height: ${remSize(32)};
+ padding: ${remSize(4)}
}
`;
+const IconContainer = styled.div`
+ margin-left: ${props => (props.leftButton ? remSize(32) : remSize(4))};
+ display: flex;
+`;
+
+
+const TitleContainer = styled.div`
+ margin-left: ${remSize(4)};
+ margin-right: auto;
+
+ ${props => props.padded && `h2{
+ padding-top: ${remSize(10)};
+ padding-bottom: ${remSize(10)};
+ }`}
+`;
+
+const Header = ({
+ title, subtitle, leftButton, children, transparent
+}) => (
+
+ {leftButton}
+
+ {title && {title}
}
+ {subtitle && {subtitle}
}
+
+
+ {children}
+
+
+);
+
+Header.propTypes = {
+ title: PropTypes.string,
+ subtitle: PropTypes.string,
+ leftButton: PropTypes.element,
+ children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
+ transparent: PropTypes.bool
+};
+
+Header.defaultProps = {
+ title: null,
+ subtitle: null,
+ leftButton: null,
+ children: [],
+ transparent: false
+};
+
export default Header;
diff --git a/client/components/mobile/IconButton.jsx b/client/components/mobile/IconButton.jsx
index 248dd014..08f05311 100644
--- a/client/components/mobile/IconButton.jsx
+++ b/client/components/mobile/IconButton.jsx
@@ -2,9 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import Button from '../../common/Button';
+import { remSize } from '../../theme';
const ButtonWrapper = styled(Button)`
-width: 3rem;
+width: ${remSize(48)};
> svg {
width: 100%;
height: 100%;
diff --git a/client/components/mobile/MobileScreen.jsx b/client/components/mobile/MobileScreen.jsx
index 1e50f80a..e78baa2c 100644
--- a/client/components/mobile/MobileScreen.jsx
+++ b/client/components/mobile/MobileScreen.jsx
@@ -1,13 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
-const Screen = ({ children }) => (
-
+const Screen = ({ children, fullscreen }) => (
+
{children}
);
+
+Screen.defaultProps = {
+ fullscreen: false
+};
+
Screen.propTypes = {
- children: PropTypes.node.isRequired
+ children: PropTypes.node.isRequired,
+ fullscreen: PropTypes.bool
};
export default Screen;
diff --git a/client/components/mobile/PreferencePicker.jsx b/client/components/mobile/PreferencePicker.jsx
new file mode 100644
index 00000000..0e2e085a
--- /dev/null
+++ b/client/components/mobile/PreferencePicker.jsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+import { prop, remSize } from '../../theme';
+
+
+const PreferenceTitle = styled.h4.attrs(props => ({ ...props, className: 'preference__title' }))`
+ color: ${prop('primaryTextColor')};
+`;
+
+const Preference = styled.div.attrs(props => ({ ...props, className: 'preference' }))`
+ flex-wrap: nowrap !important;
+ align-items: baseline !important;
+ justify-items: space-between;
+`;
+
+const OptionLabel = styled.label.attrs({ className: 'preference__option' })`
+ font-size: ${remSize(14)} !important;
+`;
+
+const PreferencePicker = ({
+ title, value, onSelect, options,
+}) => (
+
+ {title}
+
+ {options.map(option => (
+
+ onSelect(option.value)}
+ aria-label={option.ariaLabel}
+ name={option.name}
+ key={`${option.name}-${option.id}-input`}
+ id={option.id}
+ className="preference__radio-button"
+ value={option.value}
+ checked={value === option.value}
+ />
+
+ {option.label}
+
+ ))}
+
+
+);
+
+PreferencePicker.defaultProps = {
+ options: []
+};
+
+PreferencePicker.propTypes = {
+ title: PropTypes.string.isRequired,
+ value: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]).isRequired,
+ options: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string,
+ name: PropTypes.string,
+ label: PropTypes.string,
+ ariaLabel: PropTypes.string,
+ })),
+ onSelect: PropTypes.func.isRequired,
+};
+
+export default PreferencePicker;
diff --git a/client/modules/IDE/pages/IDEViewMobile.jsx b/client/modules/IDE/pages/MobileIDEView.jsx
similarity index 89%
rename from client/modules/IDE/pages/IDEViewMobile.jsx
rename to client/modules/IDE/pages/MobileIDEView.jsx
index 9a638d60..180cbb2e 100644
--- a/client/modules/IDE/pages/IDEViewMobile.jsx
+++ b/client/modules/IDE/pages/MobileIDEView.jsx
@@ -1,7 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
-import styled from 'styled-components';
-import { Link } from 'react-router';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { useState } from 'react';
@@ -27,16 +25,10 @@ import Header from '../../../components/mobile/Header';
import Screen from '../../../components/mobile/MobileScreen';
import Footer from '../../../components/mobile/Footer';
import IDEWrapper from '../../../components/mobile/IDEWrapper';
-import { remSize } from '../../../theme';
-
-const IconContainer = styled.div`
- margin-left: ${remSize(32)};
- display: flex;
-`;
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
-const IDEViewMobile = (props) => {
+const MobileIDEView = (props) => {
const {
preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage,
selectedFile, updateFileContent, files,
@@ -49,22 +41,21 @@ const IDEViewMobile = (props) => {
const [overlay, setOverlay] = useState(null); // eslint-disable-line
return (
-
-
@@ -109,7 +100,7 @@ const IDEViewMobile = (props) => {
};
-IDEViewMobile.propTypes = {
+MobileIDEView.propTypes = {
preferences: PropTypes.shape({
fontSize: PropTypes.number.isRequired,
@@ -256,4 +247,4 @@ function mapDispatchToProps(dispatch) {
}
-export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IDEViewMobile));
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView));
diff --git a/client/modules/Mobile/MobilePreferences.jsx b/client/modules/Mobile/MobilePreferences.jsx
new file mode 100644
index 00000000..a9dc9f34
--- /dev/null
+++ b/client/modules/Mobile/MobilePreferences.jsx
@@ -0,0 +1,226 @@
+import React from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+
+import * as PreferencesActions from '../IDE/actions/preferences';
+import * as IdeActions from '../IDE/actions/ide';
+
+import IconButton from '../../components/mobile/IconButton';
+import Screen from '../../components/mobile/MobileScreen';
+import Header from '../../components/mobile/Header';
+import PreferencePicker from '../../components/mobile/PreferencePicker';
+import { ExitIcon } from '../../common/icons';
+import { remSize, prop } from '../../theme';
+
+const Content = styled.div`
+ z-index: 0;
+ margin-top: ${remSize(68)};
+`;
+
+
+const SettingsHeader = styled(Header)`
+ background: transparent;
+`;
+
+const SectionHeader = styled.h2`
+ color: ${prop('primaryTextColor')};
+ padding-top: ${remSize(32)};
+`;
+
+const SectionSubeader = styled.h3`
+ color: ${prop('primaryTextColor')};
+`;
+
+
+const MobilePreferences = (props) => {
+ const {
+ setTheme, setAutosave, setLinewrap, setTextOutput, setGridOutput, setSoundOutput, lineNumbers, lintWarning
+ } = props;
+ const {
+ theme, autosave, linewrap, textOutput, gridOutput, soundOutput, setLineNumbers, setLintWarning
+ } = props;
+
+ const generalSettings = [
+ {
+ title: 'Theme',
+ value: theme,
+ options: [
+ {
+ value: 'light', label: 'light', ariaLabel: 'light theme on', name: 'light theme', id: 'light-theme-on'
+ },
+ {
+ value: 'dark', label: 'dark', ariaLabel: 'dark theme on', name: 'dark theme', id: 'dark-theme-on'
+ },
+ {
+ value: 'contrast',
+ label: 'contrast',
+ ariaLabel: 'contrast theme on',
+ name: 'contrast theme',
+ id: 'contrast-theme-on'
+ }
+ ],
+ onSelect: x => setTheme(x) // setTheme
+ },
+
+ {
+ title: 'Autosave',
+ value: autosave,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'autosave on', name: 'autosave', id: 'autosave-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'autosave off', name: 'autosave', id: 'autosave-off'
+ },
+ ],
+ onSelect: x => setAutosave(x) // setAutosave
+ },
+
+ {
+ title: 'Word Wrap',
+ value: linewrap,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'linewrap on', name: 'linewrap', id: 'linewrap-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'linewrap off', name: 'linewrap', id: 'linewrap-off'
+ },
+ ],
+ onSelect: x => setLinewrap(x)
+ }
+ ];
+
+ const outputSettings = [
+ {
+ title: 'Plain-text',
+ value: textOutput,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'text output on', name: 'text output', id: 'text-output-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'text output off', name: 'text output', id: 'text-output-off'
+ },
+ ],
+ onSelect: x => setTextOutput(x)
+ },
+ {
+ title: 'Table-text',
+ value: gridOutput,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'table output on', name: 'table output', id: 'table-output-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'table output off', name: 'table output', id: 'table-output-off'
+ },
+ ],
+ onSelect: x => setGridOutput(x)
+ },
+ {
+ title: 'Sound',
+ value: soundOutput,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'sound output on', name: 'sound output', id: 'sound-output-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'sound output off', name: 'sound output', id: 'sound-output-off'
+ },
+ ],
+ onSelect: x => setSoundOutput(x)
+ },
+ ];
+
+ const accessibilitySettings = [
+ {
+ title: 'Line Numbers',
+ value: lineNumbers,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'line numbers on', name: 'line numbers', id: 'line-numbers-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'line numbers off', name: 'line numbers', id: 'line-numbers-off'
+ },
+ ],
+ onSelect: x => setLineNumbers(x)
+ },
+ {
+ title: 'Lint Warning Sound',
+ value: lintWarning,
+ options: [
+ {
+ value: true, label: 'On', ariaLabel: 'lint warning on', name: 'lint warning', id: 'lint-warning-on'
+ },
+ {
+ value: false, label: 'Off', ariaLabel: 'lint warning off', name: 'lint warning', id: 'lint-warning-off'
+ },
+ ],
+ onSelect: x => setLintWarning(x)
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+ General Settings
+ { generalSettings.map(option => ) }
+
+ Accessibility
+ { accessibilitySettings.map(option => ) }
+
+ Accessible Output
+ Used with screen reader
+ { outputSettings.map(option => ) }
+
+
+
+
+ );
+};
+
+
+MobilePreferences.propTypes = {
+ fontSize: PropTypes.number.isRequired,
+ lineNumbers: PropTypes.bool.isRequired,
+ autosave: PropTypes.bool.isRequired,
+ linewrap: PropTypes.bool.isRequired,
+ textOutput: PropTypes.bool.isRequired,
+ gridOutput: PropTypes.bool.isRequired,
+ soundOutput: PropTypes.bool.isRequired,
+ lintWarning: PropTypes.bool.isRequired,
+ theme: PropTypes.string.isRequired,
+
+ setLinewrap: PropTypes.func.isRequired,
+ setLintWarning: PropTypes.func.isRequired,
+ setTheme: PropTypes.func.isRequired,
+ setFontSize: PropTypes.func.isRequired,
+ setLineNumbers: PropTypes.func.isRequired,
+ setAutosave: PropTypes.func.isRequired,
+ setTextOutput: PropTypes.func.isRequired,
+ setGridOutput: PropTypes.func.isRequired,
+ setSoundOutput: PropTypes.func.isRequired,
+};
+
+const mapStateToProps = state => ({
+ ...state.preferences,
+});
+
+const mapDispatchToProps = dispatch => bindActionCreators({
+ ...PreferencesActions,
+ ...IdeActions
+}, dispatch);
+
+
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobilePreferences));
diff --git a/client/modules/Mobile/MobileSketchView.jsx b/client/modules/Mobile/MobileSketchView.jsx
index 37dc5ba9..64eabb5e 100644
--- a/client/modules/Mobile/MobileSketchView.jsx
+++ b/client/modules/Mobile/MobileSketchView.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Link } from 'react-router';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import styled from 'styled-components';
@@ -48,14 +47,13 @@ const MobileSketchView = (props) => {
const { preferences, ide } = props;
return (
-
-
-
-
-
{projectName}
-
-
-
+
+
+ }
+ title={projectName}
+ />
(
-
+
+
);
diff --git a/server/routes/server.routes.js b/server/routes/server.routes.js
index f7666f29..c038a3c7 100644
--- a/server/routes/server.routes.js
+++ b/server/routes/server.routes.js
@@ -123,7 +123,7 @@ if (process.env.MOBILE_ENABLED) {
res.send(renderIndex());
});
- router.get('/mobile/*', (req, res) => {
+ router.get('/mobile/preferences', (req, res) => {
res.send(renderIndex());
});
}