diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-05-25 12:59:26 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-05-25 12:59:26 +0200 |
| commit | 119510c7356ab4c6858a01d330d3b1452026c3ab (patch) | |
| tree | 3fbf3d7ca7da56f3be4431a1117b1da0dc7d6b3f /gui/src | |
| parent | 4374667d5b8b76a90dc2a2a5c466102bb9c84add (diff) | |
| parent | 80a9394c56888af208933566c76f9fb99fe7218e (diff) | |
| download | mullvadvpn-119510c7356ab4c6858a01d330d3b1452026c3ab.tar.xz mullvadvpn-119510c7356ab4c6858a01d330d3b1452026c3ab.zip | |
Merge branch 'improve-beta-program-gui'
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/index.ts | 22 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/Cell.tsx | 57 | ||||
| -rw-r--r-- | gui/src/renderer/components/CellStyles.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/Preferences.tsx | 16 | ||||
| -rw-r--r-- | gui/src/renderer/components/Switch.tsx | 56 | ||||
| -rw-r--r-- | gui/src/renderer/containers/PreferencesPage.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/redux/version/actions.ts | 10 | ||||
| -rw-r--r-- | gui/src/renderer/redux/version/reducers.ts | 7 |
9 files changed, 129 insertions, 52 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 91eaf52b57..b2d33a700e 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -65,10 +65,12 @@ export interface ICurrentAppVersionInfo { gui: string; daemon: string; isConsistent: boolean; + currentIsBeta: boolean; } export interface IAppUpgradeInfo extends IAppVersionInfo { - nextUpgrade?: string; + // Null is used since undefined properties get filtered out when sending through IPC. + nextUpgrade: string | null; } type AccountVerification = { status: 'verified' } | { status: 'deferred'; error: Error }; @@ -136,6 +138,7 @@ class ApplicationMain { daemon: '', gui: '', isConsistent: true, + currentIsBeta: false, }; private upgradeVersion: IAppUpgradeInfo = { @@ -143,7 +146,7 @@ class ApplicationMain { latestStable: '', latestBeta: '', latest: '', - nextUpgrade: undefined, + nextUpgrade: null, }; // The UI locale which is set once from onReady handler @@ -626,6 +629,10 @@ class ApplicationMain { consumePromise(this.fetchWireguardKey()); } + if (oldSettings.showBetaReleases !== newSettings.showBetaReleases) { + this.setLatestVersion(this.upgradeVersion); + } + if (this.windowController) { IpcMainEventChannel.settings.notify(this.windowController.webContents, newSettings); } @@ -735,6 +742,7 @@ class ApplicationMain { daemon: daemonVersion, gui: guiVersion, isConsistent: daemonVersion === guiVersion, + currentIsBeta: guiVersion.includes('beta'), }; this.currentVersion = versionInfo; @@ -748,15 +756,11 @@ class ApplicationMain { private setLatestVersion(latestVersionInfo: IAppVersionInfo) { const settings = this.settings; - function nextUpgrade( - current: string, - latest: string, - latestStable: string, - ): string | undefined { + function nextUpgrade(current: string, latest: string, latestStable: string): string | null { if (settings.showBetaReleases) { - return current === latest ? undefined : latest; + return current === latest ? null : latest; } else { - return current === latestStable ? undefined : latestStable; + return current === latestStable ? null : latestStable; } } diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 82858ad3d9..f9d4130419 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -731,7 +731,11 @@ export default class AppRenderer { } private setCurrentVersion(versionInfo: ICurrentAppVersionInfo) { - this.reduxActions.version.updateVersion(versionInfo.gui, versionInfo.isConsistent); + this.reduxActions.version.updateVersion( + versionInfo.gui, + versionInfo.isConsistent, + versionInfo.currentIsBeta, + ); } private setUpgradeVersion(upgradeVersion: IAppUpgradeInfo) { diff --git a/gui/src/renderer/components/Cell.tsx b/gui/src/renderer/components/Cell.tsx index 23f63114b7..4abcb9f49e 100644 --- a/gui/src/renderer/components/Cell.tsx +++ b/gui/src/renderer/components/Cell.tsx @@ -4,26 +4,38 @@ import { StyledAutoSizingTextInputWrapper, StyledAutoSizingTextInputFiller, StyledCellButton, - StyledSection, + StyledContainer, + StyledLabel, StyledInput, + StyledSection, } from './CellStyles'; +import { default as StandaloneSwitch } from './Switch'; export { - StyledContainer as Container, StyledFooter as Footer, StyledFooterBoldText as FooterBoldText, StyledFooterText as FooterText, StyledIcon as UntintedIcon, StyledInputFrame as InputFrame, - StyledLabel as Label, StyledSectionTitle as SectionTitle, StyledSubText as SubText, StyledTintedIcon as Icon, } from './CellStyles'; -export { default as Switch } from './Switch'; - const CellSectionContext = React.createContext<boolean>(false); +const CellDisabledContext = React.createContext<boolean>(false); + +interface IContainerProps extends React.HTMLAttributes<HTMLDivElement> { + disabled?: boolean; +} + +export function Container({ disabled, ...otherProps }: IContainerProps) { + return ( + <CellDisabledContext.Provider value={disabled ?? false}> + <StyledContainer {...otherProps} /> + </CellDisabledContext.Provider> + ); +} interface ICellButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { selected?: boolean; @@ -50,6 +62,16 @@ export function Section(props: ISectionProps) { ); } +export function Label(props: React.HTMLAttributes<HTMLDivElement>) { + const disabled = useContext(CellDisabledContext); + return <StyledLabel disabled={disabled} {...props} />; +} + +export function Switch(props: StandaloneSwitch['props']) { + const disabled = useContext(CellDisabledContext); + return <StandaloneSwitch disabled={disabled} {...props} />; +} + interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> { value?: string; validateValue?: (value: string) => boolean; @@ -104,16 +126,21 @@ export class Input extends React.Component<IInputProps, IInputState> { } = this.props; return ( - <StyledInput - type="text" - valid={validateValue?.(this.state.value)} - onChange={this.onChange} - onFocus={this.onFocus} - onBlur={this.onBlur} - onKeyPress={this.onKeyPress} - value={this.state.value} - {...otherProps} - /> + <CellDisabledContext.Consumer> + {(disabled) => ( + <StyledInput + type="text" + valid={validateValue?.(this.state.value)} + onChange={this.onChange} + onFocus={this.onFocus} + onBlur={this.onBlur} + onKeyPress={this.onKeyPress} + value={this.state.value} + disabled={disabled} + {...otherProps} + /> + )} + </CellDisabledContext.Consumer> ); } diff --git a/gui/src/renderer/components/CellStyles.tsx b/gui/src/renderer/components/CellStyles.tsx index 40b27abbb0..9ae67f733c 100644 --- a/gui/src/renderer/components/CellStyles.tsx +++ b/gui/src/renderer/components/CellStyles.tsx @@ -51,7 +51,7 @@ export const StyledCellButton = styled.button({}, (props: IStyledCellButtonProps }, })); -export const StyledLabel = styled.div({ +export const StyledLabel = styled.div({}, (props: { disabled: boolean }) => ({ margin: '14px 0 14px 8px', flex: 1, fontFamily: 'DINPro', @@ -59,9 +59,9 @@ export const StyledLabel = styled.div({ fontWeight: 900, lineHeight: '26px', letterSpacing: -0.2, - color: colors.white, + color: props.disabled ? colors.white40 : colors.white, textAlign: 'left', -}); +})); export const StyledSubText = styled.span({ color: colors.white60, diff --git a/gui/src/renderer/components/Preferences.tsx b/gui/src/renderer/components/Preferences.tsx index eaf695be84..a7d28e145d 100644 --- a/gui/src/renderer/components/Preferences.tsx +++ b/gui/src/renderer/components/Preferences.tsx @@ -19,6 +19,7 @@ export interface IProps { autoConnect: boolean; allowLan: boolean; showBetaReleases: boolean; + isBeta: boolean; enableSystemNotifications: boolean; monochromaticIcon: boolean; startMinimized: boolean; @@ -165,7 +166,7 @@ export default class Preferences extends Component<IProps> { </React.Fragment> ) : undefined} - <Cell.Container> + <Cell.Container disabled={this.props.isBeta}> <Cell.Label> {messages.pgettext('preferences-view', 'Beta program')} </Cell.Label> @@ -176,10 +177,15 @@ export default class Preferences extends Component<IProps> { </Cell.Container> <Cell.Footer> <Cell.FooterText> - {messages.pgettext( - 'preferences-view', - 'Enable to get notified when new beta versions of the app are released.', - )} + {this.props.isBeta + ? messages.pgettext( + 'preferences-view', + 'This option is unavailable while using a beta version.', + ) + : messages.pgettext( + 'preferences-view', + 'Enable to get notified when new beta versions of the app are released.', + )} </Cell.FooterText> </Cell.Footer> </View> diff --git a/gui/src/renderer/components/Switch.tsx b/gui/src/renderer/components/Switch.tsx index 7a1555c761..5279181cda 100644 --- a/gui/src/renderer/components/Switch.tsx +++ b/gui/src/renderer/components/Switch.tsx @@ -6,6 +6,7 @@ interface IProps { isOn: boolean; onChange?: (isOn: boolean) => void; className?: string; + disabled?: boolean; } interface IState { @@ -15,30 +16,37 @@ interface IState { const PAN_DISTANCE = 10; -const SwitchContainer = styled.div({ +const SwitchContainer = styled.div({}, (props: { disabled: boolean }) => ({ position: 'relative', width: '52px', height: '32px', - borderColor: colors.white, + borderColor: props.disabled ? colors.white20 : colors.white80, borderWidth: '2px', borderStyle: 'solid', borderRadius: '16px', padding: '2px', -}); - -const Knob = styled.div({}, (props: { isOn: boolean; isPressed: boolean }) => ({ - position: 'absolute', - height: '24px', - borderRadius: '12px', - transition: 'all 200ms linear', - width: props.isPressed ? '28px' : '24px', - backgroundColor: props.isOn ? colors.green : colors.red, - // When enabled the button should be placed all the way to the right (100%) minus padding (2px). - left: props.isOn ? 'calc(100% - 2px)' : '2px', - // This moves the knob to the left making the right side aligned with the parent's right side. - transform: `translateX(${props.isOn ? '-100%' : '0'})`, })); +const Knob = styled.div({}, (props: { isOn: boolean; isPressed: boolean; disabled: boolean }) => { + let backgroundColor = props.isOn ? colors.green : colors.red; + if (props.disabled) { + backgroundColor = props.isOn ? colors.green40 : colors.red40; + } + + return { + position: 'absolute', + height: '24px', + borderRadius: '12px', + transition: 'all 200ms linear', + width: props.isPressed ? '28px' : '24px', + backgroundColor, + // When enabled the button should be placed all the way to the right (100%) minus padding (2px). + left: props.isOn ? 'calc(100% - 2px)' : '2px', + // This moves the knob to the left making the right side aligned with the parent's right side. + transform: `translateX(${props.isOn ? '-100%' : '0'})`, + }; +}); + export default class Switch extends React.Component<IProps, IState> { public state: IState = { isOn: this.props.isOn, @@ -74,8 +82,10 @@ export default class Switch extends React.Component<IProps, IState> { <SwitchContainer ref={this.containerRef} onClick={this.handleClick} + disabled={this.props.disabled ?? false} className={this.props.className}> <Knob + disabled={this.props.disabled ?? false} isOn={this.state.isOn} isPressed={this.state.isPressed} onMouseDown={this.handleMouseDown} @@ -85,6 +95,10 @@ export default class Switch extends React.Component<IProps, IState> { } private handleClick = () => { + if (this.props.disabled) { + return; + } + if (!this.changedDuringPan) { this.setState((state) => ({ isOn: !state.isOn }), this.notify); } @@ -94,6 +108,10 @@ export default class Switch extends React.Component<IProps, IState> { }; private handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => { + if (this.props.disabled) { + return; + } + this.isPanning = true; this.startPos = event.clientX; this.changedDuringPan = false; @@ -103,6 +121,10 @@ export default class Switch extends React.Component<IProps, IState> { }; private handleMouseUp = (event: MouseEvent) => { + if (this.props.disabled) { + return; + } + document.removeEventListener('mouseup', this.handleMouseUp); document.removeEventListener('mousemove', this.handleMouseMove); @@ -119,6 +141,10 @@ export default class Switch extends React.Component<IProps, IState> { }; private handleMouseMove = (event: MouseEvent) => { + if (this.props.disabled) { + return; + } + if (this.isPanning) { this.setState({ isPressed: true }); diff --git a/gui/src/renderer/containers/PreferencesPage.tsx b/gui/src/renderer/containers/PreferencesPage.tsx index 131d85a1ae..538c44dcfa 100644 --- a/gui/src/renderer/containers/PreferencesPage.tsx +++ b/gui/src/renderer/containers/PreferencesPage.tsx @@ -11,6 +11,7 @@ const mapStateToProps = (state: IReduxState) => ({ autoStart: state.settings.autoStart, allowLan: state.settings.allowLan, showBetaReleases: state.settings.showBetaReleases, + isBeta: state.version.currentIsBeta, autoConnect: state.settings.guiSettings.autoConnect, enableSystemNotifications: state.settings.guiSettings.enableSystemNotifications, monochromaticIcon: state.settings.guiSettings.monochromaticIcon, diff --git a/gui/src/renderer/redux/version/actions.ts b/gui/src/renderer/redux/version/actions.ts index eb8a81a43d..628bc2c39a 100644 --- a/gui/src/renderer/redux/version/actions.ts +++ b/gui/src/renderer/redux/version/actions.ts @@ -1,7 +1,7 @@ import { IAppVersionInfo } from '../../../shared/daemon-rpc-types'; interface IUpdateLatestActionPayload extends IAppVersionInfo { - nextUpgrade?: string; + nextUpgrade: string | null; } export interface IUpdateLatestAction { @@ -13,6 +13,7 @@ export interface IUpdateVersionAction { type: 'UPDATE_VERSION'; version: string; consistent: boolean; + currentIsBeta: boolean; } export type VersionAction = IUpdateLatestAction | IUpdateVersionAction; @@ -24,11 +25,16 @@ function updateLatest(latestInfo: IUpdateLatestActionPayload): IUpdateLatestActi }; } -function updateVersion(version: string, consistent: boolean): IUpdateVersionAction { +function updateVersion( + version: string, + consistent: boolean, + currentIsBeta: boolean, +): IUpdateVersionAction { return { type: 'UPDATE_VERSION', version, consistent, + currentIsBeta, }; } diff --git a/gui/src/renderer/redux/version/reducers.ts b/gui/src/renderer/redux/version/reducers.ts index 9932628e02..279b4ed6f4 100644 --- a/gui/src/renderer/redux/version/reducers.ts +++ b/gui/src/renderer/redux/version/reducers.ts @@ -3,18 +3,20 @@ import { ReduxAction } from '../store'; export interface IVersionReduxState { current: string; currentIsSupported: boolean; + currentIsBeta: boolean; latest?: string; latestStable?: string; - nextUpgrade?: string; + nextUpgrade: string | null; consistent: boolean; } const initialState: IVersionReduxState = { current: '', currentIsSupported: true, + currentIsBeta: false, latest: undefined, latestStable: undefined, - nextUpgrade: undefined, + nextUpgrade: null, consistent: true, }; @@ -34,6 +36,7 @@ export default function ( ...state, current: action.version, consistent: action.consistent, + currentIsBeta: action.currentIsBeta, }; default: |
