diff options
Diffstat (limited to 'app')
77 files changed, 2629 insertions, 2303 deletions
diff --git a/app/app.android.js b/app/app.android.js index 0f31116447..38bb927a93 100644 --- a/app/app.android.js +++ b/app/app.android.js @@ -31,8 +31,8 @@ DeviceEventEmitter.addListener('com.mullvad.backend-info', async (_event, args) await backend.fetchSecurityState(); await backend.connect(); } catch (e) { - if(e instanceof BackendError) { - if(e.type === 'NO_ACCOUNT') { + if (e instanceof BackendError) { + if (e.type === 'NO_ACCOUNT') { log.debug('No user set in the backend, showing window'); MobileAppBridge.showWindow(); } @@ -40,16 +40,18 @@ DeviceEventEmitter.addListener('com.mullvad.backend-info', async (_event, args) } }); -MobileAppBridge.startBackend().then(_response => {}).catch(e => { - log.error('Failed starting backend:', e); -}); +MobileAppBridge.startBackend() + .then((_response) => {}) + .catch((e) => { + log.error('Failed starting backend:', e); + }); const _isPortrait = () => { const dim = RX.UserInterface.measureWindow(); return dim.height >= dim.width; }; -export default class App extends Component{ +export default class App extends Component { constructor() { super(); @@ -59,17 +61,15 @@ export default class App extends Component{ Dimensions.addEventListener('change', () => { this.setState({ - orientation: _isPortrait() ? 'portrait' : 'landscape' + orientation: _isPortrait() ? 'portrait' : 'landscape', }); }); } render() { return ( - <Provider store={ store }> - <Router history={ memoryHistory }> - { makeRoutes(store.getState, { backend }) } - </Router> + <Provider store={store}> + <Router history={memoryHistory}>{makeRoutes(store.getState, { backend })}</Router> </Provider> ); } diff --git a/app/app.js b/app/app.js index 429c7e39cd..1133a2b4fe 100644 --- a/app/app.js +++ b/app/app.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; -import { Component} from 'reactxp'; +import { Component } from 'reactxp'; import { Provider } from 'react-redux'; import { ConnectedRouter } from 'react-router-redux'; import { createMemoryHistory } from 'history'; @@ -31,8 +31,8 @@ ipcRenderer.on('backend-info', async (_event, args) => { await backend.fetchSecurityState(); await backend.connect(); } catch (e) { - if(e instanceof BackendError) { - if(e.type === 'NO_ACCOUNT') { + if (e instanceof BackendError) { + if (e.type === 'NO_ACCOUNT') { log.debug('No user set in the backend, showing window'); ipcRenderer.send('show-window'); } @@ -42,25 +42,24 @@ ipcRenderer.on('backend-info', async (_event, args) => { ipcRenderer.on('shutdown', () => { log.info('Been told by the node process to shutdown'); - backend.shutdown() - .catch( e => { - log.warn('Unable to shut down the backend', e.message); - }); + backend.shutdown().catch((e) => { + log.warn('Unable to shut down the backend', e.message); + }); }); ipcRenderer.on('disconnect', () => { log.info('Been told by the node process to disconnect the tunnel'); - backend.disconnect() - .catch( e => { - log.warn('Unable to disconnect the tunnel', e.message); - }); + backend.disconnect().catch((e) => { + log.warn('Unable to disconnect the tunnel', e.message); + }); }); ipcRenderer.on('app-shutdown', () => { log.info('Been told by the renderer process that the app is shutting down'); // The shutdown behaviour may have to be different on mobile platforms - const shutdown_func = process.platform === 'darwin' ? () => backend.shutdown() : () => backend.disconnect(); - shutdown_func().catch( e => { + const shutdown_func = + process.platform === 'darwin' ? () => backend.shutdown() : () => backend.disconnect(); + shutdown_func().catch((e) => { log.error('Failed to shutdown tunnel: ', e); }); @@ -79,10 +78,13 @@ ipcRenderer.on('app-shutdown', () => { * Get tray icon type based on connection state */ const getIconType = (s: ConnectionState): TrayIconType => { - switch(s) { - case 'connected': return 'secured'; - case 'connecting': return 'securing'; - default: return 'unsecured'; + switch (s) { + case 'connected': + return 'secured'; + case 'connecting': + return 'securing'; + default: + return 'unsecured'; } }; @@ -106,13 +108,12 @@ webFrame.setZoomLevelLimits(1, 1); ipcRenderer.send('on-browser-window-ready'); - -export default class App extends Component{ +export default class App extends Component { render() { return ( - <Provider store={ store }> - <ConnectedRouter history={ memoryHistory }> - { makeRoutes(store.getState, { backend }) } + <Provider store={store}> + <ConnectedRouter history={memoryHistory}> + {makeRoutes(store.getState, { backend })} </ConnectedRouter> </Provider> ); diff --git a/app/components/Accordion.js b/app/components/Accordion.js index 3f2ceb1a06..f69389f6c7 100644 --- a/app/components/Accordion.js +++ b/app/components/Accordion.js @@ -6,7 +6,7 @@ import { Component, View, Styles, Animated, UserInterface } from 'reactxp'; export type AccordionProps = { height: number | 'auto', animationDuration?: number, - children?: React.Node + children?: React.Node, }; export type AccordionState = { @@ -18,7 +18,7 @@ const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' }); export default class Accordion extends Component<AccordionProps, AccordionState> { static defaultProps = { height: 'auto', - animationDuration: 350 + animationDuration: 350, }; state: AccordionState = { @@ -34,36 +34,44 @@ export default class Accordion extends Component<AccordionProps, AccordionState> super(props); // set the initial height if it's known - if(typeof(props.height) === 'number') { + if (typeof props.height === 'number') { this.state = { - animatedValue: Animated.createValue(props.height) + animatedValue: Animated.createValue(props.height), }; } } componentWillUnmount() { - if(this._animation) { + if (this._animation) { this._animation.stop(); } } shouldComponentUpdate(nextProps: AccordionProps, nextState: AccordionState) { - return nextState.animatedValue !== this.state.animatedValue || - nextProps.height !== this.props.height || - nextProps.children !== this.props.children; + return ( + nextState.animatedValue !== this.state.animatedValue || + nextProps.height !== this.props.height || + nextProps.children !== this.props.children + ); } componentDidUpdate(prevProps: AccordionProps, _prevState: AccordionState) { - if(prevProps.height !== this.props.height) { + if (prevProps.height !== this.props.height) { this._animateHeightChanges(); } } render() { - const { style: style, height: _height, children, animationDuration: _animationDuration, ...otherProps } = this.props; + const { + style: style, + height: _height, + children, + animationDuration: _animationDuration, + ...otherProps + } = this.props; const containerStyles = [style]; - if(this.state.animatedValue !== null) { + if (this.state.animatedValue !== null) { const animatedStyle = Styles.createAnimatedViewStyle({ height: this.state.animatedValue, }); @@ -72,59 +80,59 @@ export default class Accordion extends Component<AccordionProps, AccordionState> } return ( - <Animated.View { ...otherProps } style={ containerStyles } ref={ (node) => this._containerView = node }> - <View onLayout={ this._contentLayoutDidChange }> - { children } - </View> + <Animated.View + {...otherProps} + style={containerStyles} + ref={(node) => (this._containerView = node)}> + <View onLayout={this._contentLayoutDidChange}>{children}</View> </Animated.View> ); } _animateHeightChanges() { const containerView = this._containerView; - if(!containerView) { + if (!containerView) { return; } - if(this._animation) { + if (this._animation) { this._animation.stop(); this._animation = null; } - UserInterface.measureLayoutRelativeToWindow(containerView) - .then((layout) => { - const fromValue = this.state.animatedValue || Animated.createValue(layout.height); - const toValue = this.props.height === 'auto' ? this._contentHeight : this.props.height; + UserInterface.measureLayoutRelativeToWindow(containerView).then((layout) => { + const fromValue = this.state.animatedValue || Animated.createValue(layout.height); + const toValue = this.props.height === 'auto' ? this._contentHeight : this.props.height; - // calculate the animation duration based on travel distance - const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this._contentHeight); - const duration = Math.ceil(this.props.animationDuration * multiplier); + // calculate the animation duration based on travel distance + const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this._contentHeight); + const duration = Math.ceil(this.props.animationDuration * multiplier); - const animation = Animated.timing(fromValue, { - toValue: toValue, - easing: Animated.Easing.InOut(), - duration: duration, - useNativeDriver: true, - }); + const animation = Animated.timing(fromValue, { + toValue: toValue, + easing: Animated.Easing.InOut(), + duration: duration, + useNativeDriver: true, + }); - this._animation = animation; - this.setState({ animatedValue: fromValue }, () => { - animation.start(this._onAnimationEnd); - }); + this._animation = animation; + this.setState({ animatedValue: fromValue }, () => { + animation.start(this._onAnimationEnd); }); + }); } _onAnimationEnd = ({ finished }) => { - if(finished) { + if (finished) { this._animation = null; // reset height after transition to let element layout naturally // if animation finished without interruption - if(this.props.height === 'auto') { + if (this.props.height === 'auto') { this.setState({ animatedValue: null }); } } - } + }; - _contentLayoutDidChange = ({ height }) => this._contentHeight = height; -}
\ No newline at end of file + _contentLayoutDidChange = ({ height }) => (this._contentHeight = height); +} diff --git a/app/components/Account.js b/app/components/Account.js index 4ec57aaf1e..b4eef86712 100644 --- a/app/components/Account.js +++ b/app/components/Account.js @@ -11,10 +11,10 @@ import { formatAccount } from '../lib/formatters'; import type { AccountReduxState } from '../redux/account/reducers'; export type AccountProps = { - account: AccountReduxState; - onLogout: () => void; - onClose: () => void; - onBuyMore: () => void; + account: AccountReduxState, + onLogout: () => void, + onClose: () => void, + onBuyMore: () => void, }; export default class Account extends Component { @@ -30,51 +30,49 @@ export default class Account extends Component { <Layout> <Container> <View style={styles.account}> - <Button style={styles.account__close} - onPress={ this.props.onClose } - testName='account__close'> + <Button + style={styles.account__close} + onPress={this.props.onClose} + testName="account__close"> <Img height={24} width={24} style={styles.account__close_icon} source="icon-back" /> <Text style={styles.account__close_title}>Settings</Text> </Button> <View style={styles.account__container}> - <View style={styles.account__header}> <Text style={styles.account__title}>Account</Text> </View> <View style={styles.account__content}> <View style={styles.account__main}> - <View style={styles.account__row}> <Text style={styles.account__row_label}>Account ID</Text> - <Text style={styles.account__row_value}>{ formattedAccountToken }</Text> + <Text style={styles.account__row_value}>{formattedAccountToken}</Text> </View> <View style={styles.account__row}> <Text style={styles.account__row_label}>Paid until</Text> - { isOutOfTime ? - <Text style={styles.account__out_of_time} testName='account__out_of_time'>OUT OF TIME</Text> - : - <Text style={styles.account__row_value}>{ formattedExpiry }</Text> - } + {isOutOfTime ? ( + <Text style={styles.account__out_of_time} testName="account__out_of_time"> + OUT OF TIME + </Text> + ) : ( + <Text style={styles.account__row_value}>{formattedExpiry}</Text> + )} </View> <View style={styles.account__footer}> <GreenButton - onPress={ this.props.onBuyMore } - text='Buy more credit' - icon='icon-extLink' - testName='account__buymore'> + onPress={this.props.onBuyMore} + text="Buy more credit" + icon="icon-extLink" + testName="account__buymore"> <Label>Buy more credit</Label> - <Img source='icon-extLink' height={16} width={16} /> + <Img source="icon-extLink" height={16} width={16} /> </GreenButton> - <RedButton - onPress={ this.props.onLogout } - testName='account__logout'> + <RedButton onPress={this.props.onLogout} testName="account__logout"> Log out </RedButton> </View> - </View> </View> </View> diff --git a/app/components/AccountInput.js b/app/components/AccountInput.js index e113b1679c..5a8fa47336 100644 --- a/app/components/AccountInput.js +++ b/app/components/AccountInput.js @@ -18,14 +18,14 @@ declare class ClipboardEvent extends Event { } export type AccountInputProps = { - value: string; - onEnter: ?(() => void); - onChange: ?((newValue: string) => void); + value: string, + onEnter: ?() => void, + onChange: ?(newValue: string) => void, }; type AccountInputState = { - value: string; - selectionRange: SelectionRange; + value: string, + selectionRange: SelectionRange, }; type SelectionRange = [number, number]; @@ -34,12 +34,12 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc static defaultProps = { value: '', onEnter: null, - onChange: null + onChange: null, }; state = { value: '', - selectionRange: [0, 0] + selectionRange: [0, 0], }; _ref: ?TextInput; @@ -53,25 +53,27 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc this.state = { value: val, - selectionRange: [val.length, val.length] + selectionRange: [val.length, val.length], }; } componentWillReceiveProps(nextProps: AccountInputProps) { const nextVal = this.sanitize(nextProps.value); - if(nextVal !== this.state.value) { + if (nextVal !== this.state.value) { const len = nextVal.length; this.setState({ value: nextVal, selectionRange: [len, len] }); } } shouldComponentUpdate(nextProps: AccountInputProps, nextState: AccountInputState) { - return (this.props.value !== nextProps.value || - this.props.onEnter !== nextProps.onEnter || - this.props.onChange !== nextProps.onChange || - this.state.value !== nextState.value || - this.state.selectionRange[0] !== nextState.selectionRange[0] || - this.state.selectionRange[1] !== nextState.selectionRange[1]); + return ( + this.props.value !== nextProps.value || + this.props.onEnter !== nextProps.onEnter || + this.props.onChange !== nextProps.onChange || + this.state.value !== nextState.value || + this.state.selectionRange[0] !== nextState.selectionRange[0] || + this.state.selectionRange[1] !== nextState.selectionRange[1] + ); } render() { @@ -79,19 +81,20 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc // eslint-disable-next-line no-unused-vars const { value, onChange, onEnter, ...otherProps } = this.props; return ( - <TextInput { ...otherProps } - value={ displayString } - onSelectionChange={ this.onSelect } - onPaste={ this.onPaste } - onCut={ this.onCut } - ref={ (ref) => this.onRef(ref) } - autoCorrect={ false } - onChangeText={ () => {} } - onKeyPress={ this.onKeyPress } + <TextInput + {...otherProps} + value={displayString} + onSelectionChange={this.onSelect} + onPaste={this.onPaste} + onCut={this.onCut} + ref={(ref) => this.onRef(ref)} + autoCorrect={false} + onChangeText={() => {}} + onKeyPress={this.onKeyPress} returnKeyType="done" keyboardType="numeric" placeholderTextColor={colors.blue20} - testName='AccountInput' + testName="AccountInput" /> ); } @@ -123,7 +126,6 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc return { value: newVal, selectionRange: [selectionOffset, selectionOffset] }; } - /** * Modify string by removing single character or range of characters based on selection range. * @@ -137,7 +139,7 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc remove(val: string, selRange: SelectionRange): AccountInputState { let newVal, selectionOffset; - if(selRange[0] === selRange[1]) { + if (selRange[0] === selRange[1]) { const oneOff = Math.max(0, selRange[0] - 1); const head = val.slice(0, oneOff); const tail = val.slice(selRange[0], val.length); @@ -153,7 +155,6 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc return { value: newVal, selectionRange: [selectionOffset, selectionOffset] }; } - /** * Convert DOM selection range to internal selection range * @@ -176,12 +177,11 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc const within = countSpaces(fmt.slice(start, end)); start -= before; - end -= (before + within); + end -= before + within; - return [ start, end ]; + return [start, end]; } - /** * Convert internal selection range to DOM selection range * @@ -194,7 +194,9 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc */ toDomSelection(val: string, selRange: SelectionRange): SelectionRange { const countSpaces = (val, untilIndex) => { - if(val.length > 12) { return 0; } + if (val.length > 12) { + return 0; + } return Math.floor(untilIndex / 4); // groups of 4 digits }; @@ -206,7 +208,7 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc start += startSpaces; end += startSpaces + (endSpaces - startSpaces); - return [ start, end ]; + return [start, end]; } // Events @@ -214,40 +216,42 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc onKeyPress = (e: KeyboardEvent) => { const { value, selectionRange } = this.state; - if(e.which === 8) { // backspace + if (e.which === 8) { + // backspace this._ignoreSelect = true; const result = this.remove(value, selectionRange); e.preventDefault(); this.setState(result, () => { this._ignoreSelect = false; - if(this.props.onChange) { + if (this.props.onChange) { this.props.onChange(result.value); } }); - } else if(/^[0-9]$/.test(e.key)) { // digits or cmd+v + } else if (/^[0-9]$/.test(e.key)) { + // digits or cmd+v this._ignoreSelect = true; const result = this.insert(value, e.key, selectionRange); e.preventDefault(); this.setState(result, () => { this._ignoreSelect = false; - if(this.props.onChange) { + if (this.props.onChange) { this.props.onChange(result.value); } }); - } else if(e.which === 13 && this.props.onEnter) { + } else if (e.which === 13 && this.props.onEnter) { this.props.onEnter(); } - } + }; onSelect = (start: number, end: number) => { - if (this._ignoreSelect){ + if (this._ignoreSelect) { return; } const selRange = this.toInternalSelectionRange(this.sanitize(this.state.value), [start, end]); this.setState({ selectionRange: selRange }); - } + }; onPaste = (e: ClipboardEvent) => { const { value, selectionRange } = this.state; @@ -256,15 +260,15 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc const result = this.insert(value, filteredData, selectionRange); e.preventDefault(); this.setState(result, () => { - if(this.props.onChange) { + if (this.props.onChange) { this.props.onChange(result.value); } }); - } + }; onCut = (e: ClipboardEvent) => { const target = e.target; - if(!(target instanceof HTMLInputElement)) { + if (!(target instanceof HTMLInputElement)) { throw new Error('ref must be an instance of HTMLInputElement'); } @@ -273,7 +277,7 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc e.preventDefault(); // range is not empty? - if(selectionRange[0] !== selectionRange[1]) { + if (selectionRange[0] !== selectionRange[1]) { const result = this.remove(value, selectionRange); const domSelectionRange = this.toDomSelection(value, selectionRange); const slice = target.value.slice(domSelectionRange[0], domSelectionRange[1]); @@ -281,26 +285,28 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc e.clipboardData.setData('text', slice); this.setState(result, () => { - if(this.props.onChange) { + if (this.props.onChange) { this.props.onChange(result.value); } }); } - } + }; onRef = (ref: ?TextInput) => { this._ref = ref; - if(!ref) { return; } + if (!ref) { + return; + } const { value, selectionRange } = this.state; const domRange = this.toDomSelection(value, selectionRange); ref.selectRange(domRange[0], domRange[1]); - } + }; focus() { - if(this._ref) { + if (this._ref) { this._ref.focus(); } } -}
\ No newline at end of file +} diff --git a/app/components/AccountStyles.js b/app/components/AccountStyles.js index b5ac785fe0..67c16900a7 100644 --- a/app/components/AccountStyles.js +++ b/app/components/AccountStyles.js @@ -104,6 +104,6 @@ export default { lineHeight: 20, letterSpacing: -0.2, color: colors.white80, - } - }) -};
\ No newline at end of file + }, + }), +}; diff --git a/app/components/AdvancedSettings.js b/app/components/AdvancedSettings.js index c4f014d210..eca00a4da1 100644 --- a/app/components/AdvancedSettings.js +++ b/app/components/AdvancedSettings.js @@ -9,7 +9,6 @@ import styles from './AdvancedSettingsStyles'; import Img from './Img'; export class AdvancedSettings extends Component { - props: { protocol: string, port: string | number, @@ -27,62 +26,66 @@ export class AdvancedSettings extends Component { portSelector = this._createPortSelector(); } - return <BaseLayout onClose={ this.props.onClose }> - - <Selector - title={ 'Network protocols' } - values={ ['Automatic', 'UDP', 'TCP'] } - value={ protocol } - onSelect={ protocol => { - this.props.onUpdate(protocol, 'Automatic'); - }}/> + return ( + <BaseLayout onClose={this.props.onClose}> + <Selector + title={'Network protocols'} + values={['Automatic', 'UDP', 'TCP']} + value={protocol} + onSelect={(protocol) => { + this.props.onUpdate(protocol, 'Automatic'); + }} + /> - <View style={ styles.advanced_settings__cell_spacer }></View> + <View style={styles.advanced_settings__cell_spacer} /> - { portSelector } - - </BaseLayout>; + {portSelector} + </BaseLayout> + ); } _createPortSelector() { const protocol = this.props.protocol.toUpperCase(); - const ports = protocol === 'TCP' - ? ['Automatic', 80, 443] - : ['Automatic', 1194, 1195, 1196, 1197, 1300, 1301, 1302]; + const ports = + protocol === 'TCP' + ? ['Automatic', 80, 443] + : ['Automatic', 1194, 1195, 1196, 1197, 1300, 1301, 1302]; - return <Selector - title={ protocol + ' port' } - values={ ports } - value={ this.props.port } - onSelect={ port => { - this.props.onUpdate(protocol, port); - }} />; + return ( + <Selector + title={protocol + ' port'} + values={ports} + value={this.props.port} + onSelect={(port) => { + this.props.onUpdate(protocol, port); + }} + /> + ); } } class Selector extends Component { - props: { title: string, values: Array<*>, value: *, onSelect: (*) => void, - } + }; state = { hoveredButtonIndex: -1 }; handleButtonHover = (value) => { this.setState({ hoveredButtonIndex: value }); - } + }; render() { - return <View> - <View style={ styles.advanced_settings__section_title }> - { this.props.title } - </View> + return ( + <View> + <View style={styles.advanced_settings__section_title}>{this.props.title}</View> - { this.props.values.map(value => this._renderCell(value)) } - </View>; + {this.props.values.map((value) => this._renderCell(value))} + </View> + ); } _renderCell(value) { @@ -95,54 +98,75 @@ class Selector extends Component { } _renderSelectedCell(value) { - return <Button style={[ styles.advanced_settings__cell, value === this.state.hoveredButtonIndex ? styles.advanced_settings__cell_selected_hover : null ]} - onPress={ () => this.props.onSelect(value) } - onHoverStart={() => this.handleButtonHover(value)} - onHoverEnd={() => this.handleButtonHover(-1)} - key={ value }> - <Img style={ styles.advanced_settings__cell_icon } - source='icon-tick' - tintColor='currentColor'/> - <Text style={ styles.advanced_settings__cell_label }>{ value }</Text> - </Button>; + return ( + <Button + style={[ + styles.advanced_settings__cell, + value === this.state.hoveredButtonIndex + ? styles.advanced_settings__cell_selected_hover + : null, + ]} + onPress={() => this.props.onSelect(value)} + onHoverStart={() => this.handleButtonHover(value)} + onHoverEnd={() => this.handleButtonHover(-1)} + key={value}> + <Img + style={styles.advanced_settings__cell_icon} + source="icon-tick" + tintColor="currentColor" + /> + <Text style={styles.advanced_settings__cell_label}>{value}</Text> + </Button> + ); } _renderUnselectedCell(value) { - return <Button style={[ styles.advanced_settings__cell_dimmed, value === this.state.hoveredButtonIndex ? styles.advanced_settings__cell_hover : null ]} - onPress={ () => this.props.onSelect(value) } - onHoverStart={() => this.handleButtonHover(value)} - onHoverEnd={() => this.handleButtonHover(-1)} - key={ value }> - <View style={ styles.advanced_settings__cell_icon }></View> - <Text style={ styles.advanced_settings__cell_label }>{ value }</Text> - </Button>; + return ( + <Button + style={[ + styles.advanced_settings__cell_dimmed, + value === this.state.hoveredButtonIndex ? styles.advanced_settings__cell_hover : null, + ]} + onPress={() => this.props.onSelect(value)} + onHoverStart={() => this.handleButtonHover(value)} + onHoverEnd={() => this.handleButtonHover(-1)} + key={value}> + <View style={styles.advanced_settings__cell_icon} /> + <Text style={styles.advanced_settings__cell_label}>{value}</Text> + </Button> + ); } } function BaseLayout(props) { - return <Layout> - <Container> - <View style={ styles.advanced_settings }> - <Button style={ styles.advanced_settings__close } - onPress={ props.onClose } - testName='closeButton'> - <View style={ styles.advanced_settings__close_content }> - <Img height={24} width={24} style={ styles.advanced_settings__close_icon } source="icon-back" /> - <Text style={ styles.advanced_settings__close_title }>Settings</Text> - </View> - </Button> - <View style={ styles.advanced_settings__container }> - <View style={ styles.advanced_settings__header }> - <Text style={ styles.advanced_settings__title }>Advanced</Text> - </View> - <CustomScrollbars style={styles.advanced_settings__scrollview} autoHide={ true }> - <View style={ styles.advanced_settings__content }> - { props.children } + return ( + <Layout> + <Container> + <View style={styles.advanced_settings}> + <Button + style={styles.advanced_settings__close} + onPress={props.onClose} + testName="closeButton"> + <View style={styles.advanced_settings__close_content}> + <Img + height={24} + width={24} + style={styles.advanced_settings__close_icon} + source="icon-back" + /> + <Text style={styles.advanced_settings__close_title}>Settings</Text> </View> - </CustomScrollbars> + </Button> + <View style={styles.advanced_settings__container}> + <View style={styles.advanced_settings__header}> + <Text style={styles.advanced_settings__title}>Advanced</Text> + </View> + <CustomScrollbars style={styles.advanced_settings__scrollview} autoHide={true}> + <View style={styles.advanced_settings__content}>{props.children}</View> + </CustomScrollbars> + </View> </View> - </View> - </Container> - </Layout>; + </Container> + </Layout> + ); } - diff --git a/app/components/AdvancedSettingsStyles.js b/app/components/AdvancedSettingsStyles.js index 75ed925b70..95ec71fb41 100644 --- a/app/components/AdvancedSettingsStyles.js +++ b/app/components/AdvancedSettingsStyles.js @@ -137,7 +137,7 @@ export default { fontWeight: '600', lineHeight: 20, letterSpacing: -0.2, - color: 'rgba(255,255,255,0.8)' - } - }) -};
\ No newline at end of file + color: 'rgba(255,255,255,0.8)', + }, + }), +}; diff --git a/app/components/Connect.js b/app/components/Connect.js index 79d14df743..3fbe04cefa 100644 --- a/app/components/Connect.js +++ b/app/components/Connect.js @@ -50,7 +50,6 @@ export default class Connect extends Component<ConnectProps, ConnectState> { // shallow compare the connection !shallowCompare(prevConnection, nextConnection) || !shallowCompare(otherPrevProps, otherNextProps) || - prevState.mapOffset[0] !== nextState.mapOffset[0] || prevState.mapOffset[1] !== nextState.mapOffset[1] || prevState.showCopyIPMessage !== nextState.showCopyIPMessage @@ -58,7 +57,7 @@ export default class Connect extends Component<ConnectProps, ConnectState> { } componentWillUnmount() { - if(this._copyTimer) { + if (this._copyTimer) { clearTimeout(this._copyTimer); } } @@ -69,10 +68,13 @@ export default class Connect extends Component<ConnectProps, ConnectState> { return ( <Layout> - <Header style={ this.headerStyle() } showSettings={ true } onSettings={ this.props.onSettings } testName='header'/> - <Container> - { child } - </Container> + <Header + style={this.headerStyle()} + showSettings={true} + onSettings={this.props.onSettings} + testName="header" + /> + <Container>{child}</Container> </Layout> ); } @@ -84,21 +86,16 @@ export default class Connect extends Component<ConnectProps, ConnectState> { <Img source="icon-fail" height={60} width={60} alt="" /> </View> <View style={styles.status}> - <View style={styles.error_title}> - { error.title } - </View> - <View style={styles.error_message}> - { error.message } - </View> - { error.type === 'NO_CREDIT' ? + <View style={styles.error_title}>{error.title}</View> + <View style={styles.error_message}>{error.message}</View> + {error.type === 'NO_CREDIT' ? ( <View> - <GreenButton onPress={ this.onExternalLink.bind(this, 'purchase') }> + <GreenButton onPress={this.onExternalLink.bind(this, 'purchase')}> <Label>Buy more time</Label> - <Img source='icon-extLink' height={16} width={16} /> + <Img source="icon-extLink" height={16} width={16} /> </GreenButton> </View> - : null - } + ) : null} </View> </View> ); @@ -108,7 +105,7 @@ export default class Connect extends Component<ConnectProps, ConnectState> { const { longitude, latitude, status } = this.props.connection; // when the user location is known - if(typeof(longitude) === 'number' && typeof(latitude) === 'number') { + if (typeof longitude === 'number' && typeof latitude === 'number') { return { center: [longitude, latitude], // do not show the marker when connecting @@ -133,141 +130,158 @@ export default class Connect extends Component<ConnectProps, ConnectState> { } _updateMapOffset = (spinnerNode: ?HTMLElement) => { - if(spinnerNode) { + if (spinnerNode) { // calculate the vertical offset from the center of the map // to shift the center of the map upwards to align the centers // of spinner and marker on the map const y = spinnerNode.offsetTop + spinnerNode.clientHeight * 0.5; this.setState({ - mapOffset: [0, y] + mapOffset: [0, y], }); } - } + }; renderMap() { - let [ isConnecting, isConnected, isDisconnected ] = [false, false, false]; - switch(this.props.connection.status) { - case 'connecting': isConnecting = true; break; - case 'connected': isConnected = true; break; - case 'disconnected': isDisconnected = true; break; + let [isConnecting, isConnected, isDisconnected] = [false, false, false]; + switch (this.props.connection.status) { + case 'connecting': + isConnecting = true; + break; + case 'connected': + isConnected = true; + break; + case 'disconnected': + isDisconnected = true; + break; } return ( <View style={styles.connect}> <View style={styles.map}> - <Map style={{ width: '100%', height: '100%' }} { ...this._getMapProps() } /> + <Map style={{ width: '100%', height: '100%' }} {...this._getMapProps()} /> </View> <View style={styles.container}> + {this._renderIsBlockingInternetMessage()} - { this._renderIsBlockingInternetMessage() } - - { /* show spinner when connecting */ } - { isConnecting ? - <View style={ styles.status_icon }> - <Img source='icon-spinner' height={60} width={60} alt="" ref={ this._updateMapOffset } /> + {/* show spinner when connecting */} + {isConnecting ? ( + <View style={styles.status_icon}> + <Img + source="icon-spinner" + height={60} + width={60} + alt="" + ref={this._updateMapOffset} + /> </View> - : null - } + ) : null} <View style={styles.status}> + <View style={this.networkSecurityStyle()} testName="networkSecurityMessage"> + {this.networkSecurityMessage()} + </View> - <View style={ this.networkSecurityStyle() } testName='networkSecurityMessage'>{ this.networkSecurityMessage() }</View> - - { /* + {/* ********************************** Begin: Location block ********************************** - */ } + */} - { /* location when connecting or disconnected */ } - { isConnecting || isDisconnected ? - <Text style={styles.status_location} testName='location'> - { this.props.connection.country } + {/* location when connecting or disconnected */} + {isConnecting || isDisconnected ? ( + <Text style={styles.status_location} testName="location"> + {this.props.connection.country} </Text> - : null - } + ) : null} - { /* location when connected */ } - { isConnected ? - <Text style={styles.status_location} testName='location'> - { this.props.connection.city } - { this.props.connection.city && <br/> } - { this.props.connection.country } + {/* location when connected */} + {isConnected ? ( + <Text style={styles.status_location} testName="location"> + {this.props.connection.city} + {this.props.connection.city && <br />} + {this.props.connection.country} </Text> - :null - } + ) : null} - { /* + {/* ********************************** End: Location block ********************************** - */ } + */} - <Text style={ this.ipAddressStyle() } onPress={ this.onIPAddressClick.bind(this) }> - { (isConnected || isDisconnected) ? ( - <Text testName='ipAddress'>{ - this.state.showCopyIPMessage ? - 'IP copied to clipboard!' : - this.props.connection.ip - }</Text>) : null } + <Text style={this.ipAddressStyle()} onPress={this.onIPAddressClick.bind(this)}> + {isConnected || isDisconnected ? ( + <Text testName="ipAddress"> + {this.state.showCopyIPMessage + ? 'IP copied to clipboard!' + : this.props.connection.ip} + </Text> + ) : null} </Text> </View> - - { /* + {/* ********************************** Begin: Footer block ********************************** - */ } + */} - { /* footer when disconnected */ } - { isDisconnected ? + {/* footer when disconnected */} + {isDisconnected ? ( <View style={styles.footer}> - <TransparentButton onPress={ this.props.onSelectLocation }> - <Label>{ this.props.selectedRelayName }</Label> - <Img height={12} width={7} source='icon-chevron' /> + <TransparentButton onPress={this.props.onSelectLocation}> + <Label>{this.props.selectedRelayName}</Label> + <Img height={12} width={7} source="icon-chevron" /> </TransparentButton> - <GreenButton onPress={ this.props.onConnect } testName='secureConnection'>Secure my connection</GreenButton> + <GreenButton onPress={this.props.onConnect} testName="secureConnection"> + Secure my connection + </GreenButton> </View> - : null - } + ) : null} - { /* footer when connecting */ } - { isConnecting ? + {/* footer when connecting */} + {isConnecting ? ( <View style={styles.footer}> - <TransparentButton onPress={ this.props.onSelectLocation }>Switch location</TransparentButton> - <RedTransparentButton onPress={ this.props.onDisconnect }>Cancel</RedTransparentButton> + <TransparentButton onPress={this.props.onSelectLocation}> + Switch location + </TransparentButton> + <RedTransparentButton onPress={this.props.onDisconnect}>Cancel</RedTransparentButton> </View> - : null - } + ) : null} - { /* footer when connected */ } - { isConnected ? + {/* footer when connected */} + {isConnected ? ( <View style={styles.footer}> - <TransparentButton onPress={ this.props.onSelectLocation }>Switch location</TransparentButton> - <RedTransparentButton onPress={ this.props.onDisconnect } testName='disconnect'>Disconnect</RedTransparentButton> + <TransparentButton onPress={this.props.onSelectLocation}> + Switch location + </TransparentButton> + <RedTransparentButton onPress={this.props.onDisconnect} testName="disconnect"> + Disconnect + </RedTransparentButton> </View> - : null - } + ) : null} - { /* + {/* ********************************** End: Footer block ********************************** - */ } - + */} </View> </View> ); } _renderIsBlockingInternetMessage() { - return <Accordion style={styles.blocking_container} height={ (this.props.connection.status === 'connecting') ? 'auto' : 0 }> - <Text style={styles.blocking_message}> - <Text style={styles.blocking_icon}> </Text> - <Text>BLOCKING INTERNET</Text> - </Text> - </Accordion>; + return ( + <Accordion + style={styles.blocking_container} + height={this.props.connection.status === 'connecting' ? 'auto' : 0}> + <Text style={styles.blocking_message}> + <Text style={styles.blocking_icon}> </Text> + <Text>BLOCKING INTERNET</Text> + </Text> + </Accordion> + ); } // Handlers @@ -286,37 +300,40 @@ export default class Connect extends Component<ConnectProps, ConnectState> { // Private headerStyle(): HeaderBarStyle { - switch(this.props.connection.status) { - case 'disconnected': - return 'error'; - case 'connecting': - case 'connected': - return 'success'; + switch (this.props.connection.status) { + case 'disconnected': + return 'error'; + case 'connecting': + case 'connected': + return 'success'; } throw new Error('Invalid ConnectionState'); } networkSecurityStyle(): Types.Style { let classes = [styles.status_security]; - if(this.props.connection.status === 'connected') { + if (this.props.connection.status === 'connected') { classes.push(styles.status_security__secure); - } else if(this.props.connection.status === 'disconnected') { + } else if (this.props.connection.status === 'disconnected') { classes.push(styles.status_security__unsecured); } return classes; } networkSecurityMessage(): string { - switch(this.props.connection.status) { - case 'connected': return 'SECURE CONNECTION'; - case 'connecting': return 'CREATING SECURE CONNECTION'; - default: return 'UNSECURED CONNECTION'; + switch (this.props.connection.status) { + case 'connected': + return 'SECURE CONNECTION'; + case 'connecting': + return 'CREATING SECURE CONNECTION'; + default: + return 'UNSECURED CONNECTION'; } } ipAddressStyle(): Types.Style { var classes = [styles.status_ipaddress]; - if(this.props.connection.status === 'connecting') { + if (this.props.connection.status === 'connecting') { classes.push(styles.status_ipaddress__invisible); } return classes; @@ -324,13 +341,13 @@ export default class Connect extends Component<ConnectProps, ConnectState> { displayError(): ?BackendError { // Offline? - if(!this.props.connection.isOnline) { + if (!this.props.connection.isOnline) { return new BackendError('NO_INTERNET'); } // No credit? const expiry = this.props.accountExpiry; - if(expiry && moment(expiry).isSameOrBefore(moment())) { + if (expiry && moment(expiry).isSameOrBefore(moment())) { return new BackendError('NO_CREDIT'); } @@ -340,8 +357,5 @@ export default class Connect extends Component<ConnectProps, ConnectState> { function shallowCompare(lhs: Object, rhs: Object) { const keys = Object.keys(lhs); - return ( - keys.length === Object.keys(rhs).length && - keys.every(key => lhs[key] === rhs[key]) - ); + return keys.length === Object.keys(rhs).length && keys.every((key) => lhs[key] === rhs[key]); } diff --git a/app/components/ConnectStyles.js b/app/components/ConnectStyles.js index 413d2f0c1d..85558d6a12 100644 --- a/app/components/ConnectStyles.js +++ b/app/components/ConnectStyles.js @@ -21,11 +21,10 @@ export default { container: { flexDirection: 'column', flex: 1, - position: 'relative', /* need this for z-index to work to cover map */ + position: 'relative' /* need this for z-index to work to cover map */, zIndex: 1, - }, - footer:{ + footer: { flex: 0, marginBottom: 16, }, @@ -62,7 +61,7 @@ export default { flex: 1, }, status_icon: { - position:'absolute', + position: 'absolute', alignSelf: 'center', width: 60, height: 60, @@ -92,7 +91,7 @@ export default { letterSpacing: -0.7, color: colors.white, marginBottom: 7, - flex:0, + flex: 0, }, error_title: { fontFamily: 'DINPro', @@ -143,4 +142,4 @@ export default { marginBottom: 4, }, }), -};
\ No newline at end of file +}; diff --git a/app/components/CustomScrollbars.android.js b/app/components/CustomScrollbars.android.js index 8c9ea03da2..dadc462234 100644 --- a/app/components/CustomScrollbars.android.js +++ b/app/components/CustomScrollbars.android.js @@ -28,9 +28,6 @@ export default class CustomScrollbars extends Component<Props, State> { render() { const { autoHide: _autoHide, thumbInset: _thumbInset, children, ...otherProps } = this.props; - return ( - <View { ...otherProps }> - { children } - </View>); + return <View {...otherProps}>{children}</View>; } } diff --git a/app/components/CustomScrollbars.js b/app/components/CustomScrollbars.js index cb1014fd0a..6539a91185 100644 --- a/app/components/CustomScrollbars.js +++ b/app/components/CustomScrollbars.js @@ -39,7 +39,7 @@ export default class CustomScrollbars extends React.Component<Props, State> { scrollTo(x: number, y: number) { const scrollable = this._scrollableElement; - if(scrollable) { + if (scrollable) { scrollable.scrollLeft = x; scrollable.scrollTop = y; } @@ -47,10 +47,12 @@ export default class CustomScrollbars extends React.Component<Props, State> { scrollToElement(child: HTMLElement, scrollPosition: ScrollPosition) { const scrollable = this._scrollableElement; - if(scrollable) { + if (scrollable) { // throw if child is not a descendant of scroll view - if(!scrollable.contains(child)) { - throw new Error('Cannot scroll to an element which is not a descendant of CustomScrollbars.'); + if (!scrollable.contains(child)) { + throw new Error( + 'Cannot scroll to an element which is not a descendant of CustomScrollbars.', + ); } const scrollTop = this._computeScrollTop(scrollable, child, scrollPosition); @@ -62,11 +64,11 @@ export default class CustomScrollbars extends React.Component<Props, State> { componentDidMount() { this._updateScrollbarsHelper({ position: true, - size: true + size: true, }); // show scroll indicators briefly when mounted - if(this.props.autoHide) { + if (this.props.autoHide) { this._startAutoHide(); } } @@ -78,7 +80,7 @@ export default class CustomScrollbars extends React.Component<Props, State> { componentDidUpdate() { this._updateScrollbarsHelper({ position: true, - size: true + size: true, }); } @@ -87,15 +89,18 @@ export default class CustomScrollbars extends React.Component<Props, State> { const showScrollbars = this.state.canScroll && this.state.showScrollIndicators; const thumbAnimationClass = showScrollbars ? ' custom-scrollbars__thumb--visible' : ''; return ( - <div {...otherProps } className="custom-scrollbars"> - <div className={ `custom-scrollbars__thumb ${thumbAnimationClass}` } + <div {...otherProps} className="custom-scrollbars"> + <div + className={`custom-scrollbars__thumb ${thumbAnimationClass}`} style={{ position: 'absolute', top: 0, right: 0 }} - ref={ this._onThumbRef }></div> - <div className="custom-scrollbars__scrollable" + ref={this._onThumbRef} + /> + <div + className="custom-scrollbars__scrollable" style={{ overflow: 'auto' }} - onScroll={ this._onScroll } - ref={ this._onScrollableRef }> - { children } + onScroll={this._onScroll} + ref={this._onScrollableRef}> + {children} </div> </div> ); @@ -103,22 +108,22 @@ export default class CustomScrollbars extends React.Component<Props, State> { _onScrollableRef = (ref) => { this._scrollableElement = ref; - } + }; _onThumbRef = (ref) => { this._thumbElement = ref; - } + }; _onScroll = () => { this._updateScrollbarsHelper({ position: true }); - if(this.props.autoHide) { + if (this.props.autoHide) { this._startAutoHide(); } - } + }; _startAutoHide() { - if(this._autoHideTimer) { + if (this._autoHideTimer) { clearTimeout(this._autoHideTimer); } @@ -128,7 +133,7 @@ export default class CustomScrollbars extends React.Component<Props, State> { }); }, AUTOHIDE_TIMEOUT); - if(!this.state.showScrollIndicators) { + if (!this.state.showScrollIndicators) { this.setState({ showScrollIndicators: true, }); @@ -136,25 +141,25 @@ export default class CustomScrollbars extends React.Component<Props, State> { } _stopAutoHide() { - if(this._autoHideTimer) { + if (this._autoHideTimer) { clearTimeout(this._autoHideTimer); this._autoHideTimer = null; } } _computeScrollTop(scrollable: HTMLElement, child: HTMLElement, scrollPosition: ScrollPosition) { - switch(scrollPosition) { - case 'top': - return child.offsetTop; + switch (scrollPosition) { + case 'top': + return child.offsetTop; - case 'bottom': - return child.offsetTop - (scrollable.offsetHeight - child.clientHeight); + case 'bottom': + return child.offsetTop - (scrollable.offsetHeight - child.clientHeight); - case 'middle': - return child.offsetTop - ((scrollable.offsetHeight - child.clientHeight) * 0.5); + case 'middle': + return child.offsetTop - (scrollable.offsetHeight - child.clientHeight) * 0.5; - default: - throw new Error(`Unknown enum type for ScrollPosition: ${ scrollPosition }`); + default: + throw new Error(`Unknown enum type for ScrollPosition: ${scrollPosition}`); } } @@ -178,11 +183,11 @@ export default class CustomScrollbars extends React.Component<Props, State> { // calculate the thumb boundary to make sure that the visual appearance of // a thumb at lowest point matches the bottom of scrollable view - const thumbBoundary = visibleHeight - thumbHeight - (this.props.thumbInset.y * 2); + const thumbBoundary = visibleHeight - thumbHeight - this.props.thumbInset.y * 2; // calculate thumb position based on scroll progress and thumb boundary // adding vertical inset to adjust the thumb's appearance - const thumbPosition = (thumbBoundary * scrollPosition) + this.props.thumbInset.y; + const thumbPosition = thumbBoundary * scrollPosition + this.props.thumbInset.y; return { x: -this.props.thumbInset.x, @@ -203,29 +208,33 @@ export default class CustomScrollbars extends React.Component<Props, State> { _updateScrollbarsHelper(updateFlags: $Shape<ScrollbarUpdateContext>) { const scrollable = this._scrollableElement; const thumb = this._thumbElement; - if(scrollable && thumb) { + if (scrollable && thumb) { this._updateScrollbars(scrollable, thumb, updateFlags); } } - _updateScrollbars(scrollable: HTMLElement, thumb: HTMLElement, context: $Shape<ScrollbarUpdateContext>) { - if(context.size) { + _updateScrollbars( + scrollable: HTMLElement, + thumb: HTMLElement, + context: $Shape<ScrollbarUpdateContext>, + ) { + if (context.size) { const thumbHeight = this._computeThumbHeight(scrollable); thumb.style.setProperty('height', thumbHeight + 'px'); // hide thumb when there is nothing to scroll - const canScroll = (thumbHeight < scrollable.offsetHeight); - if(this.state.canScroll !== canScroll) { + const canScroll = thumbHeight < scrollable.offsetHeight; + if (this.state.canScroll !== canScroll) { this.setState({ canScroll }); // flash the scroll indicators when the view becomes scrollable - if(this.props.autoHide && canScroll) { + if (this.props.autoHide && canScroll) { this._startAutoHide(); } } } - if(context.position) { + if (context.position) { const { x, y } = this._computeThumbPosition(scrollable, thumb); thumb.style.setProperty('transform', `translate(${x}px, ${y}px)`); } diff --git a/app/components/HeaderBar.js b/app/components/HeaderBar.js index 6f44ff7a00..4472b05dcc 100644 --- a/app/components/HeaderBar.js +++ b/app/components/HeaderBar.js @@ -1,11 +1,6 @@ // @flow import React from 'react'; -import { - Component, - Text, - Button, - View -} from 'reactxp'; +import { Component, Text, Button, View } from 'reactxp'; import Img from './Img'; @@ -14,10 +9,10 @@ import platformStyles from './HeaderBarPlatformStyles'; export type HeaderBarStyle = 'default' | 'defaultDark' | 'error' | 'success'; export type HeaderBarProps = { - style: HeaderBarStyle; - hidden: boolean; - showSettings: boolean; - onSettings: ?(() => void); + style: HeaderBarStyle, + hidden: boolean, + showSettings: boolean, + onSettings: ?() => void, }; export default class HeaderBar extends Component { @@ -26,34 +21,43 @@ export default class HeaderBar extends Component { style: 'default', hidden: false, showSettings: false, - onSettings: null + onSettings: null, }; render() { let containerClass = [ styles['headerbar'], platformStyles[process.platform], - styles['style_' + this.props.style] + styles['style_' + this.props.style], ]; - if(this.props.hidden) { + if (this.props.hidden) { containerClass.push(styles['hidden']); } return ( - <View style={ containerClass }> - {!this.props.hidden ? + <View style={containerClass}> + {!this.props.hidden ? ( <View style={styles.container} testName="headerbar__container"> - <Img height={50} width={50} source='logo-icon'/> + <Img height={50} width={50} source="logo-icon" /> <Text style={styles.title}>MULLVAD VPN</Text> </View> - : null} + ) : null} - {this.props.showSettings ? - <Button style={ styles.settings } onPress={ this.props.onSettings } testName="headerbar__settings"> - <Img height={24} width={24} source='icon-settings' style={[ styles.settings_icon, platformStyles.settings_icon ]} hoverStyle={ styles.settings_icon_hover }/> + {this.props.showSettings ? ( + <Button + style={styles.settings} + onPress={this.props.onSettings} + testName="headerbar__settings"> + <Img + height={24} + width={24} + source="icon-settings" + style={[styles.settings_icon, platformStyles.settings_icon]} + hoverStyle={styles.settings_icon_hover} + /> </Button> - : null} + ) : null} </View> ); } diff --git a/app/components/HeaderBarPlatformStyles.js b/app/components/HeaderBarPlatformStyles.js index 241bdb1a60..c60967797a 100644 --- a/app/components/HeaderBarPlatformStyles.js +++ b/app/components/HeaderBarPlatformStyles.js @@ -12,5 +12,5 @@ export default { settings_icon: { WebkitAppRegion: 'no-drag', }, - }) + }), }; diff --git a/app/components/HeaderBarStyles.js b/app/components/HeaderBarStyles.js index c3e0e2ad5c..01b3c89f5c 100644 --- a/app/components/HeaderBarStyles.js +++ b/app/components/HeaderBarStyles.js @@ -35,7 +35,7 @@ export default { alignItems: 'center', }, settings: { - padding: 0 + padding: 0, }, settings_icon: { color: colors.white60, @@ -53,6 +53,6 @@ export default { letterSpacing: -0.5, color: colors.white60, marginLeft: 8, - } - }) + }, + }), }; diff --git a/app/components/Img.android.js b/app/components/Img.android.js index c1deb53bf7..229cb2c1b7 100644 --- a/app/components/Img.android.js +++ b/app/components/Img.android.js @@ -1,5 +1,5 @@ // @flow -import React, { Component } from 'react'; +import React, { Component } from 'react'; import { StyleSheet } from 'react-native'; import { Image, Styles } from 'reactxp'; @@ -9,10 +9,10 @@ export default class Img extends Component { style: Object, tintColor?: string, height?: number, - width?:number, + width?: number, }; - render(){ + render() { const width = this.props.width || 7; const height = this.props.height || 12; const source = this.props.source || 'icon-chevron'; @@ -20,12 +20,24 @@ export default class Img extends Component { if (tintColor === 'currentColor' && this.props.style) { const { color: tint, ...otherStyles } = StyleSheet.flatten(this.props.style); - return( - <Image style={Styles.createViewStyle({ ...otherStyles, tintColor: tint, height: height, width: width }, false)} source={ source }/> + return ( + <Image + style={Styles.createViewStyle( + { ...otherStyles, tintColor: tint, height: height, width: width }, + false, + )} + source={source} + /> ); } else { - return( - <Image style={Styles.createViewStyle({ ...this.props.style, height: height, width: width }, false)} source={ source }/> + return ( + <Image + style={Styles.createViewStyle( + { ...this.props.style, height: height, width: width }, + false, + )} + source={source} + /> ); } } diff --git a/app/components/Img.js b/app/components/Img.js index d124eb7f49..fb74ac9fe6 100644 --- a/app/components/Img.js +++ b/app/components/Img.js @@ -3,22 +3,21 @@ import * as React from 'react'; import { View, Component, Types } from 'reactxp'; type ImgProps = { - source: string, - tintColor?: string, - hoverStyle?: Types.ViewStyle, - disabled?: boolean, - }; + source: string, + tintColor?: string, + hoverStyle?: Types.ViewStyle, + disabled?: boolean, +}; type State = { hovered: boolean }; export default class Img extends Component<ImgProps, State> { - state = { hovered: false }; - onHoverStart = () => !this.props.disabled ? this.setState({ hovered: true }) : null; - onHoverEnd = () => !this.props.disabled ? this.setState({ hovered: false }) : null; + onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); + onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); - getHoverStyle = () => this.state.hovered ? this.props.hoverStyle || null : null; + getHoverStyle = () => (this.state.hovered ? this.props.hoverStyle || null : null); render() { const { source, style, onMouseEnter, onMouseLeave, ...otherProps } = this.props; @@ -26,31 +25,35 @@ export default class Img extends Component<ImgProps, State> { const url = './assets/images/' + source + '.svg'; let image; - if(tintColor) { + if (tintColor) { image = ( - <div style={{ - WebkitMaskImage: `url('${url}')`, - WebkitMaskRepeat: 'no-repeat', - backgroundColor: tintColor, - lineHeight: 0, - }}> - <img src={ url } style={{ - visibility: 'hidden', - }} /> + <div + style={{ + WebkitMaskImage: `url('${url}')`, + WebkitMaskRepeat: 'no-repeat', + backgroundColor: tintColor, + lineHeight: 0, + }}> + <img + src={url} + style={{ + visibility: 'hidden', + }} + /> </div> ); } else { - image = ( - <img src={ url } /> - ); + image = <img src={url} />; } return ( - <View { ...otherProps } + <View + {...otherProps} onMouseEnter={onMouseEnter || this.onHoverStart} onMouseLeave={onMouseLeave || this.onHoverEnd} style={[style, this.getHoverStyle()]}> - { image } - </View>); + {image} + </View> + ); } } diff --git a/app/components/Layout.js b/app/components/Layout.js index dea641919c..23d8e7c20c 100644 --- a/app/components/Layout.js +++ b/app/components/Layout.js @@ -14,7 +14,7 @@ export class Header extends Component { render() { return ( <View style={styles.header}> - <HeaderBar { ...this.props } /> + <HeaderBar {...this.props} /> </View> ); } @@ -22,28 +22,20 @@ export class Header extends Component { export class Container extends Component { props: { - children: React.Node - } + children: React.Node, + }; render() { - return ( - <View style={styles.container}> - { this.props.children } - </View> - ); + return <View style={styles.container}>{this.props.children}</View>; } } export class Layout extends Component { props: { - children: Array<React.Node> | React.Node - } + children: Array<React.Node> | React.Node, + }; render() { - return ( - <View style={styles.layout}> - { this.props.children } - </View> - ); + return <View style={styles.layout}>{this.props.children}</View>; } } diff --git a/app/components/LayoutStyles.js b/app/components/LayoutStyles.js index ab32d45e7b..b3ef4f462e 100644 --- a/app/components/LayoutStyles.js +++ b/app/components/LayoutStyles.js @@ -15,6 +15,6 @@ export default { flex: 1, backgroundColor: colors.blue, overflow: 'hidden', - } + }, }), }; diff --git a/app/components/Login.js b/app/components/Login.js index b711f089b5..3100cc54f0 100644 --- a/app/components/Login.js +++ b/app/components/Login.js @@ -16,7 +16,7 @@ import type { AccountToken } from '../lib/ipc-facade'; export type LoginPropTypes = { account: AccountReduxState, onLogin: (accountToken: AccountToken) => void, - onSettings: ?(() => void), + onSettings: ?() => void, onFirstChangeAfterFailure: () => void, onExternalLink: (type: string) => void, onAccountTokenChange: (accountToken: AccountToken) => void, @@ -48,14 +48,18 @@ export default class Login extends Component<LoginPropTypes, State> { constructor(props: LoginPropTypes) { super(props); - if(props.account.status === 'failed') { + if (props.account.status === 'failed') { this.state.notifyOnFirstChangeAfterFailure = true; } this.state.footerAnimationStyle = Styles.createAnimatedViewStyle({ - transform: [{translateY: this.state.animatedFooterValue }] + transform: [{ translateY: this.state.animatedFooterValue }], }); this.state.loginButtonAnimationStyle = Styles.createAnimatedViewStyle({ - backgroundColor: Animated.interpolate(this.state.animatedLoginButtonValue, [0.0, 1.0], [colors.white, colors.green]), + backgroundColor: Animated.interpolate( + this.state.animatedLoginButtonValue, + [0.0, 1.0], + [colors.white, colors.green], + ), }); } @@ -63,7 +67,7 @@ export default class Login extends Component<LoginPropTypes, State> { const prev = this.props.account || {}; const next = nextProps.account || {}; - if(prev.status !== next.status && next.status === 'failed') { + if (prev.status !== next.status && next.status === 'failed') { this.setState({ notifyOnFirstChangeAfterFailure: true }); } @@ -73,36 +77,38 @@ export default class Login extends Component<LoginPropTypes, State> { render() { return ( <Layout> - <Header showSettings={ true } onSettings={ this.props.onSettings } /> + <Header showSettings={true} onSettings={this.props.onSettings} /> <Container> <View style={styles.login_form}> - { this._getStatusIcon() } - <Text style={styles.title}>{ this._formTitle() }</Text> + {this._getStatusIcon()} + <Text style={styles.title}>{this._formTitle()}</Text> - {this._shouldShowLoginForm() && <View> - { this._createLoginForm() } - </View>} + {this._shouldShowLoginForm() && <View>{this._createLoginForm()}</View>} </View> - <Animated.View onLayout={this._onFooterLayout} style={[styles.login_footer, this.state.footerAnimationStyle]} testName={'footerVisibility ' + this._shouldShowFooter(this.props).toString()}> - { this._createFooter() } + <Animated.View + onLayout={this._onFooterLayout} + style={[styles.login_footer, this.state.footerAnimationStyle]} + testName={'footerVisibility ' + this._shouldShowFooter(this.props).toString()}> + {this._createFooter()} </Animated.View> </Container> </Layout> ); } - _onCreateAccount = () => this.props.onExternalLink('createAccount') + _onCreateAccount = () => this.props.onExternalLink('createAccount'); - _onFocus = () => this.setState({ isActive: true }, () => { - this._animate(this.props); - }) + _onFocus = () => + this.setState({ isActive: true }, () => { + this._animate(this.props); + }); _onBlur = (e) => { const relatedTarget = e.relatedTarget; // restore focus if click happened within dropdown - if(relatedTarget) { + if (relatedTarget) { e.target.focus(); return; } @@ -110,7 +116,7 @@ export default class Login extends Component<LoginPropTypes, State> { this.setState({ isActive: false }, () => { this._animate(this.props); }); - } + }; _animate = (props: LoginPropTypes) => { if (this.state.animation) { @@ -119,95 +125,103 @@ export default class Login extends Component<LoginPropTypes, State> { const accountToken = props.account.accountToken || []; const footerPosition = this._shouldShowFooter(props) ? 0 : this.state.footerHeight; - const loginButtonValue = (accountToken.length) > 0 ? 1 : 0; - this._setAnimation(this._getFooterAnimation(footerPosition), this._getLoginButtonAnimation(loginButtonValue)); - } + const loginButtonValue = accountToken.length > 0 ? 1 : 0; + this._setAnimation( + this._getFooterAnimation(footerPosition), + this._getLoginButtonAnimation(loginButtonValue), + ); + }; - _setAnimation = (footerAnimation: Animated.CompositeAnimation, loginButtonAnimation: Animated.CompositeAnimation) => { - let compositeAnimation = Animated.parallel([ footerAnimation, loginButtonAnimation]); - this.setState({animation: compositeAnimation}, () => { - compositeAnimation.start(() => this.setState({ - animation: null - })); + _setAnimation = ( + footerAnimation: Animated.CompositeAnimation, + loginButtonAnimation: Animated.CompositeAnimation, + ) => { + let compositeAnimation = Animated.parallel([footerAnimation, loginButtonAnimation]); + this.setState({ animation: compositeAnimation }, () => { + compositeAnimation.start(() => + this.setState({ + animation: null, + }), + ); }); - } + }; _onLogin = () => { const accountToken = this.props.account.accountToken; - if(accountToken && accountToken.length > 0) { + if (accountToken && accountToken.length > 0) { this.props.onLogin(accountToken); } - } + }; _onInputChange = (value: string) => { // notify delegate on first change after login failure - if(this.state.notifyOnFirstChangeAfterFailure) { + if (this.state.notifyOnFirstChangeAfterFailure) { this.setState({ notifyOnFirstChangeAfterFailure: false }); this.props.onFirstChangeAfterFailure(); } this.props.onAccountTokenChange(value); - } + }; _formTitle() { - switch(this.props.account.status) { - case 'logging in': - return 'Logging in...'; - case 'failed': - return 'Login failed'; - case 'ok': - return 'Login successful'; - default: - return 'Login'; + switch (this.props.account.status) { + case 'logging in': + return 'Logging in...'; + case 'failed': + return 'Login failed'; + case 'ok': + return 'Login successful'; + default: + return 'Login'; } } _formSubtitle() { const { status, error } = this.props.account; - switch(status) { - case 'failed': - return (error && error.message) || 'Unknown error'; - case 'logging in': - return 'Checking account number'; - default: - return 'Enter your account number'; + switch (status) { + case 'failed': + return (error && error.message) || 'Unknown error'; + case 'logging in': + return 'Checking account number'; + default: + return 'Enter your account number'; } } _getStatusIcon() { const statusIconPath = this._getStatusIconPath(); - return <View style={ styles.status_icon}> - { statusIconPath ? - <Img source={ statusIconPath } height={48} width={48} alt="" /> : - null } - </View>; + return ( + <View style={styles.status_icon}> + {statusIconPath ? <Img source={statusIconPath} height={48} width={48} alt="" /> : null} + </View> + ); } _getStatusIconPath(): ?string { - switch(this.props.account.status) { - case 'logging in': - return 'icon-spinner'; - case 'failed': - return 'icon-fail'; - case 'ok': - return 'icon-success'; - default: - return undefined; + switch (this.props.account.status) { + case 'logging in': + return 'icon-spinner'; + case 'failed': + return 'icon-fail'; + case 'ok': + return 'icon-success'; + default: + return undefined; } } _accountInputGroupStyles(): Array<Object> { const classes = [styles.account_input_group]; - if(this.state.isActive) { + if (this.state.isActive) { classes.push(styles.account_input_group__active); } - switch(this.props.account.status) { - case 'logging in': - classes.push(styles.account_input_group__inactive); - break; - case 'failed': - classes.push(styles.account_input_group__error); - break; + switch (this.props.account.status) { + case 'logging in': + classes.push(styles.account_input_group__inactive); + break; + case 'failed': + classes.push(styles.account_input_group__error); + break; } return classes; @@ -217,7 +231,7 @@ export default class Login extends Component<LoginPropTypes, State> { const { status } = this.props.account; const classes = [styles.input_button]; - if(status === 'logging in') { + if (status === 'logging in') { classes.push(styles.input_button__invisible); } @@ -230,11 +244,11 @@ export default class Login extends Component<LoginPropTypes, State> { const { accountToken, status } = this.props.account; const classes = [styles.input_arrow]; - if(accountToken && accountToken.length > 0) { + if (accountToken && accountToken.length > 0) { classes.push(styles.input_arrow__active); } - if(status === 'logging in') { + if (status === 'logging in') { classes.push(styles.input_arrow__invisible); } @@ -247,9 +261,11 @@ export default class Login extends Component<LoginPropTypes, State> { } _shouldShowAccountHistory(props: LoginPropTypes) { - return this._shouldEnableAccountInput(props) && + return ( + this._shouldEnableAccountInput(props) && this.state.isActive && - props.account.accountHistory.length > 0; + props.account.accountHistory.length > 0 + ); } _shouldShowLoginForm() { @@ -261,7 +277,7 @@ export default class Login extends Component<LoginPropTypes, State> { return (status === 'none' || status === 'failed') && !this._shouldShowAccountHistory(props); } - _getFooterAnimation(toValue: number){ + _getFooterAnimation(toValue: number) { return Animated.timing(this.state.animatedFooterValue, { toValue: toValue, easing: Animated.Easing.InOut(), @@ -271,10 +287,10 @@ export default class Login extends Component<LoginPropTypes, State> { } _onFooterLayout = (layout) => { - this.setState({footerHeight: layout.height}); - } + this.setState({ footerHeight: layout.height }); + }; - _getLoginButtonAnimation(toValue: number){ + _getLoginButtonAnimation(toValue: number) { return Animated.timing(this.state.animatedLoginButtonValue, { toValue: toValue, easing: Animated.Easing.Linear(), @@ -286,7 +302,7 @@ export default class Login extends Component<LoginPropTypes, State> { _onSelectAccountFromHistory = (accountToken) => { this.props.onAccountTokenChange(accountToken); this.props.onLogin(accountToken); - } + }; _createLoginForm() { const { accountHistory, accountToken } = this.props.account; @@ -295,50 +311,68 @@ export default class Login extends Component<LoginPropTypes, State> { // do not refactor this into instance method, // it has to be new function each time to be called on each render const autoFocusOnFailure = (input) => { - if(this.props.account.status === 'failed' && input) { + if (this.props.account.status === 'failed' && input) { input.focus(); } }; - return <View> - <Text style={ styles.subtitle }>{ this._formSubtitle() }</Text> - <View style={ this._accountInputGroupStyles() }> - <View style={ styles.account_input_backdrop}> - <AccountInput style={styles.account_input_textfield} - type="text" - placeholder="0000 0000 0000 0000" - placeholderTextColor={colors.blue40} - onFocus={ this._onFocus } - onBlur={ this._onBlur } - onChange={ this._onInputChange } - onEnter={ this._onLogin } - value={ accountToken || '' } - disabled={ !this._shouldEnableAccountInput(this.props) } - autoFocus={ true } - ref={ autoFocusOnFailure } - testName='AccountInput'/> - <Animated.View style={this._accountInputButtonStyles()} onPress={ this._onLogin } testName='account-input-button'> - <Img style={this._accountInputArrowStyles()} source='icon-arrow' height={16} width={24} tintColor='currentColor' /> - </Animated.View> + return ( + <View> + <Text style={styles.subtitle}>{this._formSubtitle()}</Text> + <View style={this._accountInputGroupStyles()}> + <View style={styles.account_input_backdrop}> + <AccountInput + style={styles.account_input_textfield} + type="text" + placeholder="0000 0000 0000 0000" + placeholderTextColor={colors.blue40} + onFocus={this._onFocus} + onBlur={this._onBlur} + onChange={this._onInputChange} + onEnter={this._onLogin} + value={accountToken || ''} + disabled={!this._shouldEnableAccountInput(this.props)} + autoFocus={true} + ref={autoFocusOnFailure} + testName="AccountInput" + /> + <Animated.View + style={this._accountInputButtonStyles()} + onPress={this._onLogin} + testName="account-input-button"> + <Img + style={this._accountInputArrowStyles()} + source="icon-arrow" + height={16} + width={24} + tintColor="currentColor" + /> + </Animated.View> + </View> + <Accordion height={this._shouldShowAccountHistory(this.props) ? 'auto' : 0}> + { + <AccountDropdown + items={accountHistory.slice().reverse()} + onSelect={this._onSelectAccountFromHistory} + onRemove={this.props.onRemoveAccountTokenFromHistory} + /> + } + </Accordion> </View> - <Accordion height={ this._shouldShowAccountHistory(this.props) ? 'auto' : 0 }> - { <AccountDropdown - items={ accountHistory.slice().reverse() } - onSelect={ this._onSelectAccountFromHistory } - onRemove={ this.props.onRemoveAccountTokenFromHistory } /> } - </Accordion> </View> - </View>; + ); } _createFooter() { - return <View> - <Text style={ styles.login_footer__prompt}>{ 'Don\'t have an account number?' }</Text> - <BlueButton onPress={ this._onCreateAccount }> - <Label>Create account</Label> - <Img source='icon-extLink' height={16} width={16} /> - </BlueButton> - </View>; + return ( + <View> + <Text style={styles.login_footer__prompt}>{"Don't have an account number?"}</Text> + <BlueButton onPress={this._onCreateAccount}> + <Label>Create account</Label> + <Img source="icon-extLink" height={16} width={16} /> + </BlueButton> + </View> + ); } } @@ -353,13 +387,15 @@ class AccountDropdown extends React.Component<AccountDropdownProps> { const uniqueItems = [...new Set(this.props.items)]; return ( <View> - { uniqueItems.map(token => ( - <AccountDropdownItem key={ token } - value={ token } - label={ formatAccount(token) } - onSelect={ this.props.onSelect } - onRemove={ this.props.onRemove } /> - )) } + {uniqueItems.map((token) => ( + <AccountDropdownItem + key={token} + value={token} + label={formatAccount(token)} + onSelect={this.props.onSelect} + onRemove={this.props.onRemove} + /> + ))} </View> ); } @@ -374,19 +410,29 @@ type AccountDropdownItemProps = { class AccountDropdownItem extends React.Component<AccountDropdownItemProps> { render() { - return (<View> - <View style={ styles.account_dropdown__spacer }/> - <CellButton style={ styles.account_dropdown__item } cellHoverStyle={ styles.account_dropdown__item_hover }> - <Label style={styles.account_dropdown__label} cellHoverStyle={ styles.account_dropdown__label_hover } onPress={ () => this.props.onSelect(this.props.value) }> - { this.props.label } - </Label> - <Img style={styles.account_dropdown__remove} - cellHoverStyle={ styles.account_dropdown__remove_cell_hover } - hoverStyle={ styles.account_dropdown__remove_hover } - source='icon-close-sml' - height={16} width={16} - onPress={ () => this.props.onRemove(this.props.value) }/> - </CellButton> - </View>); + return ( + <View> + <View style={styles.account_dropdown__spacer} /> + <CellButton + style={styles.account_dropdown__item} + cellHoverStyle={styles.account_dropdown__item_hover}> + <Label + style={styles.account_dropdown__label} + cellHoverStyle={styles.account_dropdown__label_hover} + onPress={() => this.props.onSelect(this.props.value)}> + {this.props.label} + </Label> + <Img + style={styles.account_dropdown__remove} + cellHoverStyle={styles.account_dropdown__remove_cell_hover} + hoverStyle={styles.account_dropdown__remove_hover} + source="icon-close-sml" + height={16} + width={16} + onPress={() => this.props.onRemove(this.props.value)} + /> + </CellButton> + </View> + ); } } diff --git a/app/components/LoginStyles.js b/app/components/LoginStyles.js index 21c3aa4429..5e64e8b1eb 100644 --- a/app/components/LoginStyles.js +++ b/app/components/LoginStyles.js @@ -7,7 +7,7 @@ export default { login_footer: { backgroundColor: colors.darkBlue, paddingTop: 18, - paddingBottom:16, + paddingBottom: 16, flex: 0, }, status_icon: { @@ -16,18 +16,18 @@ export default { marginBottom: 30, justifyContent: 'center', }, - login_form:{ - flex:1, + login_form: { + flex: 1, flexDirection: 'column', - overflow:'visible', + overflow: 'visible', paddingTop: 0, paddingBottom: 0, paddingLeft: 24, paddingRight: 24, marginTop: 83, - marginBottom:0, + marginBottom: 0, marginRight: 0, - marginLeft:0, + marginLeft: 0, }, account_input_group: { borderWidth: 2, @@ -131,7 +131,7 @@ export default { letterSpacing: -0.7, color: colors.white, marginBottom: 7, - flex:0, + flex: 0, }, subtitle: { fontFamily: 'Open Sans', diff --git a/app/components/Map.js b/app/components/Map.js index 3865e8975a..72298fd2ee 100644 --- a/app/components/Map.js +++ b/app/components/Map.js @@ -17,8 +17,8 @@ export type MapProps = { type MapState = { bounds: { width: number, - height: number - } + height: number, + }, }; export default class Map extends Component<MapProps, MapState> { @@ -31,18 +31,20 @@ export default class Map extends Component<MapProps, MapState> { render() { const { width, height } = this.state.bounds; - const readyToRenderTheMap = (width > 0 && height > 0); + const readyToRenderTheMap = width > 0 && height > 0; return ( - <View style={ this.props.style } onLayout={ this._onLayout }> - { readyToRenderTheMap && ( - <SvgMap width={ width } - height={ height } - center={ this.props.center } - offset={ this.props.offset } - zoomLevel={ this._zoomLevel(this.props.zoomLevel) } - showMarker={ this.props.showMarker } - markerImagePath={ this._markerImage(this.props.markerStyle) } /> - ) } + <View style={this.props.style} onLayout={this._onLayout}> + {readyToRenderTheMap && ( + <SvgMap + width={width} + height={height} + center={this.props.center} + offset={this.props.offset} + zoomLevel={this._zoomLevel(this.props.zoomLevel)} + showMarker={this.props.showMarker} + markerImagePath={this._markerImage(this.props.markerStyle)} + /> + )} </View> ); } @@ -53,14 +55,11 @@ export default class Map extends Component<MapProps, MapState> { return ( oldProps.center[0] !== nextProps.center[0] || oldProps.center[1] !== nextProps.center[1] || - oldProps.offset[0] !== nextProps.offset[0] || oldProps.offset[1] !== nextProps.offset[1] || - oldProps.zoomLevel !== nextProps.zoomLevel || oldProps.showMarker !== nextProps.showMarker || oldProps.markerStyle !== nextProps.markerStyle || - oldState.bounds.width !== nextState.bounds.width || oldState.bounds.height !== nextState.bounds.height ); @@ -71,30 +70,32 @@ export default class Map extends Component<MapProps, MapState> { bounds: { width: layoutInfo.width, height: layoutInfo.height, - } + }, }); - } + }; // TODO: Remove zoom level in favor of center + coordinate span _zoomLevel(variant: $PropertyType<MapProps, 'zoomLevel'>) { - switch(variant) { - case 'high': return 1; - case 'medium': return 20; - case 'low': return 40; - default: - throw new Error(`Invalid enumeration type: ${variant}`); + switch (variant) { + case 'high': + return 1; + case 'medium': + return 20; + case 'low': + return 40; + default: + throw new Error(`Invalid enumeration type: ${variant}`); } } _markerImage(style: $PropertyType<MapProps, 'markerStyle'>) { - switch(style) { - case 'secure': - return './assets/images/location-marker-secure.svg'; - case 'unsecure': - return './assets/images/location-marker-unsecure.svg'; - default: - throw new Error(`Invalid enumeration type: ${style}`); + switch (style) { + case 'secure': + return './assets/images/location-marker-secure.svg'; + case 'unsecure': + return './assets/images/location-marker-unsecure.svg'; + default: + throw new Error(`Invalid enumeration type: ${style}`); } } - } diff --git a/app/components/PlatformWindow.android.js b/app/components/PlatformWindow.android.js index b2a13e86c9..9d15466f56 100644 --- a/app/components/PlatformWindow.android.js +++ b/app/components/PlatformWindow.android.js @@ -4,14 +4,10 @@ import { KeyboardAvoidingView } from 'react-native'; export default class PlatformWindow extends React.Component { props: { - children: Array<React.Node> | React.Node + children: Array<React.Node> | React.Node, }; render() { - return ( - <KeyboardAvoidingView behavior={'position'}> - { this.props.children } - </KeyboardAvoidingView> - ); + return <KeyboardAvoidingView behavior={'position'}>{this.props.children}</KeyboardAvoidingView>; } -}
\ No newline at end of file +} diff --git a/app/components/PlatformWindow.js b/app/components/PlatformWindow.js index b727cdd7b1..60f26dd6de 100644 --- a/app/components/PlatformWindow.js +++ b/app/components/PlatformWindow.js @@ -2,16 +2,12 @@ import * as React from 'react'; type Props = { - children?: React.Node + children?: React.Node, }; export default class PlatformWindow extends React.Component<Props> { render() { const chromeClass = ['window-chrome', 'window-chrome--' + process.platform]; - return ( - <div className={ chromeClass.join(' ') }> - { this.props.children } - </div> - ); + return <div className={chromeClass.join(' ')}>{this.props.children}</div>; } -}
\ No newline at end of file +} diff --git a/app/components/Preferences.js b/app/components/Preferences.js index d24d99c042..afe802d958 100644 --- a/app/components/Preferences.js +++ b/app/components/Preferences.js @@ -7,9 +7,9 @@ import Switch from './Switch'; import styles from './PreferencesStyles'; export type PreferencesProps = { - allowLan: boolean; - onChangeAllowLan: (boolean) => void; - onClose: () => void; + allowLan: boolean, + onChangeAllowLan: (boolean) => void, + onClose: () => void, }; export default class Preferences extends Component { @@ -18,33 +18,37 @@ export default class Preferences extends Component { render() { return ( <Layout> - <Header hidden={ true } style={ 'defaultDark' } /> + <Header hidden={true} style={'defaultDark'} /> <Container> - <View style={ styles.preferences }> - <Button style={ styles.preferences__close } onPress={ this.props.onClose } testName='closeButton'> - <View style={ styles.preferences__close_content }> - <Img style={ styles.preferences__close_icon } source="icon-back" /> - <Text style={ styles.preferences__close_title }>Settings</Text> + <View style={styles.preferences}> + <Button + style={styles.preferences__close} + onPress={this.props.onClose} + testName="closeButton"> + <View style={styles.preferences__close_content}> + <Img style={styles.preferences__close_icon} source="icon-back" /> + <Text style={styles.preferences__close_title}>Settings</Text> </View> </Button> - <View style={ styles.preferences__container }> - - <View style={ styles.preferences__header }> - <Text style={ styles.preferences__title }>Preferences</Text> + <View style={styles.preferences__container}> + <View style={styles.preferences__header}> + <Text style={styles.preferences__title}>Preferences</Text> </View> - <View style={ styles.preferences__content }> - <View style={ styles.preferences__cell }> - <View style={ styles.preferences__cell_label_container }> - <Text style={ styles.preferences__cell_label }>Local network sharing</Text> + <View style={styles.preferences__content}> + <View style={styles.preferences__cell}> + <View style={styles.preferences__cell_label_container}> + <Text style={styles.preferences__cell_label}>Local network sharing</Text> </View> - <View style={ styles.preferences__cell_accessory }> - <Switch isOn={ this.props.allowLan } onChange={ this.props.onChangeAllowLan } /> + <View style={styles.preferences__cell_accessory}> + <Switch isOn={this.props.allowLan} onChange={this.props.onChangeAllowLan} /> </View> </View> - <View style={ styles.preferences__cell_footer }> - <Text style={ styles.preferences__cell_footer_label }> - { 'Allows access to other devices on the same network for sharing, printing etc.' } + <View style={styles.preferences__cell_footer}> + <Text style={styles.preferences__cell_footer_label}> + { + 'Allows access to other devices on the same network for sharing, printing etc.' + } </Text> </View> </View> diff --git a/app/components/PreferencesStyles.js b/app/components/PreferencesStyles.js index f751653622..c2d41c4ecb 100644 --- a/app/components/PreferencesStyles.js +++ b/app/components/PreferencesStyles.js @@ -29,7 +29,7 @@ export default { borderWidth: 0, padding: 0, margin: 0, - zIndex: 1, /* part of .preferences__container covers the button */ + zIndex: 1 /* part of .preferences__container covers the button */, cursor: 'default', }, preferences__close_content: { @@ -80,7 +80,7 @@ export default { fontSize: 32, fontWeight: '900', lineHeight: 40, - color: colors.white + color: colors.white, }, preferences__cell_label: { fontFamily: 'DINPro', @@ -96,7 +96,7 @@ export default { fontWeight: '600', lineHeight: 20, letterSpacing: -0.2, - color: colors.white80 - } - }) -};
\ No newline at end of file + color: colors.white80, + }, + }), +}; diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js index 5a0b8c1101..d706e7fcc2 100644 --- a/app/components/SelectLocation.js +++ b/app/components/SelectLocation.js @@ -9,17 +9,21 @@ import Img from './Img'; import Accordion from './Accordion'; -import type { SettingsReduxState, RelayLocationRedux, RelayLocationCityRedux } from '../redux/settings/reducers'; +import type { + SettingsReduxState, + RelayLocationRedux, + RelayLocationCityRedux, +} from '../redux/settings/reducers'; import type { RelayLocation } from '../lib/ipc-facade'; export type SelectLocationProps = { settings: SettingsReduxState, - onClose: () => void; - onSelect: (location: RelayLocation) => void; + onClose: () => void, + onSelect: (location: RelayLocation) => void, }; type State = { - expanded: Array<string> + expanded: Array<string>, }; export default class SelectLocation extends React.Component<SelectLocationProps, State> { @@ -35,13 +39,13 @@ export default class SelectLocation extends React.Component<SelectLocationProps, // set initially expanded country based on relaySettings const relaySettings = this.props.settings.relaySettings; - if(relaySettings.normal) { + if (relaySettings.normal) { const { location } = relaySettings.normal; - if(location === 'any') { + if (location === 'any') { // no-op - } else if(location.country) { + } else if (location.country) { this.state.expanded.push(location.country); - } else if(location.city) { + } else if (location.city) { this.state.expanded.push(location.city[0]); } } @@ -52,7 +56,7 @@ export default class SelectLocation extends React.Component<SelectLocationProps, const cell = this._selectedCell; const scrollView = this._scrollView; - if(scrollView && cell) { + if (scrollView && cell) { //TODO: fix this when repairing the auto-scroll in customscrollbars. //scrollView.scrollToElement(cell, 'middle'); } @@ -63,24 +67,24 @@ export default class SelectLocation extends React.Component<SelectLocationProps, <Layout> <Container> <View style={styles.select_location}> - <Button style={styles.close} onPress={ this.props.onClose } testName='close'> - <Img style={styles.close_icon} source='icon-close'/> + <Button style={styles.close} onPress={this.props.onClose} testName="close"> + <Img style={styles.close_icon} source="icon-close" /> </Button> <View style={styles.container}> <View style={styles.header}> <Text style={styles.title}>Select location</Text> </View> - <CustomScrollbars autoHide={ true } ref={ (ref) => this._scrollView = ref }> + <CustomScrollbars autoHide={true} ref={(ref) => (this._scrollView = ref)}> <View> <Text style={styles.subtitle}> - While connected, your real location is masked with a private and secure location in the selected region + While connected, your real location is masked with a private and secure location + in the selected region </Text> - { this.props.settings.relayLocations.map((relayCountry) => { + {this.props.settings.relayLocations.map((relayCountry) => { return this._renderCountry(relayCountry); - }) } - + })} </View> </CustomScrollbars> </View> @@ -92,20 +96,25 @@ export default class SelectLocation extends React.Component<SelectLocationProps, _isSelected(selectedLocation: RelayLocation) { const { relaySettings } = this.props.settings; - if(relaySettings.normal) { + if (relaySettings.normal) { const otherLocation = relaySettings.normal.location; - if(selectedLocation.country && otherLocation.country && - selectedLocation.country === otherLocation.country) { + if ( + selectedLocation.country && + otherLocation.country && + selectedLocation.country === otherLocation.country + ) { return true; } - if(Array.isArray(selectedLocation.city) && Array.isArray(otherLocation.city)) { + if (Array.isArray(selectedLocation.city) && Array.isArray(otherLocation.city)) { const selectedCity = selectedLocation.city; const otherCity = otherLocation.city; - return selectedCity.length === otherCity.length && - selectedCity.every((v, i) => v === otherCity[i]); + return ( + selectedCity.length === otherCity.length && + selectedCity.every((v, i) => v === otherCity[i]) + ); } } return false; @@ -115,21 +124,23 @@ export default class SelectLocation extends React.Component<SelectLocationProps, this.setState((state) => { const expanded = state.expanded.slice(); const index = expanded.indexOf(countryCode); - if(index === -1) { + if (index === -1) { expanded.push(countryCode); } else { expanded.splice(index, 1); } return { expanded }; }); - } + }; _relayStatusIndicator(active: boolean, isSelected: boolean) { const statusClass = active ? styles.relay_status__active : styles.relay_status__inactive; - return ( isSelected ? - <Img style={ styles.tick_icon } source='icon-tick' height={24} width={24} /> : - <View style={[ styles.relay_status, statusClass ]}></View>); + return isSelected ? ( + <Img style={styles.tick_icon} source="icon-tick" height={24} width={24} /> + ) : ( + <View style={[styles.relay_status, statusClass]} /> + ); } _renderCountry(relayCountry: RelayLocationRedux) { @@ -138,9 +149,12 @@ export default class SelectLocation extends React.Component<SelectLocationProps, // either expanded by user or when the city selected within the country const isExpanded = this.state.expanded.includes(relayCountry.code); - const handleSelect = (relayCountry.hasActiveRelays && !isSelected) ? () => { - this.props.onSelect({ country: relayCountry.code }); - } : undefined; + const handleSelect = + relayCountry.hasActiveRelays && !isSelected + ? () => { + this.props.onSelect({ country: relayCountry.code }); + } + : undefined; const handleCollapse = (e) => { this._toggleCollapse(relayCountry.code); @@ -148,35 +162,34 @@ export default class SelectLocation extends React.Component<SelectLocationProps, }; return ( - <View key={ relayCountry.code } style={styles.country}> + <View key={relayCountry.code} style={styles.country}> <CellButton - cellHoverStyle={ isSelected ? styles.cell_selected : null } - style={ isSelected ? styles.cell_selected : styles.cell } - onPress={ handleSelect } + cellHoverStyle={isSelected ? styles.cell_selected : null} + style={isSelected ? styles.cell_selected : styles.cell} + onPress={handleSelect} disabled={!relayCountry.hasActiveRelays} - testName='country'> + testName="country"> + {this._relayStatusIndicator(relayCountry.hasActiveRelays, isSelected)} - { this._relayStatusIndicator(relayCountry.hasActiveRelays, isSelected) } + <Label>{relayCountry.name}</Label> - <Label> - { relayCountry.name } - </Label> - - { relayCountry.cities.length > 1 ? - <Img style={styles.collapse_button} + {relayCountry.cities.length > 1 ? ( + <Img + style={styles.collapse_button} hoverStyle={styles.expand_chevron_hover} - onPress={ handleCollapse } + onPress={handleCollapse} source={isExpanded ? 'icon-chevron-up' : 'icon-chevron-down'} height={24} - width={24} /> - : null } + width={24} + /> + ) : null} </CellButton> - { relayCountry.cities.length > 1 && - (<Accordion height={ isExpanded ? 'auto' : 0 }> - { relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity)) } - </Accordion>) - } + {relayCountry.cities.length > 1 && ( + <Accordion height={isExpanded ? 'auto' : 0}> + {relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity))} + </Accordion> + )} </View> ); } @@ -186,28 +199,31 @@ export default class SelectLocation extends React.Component<SelectLocationProps, const isSelected = this._isSelected(relayLocation); - const onRef = isSelected ? (element) => { - this._selectedCell = element; - } : undefined; + const onRef = isSelected + ? (element) => { + this._selectedCell = element; + } + : undefined; - const handleSelect = (relayCity.hasActiveRelays && !isSelected) ? () => { - this.props.onSelect(relayLocation); - } : undefined; + const handleSelect = + relayCity.hasActiveRelays && !isSelected + ? () => { + this.props.onSelect(relayLocation); + } + : undefined; return ( - <CellButton key={ `${countryCode}_${relayCity.code}` } - onPress={ handleSelect } + <CellButton + key={`${countryCode}_${relayCity.code}`} + onPress={handleSelect} disabled={!relayCity.hasActiveRelays} cellHoverStyle={isSelected ? styles.sub_cell__selected : null} style={isSelected ? styles.sub_cell__selected : styles.sub_cell} - testName='city' + testName="city" ref={onRef}> + {this._relayStatusIndicator(relayCity.hasActiveRelays, isSelected)} - { this._relayStatusIndicator(relayCity.hasActiveRelays, isSelected) } - - <Label> - { relayCity.name } - </Label> + <Label>{relayCity.name}</Label> </CellButton> ); } diff --git a/app/components/SelectLocationStyles.js b/app/components/SelectLocationStyles.js index 721ad64eec..a0aec306d4 100644 --- a/app/components/SelectLocationStyles.js +++ b/app/components/SelectLocationStyles.js @@ -12,7 +12,7 @@ export default { flexDirection: 'column', flex: 1, }, - header:{ + header: { flex: 0, marginBottom: 16, }, @@ -21,7 +21,7 @@ export default { marginTop: 24, cursor: 'default', }, - close_icon:{ + close_icon: { width: 24, height: 24, flex: 0, @@ -116,4 +116,4 @@ export default { flex: 0, }, }), -};
\ No newline at end of file +}; diff --git a/app/components/Settings.js b/app/components/Settings.js index 0f87da2c11..33fc371faa 100644 --- a/app/components/Settings.js +++ b/app/components/Settings.js @@ -2,7 +2,7 @@ import moment from 'moment'; import * as React from 'react'; import { Component, Text, View } from 'reactxp'; -import { Button, CellButton, RedButton, Label, SubText} from './styled'; +import { Button, CellButton, RedButton, Label, SubText } from './styled'; import { Layout, Container } from './Layout'; import CustomScrollbars from './CustomScrollbars'; import styles from './SettingsStyles'; @@ -30,8 +30,11 @@ export default class Settings extends Component<SettingsProps> { <Layout> <Container> <View style={styles.settings}> - <Button style={styles.settings__close} onPress={ this.props.onClose } testName='settings__close'> - <Img height={24} width={24} style={styles.settings__close_icon} source='icon-close'/> + <Button + style={styles.settings__close} + onPress={this.props.onClose} + testName="settings__close"> + <Img height={24} width={24} style={styles.settings__close_icon} source="icon-close" /> </Button> <View style={styles.settings__container}> @@ -39,17 +42,15 @@ export default class Settings extends Component<SettingsProps> { <Text style={styles.settings__title}>Settings</Text> </View> - <CustomScrollbars style={styles.settings__scrollview} autoHide={ true }> - + <CustomScrollbars style={styles.settings__scrollview} autoHide={true}> <View style={styles.settings__content}> <View> - { this._renderTopButtons() } - { this._renderMiddleButtons() } - { this._renderBottomButtons() } + {this._renderTopButtons()} + {this._renderMiddleButtons()} + {this._renderBottomButtons()} </View> - { this._renderQuitButton() } + {this._renderQuitButton()} </View> - </CustomScrollbars> </View> </View> @@ -64,59 +65,69 @@ export default class Settings extends Component<SettingsProps> { return null; } - let isOutOfTime = false, formattedExpiry = ''; + let isOutOfTime = false, + formattedExpiry = ''; let expiryIso = this.props.account.expiry; - if(isLoggedIn && expiryIso) { + if (isLoggedIn && expiryIso) { let expiry = moment(this.props.account.expiry); isOutOfTime = expiry.isSameOrBefore(moment()); formattedExpiry = (expiry.fromNow(true) + ' left').toUpperCase(); } - return <View> - <View style={styles.settings_account} testName='settings__account'> - {isOutOfTime ? ( - <CellButton onPress={ this.props.onViewAccount } - testName='settings__account_paid_until_button'> - <Label>Account</Label> - <SubText testName='settings__account_paid_until_subtext' style={styles.settings__account_paid_until_Label__error}>OUT OF TIME</SubText> - <Img height={12} width={7} source='icon-chevron' /> - </CellButton> - ) : ( - <CellButton onPress={ this.props.onViewAccount } - testName='settings__account_paid_until_button'> - <Label>Account</Label> - <SubText testName='settings__account_paid_until_subtext'>{ formattedExpiry }</SubText> - <Img height={12} width={7} source='icon-chevron' /> - </CellButton> - )} - </View> + return ( + <View> + <View style={styles.settings_account} testName="settings__account"> + {isOutOfTime ? ( + <CellButton + onPress={this.props.onViewAccount} + testName="settings__account_paid_until_button"> + <Label>Account</Label> + <SubText + testName="settings__account_paid_until_subtext" + style={styles.settings__account_paid_until_Label__error}> + OUT OF TIME + </SubText> + <Img height={12} width={7} source="icon-chevron" /> + </CellButton> + ) : ( + <CellButton + onPress={this.props.onViewAccount} + testName="settings__account_paid_until_button"> + <Label>Account</Label> + <SubText testName="settings__account_paid_until_subtext">{formattedExpiry}</SubText> + <Img height={12} width={7} source="icon-chevron" /> + </CellButton> + )} + </View> - <CellButton onPress={ this.props.onViewPreferences } - testName='settings__preferences'> - <Label>Preferences</Label> - <Img height={12} width={7} source='icon-chevron' /> - </CellButton> + <CellButton onPress={this.props.onViewPreferences} testName="settings__preferences"> + <Label>Preferences</Label> + <Img height={12} width={7} source="icon-chevron" /> + </CellButton> - <CellButton onPress={ this.props.onViewAdvancedSettings } - testName='settings__advanced'> - <Label>Advanced</Label> - <Img height={12} width={7} source='icon-chevron' /> - </CellButton> - <View style={styles.settings__cell_spacer}/> - </View>; + <CellButton onPress={this.props.onViewAdvancedSettings} testName="settings__advanced"> + <Label>Advanced</Label> + <Img height={12} width={7} source="icon-chevron" /> + </CellButton> + <View style={styles.settings__cell_spacer} /> + </View> + ); } _renderMiddleButtons() { - return <View> - <CellButton onPress={ this.props.onExternalLink.bind(this, 'download') } - testName='settings__version'> - <Label>App version</Label> - <SubText>{this._formattedVersion()}</SubText> - <Img height={16} width={16} source='icon-extLink' /> - </CellButton> - <View style={styles.settings__cell_spacer}/> - </View>; + return ( + <View> + <CellButton + onPress={this.props.onExternalLink.bind(this, 'download')} + testName="settings__version"> + <Label>App version</Label> + <SubText>{this._formattedVersion()}</SubText> + <Img height={16} width={16} source="icon-extLink" /> + </CellButton> + <View style={styles.settings__cell_spacer} /> + </View> + ); } _formattedVersion() { @@ -124,39 +135,42 @@ export default class Settings extends Component<SettingsProps> { // version scheme. in package.json we thus have to write YEAR.release.X-channel and // this function is responsible for removing .X part. return this.props.version - .replace('.0-', '-') // remove the .0 in 2018.1.0-beta9 + .replace('.0-', '-') // remove the .0 in 2018.1.0-beta9 .replace(/\.0$/, ''); // remove the .0 in 2018.1.0 } _renderBottomButtons() { - return <View> - <CellButton onPress={ this.props.onExternalLink.bind(this, 'faq') } - testName='settings__external_link'> - <Label>FAQs</Label> - <Img height={16} width={16} source='icon-extLink' /> - </CellButton> + return ( + <View> + <CellButton + onPress={this.props.onExternalLink.bind(this, 'faq')} + testName="settings__external_link"> + <Label>FAQs</Label> + <Img height={16} width={16} source="icon-extLink" /> + </CellButton> - <CellButton onPress={ this.props.onExternalLink.bind(this, 'guides') } - testName='settings__external_link'> - <Label>Guides</Label> - <Img height={16} width={16} source='icon-extLink' /> - </CellButton> + <CellButton + onPress={this.props.onExternalLink.bind(this, 'guides')} + testName="settings__external_link"> + <Label>Guides</Label> + <Img height={16} width={16} source="icon-extLink" /> + </CellButton> - <CellButton onPress={ this.props.onViewSupport } - testName='settings__view_support'> - <Label>Report a problem</Label> - <Img height={12} width={7} source='icon-chevron' /> - </CellButton> - </View>; + <CellButton onPress={this.props.onViewSupport} testName="settings__view_support"> + <Label>Report a problem</Label> + <Img height={12} width={7} source="icon-chevron" /> + </CellButton> + </View> + ); } _renderQuitButton() { - return <View style={styles.settings__footer}> - <RedButton - onPress={this.props.onQuit} - testName='settings__quit'> - <Label>Quit app</Label> - </RedButton> - </View>; + return ( + <View style={styles.settings__footer}> + <RedButton onPress={this.props.onQuit} testName="settings__quit"> + <Label>Quit app</Label> + </RedButton> + </View> + ); } -}
\ No newline at end of file +} diff --git a/app/components/SettingsStyles.js b/app/components/SettingsStyles.js index d84673253e..82616f7e4a 100644 --- a/app/components/SettingsStyles.js +++ b/app/components/SettingsStyles.js @@ -1,62 +1,65 @@ import { createViewStyles, createTextStyles } from '../lib/styles'; import { colors } from '../config'; -export default Object.assign(createViewStyles({ - settings: { - backgroundColor: colors.darkBlue, - flex: 1, - }, - settings__container:{ - flexDirection: 'column', - flex: 1 - }, - settings__header:{ - flexGrow: 0, - flexShrink: 0, - flexBasis: 'auto', - paddingTop: 16, - paddingRight: 24, - paddingLeft: 24, - paddingBottom: 16, - }, - settings__content: { - flexDirection: 'column', - flex: 1, - justifyContent: 'space-between', - }, - settings__scrollview: { - flexGrow: 1, - flexShrink: 1, - flexBasis: '100%', - }, - settings__close: { - marginLeft: 12, - marginTop: 24, - cursor: 'default', - }, - settings__close_icon:{ - width: 24, - height: 24, - flex: 0, - opacity: 0.6, - }, - settings__cell_spacer:{ - height: 24, - flex: 0 - }, - settings__footer: { - paddingTop: 16, - paddingBottom: 16, - }, -}), createTextStyles({ - settings__title:{ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 40, - color: colors.white - }, - settings__account_paid_until_label__error:{ - color: colors.red, - }, -})); +export default Object.assign( + createViewStyles({ + settings: { + backgroundColor: colors.darkBlue, + flex: 1, + }, + settings__container: { + flexDirection: 'column', + flex: 1, + }, + settings__header: { + flexGrow: 0, + flexShrink: 0, + flexBasis: 'auto', + paddingTop: 16, + paddingRight: 24, + paddingLeft: 24, + paddingBottom: 16, + }, + settings__content: { + flexDirection: 'column', + flex: 1, + justifyContent: 'space-between', + }, + settings__scrollview: { + flexGrow: 1, + flexShrink: 1, + flexBasis: '100%', + }, + settings__close: { + marginLeft: 12, + marginTop: 24, + cursor: 'default', + }, + settings__close_icon: { + width: 24, + height: 24, + flex: 0, + opacity: 0.6, + }, + settings__cell_spacer: { + height: 24, + flex: 0, + }, + settings__footer: { + paddingTop: 16, + paddingBottom: 16, + }, + }), + createTextStyles({ + settings__title: { + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: '900', + lineHeight: 40, + color: colors.white, + }, + settings__account_paid_until_label__error: { + color: colors.red, + }, + }), +); diff --git a/app/components/Support.js b/app/components/Support.js index 0b3e88676a..fa4e6cf62d 100644 --- a/app/components/Support.js +++ b/app/components/Support.js @@ -23,10 +23,10 @@ type SupportState = { export type SupportProps = { account: AccountReduxState, - onClose: () => void; - onViewLog: (string) => void; - onCollectLog: (Array<string>) => Promise<string>; - onSend: (email: string, message: string, savedReport: string) => void; + onClose: () => void, + onViewLog: (string) => void, + onCollectLog: (Array<string>) => Promise<string>, + onSend: (email: string, message: string, savedReport: string) => void, }; export default class Support extends Component<SupportProps, SupportState> { @@ -43,28 +43,27 @@ export default class Support extends Component<SupportProps, SupportState> { onChangeEmail = (email: string) => { this.setState({ email: email }); - } + }; onChangeDescription = (description: string) => { this.setState({ message: description }); - } + }; onViewLog = () => { - - this._getLog() - .then((path) => { - this.props.onViewLog(path); - }); - } + this._getLog().then((path) => { + this.props.onViewLog(path); + }); + }; _getLog(): Promise<string> { const accountsToRedact = this.props.account.accountHistory; const { savedReport } = this.state; - return savedReport ? - Promise.resolve(savedReport) : - this.props.onCollectLog(accountsToRedact) - .then( path => { - return new Promise(resolve => this.setState({ savedReport: path }, () => resolve(path))); + return savedReport + ? Promise.resolve(savedReport) + : this.props.onCollectLog(accountsToRedact).then((path) => { + return new Promise((resolve) => + this.setState({ savedReport: path }, () => resolve(path)), + ); }); } @@ -76,39 +75,46 @@ export default class Support extends Component<SupportProps, SupportState> { } else { this._sendProblemReport(); } - } + }; _sendProblemReport() { - this.setState({ - sendState: 'LOADING', - }, () => { - this._getLog() - .then((path) => { - return this.props.onSend(this.state.email, this.state.message, path); - }) - .then( () => { - this.setState({ - sendState: 'SUCCESS', - }); - }) - .catch( () => { - this.setState({ - sendState: 'FAILED', + this.setState( + { + sendState: 'LOADING', + }, + () => { + this._getLog() + .then((path) => { + return this.props.onSend(this.state.email, this.state.message, path); + }) + .then(() => { + this.setState({ + sendState: 'SUCCESS', + }); + }) + .catch(() => { + this.setState({ + sendState: 'FAILED', + }); }); - }); - }); + }, + ); } render() { - const { sendState } = this.state; - const header = <View style={styles.support__header}> - <Text style={styles.support__title}>Report a problem</Text> - { (sendState === 'INITIAL' || sendState === 'CONFIRM_NO_EMAIL') && <Text style={styles.support__subtitle}> - { 'To help you more effectively, your app\'s log file will be attached to this message. Your data will remain secure and private, as it is anonymised before being sent over an encrypted channel.' } - </Text> - } - </View>; + const header = ( + <View style={styles.support__header}> + <Text style={styles.support__title}>Report a problem</Text> + {(sendState === 'INITIAL' || sendState === 'CONFIRM_NO_EMAIL') && ( + <Text style={styles.support__subtitle}> + { + "To help you more effectively, your app's log file will be attached to this message. Your data will remain secure and private, as it is anonymised before being sent over an encrypted channel." + } + </Text> + )} + </View> + ); const content = this._renderContent(); @@ -116,16 +122,17 @@ export default class Support extends Component<SupportProps, SupportState> { <Layout> <Container> <View style={styles.support}> - <Button style={styles.support__close} onPress={ this.props.onClose } testName="support__close"> + <Button + style={styles.support__close} + onPress={this.props.onClose} + testName="support__close"> <Img height={24} width={24} style={styles.support__close_icon} source="icon-back" /> <Text style={styles.support__close_title}>Settings</Text> </Button> <View style={styles.support__container}> + {header} - { header } - - { content } - + {content} </View> </View> </Container> @@ -134,154 +141,150 @@ export default class Support extends Component<SupportProps, SupportState> { } _renderContent() { - switch(this.state.sendState) { - case 'INITIAL': - case 'CONFIRM_NO_EMAIL': - return this._renderForm(); - case 'LOADING': - return this._renderLoading(); - case 'SUCCESS': - return this._renderSent(); - case 'FAILED': - return this._renderFailed(); - default: - return null; + switch (this.state.sendState) { + case 'INITIAL': + case 'CONFIRM_NO_EMAIL': + return this._renderForm(); + case 'LOADING': + return this._renderLoading(); + case 'SUCCESS': + return this._renderSent(); + case 'FAILED': + return this._renderFailed(); + default: + return null; } } _renderForm() { - - return <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <TextInput style={styles.support__form_email} - placeholder="Your email" - defaultValue={ this.state.email } - onChangeText={ this.onChangeEmail } - keyboardType="email-address" /> - </View> - <View style={styles.support__form_row_message}> - <View style={styles.support__form_message_scroll_wrap}> - <TextInput style={styles.support__form_message} - placeholder="Describe your problem" - defaultValue={ this.state.message } - multiline={ true } - onChangeText={ this.onChangeDescription } - testName="support__form_message"/> + return ( + <View style={styles.support__content}> + <View style={styles.support__form}> + <View style={styles.support__form_row}> + <TextInput + style={styles.support__form_email} + placeholder="Your email" + defaultValue={this.state.email} + onChangeText={this.onChangeEmail} + keyboardType="email-address" + /> </View> - </View> - <View style={styles.support__footer}> - { - this.state.sendState === 'CONFIRM_NO_EMAIL' + <View style={styles.support__form_row_message}> + <View style={styles.support__form_message_scroll_wrap}> + <TextInput + style={styles.support__form_message} + placeholder="Describe your problem" + defaultValue={this.state.message} + multiline={true} + onChangeText={this.onChangeDescription} + testName="support__form_message" + /> + </View> + </View> + <View style={styles.support__footer}> + {this.state.sendState === 'CONFIRM_NO_EMAIL' ? this._renderNoEmailWarning() - : this._renderActionButtons() - } + : this._renderActionButtons()} + </View> </View> </View> - </View>; + ); } _renderNoEmailWarning() { - return <View> - <Text style={styles.support__no_email_warning}> - You are about to send the problem report without a way for us to get back to you. If you want an answer to your report you will have to enter an email address. - </Text> - <GreenButton - disabled={ !this.validate() } - onPress={ this.onSend } - testName='support__send_logs'> - Send anyway - </GreenButton> - </View>; + return ( + <View> + <Text style={styles.support__no_email_warning}> + You are about to send the problem report without a way for us to get back to you. If you + want an answer to your report you will have to enter an email address. + </Text> + <GreenButton + disabled={!this.validate()} + onPress={this.onSend} + testName="support__send_logs"> + Send anyway + </GreenButton> + </View> + ); } _renderActionButtons() { return [ - <BlueButton key={1} - onPress={ this.onViewLog } - testName='support__view_logs'> + <BlueButton key={1} onPress={this.onViewLog} testName="support__view_logs"> <Label>View app logs</Label> - <Img source='icon-extLink' height={16} width={16} /> + <Img source="icon-extLink" height={16} width={16} /> </BlueButton>, - <GreenButton key={2} - disabled={ !this.validate() } - onPress={ this.onSend } - testName='support__send_logs'> + <GreenButton + key={2} + disabled={!this.validate()} + onPress={this.onSend} + testName="support__send_logs"> Send - </GreenButton> + </GreenButton>, ]; } _renderLoading() { - return <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <View style={styles.support__status_icon}> - <Img source="icon-spinner" height={60} width={60} alt="" /> - </View> - <View style={styles.support__status_security__secure}> - Secure Connection + return ( + <View style={styles.support__content}> + <View style={styles.support__form}> + <View style={styles.support__form_row}> + <View style={styles.support__status_icon}> + <Img source="icon-spinner" height={60} width={60} alt="" /> + </View> + <View style={styles.support__status_security__secure}>Secure Connection</View> + <Text style={styles.support__send_status}>Sending...</Text> </View> - <Text style={styles.support__send_status}> - Sending... - </Text> </View> </View> - </View>; + ); } _renderSent() { - return <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <View style={styles.support__status_icon}> - <Img source="icon-success" height={60} width={60} alt="" /> - </View> - <Text style={styles.support__status_security__secure}> - Secure Connection - </Text> - <Text style={styles.support__send_status}> - Sent - </Text> + return ( + <View style={styles.support__content}> + <View style={styles.support__form}> + <View style={styles.support__form_row}> + <View style={styles.support__status_icon}> + <Img source="icon-success" height={60} width={60} alt="" /> + </View> + <Text style={styles.support__status_security__secure}>Secure Connection</Text> + <Text style={styles.support__send_status}>Sent</Text> - <Text style={styles.support__subtitle}> - Thanks! We will look into this. - </Text> - { this.state.email.trim().length > 0 ? - <Text style={styles.support__subtitle}>If needed we will contact you on {'\u00A0'} - <Text style={styles.support__sent_email}>{ this.state.email }</Text> - </Text> - : null } + <Text style={styles.support__subtitle}>Thanks! We will look into this.</Text> + {this.state.email.trim().length > 0 ? ( + <Text style={styles.support__subtitle}> + If needed we will contact you on {'\u00A0'} + <Text style={styles.support__sent_email}>{this.state.email}</Text> + </Text> + ) : null} + </View> </View> </View> - </View>; + ); } _renderFailed() { - return <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <View style={styles.support__status_icon}> - <Img source="icon-fail" height={60} width={60} alt="" /> + return ( + <View style={styles.support__content}> + <View style={styles.support__form}> + <View style={styles.support__form_row}> + <View style={styles.support__status_icon}> + <Img source="icon-fail" height={60} width={60} alt="" /> + </View> + <Text style={styles.support__status_security__secure}>Secure Connection</Text> + <Text style={styles.support__send_status}>Failed to send</Text> </View> - <Text style={styles.support__status_security__secure}> - Secure Connection - </Text> - <Text style={styles.support__send_status}> - Failed to send - </Text> + </View> + <View style={styles.support__footer}> + <BlueButton onPress={() => this.setState({ sendState: 'INITIAL' })}> + Edit message + </BlueButton> + <GreenButton onPress={this.onSend} testName="support__send_logs"> + Try again + </GreenButton> </View> </View> - <View style={styles.support__footer}> - <BlueButton onPress={ () => this.setState({ sendState: 'INITIAL' }) }> - Edit message - </BlueButton> - <GreenButton - onPress={ this.onSend } - testName='support__send_logs'> - Try again - </GreenButton> - </View> - </View>; + ); } } diff --git a/app/components/SupportStyles.js b/app/components/SupportStyles.js index cc6cca788c..ea1f26651d 100644 --- a/app/components/SupportStyles.js +++ b/app/components/SupportStyles.js @@ -1,157 +1,160 @@ import { createViewStyles, createTextStyles } from '../lib/styles'; import { colors } from '../config'; -export default Object.assign(createViewStyles({ - support:{ - backgroundColor: colors.darkBlue, - flex: 1, - }, - support__container:{ - display: 'flex', - flexDirection: 'column', - flex: 1 - }, - support__header:{ - flex: 0, - paddingTop: 12, - paddingBottom: 12, - paddingLeft: 24, - paddingRight: 24, - }, - support__close:{ - paddingLeft: 12, - paddingTop: 24, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-start', - }, - support__close_icon:{ - flex: 0, - opacity: 0.6, - marginRight: 8, - }, - support__content:{ - flex: 1, - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - }, - support__form:{ - display: 'flex', - flex: 1, - flexDirection: 'column', - }, - support__form_row:{ - paddingTop: 0, - paddingBottom: 8, - paddingLeft: 22, - paddingRight: 22, - }, - support__form_row_message:{ - flex: 1, - paddingTop: 0, - paddingBottom: 8, - paddingLeft: 22, - paddingRight: 22, - }, - support__form_message_scroll_wrap:{ - flex: 1, - display: 'flex', - borderRadius: 4, - overflow: 'hidden', - }, - support__footer:{ - paddingTop: 0, - paddingBottom: 16, - display: 'flex', - flexDirection: 'column', - flex: 0, - }, - support__status_icon:{ - textAlign: 'center', - marginBottom: 32, - }, -}), createTextStyles({ - support__close_title:{ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white60, - }, - support__title:{ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 40, - color: colors.white, - marginBottom: 16, - }, - support__subtitle:{ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - overflow: 'visible', - color: colors.white80, - lineHeight: 20, - letterSpacing: -0.2, - }, - support__form_email:{ - flex: 1, - borderRadius: 4, - overflow: 'hidden', - paddingTop: 14, - paddingLeft: 14, - paddingRight: 14, - paddingBottom: 14, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 26, - color: colors.blue, - backgroundColor: colors.white, - }, - support__form_message:{ - paddingTop: 14, - paddingLeft: 14, - paddingRight: 14, - paddingBottom: 14, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.blue, - backgroundColor: colors.white, - flex: 1, - }, - support__sent_email:{ - fontWeight: '900', - color: colors.white, - }, - support__status_security__secure:{ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '800', - lineHeight: 22, - marginBottom: 4, - color: colors.green, - }, - support__send_status:{ - fontFamily: 'DINPro', - fontSize: 38, - fontWeight: '900', - maxHeight: 'calc(1.16em * 2)', - overflow: 'visible', - letterSpacing: -0.9, - color: colors.white, - marginBottom: 4, - }, - support__no_email_warning: { - fontFamily: 'Open Sans', - fontSize: 13, - lineHeight: 16, - color: colors.white80, - paddingTop: 8, - paddingLeft: 24, - paddingRight: 24, - paddingBottom: 8, - }, -})); +export default Object.assign( + createViewStyles({ + support: { + backgroundColor: colors.darkBlue, + flex: 1, + }, + support__container: { + display: 'flex', + flexDirection: 'column', + flex: 1, + }, + support__header: { + flex: 0, + paddingTop: 12, + paddingBottom: 12, + paddingLeft: 24, + paddingRight: 24, + }, + support__close: { + paddingLeft: 12, + paddingTop: 24, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }, + support__close_icon: { + flex: 0, + opacity: 0.6, + marginRight: 8, + }, + support__content: { + flex: 1, + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + }, + support__form: { + display: 'flex', + flex: 1, + flexDirection: 'column', + }, + support__form_row: { + paddingTop: 0, + paddingBottom: 8, + paddingLeft: 22, + paddingRight: 22, + }, + support__form_row_message: { + flex: 1, + paddingTop: 0, + paddingBottom: 8, + paddingLeft: 22, + paddingRight: 22, + }, + support__form_message_scroll_wrap: { + flex: 1, + display: 'flex', + borderRadius: 4, + overflow: 'hidden', + }, + support__footer: { + paddingTop: 0, + paddingBottom: 16, + display: 'flex', + flexDirection: 'column', + flex: 0, + }, + support__status_icon: { + textAlign: 'center', + marginBottom: 32, + }, + }), + createTextStyles({ + support__close_title: { + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + color: colors.white60, + }, + support__title: { + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: '900', + lineHeight: 40, + color: colors.white, + marginBottom: 16, + }, + support__subtitle: { + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + overflow: 'visible', + color: colors.white80, + lineHeight: 20, + letterSpacing: -0.2, + }, + support__form_email: { + flex: 1, + borderRadius: 4, + overflow: 'hidden', + paddingTop: 14, + paddingLeft: 14, + paddingRight: 14, + paddingBottom: 14, + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + lineHeight: 26, + color: colors.blue, + backgroundColor: colors.white, + }, + support__form_message: { + paddingTop: 14, + paddingLeft: 14, + paddingRight: 14, + paddingBottom: 14, + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + color: colors.blue, + backgroundColor: colors.white, + flex: 1, + }, + support__sent_email: { + fontWeight: '900', + color: colors.white, + }, + support__status_security__secure: { + fontFamily: 'Open Sans', + fontSize: 16, + fontWeight: '800', + lineHeight: 22, + marginBottom: 4, + color: colors.green, + }, + support__send_status: { + fontFamily: 'DINPro', + fontSize: 38, + fontWeight: '900', + maxHeight: 'calc(1.16em * 2)', + overflow: 'visible', + letterSpacing: -0.9, + color: colors.white, + marginBottom: 4, + }, + support__no_email_warning: { + fontFamily: 'Open Sans', + fontSize: 13, + lineHeight: 16, + color: colors.white80, + paddingTop: 8, + paddingLeft: 24, + paddingRight: 24, + paddingBottom: 8, + }, + }), +); diff --git a/app/components/SvgMap.js b/app/components/SvgMap.js index 48288f9a8a..43f1e2ef71 100644 --- a/app/components/SvgMap.js +++ b/app/components/SvgMap.js @@ -1,7 +1,14 @@ // @flow import * as React from 'react'; -import { ComposableMap, ZoomableGroup, Geographies, Geography, Markers, Marker } from 'react-simple-maps'; +import { + ComposableMap, + ZoomableGroup, + Geographies, + Geography, + Markers, + Marker, +} from 'react-simple-maps'; import { geoTimes } from 'd3-geo-projection'; import rbush from 'rbush'; @@ -56,7 +63,7 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { }; _projectionConfig = { - scale: 160 + scale: 160, }; constructor(props: SvgMapProps) { @@ -66,8 +73,8 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { } componentWillReceiveProps(nextProps: SvgMapProps) { - if(this._shouldInvalidateState(nextProps)) { - this.setState(prevState => this._getNextState(prevState, nextProps)); + if (this._shouldInvalidateState(nextProps)) { + this.setState((prevState) => this._getNextState(prevState, nextProps)); } } @@ -75,18 +82,13 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { return ( this.props.width !== nextProps.width || this.props.height !== nextProps.height || - this.props.center[0] !== nextProps.center[0] || this.props.center[1] !== nextProps.center[1] || - this.props.offset[0] !== nextProps.offset[0] || this.props.offset[1] !== nextProps.offset[1] || - this.props.zoomLevel !== nextProps.zoomLevel || - this.props.showMarker !== nextProps.showMarker || this.props.markerImagePath !== nextProps.markerImagePath || - this.state.zoomCenter !== nextState.zoomCenter || this.state.zoomLevel !== nextState.zoomLevel ); @@ -100,7 +102,7 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { }; const zoomableGroupStyle = { - transition: `transform ${MOVE_SPEED}ms ease-in-out` + transition: `transform ${MOVE_SPEED}ms ease-in-out`, }; const geographyStyle = this._mergeRsmStyle({ @@ -108,7 +110,7 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { fill: '#294d73', stroke: '#192e45', strokeWidth: `${1 / this.state.zoomLevel}`, - } + }, }); const stateProvinceLineStyle = this._mergeRsmStyle({ @@ -116,7 +118,7 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { fill: 'transparent', stroke: '#192e45', strokeWidth: `${1 / this.state.zoomLevel}`, - } + }, }); const markerStyle = this._mergeRsmStyle({ @@ -128,71 +130,74 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { // disable CSS transition when moving between locations // by using the different "key" const userMarker = this.props.showMarker && ( - <Marker key={ `user-location-${ this.props.center.join('-') }` } + <Marker + key={`user-location-${this.props.center.join('-')}`} marker={{ coordinates: this.props.center }} - style={ markerStyle }> - <image x="-30" y="-30" href={ this.props.markerImagePath } /> + style={markerStyle}> + <image x="-30" y="-30" href={this.props.markerImagePath} /> </Marker> ); - const countryMarkers = this.state.visibleCountries.map(item => ( - <Marker key={ `country-${item.id}` } + const countryMarkers = this.state.visibleCountries.map((item) => ( + <Marker + key={`country-${item.id}`} marker={{ coordinates: item.geometry.coordinates }} - style={ markerStyle }> + style={markerStyle}> <text fill="rgba(255,255,255,.6)" fontSize="22" textAnchor="middle"> - { item.properties.name } + {item.properties.name} </text> </Marker> )); - const cityMarkers = this.state.visibleCities.map(item => ( - <Marker key={ `city-${item.id}` } + const cityMarkers = this.state.visibleCities.map((item) => ( + <Marker + key={`city-${item.id}`} marker={{ coordinates: item.geometry.coordinates }} - style={ markerStyle }> + style={markerStyle}> <circle r="2" fill="rgba(255,255,255,.6)" /> <text x="0" y="-10" fill="rgba(255,255,255,.6)" fontSize="16" textAnchor="middle"> - { item.properties.name } + {item.properties.name} </text> </Marker> )); return ( <ComposableMap - width={ this.props.width } - height={ this.props.height } - style={ mapStyle } - projection={ this._getProjection } - projectionConfig={ this._projectionConfig }> + width={this.props.width} + height={this.props.height} + style={mapStyle} + projection={this._getProjection} + projectionConfig={this._projectionConfig}> <ZoomableGroup - center={ this.state.zoomCenter } - zoom={ this.state.zoomLevel } - disablePanning={ false } - style={ zoomableGroupStyle }> - <Geographies geography={ geographyData } disableOptimization={ true }> + center={this.state.zoomCenter} + zoom={this.state.zoomLevel} + disablePanning={false} + style={zoomableGroupStyle}> + <Geographies geography={geographyData} disableOptimization={true}> {(geographies, projection) => { return this.state.visibleGeometry.map(({ id }) => ( <Geography - key={ id } - geography={ geographies[id] } - projection={ projection } - style={ geographyStyle } /> + key={id} + geography={geographies[id]} + projection={projection} + style={geographyStyle} + /> )); }} </Geographies> - <Geographies geography={ statesProvincesLinesData } disableOptimization={ true }> + <Geographies geography={statesProvincesLinesData} disableOptimization={true}> {(geographies, projection) => { return this.state.visibleStatesProvincesLines.map(({ id }) => ( <Geography - key={ id } - geography={ geographies[id] } - projection={ projection } - style={ stateProvinceLineStyle } /> + key={id} + geography={geographies[id]} + projection={projection} + style={stateProvinceLineStyle} + /> )); }} </Geographies> - <Markers> - { [...countryMarkers, ...cityMarkers, userMarker] } - </Markers> + <Markers>{[...countryMarkers, ...cityMarkers, userMarker]}</Markers> </ZoomableGroup> </ComposableMap> ); @@ -203,17 +208,21 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { return { default: defaultStyle, hover: style.hover || defaultStyle, - pressed: style.pressed || defaultStyle + pressed: style.pressed || defaultStyle, }; } - _getProjection(width: number, height: number, config: { - scale?: number, - xOffset?: number, - yOffset?: number, - rotation?: [number, number, number], - precision?: number, - }) { + _getProjection( + width: number, + height: number, + config: { + scale?: number, + xOffset?: number, + yOffset?: number, + rotation?: [number, number, number], + precision?: number, + }, + ) { const scale = config.scale || 160; const xOffset = config.xOffset || 0; const yOffset = config.yOffset || 0; @@ -222,7 +231,7 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { return geoTimes() .scale(scale) - .translate([ xOffset + width / 2, yOffset + height / 2 ]) + .translate([xOffset + width / 2, yOffset + height / 2]) .rotate(rotation) .precision(precision); } @@ -231,24 +240,22 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { center: [number, number], offset: [number, number], projection: Function, - zoom: number + zoom: number, ) { const pos = projection(center); - return projection.invert([ - pos[0] + offset[0] / zoom, - pos[1] + offset[1] / zoom - ]); + return projection.invert([pos[0] + offset[0] / zoom, pos[1] + offset[1] / zoom]); } _getViewportGeoBoundingBox( centerCoordinate: [number, number], - width: number, height: number, + width: number, + height: number, projection: Function, - zoom: number + zoom: number, ) { const center = projection(centerCoordinate); - const halfWidth = width * 0.5 / zoom; - const halfHeight = height * 0.5 / zoom; + const halfWidth = (width * 0.5) / zoom; + const halfHeight = (height * 0.5) / zoom; const northWest = projection.invert([center[0] - halfWidth, center[1] - halfHeight]); const southEast = projection.invert([center[0] + halfWidth, center[1] + halfHeight]); @@ -267,13 +274,10 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { return ( oldProps.width !== nextProps.width || oldProps.height !== nextProps.height || - oldProps.center[0] !== nextProps.center[0] || oldProps.center[1] !== nextProps.center[1] || - oldProps.offset[0] !== nextProps.offset[0] || oldProps.offset[1] !== nextProps.offset[1] || - oldProps.zoomLevel !== nextProps.zoomLevel ); } @@ -283,27 +287,38 @@ export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { const projection = this._getProjection(width, height, this._projectionConfig); const zoomCenter = this._getZoomCenter(center, offset, projection, zoomLevel); - const viewportBbox = this._getViewportGeoBoundingBox(zoomCenter, width, height, projection, zoomLevel); + const viewportBbox = this._getViewportGeoBoundingBox( + zoomCenter, + width, + height, + projection, + zoomLevel, + ); const viewportBboxMatch = { - minX: viewportBbox[0], minY: viewportBbox[1], - maxX: viewportBbox[2], maxY: viewportBbox[3], - }; - - // combine previous and current viewports to get the rough area of transition - const combinedViewportBboxMatch = prevState ? { - minX: Math.min(viewportBbox[0], prevState.viewportBbox[0]), - minY: Math.min(viewportBbox[1], prevState.viewportBbox[1]), - maxX: Math.max(viewportBbox[2], prevState.viewportBbox[2]), - maxY: Math.max(viewportBbox[3], prevState.viewportBbox[3]), - } : { minX: viewportBbox[0], minY: viewportBbox[1], maxX: viewportBbox[2], maxY: viewportBbox[3], }; - const visibleCountries = zoomLevel < 5 || zoomLevel > 20 ? [] : countryTree.search(viewportBboxMatch); + // combine previous and current viewports to get the rough area of transition + const combinedViewportBboxMatch = prevState + ? { + minX: Math.min(viewportBbox[0], prevState.viewportBbox[0]), + minY: Math.min(viewportBbox[1], prevState.viewportBbox[1]), + maxX: Math.max(viewportBbox[2], prevState.viewportBbox[2]), + maxY: Math.max(viewportBbox[3], prevState.viewportBbox[3]), + } + : { + minX: viewportBbox[0], + minY: viewportBbox[1], + maxX: viewportBbox[2], + maxY: viewportBbox[3], + }; + + const visibleCountries = + zoomLevel < 5 || zoomLevel > 20 ? [] : countryTree.search(viewportBboxMatch); const visibleCities = zoomLevel >= 40 ? cityTree.search(viewportBboxMatch) : []; const visibleGeometry = geometryTree.search(combinedViewportBboxMatch); const visibleStatesProvincesLines = provincesStatesLinesTree.search(combinedViewportBboxMatch); diff --git a/app/components/Switch.android.js b/app/components/Switch.android.js index 17bb976186..4689107868 100644 --- a/app/components/Switch.android.js +++ b/app/components/Switch.android.js @@ -3,28 +3,22 @@ import * as React from 'react'; import { Switch as _Switch } from 'react-native'; export type SwitchProps = { - isOn: boolean; - onChange?: (isOn: boolean) => void; + isOn: boolean, + onChange?: (isOn: boolean) => void, }; -type State = { -}; +type State = {}; export default class Switch extends React.Component<SwitchProps, State> { static defaultProps: SwitchProps = { isOn: false, - onChange: ()=>{}, + onChange: () => {}, }; - state = { - }; + state = {}; render() { const { isOn, ...otherProps } = this.props; - return ( - <_Switch { ...otherProps } - value={ isOn } - onValueChange={ this.props.onChange(isOn) } /> - ); + return <_Switch {...otherProps} value={isOn} onValueChange={this.props.onChange(isOn)} />; } } diff --git a/app/components/Switch.js b/app/components/Switch.js index 6bd4b8e4d0..257b2c6587 100644 --- a/app/components/Switch.js +++ b/app/components/Switch.js @@ -5,41 +5,41 @@ const CLICK_TIMEOUT = 1000; const MOVE_THRESHOLD = 10; export type SwitchProps = { - className?: string; - isOn: boolean; - onChange: ?((isOn: boolean) => void); + className?: string, + isOn: boolean, + onChange: ?(isOn: boolean) => void, }; type State = { ignoreChange: boolean, - initialPos: {x: number, y: number}, + initialPos: { x: number, y: number }, startTime: ?number, }; export default class Switch extends React.Component<SwitchProps, State> { static defaultProps: SwitchProps = { isOn: false, - onChange: null + onChange: null, }; state = { ignoreChange: false, - initialPos: {x: 0, y: 0}, - startTime: (null: ?number) + initialPos: { x: 0, y: 0 }, + startTime: (null: ?number), }; isCapturingMouseEvents = false; ref: ?HTMLInputElement; - onRef = (e: ?HTMLInputElement) => this.ref = e; + onRef = (e: ?HTMLInputElement) => (this.ref = e); handleMouseDown = (e: MouseEvent) => { const { clientX: x, clientY: y } = e; this.startCapturingMouseEvents(); this.setState({ initialPos: { x, y }, - startTime: e.timeStamp + startTime: e.timeStamp, }); - } + }; handleMouseMove = (e: MouseEvent) => { const inputElement = this.ref; @@ -47,66 +47,66 @@ export default class Switch extends React.Component<SwitchProps, State> { const { clientX: x, clientY: y } = e; const dx = Math.abs(x0 - x); - if(dx < MOVE_THRESHOLD) { + if (dx < MOVE_THRESHOLD) { return; } const isOn = !!this.props.isOn; let nextOn = isOn; - if(x < x0 && isOn) { + if (x < x0 && isOn) { nextOn = false; - } else if(x > x0 && !isOn) { + } else if (x > x0 && !isOn) { nextOn = true; } - if(isOn !== nextOn) { + if (isOn !== nextOn) { this.setState({ initialPos: { x, y }, - ignoreChange: true + ignoreChange: true, }); - if(inputElement) { + if (inputElement) { inputElement.checked = nextOn; } this.notify(nextOn); } - } + }; handleMouseUp = () => { this.stopCapturingMouseEvents(); - } + }; handleChange = (e: Event) => { const startTime = this.state.startTime; const eventTarget: Object = e.target; - if(typeof(startTime) !== 'number') { + if (typeof startTime !== 'number') { throw new Error('startTime must be a number.'); } const dt = e.timeStamp - startTime; - if(this.state.ignoreChange) { + if (this.state.ignoreChange) { this.setState({ ignoreChange: false }); e.preventDefault(); - } else if(dt > CLICK_TIMEOUT) { + } else if (dt > CLICK_TIMEOUT) { e.preventDefault(); } else { this.notify(eventTarget.checked); } - } + }; notify(isOn: boolean) { const onChange = this.props.onChange; - if(onChange) { + if (onChange) { onChange(isOn); } } startCapturingMouseEvents() { - if(this.isCapturingMouseEvents) { + if (this.isCapturingMouseEvents) { throw new Error('startCapturingMouseEvents() is called out of order.'); } document.addEventListener('mousemove', this.handleMouseMove); @@ -115,7 +115,7 @@ export default class Switch extends React.Component<SwitchProps, State> { } stopCapturingMouseEvents() { - if(!this.isCapturingMouseEvents) { + if (!this.isCapturingMouseEvents) { throw new Error('stopCapturingMouseEvents() is called out of order.'); } document.removeEventListener('mousemove', this.handleMouseMove); @@ -125,7 +125,7 @@ export default class Switch extends React.Component<SwitchProps, State> { componentWillUnmount() { // guard from abrupt programmatic unmount - if(this.isCapturingMouseEvents) { + if (this.isCapturingMouseEvents) { this.stopCapturingMouseEvents(); } } @@ -135,13 +135,15 @@ export default class Switch extends React.Component<SwitchProps, State> { const { isOn, onChange, ...otherProps } = this.props; const className = ('switch ' + (otherProps.className || '')).trim(); return ( - <input { ...otherProps } + <input + {...otherProps} type="checkbox" - ref={ this.onRef } - className={ className } - checked={ isOn } - onMouseDown={ this.handleMouseDown } - onChange={ this.handleChange } /> + ref={this.onRef} + className={className} + checked={isOn} + onMouseDown={this.handleMouseDown} + onChange={this.handleChange} + /> ); } } diff --git a/app/components/TransitionContainer.js b/app/components/TransitionContainer.js index 687a9ae9e0..1b6e8aee8f 100644 --- a/app/components/TransitionContainer.js +++ b/app/components/TransitionContainer.js @@ -6,19 +6,18 @@ import getStyles from './TransitionContainerStyles'; type TransitionContainerProps = { children: React.Node, - ...TransitionGroupProps + ...TransitionGroupProps, }; type State = { - previousChildren: ?React.Node, - childrenAnimation: Types.AnimatedViewStyleRuleSet, - previousChildrenAnimation: Types.AnimatedViewStyleRuleSet, - animationStyles: Types.AnimatedViewStyleRuleSet, - dimensions: Types.Dimensions, + previousChildren: ?React.Node, + childrenAnimation: Types.AnimatedViewStyleRuleSet, + previousChildrenAnimation: Types.AnimatedViewStyleRuleSet, + animationStyles: Types.AnimatedViewStyleRuleSet, + dimensions: Types.Dimensions, }; export default class TransitionContainer extends Component<TransitionContainerProps, State> { - constructor(props: TransitionContainerProps) { super(props); @@ -30,157 +29,172 @@ export default class TransitionContainer extends Component<TransitionContainerPr } componentWillReceiveProps(nextProps: TransitionContainerProps) { - switch (nextProps.name){ - case 'slide-up': - this.slideUpTransition(nextProps); - break; - case 'slide-down': - this.slideDownTransition(nextProps); - break; - case 'push': - this.pushTransition(nextProps); - break; - case 'pop': - this.popTransition(nextProps); - break; - default: - break; + switch (nextProps.name) { + case 'slide-up': + this.slideUpTransition(nextProps); + break; + case 'slide-down': + this.slideDownTransition(nextProps); + break; + case 'push': + this.pushTransition(nextProps); + break; + case 'pop': + this.popTransition(nextProps); + break; + default: + break; } } onFinishedAnimation() { this.setState({ childrenAnimation: getStyles().allowPointerEventsStyle, - previousChildren: null + previousChildren: null, }); } slideUpTransition(nextProps: TransitionContainerProps) { const currentTranslationValue = Animated.createValue(this.state.dimensions.height); - this.setState({ - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateY: currentTranslationValue }] - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateY: 0 }] - }), - }, () => { - Animated.timing(currentTranslationValue, { - toValue: 0, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }).start(() => this.onFinishedAnimation()); - }); + this.setState( + { + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateY: currentTranslationValue }], + }), + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateY: 0 }], + }), + }, + () => { + Animated.timing(currentTranslationValue, { + toValue: 0, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + }).start(() => this.onFinishedAnimation()); + }, + ); } slideDownTransition(nextProps: TransitionContainerProps) { const previousTranslationValue = Animated.createValue(0); - this.setState({ - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateY: 0 }] - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateY: previousTranslationValue }] - }), - }, () => { - Animated.timing(previousTranslationValue, { - toValue: this.state.dimensions.height, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }).start(() => this.onFinishedAnimation()); - }); + this.setState( + { + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateY: 0 }], + }), + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateY: previousTranslationValue }], + }), + }, + () => { + Animated.timing(previousTranslationValue, { + toValue: this.state.dimensions.height, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + }).start(() => this.onFinishedAnimation()); + }, + ); } pushTransition(nextProps: TransitionContainerProps) { const currentTranslationValue = Animated.createValue(this.state.dimensions.width); const previousTranslationValue = Animated.createValue(0); - this.setState({ - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateX: currentTranslationValue }] - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateX: previousTranslationValue }] - }), - }, () => { - const compositeAnimation = Animated.parallel([ - Animated.timing(currentTranslationValue, { - toValue: 0, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, + this.setState( + { + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateX: currentTranslationValue }], }), - Animated.timing(previousTranslationValue, { - toValue: - this.state.dimensions.width / 2, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }) - ]); - compositeAnimation.start(() => this.onFinishedAnimation()); - }); + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateX: previousTranslationValue }], + }), + }, + () => { + const compositeAnimation = Animated.parallel([ + Animated.timing(currentTranslationValue, { + toValue: 0, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + }), + Animated.timing(previousTranslationValue, { + toValue: -this.state.dimensions.width / 2, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + }), + ]); + compositeAnimation.start(() => this.onFinishedAnimation()); + }, + ); } popTransition(nextProps: TransitionContainerProps) { - const currentTranslationValue = Animated.createValue(- this.state.dimensions.width / 2 ); + const currentTranslationValue = Animated.createValue(-this.state.dimensions.width / 2); const previousTranslationValue = Animated.createValue(0); - this.setState({ - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateX: currentTranslationValue }] - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateX: previousTranslationValue }] - }), - }, () => { - const compositeAnimation = Animated.parallel([ - Animated.timing(currentTranslationValue, { - toValue: 0, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, + this.setState( + { + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateX: currentTranslationValue }], }), - Animated.timing(previousTranslationValue, { - toValue: this.state.dimensions.width, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }) - ]); - compositeAnimation.start(() => this.onFinishedAnimation()); - }); + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateX: previousTranslationValue }], + }), + }, + () => { + const compositeAnimation = Animated.parallel([ + Animated.timing(currentTranslationValue, { + toValue: 0, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + }), + Animated.timing(previousTranslationValue, { + toValue: this.state.dimensions.width, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + }), + ]); + compositeAnimation.start(() => this.onFinishedAnimation()); + }, + ); } render() { const { children } = this.props; const { previousChildren, childrenAnimation, previousChildrenAnimation } = this.state; return ( - <View style={ getStyles().transitionContainerStyle }> - { previousChildren && - (<Animated.View key={ previousChildren && previousChildren.key } + <View style={getStyles().transitionContainerStyle}> + {previousChildren && ( + <Animated.View + key={previousChildren && previousChildren.key} style={[getStyles().animationDefaultStyle, previousChildrenAnimation]}> - { previousChildren } - </Animated.View>) } + {previousChildren} + </Animated.View> + )} - <Animated.View key={ children.key } style={[getStyles().animationDefaultStyle, childrenAnimation]}> - { children } + <Animated.View + key={children.key} + style={[getStyles().animationDefaultStyle, childrenAnimation]}> + {children} </Animated.View> - </View> ); } -}
\ No newline at end of file +} diff --git a/app/components/TransitionContainerStyles.android.js b/app/components/TransitionContainerStyles.android.js index d335eea680..b234ff4be0 100644 --- a/app/components/TransitionContainerStyles.android.js +++ b/app/components/TransitionContainerStyles.android.js @@ -6,21 +6,31 @@ import { log } from '../lib/platform'; const dimensions = UserInterface.measureWindow(); let menuBarHeight; -MobileAppBridge.getMenuBarHeight().then(_response => {menuBarHeight = _response;}).catch(e => { - log.error('Failed getting menuBarHeight:', e); -}); +MobileAppBridge.getMenuBarHeight() + .then((_response) => { + menuBarHeight = _response; + }) + .catch((e) => { + log.error('Failed getting menuBarHeight:', e); + }); export default () => { return { - animationDefaultStyle: Styles.createAnimatedViewStyle({ - position: 'absolute', - width: dimensions.width, - height: dimensions.height - menuBarHeight + 24, - }, false), + animationDefaultStyle: Styles.createAnimatedViewStyle( + { + position: 'absolute', + width: dimensions.width, + height: dimensions.height - menuBarHeight + 24, + }, + false, + ), allowPointerEventsStyle: null, - transitionContainerStyle: Styles.createViewStyle({ - width: dimensions.width, - height: dimensions.height - menuBarHeight + 24, //TODO: Remove ugly hack since it seems that at least my LG is seems hard to find the real display area ... Probably needs to be fixed for some versions or models - }, false) + transitionContainerStyle: Styles.createViewStyle( + { + width: dimensions.width, + height: dimensions.height - menuBarHeight + 24, //TODO: Remove ugly hack since it seems that at least my LG is seems hard to find the real display area ... Probably needs to be fixed for some versions or models + }, + false, + ), }; -};
\ No newline at end of file +}; diff --git a/app/components/TransitionContainerStyles.js b/app/components/TransitionContainerStyles.js index eb03570d46..c6510c0600 100644 --- a/app/components/TransitionContainerStyles.js +++ b/app/components/TransitionContainerStyles.js @@ -14,9 +14,9 @@ const styles = { transitionContainerStyle: Styles.createViewStyle({ width: dimensions.width, height: dimensions.height, - }) + }), }; export default () => { return styles; -};
\ No newline at end of file +}; diff --git a/app/components/styled/AppButton.js b/app/components/styled/AppButton.js index 59cba4eefb..f54bd18024 100644 --- a/app/components/styled/AppButton.js +++ b/app/components/styled/AppButton.js @@ -15,59 +15,60 @@ class BaseButton extends Component { state = { hovered: false }; - textStyle = () => this.state.hovered ? styles.white80 : styles.white; - iconStyle = () => this.state.hovered ? styles.white80 : styles.white; - backgroundStyle = () => this.state.hovered ? styles.white80 : styles.white; + textStyle = () => (this.state.hovered ? styles.white80 : styles.white); + iconStyle = () => (this.state.hovered ? styles.white80 : styles.white); + backgroundStyle = () => (this.state.hovered ? styles.white80 : styles.white); - onHoverStart = () => !this.props.disabled ? this.setState({ hovered: true }) : null; - onHoverEnd = () => !this.props.disabled ? this.setState({ hovered: false }) : null; + onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); + onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); render() { const { children, ...otherProps } = this.props; return ( - <Button style={[ styles.common, this.backgroundStyle() ]} + <Button + style={[styles.common, this.backgroundStyle()]} onHoverStart={this.onHoverStart} onHoverEnd={this.onHoverEnd} {...otherProps}> - { - React.Children.map(children, (node) => { - if (React.isValidElement(node)) { - let updatedProps = {}; + {React.Children.map(children, (node) => { + if (React.isValidElement(node)) { + let updatedProps = {}; - if(node.type.name === 'Label') { - updatedProps = { style: [styles.label, this.textStyle()]}; - } - - if(node.type.name === 'Img') { - updatedProps = { tintColor:'currentColor', style: [styles.icon, this.iconStyle()]}; - } + if (node.type.name === 'Label') { + updatedProps = { style: [styles.label, this.textStyle()] }; + } - return React.cloneElement(node, updatedProps); - } else { - return <Label style={[styles.label, this.textStyle()]}>{children}</Label>; + if (node.type.name === 'Img') { + updatedProps = { tintColor: 'currentColor', style: [styles.icon, this.iconStyle()] }; } - }) - } + + return React.cloneElement(node, updatedProps); + } else { + return <Label style={[styles.label, this.textStyle()]}>{children}</Label>; + } + })} </Button> ); } } export class RedButton extends BaseButton { - backgroundStyle = () => this.state.hovered ? styles.redHover : styles.red; + backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red); } export class GreenButton extends BaseButton { - backgroundStyle = () => this.state.hovered ? styles.greenHover : styles.green; + backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green); } export class BlueButton extends BaseButton { - backgroundStyle = () => this.state.hovered ? styles.blueHover : styles.blue; + backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue); } export class TransparentButton extends BaseButton { - backgroundStyle = () => this.state.hovered ? blurStyles.transparentHover : blurStyles.transparent; + backgroundStyle = () => + this.state.hovered ? blurStyles.transparentHover : blurStyles.transparent; } export class RedTransparentButton extends BaseButton { - backgroundStyle = () => this.state.hovered ? blurStyles.redTransparentHover : blurStyles.redTransparent; -}
\ No newline at end of file + backgroundStyle = () => + this.state.hovered ? blurStyles.redTransparentHover : blurStyles.redTransparent; +} diff --git a/app/components/styled/AppButtonStyles.js b/app/components/styled/AppButtonStyles.js index 477a0cc54a..6049f227d5 100644 --- a/app/components/styled/AppButtonStyles.js +++ b/app/components/styled/AppButtonStyles.js @@ -4,42 +4,42 @@ import { createViewStyles, createTextStyles } from '../../lib/styles'; export default { ...createViewStyles({ - red:{ + red: { backgroundColor: colors.red95, }, redHover: { backgroundColor: colors.red, }, - green:{ + green: { backgroundColor: colors.green, }, - greenHover:{ + greenHover: { backgroundColor: colors.green90, }, - blue:{ + blue: { backgroundColor: colors.blue80, }, - blueHover:{ + blueHover: { backgroundColor: colors.blue60, }, - white80:{ + white80: { color: colors.white80, }, white: { color: colors.white, }, - icon:{ + icon: { position: 'absolute', alignSelf: 'flex-end', right: 8, marginLeft: 8, }, - iconTransparent:{ + iconTransparent: { position: 'absolute', alignSelf: 'flex-end', right: 42, }, - common:{ + common: { paddingTop: 9, paddingLeft: 9, paddingRight: 9, @@ -56,7 +56,7 @@ export default { }, }), ...createTextStyles({ - label:{ + label: { alignSelf: 'center', fontFamily: 'DINPro', fontSize: 20, @@ -65,4 +65,4 @@ export default { flex: 1, }, }), -};
\ No newline at end of file +}; diff --git a/app/components/styled/BlurAppButtonStyles.android.js b/app/components/styled/BlurAppButtonStyles.android.js index 9243a28729..c39e44cdd5 100644 --- a/app/components/styled/BlurAppButtonStyles.android.js +++ b/app/components/styled/BlurAppButtonStyles.android.js @@ -4,17 +4,17 @@ import { createViewStyles } from '../../lib/styles'; export default { ...createViewStyles({ - transparent:{ + transparent: { backgroundColor: colors.white20, }, - transparentHover:{ + transparentHover: { backgroundColor: colors.white40, }, - redTransparent:{ + redTransparent: { backgroundColor: colors.red40, }, - redTransparentHover:{ + redTransparentHover: { backgroundColor: colors.red45, }, - }) -};
\ No newline at end of file + }), +}; diff --git a/app/components/styled/BlurAppButtonStyles.js b/app/components/styled/BlurAppButtonStyles.js index 9e7b213754..72d5398ea8 100644 --- a/app/components/styled/BlurAppButtonStyles.js +++ b/app/components/styled/BlurAppButtonStyles.js @@ -4,21 +4,21 @@ import { createViewStyles } from '../../lib/styles'; export default { ...createViewStyles({ - transparent:{ + transparent: { backgroundColor: colors.white20, backdropFilter: 'blur(4px)', }, - transparentHover:{ + transparentHover: { backgroundColor: colors.white40, backdropFilter: 'blur(4px)', }, - redTransparent:{ + redTransparent: { backgroundColor: colors.red40, backdropFilter: 'blur(4px)', }, - redTransparentHover:{ + redTransparentHover: { backgroundColor: colors.red45, backdropFilter: 'blur(4px)', }, - }) -};
\ No newline at end of file + }), +}; diff --git a/app/components/styled/CellButton.js b/app/components/styled/CellButton.js index 1a0e055b01..f0251c942e 100644 --- a/app/components/styled/CellButton.js +++ b/app/components/styled/CellButton.js @@ -9,7 +9,7 @@ import { createViewStyles, createTextStyles } from '../../lib/styles'; const styles = { ...createViewStyles({ - cell:{ + cell: { backgroundColor: colors.blue80, paddingTop: 14, paddingBottom: 14, @@ -21,19 +21,19 @@ const styles = { alignItems: 'center', alignContent: 'center', }, - blue:{ + blue: { backgroundColor: colors.blue80, }, - blueHover:{ + blueHover: { backgroundColor: colors.blue60, }, - white40:{ + white40: { color: colors.white40, }, - white60:{ + white60: { color: colors.white60, }, - white80:{ + white80: { color: colors.white80, }, white: { @@ -45,7 +45,7 @@ const styles = { }, }), ...createTextStyles({ - label:{ + label: { color: colors.white, alignSelf: 'center', fontFamily: 'DINPro', @@ -55,7 +55,7 @@ const styles = { flex: 1, marginLeft: 8, }, - subtext:{ + subtext: { color: colors.white60, fontFamily: 'Open Sans', fontSize: 13, @@ -69,12 +69,11 @@ const styles = { export class SubText extends Text {} export class Label extends Text {} - type CellButtonProps = { children?: React.Node, disabled?: boolean, cellHoverStyle?: Types.ViewStyle, - style?: Types.ViewStyle + style?: Types.ViewStyle, }; type State = { hovered: boolean }; @@ -82,44 +81,55 @@ type State = { hovered: boolean }; export default class CellButton extends Component<CellButtonProps, State> { state = { hovered: false }; - textStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle : null; - iconStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle : null; - subtextStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle : null; - backgroundStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.blueHover : null; + textStyle = (cellHoverStyle?: Types.ViewStyle) => (this.state.hovered ? cellHoverStyle : null); + iconStyle = (cellHoverStyle?: Types.ViewStyle) => (this.state.hovered ? cellHoverStyle : null); + subtextStyle = (cellHoverStyle?: Types.ViewStyle) => (this.state.hovered ? cellHoverStyle : null); + backgroundStyle = (cellHoverStyle?: Types.ViewStyle) => + this.state.hovered ? cellHoverStyle || styles.blueHover : null; - onHoverStart = () => !this.props.disabled ? this.setState({ hovered: true }) : null; - onHoverEnd = () => !this.props.disabled ? this.setState({ hovered: false }) : null; + onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); + onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); render() { const { children, style, cellHoverStyle, ...otherProps } = this.props; return ( - <Button style={[ styles.cell, style, this.backgroundStyle(cellHoverStyle) ]} + <Button + style={[styles.cell, style, this.backgroundStyle(cellHoverStyle)]} onHoverStart={this.onHoverStart} onHoverEnd={this.onHoverEnd} {...otherProps}> - { - React.Children.map(children, (node) => { - if (React.isValidElement(node)) { - let updatedProps = {}; - - if(node.type.name === 'Label') { - updatedProps = { style: [styles.label, node.props.style, this.textStyle(node.props.cellHoverStyle)]}; - } + {React.Children.map(children, (node) => { + if (React.isValidElement(node)) { + let updatedProps = {}; - if(node.type.name === 'Img') { - updatedProps = { tintColor:'currentColor', style: [styles.icon, node.props.style, this.iconStyle(node.props.cellHoverStyle)]}; - } + if (node.type.name === 'Label') { + updatedProps = { + style: [styles.label, node.props.style, this.textStyle(node.props.cellHoverStyle)], + }; + } - if(node.type.name === 'SubText') { - updatedProps = { style: [styles.subtext, node.props.style, this.subtextStyle(node.props.cellHoverStyle)]}; - } + if (node.type.name === 'Img') { + updatedProps = { + tintColor: 'currentColor', + style: [styles.icon, node.props.style, this.iconStyle(node.props.cellHoverStyle)], + }; + } - return React.cloneElement(node, updatedProps); - } else if (node){ - return <Label style={[styles.label, this.textStyle()]}>{children}</Label>; + if (node.type.name === 'SubText') { + updatedProps = { + style: [ + styles.subtext, + node.props.style, + this.subtextStyle(node.props.cellHoverStyle), + ], + }; } - }) - } + + return React.cloneElement(node, updatedProps); + } else if (node) { + return <Label style={[styles.label, this.textStyle()]}>{children}</Label>; + } + })} </Button> ); } diff --git a/app/components/styled/index.js b/app/components/styled/index.js index 7efeb28389..6582da5e77 100644 --- a/app/components/styled/index.js +++ b/app/components/styled/index.js @@ -2,7 +2,13 @@ import { Button } from './Button'; import CellButton, { Label, SubText } from './CellButton'; -import { RedButton, GreenButton, BlueButton, TransparentButton, RedTransparentButton } from './AppButton'; +import { + RedButton, + GreenButton, + BlueButton, + TransparentButton, + RedTransparentButton, +} from './AppButton'; export { Button, diff --git a/app/containers/AccountPage.js b/app/containers/AccountPage.js index d4fdf92b3a..210ad4aaeb 100644 --- a/app/containers/AccountPage.js +++ b/app/containers/AccountPage.js @@ -22,8 +22,11 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => onClose: () => { pushHistory('/settings'); }, - onBuyMore: () => openLink(links['purchase']) + onBuyMore: () => openLink(links['purchase']), }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Account); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Account); diff --git a/app/containers/AdvancedSettingsPage.js b/app/containers/AdvancedSettingsPage.js index a1ca7d5a66..0c1312b87f 100644 --- a/app/containers/AdvancedSettingsPage.js +++ b/app/containers/AdvancedSettingsPage.js @@ -11,13 +11,13 @@ import type { SharedRouteProps } from '../routes'; const mapStateToProps = (state: ReduxState) => { const relaySettings = state.settings.relaySettings; - if(relaySettings.normal) { + if (relaySettings.normal) { const { protocol, port } = relaySettings.normal; return { protocol: protocol === 'any' ? 'Automatic' : protocol, port: port === 'any' ? 'Automatic' : port, }; - } else if(relaySettings.custom_tunnel_endpoint) { + } else if (relaySettings.custom_tunnel_endpoint) { const { protocol, port } = relaySettings.custom_tunnel_endpoint; return { protocol, port }; } else { @@ -33,26 +33,30 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => onUpdate: async (protocol, port) => { const relayUpdate = RelaySettingsBuilder.normal() .tunnel.openvpn((openvpn) => { - if(protocol === 'Automatic') { + if (protocol === 'Automatic') { openvpn.protocol.any(); } else { openvpn.protocol.exact(protocol.toLowerCase()); } - if(port === 'Automatic') { + if (port === 'Automatic') { openvpn.port.any(); } else { openvpn.port.exact(port); } - }).build(); + }) + .build(); try { await backend.updateRelaySettings(relayUpdate); await backend.fetchRelaySettings(); - } catch(e) { + } catch (e) { log.error('Failed to update relay settings', e.message); } }, }; }; -export default connect(mapStateToProps, mapDispatchToProps)(AdvancedSettings); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(AdvancedSettings); diff --git a/app/containers/ConnectPage.js b/app/containers/ConnectPage.js index 9ce245a927..532dfc8626 100644 --- a/app/containers/ConnectPage.js +++ b/app/containers/ConnectPage.js @@ -13,30 +13,33 @@ import type { SharedRouteProps } from '../routes'; import type { RelaySettingsRedux, RelayLocationRedux } from '../redux/settings/reducers'; -function getRelayName(relaySettings: RelaySettingsRedux, relayLocations: Array<RelayLocationRedux>): string { - if(relaySettings.normal) { +function getRelayName( + relaySettings: RelaySettingsRedux, + relayLocations: Array<RelayLocationRedux>, +): string { + if (relaySettings.normal) { const location = relaySettings.normal.location; - if(location === 'any') { + if (location === 'any') { return 'Automatic'; - } else if(location.country) { + } else if (location.country) { const country = relayLocations.find(({ code }) => code === location.country); - if(country) { + if (country) { return country.name; } - } else if(location.city) { + } else if (location.city) { const [countryCode, cityCode] = location.city; const country = relayLocations.find(({ code }) => code === countryCode); - if(country) { + if (country) { const city = country.cities.find(({ code }) => code === cityCode); - if(city) { + if (city) { return city.name; } } } return 'Unknown'; - } else if(relaySettings.custom_tunnel_endpoint) { + } else if (relaySettings.custom_tunnel_endpoint) { return 'Custom'; } else { throw new Error('Unsupported relay settings.'); @@ -46,10 +49,7 @@ function getRelayName(relaySettings: RelaySettingsRedux, relayLocations: Array<R const mapStateToProps = (state: ReduxState) => { return { accountExpiry: state.account.expiry, - selectedRelayName: getRelayName( - state.settings.relaySettings, - state.settings.relayLocations - ), + selectedRelayName: getRelayName(state.settings.relaySettings, state.settings.relayLocations), connection: state.connection, }; }; @@ -79,4 +79,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Connect); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Connect); diff --git a/app/containers/LoginPage.js b/app/containers/LoginPage.js index 52fac166fa..dbd49dea03 100644 --- a/app/containers/LoginPage.js +++ b/app/containers/LoginPage.js @@ -14,7 +14,10 @@ import type { SharedRouteProps } from '../routes'; const mapStateToProps = (state: ReduxState) => state; const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => { const { push: pushHistory } = bindActionCreators({ push }, dispatch); - const { login, resetLoginError, updateAccountToken } = bindActionCreators(accountActions, dispatch); + const { login, resetLoginError, updateAccountToken } = bindActionCreators( + accountActions, + dispatch, + ); const { backend } = props; return { onSettings: () => { @@ -34,4 +37,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Login); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Login); diff --git a/app/containers/PreferencesPage.js b/app/containers/PreferencesPage.js index 793848d35b..d4eb608038 100644 --- a/app/containers/PreferencesPage.js +++ b/app/containers/PreferencesPage.js @@ -9,7 +9,7 @@ import type { ReduxState, ReduxDispatch } from '../redux/store'; import type { SharedRouteProps } from '../routes'; const mapStateToProps = (state: ReduxState) => ({ - allowLan: state.settings.allowLan + allowLan: state.settings.allowLan, }); const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => { @@ -23,4 +23,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Preferences); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Preferences); diff --git a/app/containers/SelectLocationPage.js b/app/containers/SelectLocationPage.js index b5e12eb4ef..05b4205dfa 100644 --- a/app/containers/SelectLocationPage.js +++ b/app/containers/SelectLocationPage.js @@ -19,8 +19,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => onSelect: async (relayLocation) => { try { const relayUpdate = RelaySettingsBuilder.normal() - .location - .fromRaw(relayLocation) + .location.fromRaw(relayLocation) .build(); await backend.updateRelaySettings(relayUpdate); @@ -31,8 +30,11 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => } catch (e) { log.error('Failed to select server: ', e.message); } - } + }, }; }; -export default connect(mapStateToProps, mapDispatchToProps)(SelectLocation); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(SelectLocation); diff --git a/app/containers/SettingsPage.js b/app/containers/SettingsPage.js index 7c844252d3..24b718485b 100644 --- a/app/containers/SettingsPage.js +++ b/app/containers/SettingsPage.js @@ -30,4 +30,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) = }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Settings); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Settings); diff --git a/app/containers/SupportPage.js b/app/containers/SupportPage.js index aaa9f786cd..5d67ddce31 100644 --- a/app/containers/SupportPage.js +++ b/app/containers/SupportPage.js @@ -25,8 +25,11 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) = onSend: (email, message, savedReport) => { return sendProblemReport(email, message, savedReport); - } + }, }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Support); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Support); diff --git a/app/index.android.js b/app/index.android.js index 1e9594a1c9..e41ccc567a 100644 --- a/app/index.android.js +++ b/app/index.android.js @@ -3,4 +3,4 @@ import RX from 'reactxp'; import App from './App'; RX.App.initialize(true, true); -RX.UserInterface.setMainView(<App />);
\ No newline at end of file +RX.UserInterface.setMainView(<App />); diff --git a/app/lib/backend.js b/app/lib/backend.js index 27b4b2c927..b043a9b10e 100644 --- a/app/lib/backend.js +++ b/app/lib/backend.js @@ -11,7 +11,14 @@ import type { ReduxStore } from '../redux/store'; import type { AccountToken, BackendState, RelaySettingsUpdate } from './ipc-facade'; import type { ConnectionState } from '../redux/connection/reducers'; -export type ErrorType = 'NO_CREDIT' | 'NO_INTERNET' | 'NO_DAEMON' | 'INVALID_ACCOUNT' | 'NO_ACCOUNT' | 'COMMUNICATION_FAILURE' | 'UNKNOWN_ERROR' ; +export type ErrorType = + | 'NO_CREDIT' + | 'NO_INTERNET' + | 'NO_DAEMON' + | 'INVALID_ACCOUNT' + | 'NO_ACCOUNT' + | 'COMMUNICATION_FAILURE' + | 'UNKNOWN_ERROR'; export class BackendError extends Error { type: ErrorType; @@ -28,55 +35,50 @@ export class BackendError extends Error { } static localizedTitle(type: ErrorType): string { - switch(type) { - case 'NO_CREDIT': - return 'Out of time'; - case 'NO_INTERNET': - return 'Offline'; - default: - return 'Something went wrong'; + switch (type) { + case 'NO_CREDIT': + return 'Out of time'; + case 'NO_INTERNET': + return 'Offline'; + default: + return 'Something went wrong'; } } static localizedMessage(type: ErrorType, cause: ?Error): string { - // TODO: since instanceof now works, BackendError can be replaced by a set // of specific error types - switch(type) { - case 'NO_CREDIT': - return 'Buy more time, so you can continue using the internet securely'; - case 'NO_INTERNET': - return 'Your internet connection will be secured when you get back online'; - case 'INVALID_ACCOUNT': - return 'Invalid account number'; - case 'NO_ACCOUNT': - return 'No account was set'; - case 'NO_DAEMON': - return 'Could not connect to the Mullvad daemon'; - case 'COMMUNICATION_FAILURE': - return 'api.mullvad.net is blocked, please check your firewall'; - case 'UNKNOWN_ERROR': { - const message = cause - ? ', ' + cause.message - : ''; + switch (type) { + case 'NO_CREDIT': + return 'Buy more time, so you can continue using the internet securely'; + case 'NO_INTERNET': + return 'Your internet connection will be secured when you get back online'; + case 'INVALID_ACCOUNT': + return 'Invalid account number'; + case 'NO_ACCOUNT': + return 'No account was set'; + case 'NO_DAEMON': + return 'Could not connect to the Mullvad daemon'; + case 'COMMUNICATION_FAILURE': + return 'api.mullvad.net is blocked, please check your firewall'; + case 'UNKNOWN_ERROR': { + const message = cause ? ', ' + cause.message : ''; - return 'An unknown error occurred' + message; - } - default: - return ''; + return 'An unknown error occurred' + message; + } + default: + return ''; } } - } - export type IpcCredentials = { connectionString: string, sharedSecret: string, }; export function parseIpcCredentials(data: string): ?IpcCredentials { const [connectionString, sharedSecret] = data.split('\n', 2); - if(connectionString && sharedSecret !== undefined) { + if (connectionString && sharedSecret !== undefined) { return { connectionString, sharedSecret, @@ -86,12 +88,10 @@ export function parseIpcCredentials(data: string): ?IpcCredentials { } } - /** * Backend implementation */ export class Backend { - _ipc: IpcFacade; _credentials: ?IpcCredentials; _authenticationPromise: ?Promise<void>; @@ -101,8 +101,7 @@ export class Backend { this._store = store; this._credentials = credentials; - - if(ipc) { + if (ipc) { this._ipc = ipc; // force to re-authenticate when connection closed @@ -137,19 +136,19 @@ export class Backend { try { await this._fetchRelayLocations(); - } catch(e) { + } catch (e) { log.error('Failed to fetch the relay locations: ', e.message); } try { await this._fetchLocation(); - } catch(e) { + } catch (e) { log.error('Failed to fetch the location: ', e.message); } try { await this._fetchAllowLan(); - } catch(e) { + } catch (e) { log.error('Failed to fetch the LAN sharing policy: ', e.message); } @@ -172,9 +171,7 @@ export class Backend { log.info('Log in complete'); - this._store.dispatch( - accountActions.loginSuccessful(accountData.expiry) - ); + this._store.dispatch(accountActions.loginSuccessful(accountData.expiry)); await this.fetchRelaySettings(); // Redirect the user after some time to allow for @@ -186,8 +183,7 @@ export class Backend { }, 1000); await this._fetchAccountHistory(); - - } catch(e) { + } catch (e) { log.error('Failed to log in,', e.message); const err = this._rpcErrorToBackendError(e); @@ -202,15 +198,15 @@ export class Backend { const isJsonRpcError = e.hasOwnProperty('code'); if (isJsonRpcError) { - switch(e.code) { - case -200: // Account doesn't exist - return new BackendError('INVALID_ACCOUNT'); - case -32603: // Internal error - // We treat all internal backend errors as the user cannot reach - // api.mullvad.net. This is not always true of course, but it is - // true so often that we choose to disregard the other edge cases - // for now. - return new BackendError('COMMUNICATION_FAILURE'); + switch (e.code) { + case -200: // Account doesn't exist + return new BackendError('INVALID_ACCOUNT'); + case -32603: // Internal error + // We treat all internal backend errors as the user cannot reach + // api.mullvad.net. This is not always true of course, but it is + // true so often that we choose to disregard the other edge cases + // for now. + return new BackendError('COMMUNICATION_FAILURE'); } } @@ -230,7 +226,7 @@ export class Backend { this._store.dispatch(accountActions.startLogin()); const accountToken = await this._ipc.getAccount(); - if(!accountToken) { + if (!accountToken) { throw new BackendError('NO_ACCOUNT'); } @@ -322,19 +318,19 @@ export class Backend { const relaySettings = await this._ipc.getRelaySettings(); log.debug('Got relay settings from backend', JSON.stringify(relaySettings)); - if(relaySettings.normal) { + if (relaySettings.normal) { const payload = {}; const normal = relaySettings.normal; const tunnel = normal.tunnel; const location = normal.location; - if(location === 'any') { + if (location === 'any') { payload.location = 'any'; } else { payload.location = location.only; } - if(tunnel === 'any') { + if (tunnel === 'any') { payload.port = 'any'; payload.protocol = 'any'; } else { @@ -345,19 +341,26 @@ export class Backend { this._store.dispatch( settingsActions.updateRelay({ - normal: payload - }) + normal: payload, + }), ); - } else if(relaySettings.custom_tunnel_endpoint) { + } else if (relaySettings.custom_tunnel_endpoint) { const custom_tunnel_endpoint = relaySettings.custom_tunnel_endpoint; - const { host, tunnel: { openvpn: { port, protocol } } } = custom_tunnel_endpoint; + const { + host, + tunnel: { + openvpn: { port, protocol }, + }, + } = custom_tunnel_endpoint; this._store.dispatch( settingsActions.updateRelay({ custom_tunnel_endpoint: { - host, port, protocol - } - }) + host, + port, + protocol, + }, + }), ); } } @@ -367,7 +370,7 @@ export class Backend { await this._ensureAuthenticated(); await this._ipc.removeAccountFromHistory(accountToken); await this._fetchAccountHistory(); - } catch(e) { + } catch (e) { log.error('Failed to remove account token from history', e.message); } } @@ -376,10 +379,8 @@ export class Backend { try { await this._ensureAuthenticated(); const accountHistory = await this._ipc.getAccountHistory(); - this._store.dispatch( - accountActions.updateAccountHistory(accountHistory) - ); - } catch(e) { + this._store.dispatch(accountActions.updateAccountHistory(accountHistory)); + } catch (e) { log.info('Failed to fetch account history,', e.message); throw e; } @@ -402,12 +403,10 @@ export class Backend { latitude: city.latitude, longitude: city.longitude, hasActiveRelays: city.has_active_relays, - })) + })), })); - this._store.dispatch( - settingsActions.updateRelayLocations(storedLocations) - ); + this._store.dispatch(settingsActions.updateRelayLocations(storedLocations)); } async _fetchLocation() { @@ -426,9 +425,7 @@ export class Backend { mullvadExitIp: location.mullvad_exit_ip, }; - this._store.dispatch( - connectionActions.newLocation(locationUpdate) - ); + this._store.dispatch(connectionActions.newLocation(locationUpdate)); } async setAllowLan(allowLan: boolean) { @@ -436,10 +433,8 @@ export class Backend { await this._ensureAuthenticated(); await this._ipc.setAllowLan(allowLan); - this._store.dispatch( - settingsActions.updateAllowLan(allowLan) - ); - } catch(e) { + this._store.dispatch(settingsActions.updateAllowLan(allowLan)); + } catch (e) { log.error('Failed to change the LAN sharing policy: ', e.message); } } @@ -448,9 +443,7 @@ export class Backend { await this._ensureAuthenticated(); const allowLan = await this._ipc.getAllowLan(); - this._store.dispatch( - settingsActions.updateAllowLan(allowLan) - ); + this._store.dispatch(settingsActions.updateAllowLan(allowLan)); } async fetchSecurityState() { @@ -478,9 +471,7 @@ export class Backend { // update online status in background setTimeout(() => { - const action = navigator.onLine - ? connectionActions.online() - : connectionActions.offline(); + const action = navigator.onLine ? connectionActions.online() : connectionActions.offline(); this._store.dispatch(action); }, 0); @@ -488,8 +479,7 @@ export class Backend { async _registerIpcListeners() { await this._ensureAuthenticated(); - this._ipc.registerStateListener(newState => { - + this._ipc.registerStateListener((newState) => { const connectionState = this._securityStateToConnectionState(newState); log.debug(`Got new state from backend {state: ${newState.state}, \ target_state: ${newState.target_state}}, translated to '${connectionState}'`); @@ -510,23 +500,23 @@ export class Backend { } _dispatchConnectionState(connectionState: ConnectionState) { - switch(connectionState) { - case 'connecting': - this._store.dispatch(connectionActions.connecting()); - break; - case 'connected': - this._store.dispatch(connectionActions.connected()); - break; - case 'disconnected': - this._store.dispatch(connectionActions.disconnected()); - break; + switch (connectionState) { + case 'connecting': + this._store.dispatch(connectionActions.connecting()); + break; + case 'connected': + this._store.dispatch(connectionActions.connected()); + break; + case 'disconnected': + this._store.dispatch(connectionActions.disconnected()); + break; } } _ensureAuthenticated(): Promise<void> { const credentials = this._credentials; - if(credentials) { - if(!this._authenticationPromise) { + if (credentials) { + if (!this._authenticationPromise) { this._authenticationPromise = this._authenticate(credentials.sharedSecret); } return this._authenticationPromise; diff --git a/app/lib/formatters.js b/app/lib/formatters.js index 89d45d44a0..437351621d 100644 --- a/app/lib/formatters.js +++ b/app/lib/formatters.js @@ -1,9 +1,9 @@ // @flow export const formatAccount = (val: string): string => { // display number altogether when longer than 12 - if(val.length > 12) { + if (val.length > 12) { return val; } // display quartets return val.replace(/([0-9]{4})/g, '$1 ').trim(); -};
\ No newline at end of file +}; diff --git a/app/lib/ipc-facade.js b/app/lib/ipc-facade.js index 0a64a5cc68..0aa1d48d3f 100644 --- a/app/lib/ipc-facade.js +++ b/app/lib/ipc-facade.js @@ -1,7 +1,16 @@ // @flow import JsonRpcWs, { InvalidReply } from './jsonrpc-ws-ipc'; -import { object, maybe, string, number, boolean, enumeration, arrayOf, oneOf } from 'validated/schema'; +import { + object, + maybe, + string, + number, + boolean, + enumeration, + arrayOf, + oneOf, +} from 'validated/schema'; import { validate } from 'validated/object'; import type { Node as SchemaNode } from 'validated/schema'; @@ -45,12 +54,16 @@ type TunnelOptions<TOpenVpnParameters> = { }; type RelaySettingsNormal<TTunnelOptions> = { - location: 'any' | { - only: RelayLocation, - }, - tunnel: 'any' | { - only: TTunnelOptions - }, + location: + | 'any' + | { + only: RelayLocation, + }, + tunnel: + | 'any' + | { + only: TTunnelOptions, + }, }; // types describing the structure of RelaySettings @@ -59,46 +72,60 @@ export type RelaySettingsCustom = { tunnel: { openvpn: { port: number, - protocol: RelayProtocol - } - } + protocol: RelayProtocol, + }, + }, }; -export type RelaySettings = {| - normal: RelaySettingsNormal<TunnelOptions<OpenVpnParameters>> -|} | {| - custom_tunnel_endpoint: RelaySettingsCustom -|}; +export type RelaySettings = + | {| + normal: RelaySettingsNormal<TunnelOptions<OpenVpnParameters>>, + |} + | {| + custom_tunnel_endpoint: RelaySettingsCustom, + |}; // types describing the partial update of RelaySettings -export type RelaySettingsNormalUpdate = $Shape< RelaySettingsNormal< TunnelOptions<$Shape<OpenVpnParameters> > > >; -export type RelaySettingsUpdate = {| - normal: RelaySettingsNormalUpdate -|} | {| - custom_tunnel_endpoint: RelaySettingsCustom -|}; +export type RelaySettingsNormalUpdate = $Shape< + RelaySettingsNormal<TunnelOptions<$Shape<OpenVpnParameters>>>, +>; +export type RelaySettingsUpdate = + | {| + normal: RelaySettingsNormalUpdate, + |} + | {| + custom_tunnel_endpoint: RelaySettingsCustom, + |}; -const constraint = <T>(constraintValue: SchemaNode<T>) => oneOf(string, object({ - only: constraintValue, -})); +const constraint = <T>(constraintValue: SchemaNode<T>) => + oneOf( + string, + object({ + only: constraintValue, + }), + ); const RelaySettingsSchema = oneOf( object({ normal: object({ - location: constraint(oneOf( - object({ - city: arrayOf(string), - }), + location: constraint( + oneOf( + object({ + city: arrayOf(string), + }), + object({ + country: string, + }), + ), + ), + tunnel: constraint( object({ - country: string + openvpn: object({ + port: constraint(number), + protocol: constraint(enumeration('udp', 'tcp')), + }), }), - )), - tunnel: constraint(object({ - openvpn: object({ - port: constraint(number), - protocol: constraint(enumeration('udp', 'tcp')), - }), - })), - }) + ), + }), }), object({ custom_tunnel_endpoint: object({ @@ -107,10 +134,10 @@ const RelaySettingsSchema = oneOf( openvpn: object({ port: number, protocol: enumeration('udp', 'tcp'), - }) - }) - }) - }) + }), + }), + }), + }), ); export type RelayList = { @@ -132,44 +159,46 @@ export type RelayListCity = { }; const RelayListSchema = object({ - countries: arrayOf(object({ - name: string, - code: string, - cities: arrayOf(object({ + countries: arrayOf( + object({ name: string, code: string, - latitude: number, - longitude: number, - has_active_relays: boolean, - })), - })), + cities: arrayOf( + object({ + name: string, + code: string, + latitude: number, + longitude: number, + has_active_relays: boolean, + }), + ), + }), + ), }); - export interface IpcFacade { - setConnectionString(string): void, - getAccountData(AccountToken): Promise<AccountData>, - getRelayLocations(): Promise<RelayList>, - getAccount(): Promise<?AccountToken>, - setAccount(accountToken: ?AccountToken): Promise<void>, - updateRelaySettings(RelaySettingsUpdate): Promise<void>, - getRelaySettings(): Promise<RelaySettings>, - setAllowLan(boolean): Promise<void>, - getAllowLan(): Promise<boolean>, - connect(): Promise<void>, - disconnect(): Promise<void>, - shutdown(): Promise<void>, - getLocation(): Promise<Location>, - getState(): Promise<BackendState>, - registerStateListener((BackendState) => void): void, - setCloseConnectionHandler(() => void): void, - authenticate(sharedSecret: string): Promise<void>, - getAccountHistory(): Promise<Array<AccountToken>>, - removeAccountFromHistory(accountToken: AccountToken): Promise<void>, + setConnectionString(string): void; + getAccountData(AccountToken): Promise<AccountData>; + getRelayLocations(): Promise<RelayList>; + getAccount(): Promise<?AccountToken>; + setAccount(accountToken: ?AccountToken): Promise<void>; + updateRelaySettings(RelaySettingsUpdate): Promise<void>; + getRelaySettings(): Promise<RelaySettings>; + setAllowLan(boolean): Promise<void>; + getAllowLan(): Promise<boolean>; + connect(): Promise<void>; + disconnect(): Promise<void>; + shutdown(): Promise<void>; + getLocation(): Promise<Location>; + getState(): Promise<BackendState>; + registerStateListener((BackendState) => void): void; + setCloseConnectionHandler(() => void): void; + authenticate(sharedSecret: string): Promise<void>; + getAccountHistory(): Promise<Array<AccountToken>>; + removeAccountFromHistory(accountToken: AccountToken): Promise<void>; } export class RealIpc implements IpcFacade { - _ipc: JsonRpcWs; constructor(connectionString: string) { @@ -184,14 +213,13 @@ export class RealIpc implements IpcFacade { // send the IPC with 30s timeout since the backend will wait // for a HTTP request before replying - return this._ipc.send('get_account_data', accountToken, 30000) - .then(raw => { - if (typeof raw === 'object' && raw && raw.expiry) { - return raw; - } else { - throw new InvalidReply(raw, 'Expected an object with expiry'); - } - }); + return this._ipc.send('get_account_data', accountToken, 30000).then((raw) => { + if (typeof raw === 'object' && raw && raw.expiry) { + return raw; + } else { + throw new InvalidReply(raw, 'Expected an object with expiry'); + } + }); } async getRelayLocations(): Promise<RelayList> { @@ -205,19 +233,17 @@ export class RealIpc implements IpcFacade { } getAccount(): Promise<?AccountToken> { - return this._ipc.send('get_account') - .then( raw => { - if (raw === undefined || raw === null || typeof raw === 'string') { - return raw; - } else { - throw new InvalidReply(raw); - } - }); + return this._ipc.send('get_account').then((raw) => { + if (raw === undefined || raw === null || typeof raw === 'string') { + return raw; + } else { + throw new InvalidReply(raw); + } + }); } setAccount(accountToken: ?AccountToken): Promise<void> { - return this._ipc.send('set_account', accountToken) - .then(this._ignoreResponse); + return this._ipc.send('set_account', accountToken).then(this._ignoreResponse); } _ignoreResponse(_response: mixed): void { @@ -225,30 +251,27 @@ export class RealIpc implements IpcFacade { } updateRelaySettings(relaySettings: RelaySettingsUpdate): Promise<void> { - return this._ipc.send('update_relay_settings', [relaySettings]) - .then(this._ignoreResponse); + return this._ipc.send('update_relay_settings', [relaySettings]).then(this._ignoreResponse); } getRelaySettings(): Promise<RelaySettings> { - return this._ipc.send('get_relay_settings') - .then( raw => { - try { - const validated: any = validate(RelaySettingsSchema, raw); - return (validated: RelaySettings); - } catch (e) { - throw new InvalidReply(raw, e); - } - }); + return this._ipc.send('get_relay_settings').then((raw) => { + try { + const validated: any = validate(RelaySettingsSchema, raw); + return (validated: RelaySettings); + } catch (e) { + throw new InvalidReply(raw, e); + } + }); } setAllowLan(allowLan: boolean): Promise<void> { - return this._ipc.send('set_allow_lan', [allowLan]) - .then(this._ignoreResponse); + return this._ipc.send('set_allow_lan', [allowLan]).then(this._ignoreResponse); } async getAllowLan(): Promise<boolean> { const raw = await this._ipc.send('get_allow_lan'); - if(typeof(raw) === 'boolean') { + if (typeof raw === 'boolean') { return raw; } else { throw new InvalidReply(raw, 'Expected a boolean'); @@ -256,45 +279,39 @@ export class RealIpc implements IpcFacade { } connect(): Promise<void> { - return this._ipc.send('connect') - .then(this._ignoreResponse); + return this._ipc.send('connect').then(this._ignoreResponse); } disconnect(): Promise<void> { - return this._ipc.send('disconnect') - .then(this._ignoreResponse); + return this._ipc.send('disconnect').then(this._ignoreResponse); } shutdown(): Promise<void> { - return this._ipc.send('shutdown') - .then(this._ignoreResponse); + return this._ipc.send('shutdown').then(this._ignoreResponse); } getLocation(): Promise<Location> { // send the IPC with 30s timeout since the backend will wait // for a HTTP request before replying - return this._ipc.send('get_current_location', [], 30000) - .then(raw => { - try { - const validated: any = validate(LocationSchema, raw); - return (validated: Location); - } catch (e) { - throw new InvalidReply(raw, e); - } - }); + return this._ipc.send('get_current_location', [], 30000).then((raw) => { + try { + const validated: any = validate(LocationSchema, raw); + return (validated: Location); + } catch (e) { + throw new InvalidReply(raw, e); + } + }); } getState(): Promise<BackendState> { - return this._ipc.send('get_state') - .then(raw => { - return this._parseBackendState(raw); - }); + return this._ipc.send('get_state').then((raw) => { + return this._parseBackendState(raw); + }); } _parseBackendState(raw: mixed): BackendState { if (raw && raw.state && raw.target_state) { - const uncheckedRaw: any = raw; const states: Array<SecurityState> = ['secured', 'unsecured']; @@ -313,7 +330,7 @@ export class RealIpc implements IpcFacade { registerStateListener(listener: (BackendState) => void) { this._ipc.on('new_state', (rawEvent) => { - const parsedEvent : BackendState = this._parseBackendState(rawEvent); + const parsedEvent: BackendState = this._parseBackendState(rawEvent); listener(parsedEvent); }); @@ -324,24 +341,21 @@ export class RealIpc implements IpcFacade { } authenticate(sharedSecret: string): Promise<void> { - return this._ipc.send('auth', sharedSecret) - .then(this._ignoreResponse); + return this._ipc.send('auth', sharedSecret).then(this._ignoreResponse); } getAccountHistory(): Promise<Array<AccountToken>> { - return this._ipc.send('get_account_history') - .then(raw => { - if(Array.isArray(raw) && raw.every(i => typeof i === 'string')) { - const checked: any = raw; - return (checked: Array<AccountToken>); - } else { - throw new InvalidReply(raw, 'Expected an array of strings'); - } - }); + return this._ipc.send('get_account_history').then((raw) => { + if (Array.isArray(raw) && raw.every((i) => typeof i === 'string')) { + const checked: any = raw; + return (checked: Array<AccountToken>); + } else { + throw new InvalidReply(raw, 'Expected an array of strings'); + } + }); } removeAccountFromHistory(accountToken: AccountToken): Promise<void> { - return this._ipc.send('remove_account_from_history', accountToken) - .then(this._ignoreResponse); + return this._ipc.send('remove_account_from_history', accountToken).then(this._ignoreResponse); } } diff --git a/app/lib/jsonrpc-ws-ipc.js b/app/lib/jsonrpc-ws-ipc.js index ecf6309380..eb84607be1 100644 --- a/app/lib/jsonrpc-ws-ipc.js +++ b/app/lib/jsonrpc-ws-ipc.js @@ -9,7 +9,7 @@ export type UnansweredRequest = { reject: (mixed) => void, timerId: TimeoutID, message: Object, -} +}; export type JsonRpcError = { type: 'error', @@ -17,9 +17,9 @@ export type JsonRpcError = { id: string, error: { message: string, - } - } -} + }, + }, +}; export type JsonRpcNotification = { type: 'notification', payload: { @@ -27,16 +27,16 @@ export type JsonRpcNotification = { params: { subscription: string, result: mixed, - } - } -} + }, + }, +}; export type JsonRpcSuccess = { type: 'success', payload: { id: string, result: mixed, - } -} + }, +}; export type JsonRpcMessage = JsonRpcError | JsonRpcNotification | JsonRpcSuccess; export class TimeOutError extends Error { @@ -57,7 +57,7 @@ export class InvalidReply extends Error { this.name = 'InvalidReply'; this.reply = reply; - if(msg) { + if (msg) { this.message = msg + ' - '; } this.message += JSON.stringify(reply); @@ -67,22 +67,22 @@ export class InvalidReply extends Error { const DEFAULT_TIMEOUT_MILLIS = 5000; export default class Ipc { - _connectionString: ?string; - _onConnect: Array<{resolve: ()=>void}>; + _onConnect: Array<{ resolve: () => void }>; _unansweredRequests: Map<string, UnansweredRequest>; - _subscriptions: Map<string|number, (mixed) => void>; + _subscriptions: Map<string | number, (mixed) => void>; _websocket: WebSocket; _backoff: ReconnectionBackoff; _websocketFactory: (string) => WebSocket; _closeConnectionHandler: ?() => void; - constructor(connectionString: string, websocketFactory: ?(string)=>WebSocket) { + constructor(connectionString: string, websocketFactory: ?(string) => WebSocket) { this._connectionString = connectionString; this._onConnect = []; this._unansweredRequests = new Map(); this._subscriptions = new Map(); - this._websocketFactory = websocketFactory || (connectionString => new WebSocket(connectionString)); + this._websocketFactory = + websocketFactory || ((connectionString) => new WebSocket(connectionString)); this._backoff = new ReconnectionBackoff(); this._reconnect(); @@ -97,17 +97,19 @@ export default class Ipc { } on(event: string, listener: (mixed) => void): Promise<*> { - log.debug('Adding a listener to', event); return this.send(event + '_subscribe') - .then(subscriptionId => { + .then((subscriptionId) => { if (typeof subscriptionId === 'string' || typeof subscriptionId === 'number') { this._subscriptions.set(subscriptionId, listener); } else { - throw new InvalidReply(subscriptionId, 'The subscription id was not a string or a number'); + throw new InvalidReply( + subscriptionId, + 'The subscription id was not a string or a number', + ); } }) - .catch(e => { + .catch((e) => { log.error('Failed adding listener to', event, ':', e); }); } @@ -116,7 +118,7 @@ export default class Ipc { return new Promise((resolve, reject) => { const id = uuid.v4(); - const params = this._prepareParams(data); + const params = this._prepareParams(data); const timerId = setTimeout(() => this._onTimeout(id), timeout); const jsonrpcMessage = jsonrpc.request(id, action, params); this._unansweredRequests.set(id, { @@ -127,27 +129,27 @@ export default class Ipc { }); this._getWebSocket() - .then(ws => { + .then((ws) => { log.debug('Sending message', id, action); ws.send(jsonrpcMessage); }) - .catch(e => { + .catch((e) => { log.error('Failed sending RPC message "' + action + '":', e); reject(e); }); }); } - _prepareParams(data: mixed): Array<mixed>|Object { + _prepareParams(data: mixed): Array<mixed> | Object { // JSONRPC only accepts arrays and objects as params, but // this isn't very nice to use, so this method wraps other // types in an array. The choice of array is based on try-and-error - if(data === undefined) { + if (data === undefined) { return []; } else if (data === null) { return [null]; - } else if (Array.isArray(data) || typeof(data) === 'object') { + } else if (Array.isArray(data) || typeof data === 'object') { return data; } else { return [data]; @@ -155,8 +157,9 @@ export default class Ipc { } _getWebSocket(): Promise<WebSocket> { - return new Promise(resolve => { - if (this._websocket && this._websocket.readyState === 1) { // Connected + return new Promise((resolve) => { + if (this._websocket && this._websocket.readyState === 1) { + // Connected resolve(this._websocket); } else { log.debug('Waiting for websocket to connect'); @@ -236,7 +239,7 @@ export default class Ipc { log.debug('Websocket is connected'); this._backoff.successfullyConnected(); - while(this._onConnect.length > 0) { + while (this._onConnect.length > 0) { this._onConnect.pop().resolve(); } }; @@ -251,12 +254,16 @@ export default class Ipc { }; this._websocket.onclose = () => { - if(this._closeConnectionHandler) { + if (this._closeConnectionHandler) { this._closeConnectionHandler(); } const delay = this._backoff.getIncreasedBackoff(); - log.warn('The websocket connetion closed, attempting to reconnect it in', delay, 'milliseconds'); + log.warn( + 'The websocket connetion closed, attempting to reconnect it in', + delay, + 'milliseconds', + ); setTimeout(() => this._reconnect(), delay); }; } diff --git a/app/lib/keyframe-animation.js b/app/lib/keyframe-animation.js index 62d9be6cd8..1b52f859c6 100644 --- a/app/lib/keyframe-animation.js +++ b/app/lib/keyframe-animation.js @@ -8,12 +8,11 @@ export type KeyframeAnimationOptions = { startFrame?: number, endFrame?: number, beginFromCurrentState?: boolean, - advanceTo?: 'end' + advanceTo?: 'end', }; export type KeyframeAnimationRange = [number, number]; export default class KeyframeAnimation { - _speed: number = 200; // ms _repeat: boolean = false; _reverse: boolean = false; @@ -33,40 +32,68 @@ export default class KeyframeAnimation { _timeout = null; - set onFrame(newValue: ?OnFrameFn) { this._onFrame = newValue; } - get onFrame(): ?OnFrameFn { this._onFrame; } + set onFrame(newValue: ?OnFrameFn) { + this._onFrame = newValue; + } + get onFrame(): ?OnFrameFn { + this._onFrame; + } // called when animation finished for non-repeating animations. - set onFinish(newValue: ?OnFinishFn) { this._onFinish = newValue; } - get onFinish(): ?OnFinishFn { this._onFinish; } + set onFinish(newValue: ?OnFinishFn) { + this._onFinish = newValue; + } + get onFinish(): ?OnFinishFn { + this._onFinish; + } // pace per frame in ms - set speed(newValue: number) { this._speed = parseInt(newValue); } - get speed(): number { return this._speed; } + set speed(newValue: number) { + this._speed = parseInt(newValue); + } + get speed(): number { + return this._speed; + } - set repeat(newValue: boolean) { this._repeat = newValue; } - get repeat(): boolean { return this._repeat; } + set repeat(newValue: boolean) { + this._repeat = newValue; + } + get repeat(): boolean { + return this._repeat; + } - set reverse(newValue: boolean) { this._reverse = newValue; } - get reverse(): boolean { return this._repeat; } + set reverse(newValue: boolean) { + this._reverse = newValue; + } + get reverse(): boolean { + return this._repeat; + } // alternates the animation direction when it reaches the end // only for repeating animations - set alternate(newValue: boolean) { this._alternate = !!newValue; } - get alternate(): boolean { return this._alternate; } + set alternate(newValue: boolean) { + this._alternate = !!newValue; + } + get alternate(): boolean { + return this._alternate; + } - get nativeImages(): Array<NativeImage> { return this._nativeImages.slice(); } - get isFinished(): boolean { return this._isFinished; } + get nativeImages(): Array<NativeImage> { + return this._nativeImages.slice(); + } + get isFinished(): boolean { + return this._isFinished; + } // create animation from files matching filename pattern. i.e (bubble-frame-{}.png) static fromFilePattern(filePattern: string, range: KeyframeAnimationRange): KeyframeAnimation { const images: Array<NativeImage> = []; - if(range.length !== 2 || range[0] > range[1]) { + if (range.length !== 2 || range[0] > range[1]) { throw new Error('the animation range is invalid'); } - for(let i = range[0]; i <= range[1]; i++) { + for (let i = range[0]; i <= range[1]; i++) { const filePath = filePattern.replace('{}', i.toString()); const image = nativeImage.createFromPath(filePath); images.push(image); @@ -75,13 +102,15 @@ export default class KeyframeAnimation { } static fromFileSequence(files: Array<string>): KeyframeAnimation { - const images: Array<NativeImage> = files.map(filePath => nativeImage.createFromPath(filePath)); + const images: Array<NativeImage> = files.map((filePath) => + nativeImage.createFromPath(filePath), + ); return new KeyframeAnimation(images); } constructor(images: Array<NativeImage>) { const len = images.length; - if(len < 1) { + if (len < 1) { throw new Error('too few images in animation'); } @@ -97,33 +126,33 @@ export default class KeyframeAnimation { play(options: KeyframeAnimationOptions = {}) { let { startFrame, endFrame, beginFromCurrentState, advanceTo } = options; - if(startFrame !== undefined && endFrame !== undefined) { - if(startFrame < 0 || startFrame >= this._numFrames) { + if (startFrame !== undefined && endFrame !== undefined) { + if (startFrame < 0 || startFrame >= this._numFrames) { throw new Error('Invalid start frame'); } - if(endFrame < 0 || endFrame >= this._numFrames) { + if (endFrame < 0 || endFrame >= this._numFrames) { throw new Error('Invalid end frame'); } - if(startFrame < endFrame) { - this._frameRange = [ startFrame, endFrame ]; + if (startFrame < endFrame) { + this._frameRange = [startFrame, endFrame]; } else { - this._frameRange = [ endFrame, startFrame ]; + this._frameRange = [endFrame, startFrame]; } } else { - this._frameRange = [ 0, this._numFrames - 1 ]; + this._frameRange = [0, this._numFrames - 1]; } - if(!beginFromCurrentState || this._isFirstRun) { + if (!beginFromCurrentState || this._isFirstRun) { this._currentFrame = this._frameRange[this._reverse ? 1 : 0]; } - if(this._isFirstRun) { + if (this._isFirstRun) { this._isFirstRun = false; } - if(advanceTo === 'end') { + if (advanceTo === 'end') { this._currentFrame = this._frameRange[this._reverse ? 0 : 1]; } @@ -142,7 +171,7 @@ export default class KeyframeAnimation { } _unscheduleUpdate() { - if(this._timeout) { + if (this._timeout) { clearTimeout(this._timeout); this._timeout = null; } @@ -153,7 +182,7 @@ export default class KeyframeAnimation { } _render() { - if(this._onFrame) { + if (this._onFrame) { this._onFrame(this._nativeImages[this._currentFrame]); } } @@ -161,7 +190,7 @@ export default class KeyframeAnimation { _didFinish() { this._isFinished = true; - if(this._onFinish) { + if (this._onFinish) { this._onFinish(); } } @@ -169,32 +198,34 @@ export default class KeyframeAnimation { _onUpdateFrame() { this._advanceFrame(); - if(this._isFinished) { + if (this._isFinished) { // mark animation as not running when finished this._isRunning = false; } else { this._render(); // check once again since onFrame() may stop animation - if(this._isRunning) { + if (this._isRunning) { this._scheduleUpdate(); } } } _advanceFrame() { - if(this._isFinished) { return; } + if (this._isFinished) { + return; + } let lastFrame = this._frameRange[this._reverse ? 0 : 1]; - if(this._currentFrame === lastFrame) { + if (this._currentFrame === lastFrame) { // mark animation as finished if it's not repeating - if(!this._repeat) { + if (!this._repeat) { this._didFinish(); return; } // change animation direction if marked for alternation - if(this._alternate) { + if (this._alternate) { this._reverse = !this._reverse; this._currentFrame = this._nextFrame(this._currentFrame, this._frameRange, this._reverse); @@ -207,20 +238,19 @@ export default class KeyframeAnimation { } _nextFrame(cur: number, frameRange: KeyframeAnimationRange, isReverse: boolean): number { - if(isReverse) { - if(cur < frameRange[0]) { + if (isReverse) { + if (cur < frameRange[0]) { return cur + 1; - } else if(cur > frameRange[0]) { + } else if (cur > frameRange[0]) { return cur - 1; } } else { - if(cur > frameRange[1]) { + if (cur > frameRange[1]) { return cur - 1; - } else if(cur < frameRange[1]) { + } else if (cur < frameRange[1]) { return cur + 1; } } return cur; } - } diff --git a/app/lib/problem-report.android.js b/app/lib/problem-report.android.js index 63deb102e4..b0bee2264d 100644 --- a/app/lib/problem-report.android.js +++ b/app/lib/problem-report.android.js @@ -9,4 +9,4 @@ const sendProblemReport = (email: string, message: string, savedReport: string) return MobileAppBridge.sendProblemReport(email, message, savedReport); }; -export { collectProblemReport, sendProblemReport };
\ No newline at end of file +export { collectProblemReport, sendProblemReport }; diff --git a/app/lib/problem-report.js b/app/lib/problem-report.js index 94a435b617..32acb32b93 100644 --- a/app/lib/problem-report.js +++ b/app/lib/problem-report.js @@ -22,14 +22,16 @@ const collectProblemReport = (toRedact: Array<string>): Promise<string> => { }, 10000); responseListener = (_event, id, error, reportPath) => { - if(id !== requestId) { return; } + if (id !== requestId) { + return; + } clearTimeout(requestTimeout); removeResponseListener(); - if(error) { - log.error(`Cannot collect a problem report: ${ error.err }`); - log.error(`Stdout: ${ error.stdout }`); + if (error) { + log.error(`Cannot collect a problem report: ${error.err}`); + log.error(`Stdout: ${error.stdout}`); reject(error); } else { resolve(reportPath); @@ -45,11 +47,7 @@ const collectProblemReport = (toRedact: Array<string>): Promise<string> => { }; const sendProblemReport = (email: string, message: string, savedReport: string) => { - const args = ['send', - '--email', email, - '--message', message, - '--report', savedReport, - ]; + const args = ['send', '--email', email, '--message', message, '--report', savedReport]; const binPath = resolveBin('problem-report'); @@ -71,4 +69,4 @@ const sendProblemReport = (email: string, message: string, savedReport: string) }); }; -export { collectProblemReport, sendProblemReport };
\ No newline at end of file +export { collectProblemReport, sendProblemReport }; diff --git a/app/lib/proc.js b/app/lib/proc.js index d0cd46f886..5e7396fa9a 100644 --- a/app/lib/proc.js +++ b/app/lib/proc.js @@ -17,10 +17,10 @@ function getBasePath() { function getExtension() { switch (process.platform) { - case 'win32': - return '.exe'; + case 'win32': + return '.exe'; - default: - return ''; + default: + return ''; } } diff --git a/app/lib/relay-settings-builder.js b/app/lib/relay-settings-builder.js index 4d83956a00..0386d1ef4f 100644 --- a/app/lib/relay-settings-builder.js +++ b/app/lib/relay-settings-builder.js @@ -5,7 +5,7 @@ import type { RelayProtocol, RelaySettingsUpdate, RelaySettingsNormalUpdate, - RelaySettingsCustom + RelaySettingsCustom, } from './ipc-facade'; type LocationBuilder<Self> = { @@ -18,16 +18,16 @@ type LocationBuilder<Self> = { type OpenVPNConfigurator<Self> = { port: { exact: (port: number) => Self, - any: () => Self + any: () => Self, }, protocol: { exact: (protocol: RelayProtocol) => Self, - any: () => Self - } + any: () => Self, + }, }; type TunnelBuilder<Self> = { - openvpn: (configurator: (OpenVPNConfigurator<*>) => void) => Self + openvpn: (configurator: (OpenVPNConfigurator<*>) => void) => Self, }; class NormalRelaySettingsBuilder { @@ -35,7 +35,7 @@ class NormalRelaySettingsBuilder { build(): RelaySettingsUpdate { return { - normal: this._payload + normal: this._payload, }; } @@ -53,22 +53,23 @@ class NormalRelaySettingsBuilder { this._payload.location = 'any'; return this; }, - fromRaw: function (location: 'any' | RelayLocation) { - if(location === 'any') { + fromRaw: function(location: 'any' | RelayLocation) { + if (location === 'any') { return this.any(); } - if(location.city) { + if (location.city) { const [country, city] = location.city; return this.city(country, city); } - if(location.country) { + if (location.country) { return this.country(location.country); } - throw new Error('Unsupported value of RelayLocation' + - (location && JSON.stringify(location)) ); + throw new Error( + 'Unsupported value of RelayLocation' + (location && JSON.stringify(location)), + ); }, }; } @@ -76,18 +77,18 @@ class NormalRelaySettingsBuilder { get tunnel(): TunnelBuilder<NormalRelaySettingsBuilder> { const updateOpenvpn = (next) => { const tunnel = this._payload.tunnel; - if(typeof(tunnel) === 'string' || typeof(tunnel) === 'undefined') { + if (typeof tunnel === 'string' || typeof tunnel === 'undefined') { this._payload.tunnel = { only: { - openvpn: next - } + openvpn: next, + }, }; - } else if(typeof(tunnel) === 'object') { + } else if (typeof tunnel === 'object') { const prev = (tunnel.only && tunnel.only.openvpn) || {}; this._payload.tunnel = { only: { - openvpn: { ...prev, ...next } - } + openvpn: { ...prev, ...next }, + }, }; } }; @@ -114,7 +115,7 @@ class NormalRelaySettingsBuilder { exact: (value: RelayProtocol) => apply({ only: value }), any: () => apply('any'), }; - } + }, }; configurator(openvpnBuilder); @@ -124,20 +125,18 @@ class NormalRelaySettingsBuilder { any: () => { this._payload.tunnel = 'any'; return this; - } + }, }; } - } - type CustomOpenVPNConfigurator<Self> = { port: (port: number) => Self, - protocol: (protocol: RelayProtocol) => Self + protocol: (protocol: RelayProtocol) => Self, }; type CustomTunnelBuilder<Self> = { - openvpn: (configurator: (CustomOpenVPNConfigurator<*>) => void) => Self + openvpn: (configurator: (CustomOpenVPNConfigurator<*>) => void) => Self, }; class CustomRelaySettingsBuilder { @@ -146,14 +145,14 @@ class CustomRelaySettingsBuilder { tunnel: { openvpn: { port: 0, - protocol: 'udp' - } - } + protocol: 'udp', + }, + }, }; build(): RelaySettingsUpdate { return { - custom_tunnel_endpoint: this._payload + custom_tunnel_endpoint: this._payload, }; } @@ -167,24 +166,24 @@ class CustomRelaySettingsBuilder { const tunnel = this._payload.tunnel || {}; const prev = tunnel.openvpn || {}; this._payload.tunnel = { - openvpn: { ...prev, ...next } + openvpn: { ...prev, ...next }, }; }; return { openvpn: (configurator) => { configurator({ - port: function (port: number) { + port: function(port: number) { updateOpenvpn({ port }); return this; }, - protocol: function (protocol: RelayProtocol) { + protocol: function(protocol: RelayProtocol) { updateOpenvpn({ protocol }); return this; - } + }, }); return this; - } + }, }; } } @@ -192,4 +191,4 @@ class CustomRelaySettingsBuilder { export default { normal: () => new NormalRelaySettingsBuilder(), custom: () => new CustomRelaySettingsBuilder(), -};
\ No newline at end of file +}; diff --git a/app/lib/rpc-file-security.js b/app/lib/rpc-file-security.js index eb1901f67f..fa88111c02 100644 --- a/app/lib/rpc-file-security.js +++ b/app/lib/rpc-file-security.js @@ -4,14 +4,14 @@ import fs from 'fs'; export function canTrustRpcAddressFile(path: string): boolean { const platform = process.platform; - switch(platform) { - case 'win32': - return isOwnedByLocalSystem(path); - case 'darwin': - case 'linux': - return isOwnedAndOnlyWritableByRoot(path); - default: - throw new Error(`Unknown platform: ${platform}`); + switch (platform) { + case 'win32': + return isOwnedByLocalSystem(path); + case 'darwin': + case 'linux': + return isOwnedAndOnlyWritableByRoot(path); + default: + throw new Error(`Unknown platform: ${platform}`); } } @@ -27,7 +27,10 @@ function isOwnedByLocalSystem(path: string): boolean { // $FlowFixMe: this module is only available on Windows const winsec = require('windows-security'); const ownerSid = winsec.getFileOwnerSid(path, null); - const isWellKnownSid = winsec.isWellKnownSid(ownerSid, winsec.WellKnownSid.BuiltinAdministratorsSid); + const isWellKnownSid = winsec.isWellKnownSid( + ownerSid, + winsec.WellKnownSid.BuiltinAdministratorsSid, + ); return isWellKnownSid; -}
\ No newline at end of file +} diff --git a/app/lib/styles.js b/app/lib/styles.js index 840c7c79ab..4cde4fdfce 100644 --- a/app/lib/styles.js +++ b/app/lib/styles.js @@ -4,7 +4,9 @@ import { Styles } from 'reactxp'; type ExtractReturnType = (*) => Object; -export function createViewStyles<T: { [string]: Object }>(styles: T): $ObjMap<T, ExtractReturnType> { +export function createViewStyles<T: { [string]: Object }>( + styles: T, +): $ObjMap<T, ExtractReturnType> { const viewStyles = {}; for (const style of Object.keys(styles)) { viewStyles[style] = Styles.createViewStyle(styles[style]); @@ -12,10 +14,12 @@ export function createViewStyles<T: { [string]: Object }>(styles: T): $ObjMap<T, return viewStyles; } -export function createTextStyles<T: { [string]: Object }>(styles: T): $ObjMap<T, ExtractReturnType> { +export function createTextStyles<T: { [string]: Object }>( + styles: T, +): $ObjMap<T, ExtractReturnType> { const textStyles = {}; for (const style of Object.keys(styles)) { textStyles[style] = Styles.createTextStyle(styles[style]); } return textStyles; -}
\ No newline at end of file +} diff --git a/app/lib/tempdir.js b/app/lib/tempdir.js index e9f65cd03b..a3b4fa6d89 100644 --- a/app/lib/tempdir.js +++ b/app/lib/tempdir.js @@ -3,19 +3,19 @@ import path from 'path'; export function getSystemTemporaryDirectory() { - switch(process.platform) { - case 'win32': { - const windowsPath = process.env.windir; - if(windowsPath) { - return path.join(windowsPath, 'Temp'); - } else { - throw new Error('Missing windir in environment variables.'); + switch (process.platform) { + case 'win32': { + const windowsPath = process.env.windir; + if (windowsPath) { + return path.join(windowsPath, 'Temp'); + } else { + throw new Error('Missing windir in environment variables.'); + } } - } - case 'darwin': - case 'linux': - return '/tmp'; - default: - throw new Error(`Not implemented for ${process.platform}`); + case 'darwin': + case 'linux': + return '/tmp'; + default: + throw new Error(`Not implemented for ${process.platform}`); } } diff --git a/app/lib/transition-rule.js b/app/lib/transition-rule.js index a91ba4da66..dfb56b02ce 100644 --- a/app/lib/transition-rule.js +++ b/app/lib/transition-rule.js @@ -2,21 +2,20 @@ export type TransitionDescriptor = { name: string, - duration: number + duration: number, }; export type TransitionFork = { forward: TransitionDescriptor, - backward: TransitionDescriptor + backward: TransitionDescriptor, }; export type TransitionMatch = { direction: 'forward' | 'backward', - descriptor: TransitionDescriptor + descriptor: TransitionDescriptor, }; export default class TransitionRule { - _from: ?string; _to: string; _fork: TransitionFork; @@ -28,20 +27,20 @@ export default class TransitionRule { } match(fromRoute: ?string, toRoute: string): ?TransitionMatch { - if((!this._from || this._from === fromRoute) && this._to === toRoute) { + if ((!this._from || this._from === fromRoute) && this._to === toRoute) { return { direction: 'forward', - descriptor: this._fork['forward'] + descriptor: this._fork['forward'], }; } - if((!this._from || this._from === toRoute) && this._to === fromRoute) { + if ((!this._from || this._from === toRoute) && this._to === fromRoute) { return { direction: 'backward', - descriptor: this._fork['backward'] + descriptor: this._fork['backward'], }; } return null; } -}
\ No newline at end of file +} diff --git a/app/lib/tray-icon-manager.js b/app/lib/tray-icon-manager.js index 0669c0994a..915bd2c8b8 100644 --- a/app/lib/tray-icon-manager.js +++ b/app/lib/tray-icon-manager.js @@ -7,7 +7,6 @@ import type { Tray } from 'electron'; export type TrayIconType = 'unsecured' | 'securing' | 'secured'; export default class TrayIconManager { - _animation: ?KeyframeAnimation; _iconType: TrayIconType; @@ -22,7 +21,7 @@ export default class TrayIconManager { } destroy() { - if(this._animation) { + if (this._animation) { this._animation.stop(); this._animation = null; } @@ -36,7 +35,7 @@ export default class TrayIconManager { return animation; } - _isReverseAnimation(type: TrayIconType): bool { + _isReverseAnimation(type: TrayIconType): boolean { return type === 'unsecured'; } @@ -45,13 +44,14 @@ export default class TrayIconManager { } set iconType(type: TrayIconType) { - if(this._iconType === type || !this._animation) { return; } + if (this._iconType === type || !this._animation) { + return; + } const animation = this._animation; if (type === 'secured') { animation.reverse = true; animation.play({ beginFromCurrentState: true, startFrame: 8, endFrame: 9 }); - } else { animation.reverse = this._isReverseAnimation(type); animation.play({ beginFromCurrentState: true }); diff --git a/app/main.js b/app/main.js index 9c4d66f0d7..d96b80a5e9 100644 --- a/app/main.js +++ b/app/main.js @@ -15,7 +15,7 @@ import uuid from 'uuid'; import type { TrayIconType } from './lib/tray-icon-manager'; -const isDevelopment = (process.env.NODE_ENV === 'development'); +const isDevelopment = process.env.NODE_ENV === 'development'; // The name for application directory used for // scoping logs and user data in platform special folders @@ -43,7 +43,6 @@ const appDelegate = { }, _initLogging: () => { - const logDirectory = appDelegate._getLogsDirectory(); const format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}][{level}] {text}'; @@ -71,14 +70,14 @@ const appDelegate = { // 1. https://github.com/electron/electron/issues/10118 // 2. https://github.com/electron/electron/pull/10191 _getLogsDirectory: () => { - switch(process.platform) { - case 'darwin': - // macOS: ~/Library/Logs/{appname} - return path.join(app.getPath('home'), 'Library/Logs', appDirectoryName); - default: - // Windows: %APPDATA%\{appname}\logs - // Linux: ~/.config/{appname}/logs - return path.join(app.getPath('userData'), 'logs'); + switch (process.platform) { + case 'darwin': + // macOS: ~/Library/Logs/{appname} + return path.join(app.getPath('home'), 'Library/Logs', appDirectoryName); + default: + // Windows: %APPDATA%\{appname}\logs + // Linux: ~/.config/{appname}/logs + return path.join(app.getPath('userData'), 'logs'); } }, @@ -88,7 +87,7 @@ const appDelegate = { }, onReady: async () => { - const window = appDelegate._window = appDelegate._createWindow(); + const window = (appDelegate._window = appDelegate._createWindow()); ipcMain.on('on-browser-window-ready', () => { browserWindowReady = true; @@ -112,21 +111,15 @@ const appDelegate = { const reportPath = path.join(app.getPath('temp'), uuid.v4() + '.log'); const binPath = resolveBin('problem-report'); - let args = [ - 'collect', - '--output', reportPath, - ]; + let args = ['collect', '--output', reportPath]; if (toRedact.length > 0) { - args = args.concat([ - '--redact', ...toRedact, - '--', - ]); + args = args.concat(['--redact', ...toRedact, '--']); } args = args.concat([appDelegate._logFilePath]); - execFile(binPath, args, {windowsHide: true}, (err) => { + execFile(binPath, args, { windowsHide: true }, (err) => { if (err) { event.sender.send('collect-logs-reply', id, err); } else { @@ -141,7 +134,7 @@ const appDelegate = { appDelegate._setAppMenu(); appDelegate._addContextMenu(window); - if(isDevelopment) { + if (isDevelopment) { await appDelegate._installDevTools(); window.openDevTools({ mode: 'detach' }); } @@ -158,25 +151,26 @@ const appDelegate = { _getRpcAddressFilePath: () => { const rpcAddressFileName = '.mullvad_rpc_address'; - switch(process.platform) { - case 'win32': { - // Windows: %ALLUSERSPROFILE%\{appname} - let programDataDirectory = process.env.ALLUSERSPROFILE; - if (typeof programDataDirectory === 'undefined' || programDataDirectory === null) { - throw new Error('Missing %ALLUSERSPROFILE% environment variable'); - } else { - let appDataDirectory = path.join(programDataDirectory, appDirectoryName); - return path.join(appDataDirectory, rpcAddressFileName); + switch (process.platform) { + case 'win32': { + // Windows: %ALLUSERSPROFILE%\{appname} + let programDataDirectory = process.env.ALLUSERSPROFILE; + if (typeof programDataDirectory === 'undefined' || programDataDirectory === null) { + throw new Error('Missing %ALLUSERSPROFILE% environment variable'); + } else { + let appDataDirectory = path.join(programDataDirectory, appDirectoryName); + return path.join(appDataDirectory, rpcAddressFileName); + } } - } - default: - return path.join(getSystemTemporaryDirectory(), rpcAddressFileName); + default: + return path.join(getSystemTemporaryDirectory(), rpcAddressFileName); } }, _pollForConnectionInfoFile: () => { - if (appDelegate.connectionFilePollInterval) { - log.warn('Attempted to start polling for the RPC connection info file while another polling was already running'); + log.warn( + 'Attempted to start polling for the RPC connection info file while another polling was already running', + ); return; } @@ -184,9 +178,7 @@ const appDelegate = { const pollIntervalMs = 200; appDelegate.connectionFilePollInterval = setInterval(() => { - if (browserWindowReady && fs.existsSync(rpcAddressFile)) { - if (appDelegate.connectionFilePollInterval) { clearInterval(appDelegate.connectionFilePollInterval); appDelegate.connectionFilePollInterval = null; @@ -194,7 +186,6 @@ const appDelegate = { appDelegate._sendBackendInfo(rpcAddressFile); } - }, pollIntervalMs); }, _sendBackendInfo: (rpcAddressFile: string) => { @@ -211,7 +202,7 @@ const appDelegate = { log.error(`Not trusting the contents of "${rpcAddressFile}".`); return; } - } catch(e) { + } catch (e) { log.error(`Cannot verify the credibility of RPC address file: ${e.message}`); return; } @@ -221,13 +212,13 @@ const appDelegate = { // permissions and read the contents of the file. We deem the chance // of that to be small enough to ignore. - fs.readFile(rpcAddressFile, 'utf8', function (err, data) { + fs.readFile(rpcAddressFile, 'utf8', function(err, data) { if (err) { return log.error('Could not find backend connection info', err); } const credentials = parseIpcCredentials(data); - if(credentials) { + if (credentials) { log.debug('Read IPC connection info', credentials.connectionString); window.webContents.send('backend-info', { credentials }); } else { @@ -240,7 +231,7 @@ const appDelegate = { const installer = require('electron-devtools-installer'); const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - for(const name of extensions) { + for (const name of extensions) { try { await installer.default(installer[name], forceDownload); } catch (e) { @@ -266,42 +257,42 @@ const appDelegate = { // prevents renderer process code from not running when window is hidden backgroundThrottling: false, // Enable experimental features - blinkFeatures: 'CSSBackdropFilter' - } + blinkFeatures: 'CSSBackdropFilter', + }, }; - switch(process.platform) { - case 'darwin': { - // setup window flags to mimic popover on macOS - const appWindow = new BrowserWindow({ - ...options, - // 12 is the size of transparent area around arrow - height: contentHeight + 12, - minHeight: contentHeight + 12, - transparent: true - }); + switch (process.platform) { + case 'darwin': { + // setup window flags to mimic popover on macOS + const appWindow = new BrowserWindow({ + ...options, + // 12 is the size of transparent area around arrow + height: contentHeight + 12, + minHeight: contentHeight + 12, + transparent: true, + }); - // make the window visible on all workspaces - appWindow.setVisibleOnAllWorkspaces(true); + // make the window visible on all workspaces + appWindow.setVisibleOnAllWorkspaces(true); - return appWindow; - } + return appWindow; + } - case 'win32': - // setup window flags to mimic an overlay window - return new BrowserWindow({ - ...options, - transparent: true - }); + case 'win32': + // setup window flags to mimic an overlay window + return new BrowserWindow({ + ...options, + transparent: true, + }); - case 'linux': - return new BrowserWindow({ - ...options, - show: true, - }); + case 'linux': + return new BrowserWindow({ + ...options, + show: true, + }); - default: - return new BrowserWindow(options); + default: + return new BrowserWindow(options); } }, @@ -309,11 +300,7 @@ const appDelegate = { const template = [ { label: 'Mullvad', - submenu: [ - { role: 'about' }, - { type: 'separator' }, - { role: 'quit' } - ] + submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'quit' }], }, { label: 'Edit', @@ -322,9 +309,9 @@ const appDelegate = { { role: 'copy' }, { role: 'paste' }, { type: 'separator' }, - { role: 'selectall' } - ] - } + { role: 'selectall' }, + ], + }, ]; Menu.setApplicationMenu(Menu.buildFromTemplate(template)); }, @@ -335,29 +322,31 @@ const appDelegate = { { role: 'copy' }, { role: 'paste' }, { type: 'separator' }, - { role: 'selectall' } + { role: 'selectall' }, ]; // add inspect element on right click menu window.webContents.on('context-menu', (_e: Event, props: { x: number, y: number }) => { - let inspectTemplate = [{ - label: 'Inspect element', - click() { - window.openDevTools({ mode: 'detach' }); - window.inspectElement(props.x, props.y); - } - }]; + let inspectTemplate = [ + { + label: 'Inspect element', + click() { + window.openDevTools({ mode: 'detach' }); + window.inspectElement(props.x, props.y); + }, + }, + ]; - if(props.isEditable) { + if (props.isEditable) { let inputMenu = menuTemplate; // mixin 'inspect element' into standard menu when in development mode - if(isDevelopment) { - inputMenu = menuTemplate.concat([{type: 'separator'}], inspectTemplate); + if (isDevelopment) { + inputMenu = menuTemplate.concat([{ type: 'separator' }], inspectTemplate); } Menu.buildFromTemplate(inputMenu).popup(window); - } else if(isDevelopment) { + } else if (isDevelopment) { // display inspect element for all non-editable // elements when in development mode Menu.buildFromTemplate(inspectTemplate).popup(window); @@ -366,7 +355,7 @@ const appDelegate = { }, _toggleWindow: (window: BrowserWindow, tray: ?Tray) => { - if(window.isVisible()) { + if (window.isVisible()) { window.hide(); } else { appDelegate._showWindow(window, tray); @@ -379,7 +368,7 @@ const appDelegate = { }, _showWindow: (window: BrowserWindow, tray: ?Tray) => { - if(tray) { + if (tray) { appDelegate._updateWindowPosition(window, tray); } @@ -388,28 +377,28 @@ const appDelegate = { }, _getTrayPlacement: () => { - switch(process.platform) { - case 'darwin': - // macOS has menubar always placed at the top - return 'top'; + switch (process.platform) { + case 'darwin': + // macOS has menubar always placed at the top + return 'top'; - case 'win32': { - // taskbar occupies some part of the screen excluded from work area - const primaryDisplay = electron.screen.getPrimaryDisplay(); - const displaySize = primaryDisplay.size; - const workArea = primaryDisplay.workArea; + case 'win32': { + // taskbar occupies some part of the screen excluded from work area + const primaryDisplay = electron.screen.getPrimaryDisplay(); + const displaySize = primaryDisplay.size; + const workArea = primaryDisplay.workArea; - if(workArea.width < displaySize.width) { - return workArea.x > 0 ? 'left' : 'right'; - } else if(workArea.height < displaySize.height) { - return workArea.y > 0 ? 'top' : 'bottom'; - } else { - return 'none'; + if (workArea.width < displaySize.width) { + return workArea.x > 0 ? 'left' : 'right'; + } else if (workArea.height < displaySize.height) { + return workArea.y > 0 ? 'top' : 'bottom'; + } else { + return 'none'; + } } - } - default: - return 'none'; + default: + return 'none'; } }, @@ -423,32 +412,33 @@ const appDelegate = { const maxX = workArea.x + workArea.width - windowBounds.width; const maxY = workArea.y + workArea.height - windowBounds.height; - let x = 0, y = 0; - switch(placement) { - case 'top': - x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5; - y = workArea.y; - break; + let x = 0, + y = 0; + switch (placement) { + case 'top': + x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5; + y = workArea.y; + break; - case 'bottom': - x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5; - y = workArea.y + workArea.height - windowBounds.height; - break; + case 'bottom': + x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5; + y = workArea.y + workArea.height - windowBounds.height; + break; - case 'left': - x = workArea.x; - y = trayBounds.y + (trayBounds.height - windowBounds.height) * 0.5; - break; + case 'left': + x = workArea.x; + y = trayBounds.y + (trayBounds.height - windowBounds.height) * 0.5; + break; - case 'right': - x = workArea.width - windowBounds.width; - y = trayBounds.y + (trayBounds.height - windowBounds.height) * 0.5; - break; + case 'right': + x = workArea.width - windowBounds.width; + y = trayBounds.y + (trayBounds.height - windowBounds.height) * 0.5; + break; - case 'none': - x = workArea.x + (workArea.width - windowBounds.width) * 0.5; - y = workArea.y + (workArea.height - windowBounds.height) * 0.5; - break; + case 'none': + x = workArea.x + (workArea.width - windowBounds.width) * 0.5; + y = workArea.y + (workArea.height - windowBounds.height) * 0.5; + break; } x = Math.min(Math.max(x, workArea.x), maxX); @@ -456,7 +446,7 @@ const appDelegate = { return { x: Math.round(x), - y: Math.round(y) + y: Math.round(y), }; }, @@ -469,14 +459,17 @@ const appDelegate = { // add display metrics change handler electron.screen.addListener('display-metrics-changed', (_event, _display, changedMetrics) => { - if(changedMetrics.includes('workArea') && window.isVisible()) { + if (changedMetrics.includes('workArea') && window.isVisible()) { appDelegate._updateWindowPosition(window, tray); } }); // add IPC handler to change tray icon from renderer const trayIconManager = new TrayIconManager(tray, 'unsecured'); - ipcMain.on('changeTrayIcon', (_: Event, type: TrayIconType) => trayIconManager.iconType = type); + ipcMain.on( + 'changeTrayIcon', + (_: Event, type: TrayIconType) => (trayIconManager.iconType = type), + ); // setup event handlers window.on('close', () => window.closeDevTools()); @@ -484,7 +477,7 @@ const appDelegate = { window.on('blur', () => !window.isDevToolsOpened() && window.hide()); } - if(process.platform === 'darwin') { + if (process.platform === 'darwin') { // disable icon highlight on macOS tray.setHighlightMode('never'); @@ -506,7 +499,6 @@ const appDelegate = { window.on('show', () => macEventMonitor.start(eventMask, () => window.hide())); window.on('hide', () => macEventMonitor.stop()); }, - }; try { diff --git a/app/redux/account/actions.js b/app/redux/account/actions.js index 8932e6ae37..ca2bbdc7b6 100644 --- a/app/redux/account/actions.js +++ b/app/redux/account/actions.js @@ -36,13 +36,14 @@ type UpdateAccountHistoryAction = { accountHistory: Array<AccountToken>, }; -export type AccountAction = StartLoginAction - | LoginSuccessfulAction - | LoginFailedAction - | LoggedOutAction - | ResetLoginErrorAction - | UpdateAccountTokenAction - | UpdateAccountHistoryAction; +export type AccountAction = + | StartLoginAction + | LoginSuccessfulAction + | LoginFailedAction + | LoggedOutAction + | ResetLoginErrorAction + | UpdateAccountTokenAction + | UpdateAccountHistoryAction; function startLogin(accountToken?: AccountToken): StartLoginAction { return { diff --git a/app/redux/account/reducers.js b/app/redux/account/reducers.js index 7585d7adc0..fe10dce742 100644 --- a/app/redux/account/reducers.js +++ b/app/redux/account/reducers.js @@ -10,7 +10,7 @@ export type AccountReduxState = { accountHistory: Array<AccountToken>, expiry: ?string, // ISO8601 status: LoginState, - error: ?BackendError + error: ?BackendError, }; const initialState: AccountReduxState = { @@ -18,50 +18,73 @@ const initialState: AccountReduxState = { accountHistory: [], expiry: null, status: 'none', - error: null + error: null, }; -export default function(state: AccountReduxState = initialState, action: ReduxAction): AccountReduxState { - +export default function( + state: AccountReduxState = initialState, + action: ReduxAction, +): AccountReduxState { switch (action.type) { - case 'START_LOGIN': - return { ...state, ...{ - status: 'logging in', - accountToken: action.accountToken, - error: null, - }}; - case 'LOGIN_SUCCESSFUL': - return { ...state, ...{ - status: 'ok', - error: null, - expiry: action.expiry, - }}; - case 'LOGIN_FAILED': - return { ...state, ...{ - status: 'failed', - accountToken: null, - error: action.error, - }}; - case 'LOGGED_OUT': - return { ...state, ...{ - status: 'none', - accountToken: null, - expiry: null, - error: null, - }}; - case 'RESET_LOGIN_ERROR': - return { ...state, ...{ - status: 'none', - error: null, - }}; - case 'UPDATE_ACCOUNT_TOKEN': - return { ...state, ...{ - accountToken: action.token, - }}; - case 'UPDATE_ACCOUNT_HISTORY': - return { ...state, ...{ - accountHistory: action.accountHistory, - }}; + case 'START_LOGIN': + return { + ...state, + ...{ + status: 'logging in', + accountToken: action.accountToken, + error: null, + }, + }; + case 'LOGIN_SUCCESSFUL': + return { + ...state, + ...{ + status: 'ok', + error: null, + expiry: action.expiry, + }, + }; + case 'LOGIN_FAILED': + return { + ...state, + ...{ + status: 'failed', + accountToken: null, + error: action.error, + }, + }; + case 'LOGGED_OUT': + return { + ...state, + ...{ + status: 'none', + accountToken: null, + expiry: null, + error: null, + }, + }; + case 'RESET_LOGIN_ERROR': + return { + ...state, + ...{ + status: 'none', + error: null, + }, + }; + case 'UPDATE_ACCOUNT_TOKEN': + return { + ...state, + ...{ + accountToken: action.token, + }, + }; + case 'UPDATE_ACCOUNT_HISTORY': + return { + ...state, + ...{ + accountHistory: action.accountHistory, + }, + }; } return state; diff --git a/app/redux/connection/actions.js b/app/redux/connection/actions.js index 6f0da8cecf..b4f1df09be 100644 --- a/app/redux/connection/actions.js +++ b/app/redux/connection/actions.js @@ -11,13 +11,12 @@ const disconnect = (backend: Backend) => () => backend.disconnect(); const copyIPAddress = (): ReduxThunk => { return (_, getState) => { const ip = getState().connection.ip; - if(ip) { + if (ip) { Clipboard.setText(ip); } }; }; - type ConnectingAction = { type: 'CONNECTING', }; @@ -48,12 +47,13 @@ type OfflineAction = { type: 'OFFLINE', }; -export type ConnectionAction = NewLocationAction - | ConnectingAction - | ConnectedAction - | DisconnectedAction - | OnlineAction - | OfflineAction; +export type ConnectionAction = + | NewLocationAction + | ConnectingAction + | ConnectedAction + | DisconnectedAction + | OnlineAction + | OfflineAction; function connecting(): ConnectingAction { return { @@ -92,6 +92,14 @@ function offline(): OfflineAction { }; } - -export default { connect, disconnect, copyIPAddress, newLocation, connecting, connected, disconnected, online, offline }; - +export default { + connect, + disconnect, + copyIPAddress, + newLocation, + connecting, + connected, + disconnected, + online, + offline, +}; diff --git a/app/redux/connection/reducers.js b/app/redux/connection/reducers.js index f21d8ca544..a50fbf0757 100644 --- a/app/redux/connection/reducers.js +++ b/app/redux/connection/reducers.js @@ -24,29 +24,30 @@ const initialState: ConnectionReduxState = { city: null, }; - -export default function(state: ConnectionReduxState = initialState, action: ReduxAction): ConnectionReduxState { - +export default function( + state: ConnectionReduxState = initialState, + action: ReduxAction, +): ConnectionReduxState { switch (action.type) { - case 'NEW_LOCATION': - return { ...state, ...action.newLocation }; + case 'NEW_LOCATION': + return { ...state, ...action.newLocation }; - case 'CONNECTING': - return { ...state, ...{ status: 'connecting' }}; + case 'CONNECTING': + return { ...state, ...{ status: 'connecting' } }; - case 'CONNECTED': - return { ...state, ...{ status: 'connected' }}; + case 'CONNECTED': + return { ...state, ...{ status: 'connected' } }; - case 'DISCONNECTED': - return { ...state, ...{ status: 'disconnected' }}; + case 'DISCONNECTED': + return { ...state, ...{ status: 'disconnected' } }; - case 'ONLINE': - return { ...state, ...{ isOnline: true }}; + case 'ONLINE': + return { ...state, ...{ isOnline: true } }; - case 'OFFLINE': - return { ...state, ...{ isOnline: false }}; + case 'OFFLINE': + return { ...state, ...{ isOnline: false } }; - default: - return state; + default: + return state; } } diff --git a/app/redux/settings/actions.js b/app/redux/settings/actions.js index 02debd5b77..0d3e252778 100644 --- a/app/redux/settings/actions.js +++ b/app/redux/settings/actions.js @@ -26,7 +26,9 @@ function updateRelay(relay: RelaySettingsRedux): UpdateRelayAction { }; } -function updateRelayLocations(relayLocations: Array<RelayLocationRedux>): UpdateRelayLocationsAction { +function updateRelayLocations( + relayLocations: Array<RelayLocationRedux>, +): UpdateRelayLocationsAction { return { type: 'UPDATE_RELAY_LOCATIONS', relayLocations: relayLocations, diff --git a/app/redux/settings/reducers.js b/app/redux/settings/reducers.js index 39005fd486..f559c1f725 100644 --- a/app/redux/settings/reducers.js +++ b/app/redux/settings/reducers.js @@ -3,19 +3,21 @@ import type { ReduxAction } from '../store'; import type { RelayProtocol, RelayLocation } from '../../lib/ipc-facade'; -export type RelaySettingsRedux = {| - normal: { - location: 'any' | RelayLocation, - port: 'any' | number, - protocol: 'any' | RelayProtocol, - } -|} | {| - custom_tunnel_endpoint: { - host: string, - port: number, - protocol: RelayProtocol, - } -|}; +export type RelaySettingsRedux = + | {| + normal: { + location: 'any' | RelayLocation, + port: 'any' | number, + protocol: 'any' | RelayProtocol, + }, + |} + | {| + custom_tunnel_endpoint: { + host: string, + port: number, + protocol: RelayProtocol, + }, + |}; export type RelayLocationCityRedux = { name: string, @@ -44,31 +46,36 @@ const initialState: SettingsReduxState = { location: 'any', port: 'any', protocol: 'any', - } + }, }, relayLocations: [], allowLan: false, }; -export default function(state: SettingsReduxState = initialState, action: ReduxAction): SettingsReduxState { - - switch(action.type) { - case 'UPDATE_RELAY': - return { ...state, - relaySettings: action.relay, - }; +export default function( + state: SettingsReduxState = initialState, + action: ReduxAction, +): SettingsReduxState { + switch (action.type) { + case 'UPDATE_RELAY': + return { + ...state, + relaySettings: action.relay, + }; - case 'UPDATE_RELAY_LOCATIONS': - return { ...state, - relayLocations: action.relayLocations, - }; + case 'UPDATE_RELAY_LOCATIONS': + return { + ...state, + relayLocations: action.relayLocations, + }; - case 'UPDATE_ALLOW_LAN': - return { ...state, - allowLan: action.allowLan, - }; + case 'UPDATE_ALLOW_LAN': + return { + ...state, + allowLan: action.allowLan, + }; - default: - return state; + default: + return state; } } diff --git a/app/redux/store.js b/app/redux/store.js index d03849c0c4..8d74b605f6 100644 --- a/app/redux/store.js +++ b/app/redux/store.js @@ -23,7 +23,7 @@ import type { SettingsAction } from './settings/actions.js'; export type ReduxState = { account: AccountReduxState, connection: ConnectionReduxState, - settings: SettingsReduxState + settings: SettingsReduxState, }; export type ReduxAction = AccountAction | SettingsAction | ConnectionAction; @@ -32,7 +32,10 @@ export type ReduxGetState = () => ReduxState; export type ReduxDispatch = (action: ReduxAction | ReduxThunk) => any; export type ReduxThunk = (dispatch: ReduxDispatch, getState: ReduxGetState) => any; -export default function configureStore(initialState: ?ReduxState, routerHistory: History): ReduxStore { +export default function configureStore( + initialState: ?ReduxState, + routerHistory: History, +): ReduxStore { const router = routerMiddleware(routerHistory); const actionCreators: { [string]: Function } = { @@ -44,14 +47,17 @@ export default function configureStore(initialState: ?ReduxState, routerHistory: }; const reducers = { - account, connection, settings, router: routerReducer + account, + connection, + settings, + router: routerReducer, }; - const middlewares = [ thunk, router ]; + const middlewares = [thunk, router]; const composeEnhancers = (() => { const reduxCompose = window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; - if(process.env.NODE_ENV === 'development' && reduxCompose) { + if (process.env.NODE_ENV === 'development' && reduxCompose) { return reduxCompose({ actionCreators }); } return compose; @@ -59,7 +65,7 @@ export default function configureStore(initialState: ?ReduxState, routerHistory: const enhancer = composeEnhancers(applyMiddleware(...middlewares)); const rootReducer = combineReducers(reducers); - if(initialState) { + if (initialState) { return createStore(rootReducer, initialState, enhancer); } return createStore(rootReducer, enhancer); diff --git a/app/routes.js b/app/routes.js index b5e1f8aa07..87867791f2 100644 --- a/app/routes.js +++ b/app/routes.js @@ -18,30 +18,36 @@ import type { ReduxGetState } from './redux/store'; import type { Backend } from './lib/backend'; export type SharedRouteProps = { - backend: Backend + backend: Backend, }; type CustomRouteProps = { - component: React.ComponentType<*> + component: React.ComponentType<*>, }; -export default function makeRoutes(getState: ReduxGetState, componentProps: SharedRouteProps): React.Element<*> { - +export default function makeRoutes( + getState: ReduxGetState, + componentProps: SharedRouteProps, +): React.Element<*> { // Merge props and render component - const renderMergedProps = (ComponentClass: React.ComponentType<*>, ...rest: Array<Object>): React.Element<*> => { + const renderMergedProps = ( + ComponentClass: React.ComponentType<*>, + ...rest: Array<Object> + ): React.Element<*> => { const finalProps = Object.assign({}, componentProps, ...rest); - return ( - <ComponentClass { ...finalProps } /> - ); + return <ComponentClass {...finalProps} />; }; // Renders public route // example: <PublicRoute path="/" component={ MyComponent } /> const PublicRoute = ({ component, ...otherProps }: CustomRouteProps) => { return ( - <Route { ...otherProps } render={ (routeProps) => { - return renderMergedProps(component, routeProps, otherProps); - }} /> + <Route + {...otherProps} + render={(routeProps) => { + return renderMergedProps(component, routeProps, otherProps); + }} + /> ); }; @@ -49,16 +55,19 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar // example: <PrivateRoute path="/protected" component={ MyComponent } /> const PrivateRoute = ({ component, ...otherProps }: CustomRouteProps) => { return ( - <Route { ...otherProps } render={ (routeProps) => { - const { account } = getState(); - const isLoggedIn = account.status === 'ok'; + <Route + {...otherProps} + render={(routeProps) => { + const { account } = getState(); + const isLoggedIn = account.status === 'ok'; - if(isLoggedIn) { - return renderMergedProps(component, routeProps, otherProps); - } else { - return (<Redirect to={ '/' } />); - } - }} /> + if (isLoggedIn) { + return renderMergedProps(component, routeProps, otherProps); + } else { + return <Redirect to={'/'} />; + } + }} + /> ); }; @@ -67,16 +76,19 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar // example: <LoginRoute path="/login" component={ MyComponent } /> const LoginRoute = ({ component, ...otherProps }: CustomRouteProps) => { return ( - <Route { ...otherProps } render={ (routeProps) => { - const { account } = getState(); - const isLoggedIn = account.status === 'ok'; + <Route + {...otherProps} + render={(routeProps) => { + const { account } = getState(); + const isLoggedIn = account.status === 'ok'; - if(isLoggedIn) { - return (<Redirect to={ '/connect' } />); - } else { - return renderMergedProps(component, routeProps, otherProps); - } - }} /> + if (isLoggedIn) { + return <Redirect to={'/connect'} />; + } else { + return renderMergedProps(component, routeProps, otherProps); + } + }} + /> ); }; @@ -84,28 +96,30 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar let previousRoute: ?string; return ( - <Route render={({ location }) => { - const toRoute = location.pathname; - const fromRoute = previousRoute; - const transitionProps = getTransitionProps(fromRoute, toRoute); - previousRoute = toRoute; + <Route + render={({ location }) => { + const toRoute = location.pathname; + const fromRoute = previousRoute; + const transitionProps = getTransitionProps(fromRoute, toRoute); + previousRoute = toRoute; - return ( - <PlatformWindow> - <TransitionContainer { ...transitionProps }> - <Switch key={ location.key } location={ location }> - <LoginRoute exact path="/" component={ LoginPage } /> - <PrivateRoute exact path="/connect" component={ ConnectPage } /> - <PublicRoute exact path="/settings" component={ SettingsPage } /> - <PrivateRoute exact path="/settings/account" component={ AccountPage } /> - <PublicRoute exact path="/settings/preferences" component={ PreferencesPage } /> - <PublicRoute exact path="/settings/advanced" component={ AdvancedSettingsPage } /> - <PublicRoute exact path="/settings/support" component={ SupportPage } /> - <PrivateRoute exact path="/select-location" component={ SelectLocationPage } /> - </Switch> - </TransitionContainer> - </PlatformWindow> - ); - }} /> + return ( + <PlatformWindow> + <TransitionContainer {...transitionProps}> + <Switch key={location.key} location={location}> + <LoginRoute exact path="/" component={LoginPage} /> + <PrivateRoute exact path="/connect" component={ConnectPage} /> + <PublicRoute exact path="/settings" component={SettingsPage} /> + <PrivateRoute exact path="/settings/account" component={AccountPage} /> + <PublicRoute exact path="/settings/preferences" component={PreferencesPage} /> + <PublicRoute exact path="/settings/advanced" component={AdvancedSettingsPage} /> + <PublicRoute exact path="/settings/support" component={SupportPage} /> + <PrivateRoute exact path="/select-location" component={SelectLocationPage} /> + </Switch> + </TransitionContainer> + </PlatformWindow> + ); + }} + /> ); } diff --git a/app/transitions.js b/app/transitions.js index 3e825c2968..56b1d74cfb 100644 --- a/app/transitions.js +++ b/app/transitions.js @@ -8,7 +8,7 @@ export type TransitionGroupProps = { }; type TransitionMap = { - [name: string]: TransitionFork + [name: string]: TransitionFork, }; /** @@ -18,23 +18,23 @@ const transitions: TransitionMap = { slide: { forward: { name: 'slide-up', - duration: 450 + duration: 450, }, backward: { name: 'slide-down', - duration: 450 - } + duration: 450, + }, }, push: { forward: { name: 'push', - duration: 450 + duration: 450, }, backward: { name: 'pop', - duration: 450 - } - } + duration: 450, + }, + }, }; /** @@ -47,7 +47,7 @@ const transitionRules = [ r('/settings', '/settings/advanced', transitions.push), r('/settings', '/settings/support', transitions.push), r(null, '/settings', transitions.slide), - r(null, '/select-location', transitions.slide) + r(null, '/select-location', transitions.slide), ]; /** @@ -58,13 +58,13 @@ const transitionRules = [ */ export function getTransitionProps(fromRoute: ?string, toRoute: string): TransitionGroupProps { // ignore initial transition and transition between the same routes - if(!fromRoute || fromRoute === toRoute) { + if (!fromRoute || fromRoute === toRoute) { return noTransitionProps(); } - for(const rule of transitionRules) { + for (const rule of transitionRules) { const match = rule.match(fromRoute, toRoute); - if(match) { + if (match) { return toTransitionGroupProps(match.descriptor); } } @@ -77,7 +77,7 @@ export function getTransitionProps(fromRoute: ?string, toRoute: string): Transit * @param {TransitionDescriptor} descriptor */ function toTransitionGroupProps(descriptor: TransitionDescriptor): TransitionGroupProps { - const {name, duration} = descriptor; + const { name, duration } = descriptor; return { name: name, duration: duration, |
