Skip to content

Commit

Permalink
Merge pull request #756 from codaco/fix/android-keyboard-scaling-view…
Browse files Browse the repository at this point in the history
…port

Implement persistent app settings
  • Loading branch information
jthrilly committed Oct 31, 2018
2 parents 8a4d827 + 5284fbd commit 05fce06
Show file tree
Hide file tree
Showing 20 changed files with 381 additions and 162 deletions.
9 changes: 9 additions & 0 deletions config/jest/matchMedia.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

Object.defineProperty(window, 'matchMedia', {
value: () => ({
matches: false,
addListener: () => {},
removeListener: () => {},
}),
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@
"<rootDir>/config/polyfills.js",
"<rootDir>/config/jest/polyfills.js",
"<rootDir>/config/jest/enzyme.js",
"<rootDir>/config/jest/automock.js"
"<rootDir>/config/jest/automock.js",
"<rootDir>/config/jest/matchMedia.js"
],
"testPathIgnorePatterns": [
"<rootDir>[/\\\\](www|docs|node_modules|scripts)[/\\\\]"
Expand Down
178 changes: 135 additions & 43 deletions src/components/MainMenu/SettingsMenu.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,148 @@
import React from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Icon, Button } from '../../ui/components';
import Scroller from '../Scroller';
import { Toggle, Text } from '../../ui/components/Fields';
import MenuPanel from './MenuPanel';

const SettingsMenu = ({
active,
onClickInactive,
handleAddMockNodes,
handleResetAppData,
}) => (
<MenuPanel
active={active}
panel="settings"
onClickInactive={onClickInactive}
>
<Icon name="settings" />
<div className="main-menu-settings-menu">
<div className="main-menu-settings-menu__header">
<h1>Settings</h1>
</div>
<div className="main-menu-settings-menu__form">
<fieldset>
<legend>Developer Options</legend>
<p>During an active interview session, clicking this button will create mock nodes for
testing purposes.</p>
<Button
color="mustard"
onClick={handleAddMockNodes}
>
Add mock nodes
</Button>
</fieldset>
<fieldset>
<legend>Global App Settings</legend>
<p>Click the button below to reset all app data.</p>
<Button
color="mustard"
onClick={handleResetAppData}
>
Reset Network Canvas data
</Button>
</fieldset>
</div>
</div>
</MenuPanel>
);
class SettingsMenu extends PureComponent {
render() {
const {
active,
onClickInactive,
handleAddMockNodes,
handleResetAppData,
toggleUseFullScreenForms,
useFullScreenForms,
toggleUseDynamicScaling,
useDynamicScaling,
setDeviceDescription,
deviceDescription,
setInterfaceScale,
interfaceScale,
} = this.props;
return (
<MenuPanel
active={active}
panel="settings"
onClickInactive={onClickInactive}
>
<Icon name="settings" />
<div className="main-menu-settings-menu">
<div className="main-menu-settings-menu__header">
<h1>Settings</h1>
</div>
<Scroller>
<div className="main-menu-settings-menu__form">
<fieldset>
<legend>Developer Options</legend>
<section>
<p>The device name determines how your device appears to Server.</p>
<Text
input={{
value: deviceDescription,
onChange: e => setDeviceDescription(e.target.value),
}}
label="Device name"
fieldLabel=" "
/>
</section>
<section>
<p>
During an active interview session, clicking this button will create mock nodes
for testing purposes.
</p>
<Button
color="mustard"
onClick={handleAddMockNodes}
>
Add mock nodes
</Button>
</section>
<section>
<p>
Use the button below to reset all Network Canvas data. This will erase any
in-progress interviews, and all application settings.
</p>
<Button
color="mustard"
onClick={handleResetAppData}
>
Reset Network Canvas data
</Button>
</section>
</fieldset>
<fieldset>
<legend>Display Settings</legend>
<section>
<p>
This setting allows you to control the size of the Network Canvas user
interface. Increasing the interface size may limit the amount of information
visible on each screen.
</p>
<label htmlFor="scaleFactor">Interface Scale</label>
<input
type="range"
name="scaleFactor"
min="80"
max="120"
value={interfaceScale}
onChange={(e) => { setInterfaceScale(parseInt(e.target.value, 10)); }}
step="5"
/>
</section>
<section>
<p>
Dynamic scaling lets Network Canvas resize the user interface proportionally to
the size of the window. Turning it off will use a fixed size.
</p>
<Toggle
input={{
checked: true,
value: useDynamicScaling,
onChange: toggleUseDynamicScaling,
}}
label="Use dynamic scaling?"
fieldLabel=" "
/>
</section>
<section>
<p>
The full screen node form is optomised for smaller devices, or devices with
no physical keyboard.
</p>
<Toggle
input={{
checked: true,
value: useFullScreenForms,
onChange: toggleUseFullScreenForms,
}}
label="Use full screen node form?"
fieldLabel=" "
/>
</section>
</fieldset>
</div>
</Scroller>
</div>
</MenuPanel>
);
}
}

SettingsMenu.propTypes = {
active: PropTypes.bool,
onClickInactive: PropTypes.func,
handleResetAppData: PropTypes.func.isRequired,
handleAddMockNodes: PropTypes.func.isRequired,
toggleUseFullScreenForms: PropTypes.func.isRequired,
useFullScreenForms: PropTypes.bool.isRequired,
toggleUseDynamicScaling: PropTypes.func.isRequired,
useDynamicScaling: PropTypes.bool.isRequired,
setDeviceDescription: PropTypes.func.isRequired,
deviceDescription: PropTypes.string.isRequired,
setInterfaceScale: PropTypes.func.isRequired,
interfaceScale: PropTypes.number.isRequired,
};

SettingsMenu.defaultProps = {
Expand Down
11 changes: 8 additions & 3 deletions src/components/Overlay.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Component } from 'react';
import animejs from 'animejs';
import cx from 'classnames';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Modal } from '../ui/components';
import { getCSSVariableAsNumber, getCSSVariableAsObject } from '../utils/CSSVariables';
import isLarge from '../utils/isLarge';

/**
* Renders a modal window.
Expand Down Expand Up @@ -33,11 +33,12 @@ class Overlay extends Component {
onBlur,
show,
title,
useFullScreenForms,
} = this.props;

return (
<Modal show={show} onBlur={onBlur}>
<div className={cx('overlay', { 'overlay--fullscreen': !isLarge() })}>
<div className={cx('overlay', { 'overlay--fullscreen': useFullScreenForms })}>
<div className="overlay__title">
<h1>{title}</h1>
</div>
Expand All @@ -57,6 +58,7 @@ Overlay.propTypes = {
title: PropTypes.string.isRequired,
show: PropTypes.bool,
children: PropTypes.any,
useFullScreenForms: PropTypes.bool.isRequired,
};

Overlay.defaultProps = {
Expand All @@ -66,4 +68,7 @@ Overlay.defaultProps = {
children: null,
};

export default Overlay;
const mapStateToProps = state =>
({ useFullScreenForms: state.deviceSettings.useFullScreenForms });

export default connect(mapStateToProps, null)(Overlay);
80 changes: 55 additions & 25 deletions src/containers/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import cx from 'classnames';

Expand All @@ -14,40 +15,69 @@ import MainMenu from '../containers/MainMenu';
* Main app container.
* @param props {object} - children
*/
const App = props => (
<div className={cx({
app: true,
'app--electron': isElectron(),
'app--windows': isWindows(),
'app--macos': isMacOS(),
'app--linux': isLinux(),
})}
>
<MainMenu />
<div className="electron-titlebar" />
<div
id="page-wrap"
className={cx({
app__content: true,
})}
>
{ props.children }
</div>
<LoadScreen />
<DialogManager />
</div>
class App extends PureComponent {
componentDidMount() {
this.setFontSize();
}

componentDidUpdate() {
this.setFontSize();
}

);
setFontSize = () => {
const root = document.documentElement;
const newFontSize = this.props.useDynamicScaling ?
`${(1.75 * this.props.interfaceScale) / 100}vmin` :
`${(18 * this.props.interfaceScale) / 100}px`;

root.style.setProperty('--base-font-size', newFontSize);
}
render() {
const { children } = this.props;
return (
<div className={cx({
app: true,
'app--electron': isElectron(),
'app--windows': isWindows(),
'app--macos': isMacOS(),
'app--linux': isLinux(),
})}
>
<MainMenu />
<div className="electron-titlebar" />
<div
id="page-wrap"
className={cx({
app__content: true,
})}
>
{ children }
</div>
<LoadScreen />
<DialogManager />
</div>
);
}
}

App.propTypes = {
children: PropTypes.any,
interfaceScale: PropTypes.number.isRequired,
useDynamicScaling: PropTypes.bool.isRequired,
};

App.defaultProps = {
children: null,
isSettingsMenuOpen: false,
};

function mapStateToProps(state) {
return {
interfaceScale: state.deviceSettings.interfaceScale,
useDynamicScaling: state.deviceSettings.useDynamicScaling,
};
}

export default compose(
withRouter,
connect(mapStateToProps),
)(App);
14 changes: 13 additions & 1 deletion src/containers/MainMenu/SettingsMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SettingsMenu from '../../components/MainMenu/SettingsMenu';
import { actionCreators as uiActions } from '../../ducks/modules/ui';
import { actionCreators as mockActions } from '../../ducks/modules/mock';
import { actionCreators as dialogsActions } from '../../ducks/modules/dialogs';
import { actionCreators as deviceSettingsActions } from '../../ducks/modules/deviceSettings';

const settingsMenuHandlers = withHandlers({
handleResetAppData: props => () => {
Expand All @@ -31,10 +32,21 @@ const mapDispatchToProps = dispatch => ({
openDialog: bindActionCreators(dialogsActions.openDialog, dispatch),
resetState: () => dispatch(push('/reset')),
generateNodes: bindActionCreators(mockActions.generateNodes, dispatch),
setDeviceDescription: name => dispatch(deviceSettingsActions.setDescription(name)),
toggleUseFullScreenForms: () => dispatch(deviceSettingsActions.toggleSetting('useFullScreenForms')),
toggleUseDynamicScaling: () => dispatch(deviceSettingsActions.toggleSetting('useDynamicScaling')),
setInterfaceScale: scale => dispatch(deviceSettingsActions.setInterfaceScale(scale)),
});

const mapStateToProps = state => ({
useFullScreenForms: state.deviceSettings.useFullScreenForms,
useDynamicScaling: state.deviceSettings.useDynamicScaling,
deviceDescription: state.deviceSettings.description,
interfaceScale: state.deviceSettings.interfaceScale,
});

export default compose(
connect(null, mapDispatchToProps),
connect(mapStateToProps, mapDispatchToProps),
settingsMenuHandlers,
)(SettingsMenu);

0 comments on commit 05fce06

Please sign in to comment.