diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-08-08 16:53:23 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-08-08 16:53:23 +0200 |
| commit | ac98e48c68eadfdd7250eccd38aa492e6f830744 (patch) | |
| tree | 05bfd7a9f05456220f11f40329093b127a5b20a2 /app/components | |
| parent | d53fda746465c0bb6525371b9d780cbfac90f942 (diff) | |
| parent | a8f2831cd50e98b1d126b8c3169adcf1ef75b8a2 (diff) | |
| download | mullvadvpn-ac98e48c68eadfdd7250eccd38aa492e6f830744.tar.xz mullvadvpn-ac98e48c68eadfdd7250eccd38aa492e6f830744.zip | |
Merge branch 'remove-auto-login'
Diffstat (limited to 'app/components')
| -rw-r--r-- | app/components/Account.js | 27 | ||||
| -rw-r--r-- | app/components/AccountStyles.js | 1 | ||||
| -rw-r--r-- | app/components/Cell.js | 1 | ||||
| -rw-r--r-- | app/components/Connect.js | 41 | ||||
| -rw-r--r-- | app/components/CustomScrollbars.js | 2 | ||||
| -rw-r--r-- | app/components/HeaderBar.js | 163 | ||||
| -rw-r--r-- | app/components/HeaderBarPlatformStyles.android.js | 2 | ||||
| -rw-r--r-- | app/components/HeaderBarPlatformStyles.js | 16 | ||||
| -rw-r--r-- | app/components/HeaderBarStyles.js | 53 | ||||
| -rw-r--r-- | app/components/Img.js | 8 | ||||
| -rw-r--r-- | app/components/Launch.js | 58 | ||||
| -rw-r--r-- | app/components/Layout.js | 11 | ||||
| -rw-r--r-- | app/components/Login.js | 6 | ||||
| -rw-r--r-- | app/components/LoginStyles.js | 24 | ||||
| -rw-r--r-- | app/components/Settings.js | 43 |
15 files changed, 288 insertions, 168 deletions
diff --git a/app/components/Account.js b/app/components/Account.js index 2edf277fa4..700f33af2b 100644 --- a/app/components/Account.js +++ b/app/components/Account.js @@ -1,7 +1,7 @@ // @flow import moment from 'moment'; import * as React from 'react'; -import { Component, Text, View, App, Types } from 'reactxp'; +import { Component, Text, View } from 'reactxp'; import * as AppButton from './AppButton'; import { Layout, Container } from './Layout'; import NavigationBar, { BackBarItem } from './NavigationBar'; @@ -9,10 +9,11 @@ import SettingsHeader, { HeaderTitle } from './SettingsHeader'; import styles from './AccountStyles'; import Img from './Img'; import { formatAccount } from '../lib/formatters'; +import WindowStateObserver from '../lib/window-state-observer'; import type { AccountToken } from '../lib/daemon-rpc'; -export type AccountProps = { +type Props = { accountToken: AccountToken, accountExpiry: string, expiryLocale: string, @@ -28,27 +29,23 @@ type State = { showAccountTokenCopiedMessage: boolean, }; -export default class Account extends Component<AccountProps, State> { +export default class Account extends Component<Props, State> { state = { isRefreshingExpiry: false, showAccountTokenCopiedMessage: false, }; - _activationStateToken: ?Types.SubscriptionToken; - _isMounted = false; - _copyTimer: ?TimeoutID; + _windowStateObserver = new WindowStateObserver(); componentDidMount() { this._isMounted = true; this._refreshAccountExpiry(); - this._activationStateToken = App.activationStateChangedEvent.subscribe((activationState) => { - if (activationState === Types.AppActivationState.Active) { - this._refreshAccountExpiry(); - } - }); + this._windowStateObserver.onShow = () => { + this._refreshAccountExpiry(); + }; } componentWillUnmount() { @@ -58,11 +55,7 @@ export default class Account extends Component<AccountProps, State> { clearTimeout(this._copyTimer); } - const activationStateToken = this._activationStateToken; - if (activationStateToken) { - activationStateToken.unsubscribe(); - this._activationStateToken = null; - } + this._windowStateObserver.dispose(); } onAccountTokenClick() { @@ -119,7 +112,7 @@ export default class Account extends Component<AccountProps, State> { <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 + {'OUT OF TIME'} </Text> ) : ( <Text style={styles.account__row_value}>{formattedExpiry}</Text> diff --git a/app/components/AccountStyles.js b/app/components/AccountStyles.js index 14fc478a54..a96a8fcff6 100644 --- a/app/components/AccountStyles.js +++ b/app/components/AccountStyles.js @@ -56,6 +56,7 @@ export default { account__row_value: { fontFamily: 'Open Sans', fontSize: 16, + lineHeight: 19, fontWeight: '800', color: colors.white, }, diff --git a/app/components/Cell.js b/app/components/Cell.js index 2b3c06a3b1..80b783352f 100644 --- a/app/components/Cell.js +++ b/app/components/Cell.js @@ -19,6 +19,7 @@ const styles = { flexDirection: 'row', alignItems: 'center', alignContent: 'center', + cursor: 'default', }, cellHover: { backgroundColor: colors.blue80, diff --git a/app/components/Connect.js b/app/components/Connect.js index 037ab02495..9d7f52f7b1 100644 --- a/app/components/Connect.js +++ b/app/components/Connect.js @@ -3,19 +3,20 @@ import moment from 'moment'; import * as React from 'react'; import { Layout, Container, Header } from './Layout'; +import { SettingsBarButton, Brand } from './HeaderBar'; import { Component, Text, View, Types } from 'reactxp'; import * as AppButton from './AppButton'; import Img from './Img'; import Accordion from './Accordion'; import styles from './ConnectStyles'; - import { NoCreditError, NoInternetError } from '../errors'; import Map from './Map'; +import WindowStateObserver from '../lib/window-state-observer'; import type { HeaderBarStyle } from './HeaderBar'; import type { ConnectionReduxState } from '../redux/connection/reducers'; -export type ConnectProps = { +type Props = { connection: ConnectionReduxState, accountExpiry: string, selectedRelayName: string, @@ -25,22 +26,24 @@ export type ConnectProps = { onCopyIP: () => void, onDisconnect: () => void, onExternalLink: (type: string) => void, + updateAccountExpiry: () => Promise<void>, }; -type ConnectState = { +type State = { showCopyIPMessage: boolean, mapOffset: [number, number], }; -export default class Connect extends Component<ConnectProps, ConnectState> { +export default class Connect extends Component<Props, State> { state = { showCopyIPMessage: false, mapOffset: [0, 0], }; _copyTimer: ?TimeoutID; + _windowStateObserver = new WindowStateObserver(); - shouldComponentUpdate(nextProps: ConnectProps, nextState: ConnectState) { + shouldComponentUpdate(nextProps: Props, nextState: State) { const { connection: prevConnection, ...otherPrevProps } = this.props; const { connection: nextConnection, ...otherNextProps } = nextProps; @@ -56,10 +59,20 @@ export default class Connect extends Component<ConnectProps, ConnectState> { ); } + componentDidMount() { + this.props.updateAccountExpiry(); + + this._windowStateObserver.onShow = () => { + this.props.updateAccountExpiry(); + }; + } + componentWillUnmount() { if (this._copyTimer) { clearTimeout(this._copyTimer); } + + this._windowStateObserver.dispose(); } render() { @@ -68,12 +81,10 @@ export default class Connect extends Component<ConnectProps, ConnectState> { return ( <Layout> - <Header - style={this.headerStyle()} - showSettings={true} - onSettings={this.props.onSettings} - testName="header" - /> + <Header barStyle={this.headerBarStyle()} testName="header"> + <Brand /> + <SettingsBarButton onPress={this.props.onSettings} /> + </Header> <Container>{child}</Container> </Layout> ); @@ -324,15 +335,17 @@ export default class Connect extends Component<ConnectProps, ConnectState> { // Private - headerStyle(): HeaderBarStyle { - switch (this.props.connection.status) { + headerBarStyle(): HeaderBarStyle { + const { status } = this.props.connection; + switch (status) { case 'disconnected': return 'error'; case 'connecting': case 'connected': return 'success'; + default: + throw new Error(`Invalid ConnectionState: ${(status: empty)}`); } - throw new Error('Invalid ConnectionState'); } networkSecurityStyle(): Types.Style { diff --git a/app/components/CustomScrollbars.js b/app/components/CustomScrollbars.js index be74bfddb5..a149faff75 100644 --- a/app/components/CustomScrollbars.js +++ b/app/components/CustomScrollbars.js @@ -176,7 +176,7 @@ export default class CustomScrollbars extends React.Component<Props, State> { return offsetTop - (scrollable.offsetHeight - child.clientHeight) * 0.5; default: - throw new Error(`Unknown enum type for ScrollPosition: ${scrollPosition}`); + throw new Error(`Unknown enum type for ScrollPosition: ${(scrollPosition: empty)}`); } } diff --git a/app/components/HeaderBar.js b/app/components/HeaderBar.js index a46856eb46..02b1d86e31 100644 --- a/app/components/HeaderBar.js +++ b/app/components/HeaderBar.js @@ -1,55 +1,148 @@ // @flow import React from 'react'; -import { Component, Text, Button, View } from 'reactxp'; - +import { Component, Text, Button, View, Styles } from 'reactxp'; import Img from './Img'; - -import styles from './HeaderBarStyles'; -import platformStyles from './HeaderBarPlatformStyles'; +import { colors } from '../config'; export type HeaderBarStyle = 'default' | 'defaultDark' | 'error' | 'success'; -export type HeaderBarProps = { - style: HeaderBarStyle, - showSettings: boolean, - onSettings: ?() => void, +type HeaderBarProps = { + barStyle: HeaderBarStyle, +}; + +const headerBarStyles = { + container: { + base: Styles.createViewStyle({ + paddingTop: 12, + paddingBottom: 12, + paddingLeft: 12, + paddingRight: 12, + }), + platformOverride: { + darwin: Styles.createViewStyle({ + paddingTop: 24, + }), + linux: Styles.createViewStyle({ + WebkitAppRegion: 'drag', + }), + }, + }, + content: Styles.createViewStyle({ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-end', + // the size of "brand" logo + minHeight: 51, + }), + barStyle: { + default: Styles.createViewStyle({ + backgroundColor: colors.blue, + }), + defaultDark: Styles.createViewStyle({ + backgroundColor: colors.darkBlue, + }), + error: Styles.createViewStyle({ + backgroundColor: colors.red, + }), + success: Styles.createViewStyle({ + backgroundColor: colors.green, + }), + }, }; export default class HeaderBar extends Component<HeaderBarProps> { static defaultProps: HeaderBarProps = { - style: 'default', - showSettings: false, - onSettings: null, + barStyle: 'default', }; render() { - const containerClass = [ - styles['headerbar'], - platformStyles[process.platform], - styles['style_' + this.props.style], + const style = [ + headerBarStyles.container.base, + headerBarStyles.container.platformOverride[process.platform], + headerBarStyles.barStyle[this.props.barStyle], + this.props.style, ]; return ( - <View style={containerClass}> - <View style={styles.container} testName="headerbar__container"> - <Img height={50} width={50} source="logo-icon" /> - <Text style={styles.title}>MULLVAD VPN</Text> - </View> + <View style={style}> + <View style={headerBarStyles.content}>{this.props.children}</View> + </View> + ); + } +} - {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} +const brandStyles = { + container: Styles.createViewStyle({ + flex: 1, + flexDirection: 'row', + alignItems: 'center', + }), + title: Styles.createTextStyle({ + fontFamily: 'DINPro', + fontSize: 24, + fontWeight: '900', + lineHeight: 30, + letterSpacing: -0.5, + color: colors.white60, + marginLeft: 8, + }), +}; + +export class Brand extends Component { + render() { + return ( + <View style={brandStyles.container} testName="headerbar__container"> + <Img width={50} height={50} source="logo-icon" /> + <Text style={brandStyles.title}>{'MULLVAD VPN'}</Text> </View> ); } } + +type SettingsButtonProps = { + onPress: ?() => void, +}; + +const settingsBarButtonStyles = { + container: { + base: Styles.createViewStyle({ + cursor: 'default', + padding: 0, + marginLeft: 8, + }), + platformOverride: { + linux: Styles.createViewStyle({ + WebkitAppRegion: 'no-drag', + }), + }, + }, + icon: { + normal: Styles.createViewStyle({ + color: colors.white60, + }), + hover: Styles.createViewStyle({ + color: colors.white, + }), + }, +}; + +export class SettingsBarButton extends Component<SettingsButtonProps> { + render() { + return ( + <Button + style={[ + settingsBarButtonStyles.container.base, + settingsBarButtonStyles.container.platformOverride[process.platform], + ]} + onPress={this.props.onPress} + testName="headerbar__settings"> + <Img + height={24} + width={24} + source="icon-settings" + style={settingsBarButtonStyles.icon.normal} + hoverStyle={settingsBarButtonStyles.icon.hover} + /> + </Button> + ); + } +} diff --git a/app/components/HeaderBarPlatformStyles.android.js b/app/components/HeaderBarPlatformStyles.android.js deleted file mode 100644 index f2f97beb65..0000000000 --- a/app/components/HeaderBarPlatformStyles.android.js +++ /dev/null @@ -1,2 +0,0 @@ -import { createViewStyles } from '../lib/styles'; -export default { ...createViewStyles({}) }; diff --git a/app/components/HeaderBarPlatformStyles.js b/app/components/HeaderBarPlatformStyles.js deleted file mode 100644 index c60967797a..0000000000 --- a/app/components/HeaderBarPlatformStyles.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow -import { createViewStyles } from '../lib/styles'; - -export default { - ...createViewStyles({ - darwin: { - paddingTop: 24, - }, - linux: { - WebkitAppRegion: 'drag', - }, - settings_icon: { - WebkitAppRegion: 'no-drag', - }, - }), -}; diff --git a/app/components/HeaderBarStyles.js b/app/components/HeaderBarStyles.js deleted file mode 100644 index 7d3c94cdc9..0000000000 --- a/app/components/HeaderBarStyles.js +++ /dev/null @@ -1,53 +0,0 @@ -// @flow -import { createTextStyles, createViewStyles } from '../lib/styles'; -import { colors } from '../config'; - -export default { - ...createViewStyles({ - headerbar: { - paddingTop: 12, - paddingBottom: 12, - paddingLeft: 12, - paddingRight: 12, - backgroundColor: colors.blue, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - style_defaultDark: { - backgroundColor: colors.darkBlue, - }, - style_error: { - backgroundColor: colors.red, - }, - style_success: { - backgroundColor: colors.green, - }, - container: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - settings: { - cursor: 'default', - padding: 0, - }, - settings_icon: { - color: colors.white60, - }, - settings_icon_hover: { - color: colors.white, - }, - }), - ...createTextStyles({ - title: { - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - letterSpacing: -0.5, - color: colors.white60, - marginLeft: 8, - }, - }), -}; diff --git a/app/components/Img.js b/app/components/Img.js index 1d6f6613f4..6b34f99360 100644 --- a/app/components/Img.js +++ b/app/components/Img.js @@ -4,6 +4,8 @@ import { View, Component, Types } from 'reactxp'; type Props = { source: string, + width?: number, + heigth?: number, tintColor?: string, hoverStyle?: Types.ViewStyle, disabled?: boolean, @@ -20,7 +22,7 @@ export default class Img extends Component<Props, State> { getHoverStyle = () => (this.state.hovered ? this.props.hoverStyle || null : null); render() { - const { source, style, onMouseEnter, onMouseLeave, ...otherProps } = this.props; + const { source, width, heigth, style, onMouseEnter, onMouseLeave, ...otherProps } = this.props; const tintColor = this.props.tintColor; const url = './assets/images/' + source + '.svg'; let image; @@ -36,6 +38,8 @@ export default class Img extends Component<Props, State> { }}> <img src={url} + width={width} + height={heigth} style={{ visibility: 'hidden', }} @@ -43,7 +47,7 @@ export default class Img extends Component<Props, State> { </div> ); } else { - image = <img src={url} />; + image = <img src={url} width={width} height={heigth} />; } return ( diff --git a/app/components/Launch.js b/app/components/Launch.js new file mode 100644 index 0000000000..bab877dd53 --- /dev/null +++ b/app/components/Launch.js @@ -0,0 +1,58 @@ +// @flow +import * as React from 'react'; +import { Component, Styles, View, Text } from 'reactxp'; +import { Layout, Container, Header } from './Layout'; +import { SettingsBarButton } from './HeaderBar'; +import Img from './Img'; +import { colors } from '../config'; + +const styles = { + container: Styles.createViewStyle({ + flex: 1, + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + marginTop: -150, + }), + logo: Styles.createViewStyle({ + marginBottom: 4, + }), + title: Styles.createTextStyle({ + fontFamily: 'DINPro', + fontSize: 24, + fontWeight: '900', + lineHeight: 30, + letterSpacing: -0.5, + color: colors.white60, + marginBottom: 4, + }), + subtitle: Styles.createTextStyle({ + fontFamily: 'Open Sans', + fontSize: 14, + lineHeight: 20, + color: colors.white40, + }), +}; + +type Props = { + openSettings: () => void, +}; + +export default class Launch extends Component<Props> { + render() { + return ( + <Layout> + <Header> + <SettingsBarButton onPress={this.props.openSettings} /> + </Header> + <Container> + <View style={styles.container} testName="headerbar__container"> + <Img height={120} width={120} source="logo-icon" style={styles.logo} /> + <Text style={styles.title}>{'MULLVAD VPN'}</Text> + <Text style={styles.subtitle}>{'Connecting to daemon...'}</Text> + </View> + </Container> + </Layout> + ); + } +} diff --git a/app/components/Layout.js b/app/components/Layout.js index 2699b3e43d..1eb6984797 100644 --- a/app/components/Layout.js +++ b/app/components/Layout.js @@ -1,19 +1,16 @@ // @flow import * as React from 'react'; -import HeaderBar from './HeaderBar'; import { View, Component } from 'reactxp'; - -import type { HeaderBarProps } from './HeaderBar'; - +import HeaderBar from './HeaderBar'; import styles from './LayoutStyles'; -export class Header extends Component<HeaderBarProps> { +export class Header extends Component<React.ElementProps<typeof HeaderBar>> { static defaultProps = HeaderBar.defaultProps; render() { return ( - <View style={styles.header}> - <HeaderBar {...this.props} /> + <View style={[styles.header, this.props.style]}> + <HeaderBar barStyle={this.props.barStyle}>{this.props.children}</HeaderBar> </View> ); } diff --git a/app/components/Login.js b/app/components/Login.js index d902bba1bc..b410f80553 100644 --- a/app/components/Login.js +++ b/app/components/Login.js @@ -2,6 +2,7 @@ import * as React from 'react'; import { Component, Text, View, Animated, Styles, UserInterface } from 'reactxp'; import { Layout, Container, Header } from './Layout'; +import { SettingsBarButton, Brand } from './HeaderBar'; import AccountInput from './AccountInput'; import Accordion from './Accordion'; import { formatAccount } from '../lib/formatters'; @@ -94,7 +95,10 @@ export default class Login extends Component<Props, State> { render() { return ( <Layout> - <Header showSettings={true} onSettings={this.props.openSettings} /> + <Header> + <Brand /> + <SettingsBarButton onPress={this.props.openSettings} /> + </Header> <Container> <View style={styles.login_form}> {this._getStatusIcon()} diff --git a/app/components/LoginStyles.js b/app/components/LoginStyles.js index d9094a2f13..61e5026a71 100644 --- a/app/components/LoginStyles.js +++ b/app/components/LoginStyles.js @@ -14,9 +14,8 @@ export default { }, status_icon: { flex: 0, - height: 48, marginBottom: 30, - justifyContent: 'center', + alignItems: 'center', }, login_form: { flex: 1, @@ -88,13 +87,15 @@ export default { backgroundColor: colors.darkBlue, }, account_dropdown__item: { - paddingTop: 10, - paddingRight: 12, - paddingLeft: 12, - paddingBottom: 12, + paddingTop: 0, + paddingRight: 0, + paddingLeft: 0, + paddingBottom: 0, marginBottom: 0, flexDirection: 'row', + alignItems: 'stretch', backgroundColor: colors.white60, + cursor: 'default', }, account_dropdown__item_hover: { backgroundColor: colors.white40, @@ -102,6 +103,11 @@ export default { account_dropdown__remove: { justifyContent: 'center', color: colors.blue40, + paddingTop: 10, + paddingRight: 12, + paddingBottom: 12, + paddingLeft: 12, + marginLeft: 0, }, account_dropdown__remove_cell_hover: { color: colors.blue60, @@ -136,6 +142,7 @@ export default { subtitle: { fontFamily: 'Open Sans', fontSize: 13, + lineHeight: 15, fontWeight: '600', letterSpacing: -0.2, color: colors.white80, @@ -165,6 +172,11 @@ export default { borderWidth: 0, textAlign: 'left', marginLeft: 0, + paddingTop: 10, + paddingRight: 0, + paddingLeft: 12, + paddingBottom: 12, + cursor: 'default', }, }), }; diff --git a/app/components/Settings.js b/app/components/Settings.js index 4a8784fe0e..0ed45c7685 100644 --- a/app/components/Settings.js +++ b/app/components/Settings.js @@ -10,14 +10,14 @@ import SettingsHeader, { HeaderTitle } from './SettingsHeader'; import CustomScrollbars from './CustomScrollbars'; import styles from './SettingsStyles'; import Img from './Img'; +import WindowStateObserver from '../lib/window-state-observer'; -import type { AccountReduxState } from '../redux/account/reducers'; -import type { SettingsReduxState } from '../redux/settings/reducers'; +import type { LoginState } from '../redux/account/reducers'; -export type SettingsProps = { - account: AccountReduxState, - settings: SettingsReduxState, - version: string, +type Props = { + loginState: LoginState, + accountExpiry: ?string, + appVersion: string, onQuit: () => void, onClose: () => void, onViewAccount: () => void, @@ -25,9 +25,24 @@ export type SettingsProps = { onViewPreferences: () => void, onViewAdvancedSettings: () => void, onExternalLink: (type: string) => void, + updateAccountExpiry: () => Promise<void>, }; -export default class Settings extends Component<SettingsProps> { +export default class Settings extends Component<Props> { + _windowStateObserver = new WindowStateObserver(); + + componentDidMount() { + this.props.updateAccountExpiry(); + + this._windowStateObserver.onShow = () => { + this.props.updateAccountExpiry(); + }; + } + + componentWillUnmount() { + this._windowStateObserver.dispose(); + } + render() { return ( <Layout> @@ -60,17 +75,17 @@ export default class Settings extends Component<SettingsProps> { } _renderTopButtons() { - const isLoggedIn = this.props.account.status === 'ok'; + const isLoggedIn = this.props.loginState === 'ok'; if (!isLoggedIn) { return null; } - let isOutOfTime = false, - formattedExpiry = ''; - const expiryIso = this.props.account.expiry; + let isOutOfTime = false; + let formattedExpiry = ''; + const expiryIso = this.props.accountExpiry; if (isLoggedIn && expiryIso) { - const expiry = moment(this.props.account.expiry); + const expiry = moment(expiryIso); isOutOfTime = expiry.isSameOrBefore(moment()); formattedExpiry = (expiry.fromNow(true) + ' left').toUpperCase(); } @@ -86,7 +101,7 @@ export default class Settings extends Component<SettingsProps> { <Cell.SubText testName="settings__account_paid_until_subtext" style={styles.settings__account_paid_until_Label__error}> - OUT OF TIME + {'OUT OF TIME'} </Cell.SubText> <Img height={12} width={7} source="icon-chevron" /> </Cell.CellButton> @@ -136,7 +151,7 @@ export default class Settings extends Component<SettingsProps> { // the version in package.json has to be semver, but we use a YEAR.release-channel // 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 + return this.props.appVersion .replace('.0-', '-') // remove the .0 in 2018.1.0-beta9 .replace(/\.0$/, ''); // remove the .0 in 2018.1.0 } |
