summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-05-25 12:59:26 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-05-25 12:59:26 +0200
commit119510c7356ab4c6858a01d330d3b1452026c3ab (patch)
tree3fbf3d7ca7da56f3be4431a1117b1da0dc7d6b3f /gui/src
parent4374667d5b8b76a90dc2a2a5c466102bb9c84add (diff)
parent80a9394c56888af208933566c76f9fb99fe7218e (diff)
downloadmullvadvpn-119510c7356ab4c6858a01d330d3b1452026c3ab.tar.xz
mullvadvpn-119510c7356ab4c6858a01d330d3b1452026c3ab.zip
Merge branch 'improve-beta-program-gui'
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/index.ts22
-rw-r--r--gui/src/renderer/app.tsx6
-rw-r--r--gui/src/renderer/components/Cell.tsx57
-rw-r--r--gui/src/renderer/components/CellStyles.tsx6
-rw-r--r--gui/src/renderer/components/Preferences.tsx16
-rw-r--r--gui/src/renderer/components/Switch.tsx56
-rw-r--r--gui/src/renderer/containers/PreferencesPage.tsx1
-rw-r--r--gui/src/renderer/redux/version/actions.ts10
-rw-r--r--gui/src/renderer/redux/version/reducers.ts7
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: