summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-04-29 12:56:45 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-04-29 12:56:45 +0200
commitd6711a4f08c52c028a63d5336f2b3739454b4242 (patch)
tree0dc42e849a3662db17b5f6afd2875fafd6c106b3 /gui/src
parente4cb19e01dbf9ef620037a418cfa56a6ed391e2b (diff)
parentbf47a32afbd32ed218443fbb091fd2987b0c5b68 (diff)
downloadmullvadvpn-d6711a4f08c52c028a63d5336f2b3739454b4242.tar.xz
mullvadvpn-d6711a4f08c52c028a63d5336f2b3739454b4242.zip
Merge branch 'block-when-disconnected-confirm'
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/AdvancedSettings.tsx494
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx4
-rw-r--r--gui/src/renderer/components/Modal.tsx35
-rw-r--r--gui/src/renderer/components/NotificationArea.tsx2
-rw-r--r--gui/src/shared/scheduler.ts2
5 files changed, 306 insertions, 231 deletions
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx
index d5ffe6e041..2bcd0d44d8 100644
--- a/gui/src/renderer/components/AdvancedSettings.tsx
+++ b/gui/src/renderer/components/AdvancedSettings.tsx
@@ -5,8 +5,10 @@ import { BridgeState, RelayProtocol, TunnelProtocol } from '../../shared/daemon-
import { messages } from '../../shared/gettext';
import { WgKeyState } from '../redux/settings/reducers';
import styles from './AdvancedSettingsStyles';
+import * as AppButton from './AppButton';
import * as Cell from './Cell';
import { Container, Layout } from './Layout';
+import { ModalAlert, ModalAlertType, ModalContainer, ModalMessage } from './Modal';
import {
BackBarItem,
NavigationBar,
@@ -60,12 +62,20 @@ interface IProps {
onClose: () => void;
}
-export default class AdvancedSettings extends Component<IProps> {
+interface IState {
+ showConfirmBlockWhenDisconnectedAlert: boolean;
+}
+
+export default class AdvancedSettings extends Component<IProps, IState> {
private portItems: { [key in RelayProtocol]: Array<ISelectorItem<OptionalPort>> };
private protocolItems: Array<ISelectorItem<OptionalRelayProtocol>>;
private bridgeStateItems: Array<ISelectorItem<BridgeState>>;
private wireguardPortItems: Array<ISelectorItem<OptionalPort>>;
+ public state = {
+ showConfirmBlockWhenDisconnectedAlert: false,
+ };
+
constructor(props: IProps) {
super(props);
@@ -122,246 +132,247 @@ export default class AdvancedSettings extends Component<IProps> {
const hasWireguardKey = this.props.wireguardKeyState.type === 'key-set';
return (
- <Layout>
- <Container>
- <View style={styles.advanced_settings}>
- <NavigationContainer>
- <NavigationBar>
- <NavigationItems>
- <BackBarItem action={this.props.onClose}>
- {
- // TRANSLATORS: Back button in navigation bar
- messages.pgettext('navigation-bar', 'Settings')
- }
- </BackBarItem>
- <TitleBarItem>
- {
- // TRANSLATORS: Title label in navigation bar
- messages.pgettext('advanced-settings-nav', 'Advanced')
- }
- </TitleBarItem>
- </NavigationItems>
- </NavigationBar>
-
- <View style={styles.advanced_settings__container}>
- <NavigationScrollbars style={styles.advanced_settings__scrollview}>
- <SettingsHeader>
- <HeaderTitle>
- {messages.pgettext('advanced-settings-view', 'Advanced')}
- </HeaderTitle>
- </SettingsHeader>
-
- <Cell.Container>
- <Cell.Label>
- {messages.pgettext('advanced-settings-view', 'Enable IPv6')}
- </Cell.Label>
- <Cell.Switch isOn={this.props.enableIpv6} onChange={this.props.setEnableIpv6} />
- </Cell.Container>
- <Cell.Footer>
- <Cell.FooterText>
- {messages.pgettext(
- 'advanced-settings-view',
- 'Enable IPv6 communication through the tunnel.',
- )}
- </Cell.FooterText>
- </Cell.Footer>
+ <ModalContainer>
+ <Layout>
+ <Container>
+ <View style={styles.advanced_settings}>
+ <NavigationContainer>
+ <NavigationBar>
+ <NavigationItems>
+ <BackBarItem action={this.props.onClose}>
+ {
+ // TRANSLATORS: Back button in navigation bar
+ messages.pgettext('navigation-bar', 'Settings')
+ }
+ </BackBarItem>
+ <TitleBarItem>
+ {
+ // TRANSLATORS: Title label in navigation bar
+ messages.pgettext('advanced-settings-nav', 'Advanced')
+ }
+ </TitleBarItem>
+ </NavigationItems>
+ </NavigationBar>
- <Cell.Container>
- <Cell.Label textStyle={styles.advanced_settings__block_when_disconnected_label}>
- {messages.pgettext('advanced-settings-view', 'Block when disconnected')}
- </Cell.Label>
- <Cell.Switch
- isOn={this.props.blockWhenDisconnected}
- onChange={this.props.setBlockWhenDisconnected}
- />
- </Cell.Container>
- <Cell.Footer>
- <Cell.FooterText>
- {messages.pgettext(
- 'advanced-settings-view',
- "Unless connected to Mullvad, this setting will completely block your internet, even when you've disconnected or quit the app.",
- )}
- </Cell.FooterText>
+ <View style={styles.advanced_settings__container}>
+ <NavigationScrollbars style={styles.advanced_settings__scrollview}>
+ <SettingsHeader>
+ <HeaderTitle>
+ {messages.pgettext('advanced-settings-view', 'Advanced')}
+ </HeaderTitle>
+ </SettingsHeader>
- {this.props.blockWhenDisconnected && (
- <Cell.FooterBoldText
- style={styles.advanced_settings__cell_footer_internet_warning_label}>
+ <Cell.Container>
+ <Cell.Label>
+ {messages.pgettext('advanced-settings-view', 'Enable IPv6')}
+ </Cell.Label>
+ <Cell.Switch
+ isOn={this.props.enableIpv6}
+ onChange={this.props.setEnableIpv6}
+ />
+ </Cell.Container>
+ <Cell.Footer>
+ <Cell.FooterText>
{messages.pgettext(
'advanced-settings-view',
- "Warning: Your internet won't work without a VPN connection, even when you've quit the app.",
+ 'Enable IPv6 communication through the tunnel.',
)}
- </Cell.FooterBoldText>
- )}
- </Cell.Footer>
+ </Cell.FooterText>
+ </Cell.Footer>
- <View
- style={[
- styles.advanced_settings__content,
- styles.advanced_settings__tunnel_protocol,
- ]}>
- <Selector
- title={messages.pgettext('advanced-settings-view', 'Tunnel protocol')}
- values={this.tunnelProtocolItems(hasWireguardKey)}
- value={this.props.tunnelProtocol}
- onSelect={this.onSelectTunnelProtocol}
- style={styles.advanced_settings__tunnel_protocol_selector}
- />
- {!hasWireguardKey && (
- <Text style={styles.advanced_settings__wg_no_key}>
+ <Cell.Container>
+ <Cell.Label
+ textStyle={styles.advanced_settings__block_when_disconnected_label}>
+ {messages.pgettext('advanced-settings-view', 'Always require VPN')}
+ </Cell.Label>
+ <Cell.Switch
+ isOn={this.props.blockWhenDisconnected}
+ onChange={this.setBlockWhenDisconnected}
+ />
+ </Cell.Container>
+ <Cell.Footer>
+ <Cell.FooterText>
{messages.pgettext(
'advanced-settings-view',
- 'To enable WireGuard, generate a key under the "WireGuard key" setting below.',
+ 'If you disconnect or quit the app, this setting will block your internet.',
)}
- </Text>
- )}
- </View>
+ </Cell.FooterText>
+ </Cell.Footer>
- {this.props.tunnelProtocol !== 'wireguard' ? (
- <View style={styles.advanced_settings__content}>
+ <View
+ style={[
+ styles.advanced_settings__content,
+ styles.advanced_settings__tunnel_protocol,
+ ]}>
<Selector
- title={messages.pgettext(
- 'advanced-settings-view',
- 'OpenVPN transport protocol',
- )}
- values={this.protocolItems}
- value={this.props.openvpn.protocol}
- onSelect={this.onSelectOpenvpnProtocol}
+ title={messages.pgettext('advanced-settings-view', 'Tunnel protocol')}
+ values={this.tunnelProtocolItems(hasWireguardKey)}
+ value={this.props.tunnelProtocol}
+ onSelect={this.onSelectTunnelProtocol}
+ style={styles.advanced_settings__tunnel_protocol_selector}
/>
+ {!hasWireguardKey && (
+ <Text style={styles.advanced_settings__wg_no_key}>
+ {messages.pgettext(
+ 'advanced-settings-view',
+ 'To enable WireGuard, generate a key under the "WireGuard key" setting below.',
+ )}
+ </Text>
+ )}
+ </View>
- {this.props.openvpn.protocol ? (
+ {this.props.tunnelProtocol !== 'wireguard' ? (
+ <View style={styles.advanced_settings__content}>
<Selector
- title={sprintf(
- // TRANSLATORS: The title for the port selector section.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(portType)s - a selected protocol (either TCP or UDP)
- messages.pgettext(
- 'advanced-settings-view',
- 'OpenVPN %(portType)s port',
- ),
- {
- portType: this.props.openvpn.protocol.toUpperCase(),
- },
+ title={messages.pgettext(
+ 'advanced-settings-view',
+ 'OpenVPN transport protocol',
)}
- values={this.portItems[this.props.openvpn.protocol]}
- value={this.props.openvpn.port}
- onSelect={this.onSelectOpenVpnPort}
+ values={this.protocolItems}
+ value={this.props.openvpn.protocol}
+ onSelect={this.onSelectOpenvpnProtocol}
/>
- ) : undefined}
- </View>
- ) : undefined}
- {this.props.tunnelProtocol === 'wireguard' ? (
- <View style={styles.advanced_settings__content}>
- <Selector
- // TRANSLATORS: The title for the shadowsocks bridge selector section.
- title={messages.pgettext('advanced-settings-view', 'WireGuard port')}
- values={this.wireguardPortItems}
- value={this.props.wireguard.port}
- onSelect={this.onSelectWireguardPort}
- />
- </View>
- ) : undefined}
-
- <Selector
- title={
- // TRANSLATORS: The title for the shadowsocks bridge selector section.
- messages.pgettext('advanced-settings-view', 'Bridge mode')
- }
- values={this.bridgeStateItems}
- value={this.props.bridgeState}
- onSelect={this.onSelectBridgeState}
- />
+ {this.props.openvpn.protocol ? (
+ <Selector
+ title={sprintf(
+ // TRANSLATORS: The title for the port selector section.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(portType)s - a selected protocol (either TCP or UDP)
+ messages.pgettext(
+ 'advanced-settings-view',
+ 'OpenVPN %(portType)s port',
+ ),
+ {
+ portType: this.props.openvpn.protocol.toUpperCase(),
+ },
+ )}
+ values={this.portItems[this.props.openvpn.protocol]}
+ value={this.props.openvpn.port}
+ onSelect={this.onSelectOpenVpnPort}
+ />
+ ) : undefined}
+ </View>
+ ) : undefined}
- <Cell.Container>
- <Cell.Label>
- {messages.pgettext('advanced-settings-view', 'OpenVPN Mssfix')}
- </Cell.Label>
- <Cell.InputFrame style={styles.advanced_settings__input_frame}>
- <Cell.AutoSizingTextInputContainer>
- <Cell.Input
- value={this.props.mssfix ? this.props.mssfix.toString() : ''}
- keyboardType={'numeric'}
- maxLength={4}
- placeholder={messages.pgettext('advanced-settings-view', 'Default')}
- onSubmit={this.onMssfixSubmit}
- validateValue={AdvancedSettings.mssfixIsValid}
- submitOnBlur={true}
- modifyValue={AdvancedSettings.removeNonNumericCharacters}
+ {this.props.tunnelProtocol === 'wireguard' ? (
+ <View style={styles.advanced_settings__content}>
+ <Selector
+ // TRANSLATORS: The title for the shadowsocks bridge selector section.
+ title={messages.pgettext('advanced-settings-view', 'WireGuard port')}
+ values={this.wireguardPortItems}
+ value={this.props.wireguard.port}
+ onSelect={this.onSelectWireguardPort}
/>
- </Cell.AutoSizingTextInputContainer>
- </Cell.InputFrame>
- </Cell.Container>
- <Cell.Footer>
- <Cell.FooterText>
- {sprintf(
- // TRANSLATORS: The hint displayed below the Mssfix input field.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(max)d - the maximum possible mssfix value
- // TRANSLATORS: %(min)d - the minimum possible mssfix value
- messages.pgettext(
- 'advanced-settings-view',
- 'Set OpenVPN MSS value. Valid range: %(min)d - %(max)d.',
- ),
- {
- min: MIN_MSSFIX_VALUE,
- max: MAX_MSSFIX_VALUE,
- },
- )}
- </Cell.FooterText>
- </Cell.Footer>
+ </View>
+ ) : undefined}
- <Cell.Container>
- <Cell.Label>
- {messages.pgettext('advanced-settings-view', 'WireGuard MTU')}
- </Cell.Label>
- <Cell.InputFrame style={styles.advanced_settings__input_frame}>
- <Cell.AutoSizingTextInputContainer>
- <Cell.Input
- value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''}
- keyboardType={'numeric'}
- maxLength={4}
- placeholder={messages.pgettext('advanced-settings-view', 'Default')}
- onSubmit={this.onWireguardMtuSubmit}
- validateValue={AdvancedSettings.wireguarMtuIsValid}
- submitOnBlur={true}
- modifyValue={AdvancedSettings.removeNonNumericCharacters}
- />
- </Cell.AutoSizingTextInputContainer>
- </Cell.InputFrame>
- </Cell.Container>
- <Cell.Footer>
- <Cell.FooterText>
- {sprintf(
- // TRANSLATORS: The hint displayed below the WireGuard MTU input field.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value
- // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value
- messages.pgettext(
- 'advanced-settings-view',
- 'Set WireGuard MTU value. Valid range: %(min)d - %(max)d.',
- ),
- {
- min: MIN_WIREGUARD_MTU_VALUE,
- max: MAX_WIREGUARD_MTU_VALUE,
- },
- )}
- </Cell.FooterText>
- </Cell.Footer>
+ <Selector
+ title={
+ // TRANSLATORS: The title for the shadowsocks bridge selector section.
+ messages.pgettext('advanced-settings-view', 'Bridge mode')
+ }
+ values={this.bridgeStateItems}
+ value={this.props.bridgeState}
+ onSelect={this.onSelectBridgeState}
+ />
- <View style={styles.advanced_settings__wgkeys_cell}>
- <Cell.CellButton onPress={this.props.onViewWireguardKeys}>
+ <Cell.Container>
<Cell.Label>
- {messages.pgettext('advanced-settings-view', 'WireGuard key')}
+ {messages.pgettext('advanced-settings-view', 'OpenVPN Mssfix')}
</Cell.Label>
- <Cell.Icon height={12} width={7} source="icon-chevron" />
- </Cell.CellButton>
- </View>
- </NavigationScrollbars>
- </View>
- </NavigationContainer>
- </View>
- </Container>
- </Layout>
+ <Cell.InputFrame style={styles.advanced_settings__input_frame}>
+ <Cell.AutoSizingTextInputContainer>
+ <Cell.Input
+ value={this.props.mssfix ? this.props.mssfix.toString() : ''}
+ keyboardType={'numeric'}
+ maxLength={4}
+ placeholder={messages.pgettext('advanced-settings-view', 'Default')}
+ onSubmit={this.onMssfixSubmit}
+ validateValue={AdvancedSettings.mssfixIsValid}
+ submitOnBlur={true}
+ modifyValue={AdvancedSettings.removeNonNumericCharacters}
+ />
+ </Cell.AutoSizingTextInputContainer>
+ </Cell.InputFrame>
+ </Cell.Container>
+ <Cell.Footer>
+ <Cell.FooterText>
+ {sprintf(
+ // TRANSLATORS: The hint displayed below the Mssfix input field.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(max)d - the maximum possible mssfix value
+ // TRANSLATORS: %(min)d - the minimum possible mssfix value
+ messages.pgettext(
+ 'advanced-settings-view',
+ 'Set OpenVPN MSS value. Valid range: %(min)d - %(max)d.',
+ ),
+ {
+ min: MIN_MSSFIX_VALUE,
+ max: MAX_MSSFIX_VALUE,
+ },
+ )}
+ </Cell.FooterText>
+ </Cell.Footer>
+
+ <Cell.Container>
+ <Cell.Label>
+ {messages.pgettext('advanced-settings-view', 'WireGuard MTU')}
+ </Cell.Label>
+ <Cell.InputFrame style={styles.advanced_settings__input_frame}>
+ <Cell.AutoSizingTextInputContainer>
+ <Cell.Input
+ value={
+ this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''
+ }
+ keyboardType={'numeric'}
+ maxLength={4}
+ placeholder={messages.pgettext('advanced-settings-view', 'Default')}
+ onSubmit={this.onWireguardMtuSubmit}
+ validateValue={AdvancedSettings.wireguarMtuIsValid}
+ submitOnBlur={true}
+ modifyValue={AdvancedSettings.removeNonNumericCharacters}
+ />
+ </Cell.AutoSizingTextInputContainer>
+ </Cell.InputFrame>
+ </Cell.Container>
+ <Cell.Footer>
+ <Cell.FooterText>
+ {sprintf(
+ // TRANSLATORS: The hint displayed below the WireGuard MTU input field.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value
+ // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value
+ messages.pgettext(
+ 'advanced-settings-view',
+ 'Set WireGuard MTU value. Valid range: %(min)d - %(max)d.',
+ ),
+ {
+ min: MIN_WIREGUARD_MTU_VALUE,
+ max: MAX_WIREGUARD_MTU_VALUE,
+ },
+ )}
+ </Cell.FooterText>
+ </Cell.Footer>
+
+ <View style={styles.advanced_settings__wgkeys_cell}>
+ <Cell.CellButton onPress={this.props.onViewWireguardKeys}>
+ <Cell.Label>
+ {messages.pgettext('advanced-settings-view', 'WireGuard key')}
+ </Cell.Label>
+ <Cell.Icon height={12} width={7} source="icon-chevron" />
+ </Cell.CellButton>
+ </View>
+ </NavigationScrollbars>
+ </View>
+ </NavigationContainer>
+ </View>
+ </Container>
+ </Layout>
+
+ {this.state.showConfirmBlockWhenDisconnectedAlert &&
+ this.renderConfirmBlockWhenDisconnectedAlert()}
+ </ModalContainer>
);
}
@@ -390,6 +401,53 @@ export default class AdvancedSettings extends Component<IProps> {
];
};
+ private renderConfirmBlockWhenDisconnectedAlert = () => {
+ return (
+ <ModalAlert
+ type={ModalAlertType.Info}
+ buttons={[
+ <AppButton.RedButton key="confirm" onPress={this.confirmEnableBlockWhenDisconnected}>
+ {messages.pgettext('advanced-settings-view', 'Enable anyway')}
+ </AppButton.RedButton>,
+ <AppButton.BlueButton key="back" onPress={this.hideConfirmBlockWhenDisconnectedAlert}>
+ {messages.pgettext('advanced-settings-view', 'Back')}
+ </AppButton.BlueButton>,
+ ]}>
+ <ModalMessage>
+ {messages.pgettext(
+ 'advanced-settings-view',
+ 'Attention: enabling this will always require a Mullvad VPN connection in order to reach the internet.',
+ )}
+ </ModalMessage>
+ <ModalMessage>
+ {messages.pgettext(
+ 'advanced-settings-view',
+ 'The app’s built-in kill switch is always on. This setting will additionally block the internet if clicking Disconnect or Quit.',
+ )}
+ </ModalMessage>
+ </ModalAlert>
+ );
+ };
+
+ private setBlockWhenDisconnected = (newValue: boolean) => {
+ if (newValue) {
+ this.props.setBlockWhenDisconnected(true);
+ this.setState({ showConfirmBlockWhenDisconnectedAlert: true });
+ } else {
+ this.props.setBlockWhenDisconnected(false);
+ }
+ };
+
+ private hideConfirmBlockWhenDisconnectedAlert = () => {
+ this.props.setBlockWhenDisconnected(false);
+ this.setState({ showConfirmBlockWhenDisconnectedAlert: false });
+ };
+
+ private confirmEnableBlockWhenDisconnected = () => {
+ this.props.setBlockWhenDisconnected(true);
+ this.setState({ showConfirmBlockWhenDisconnectedAlert: false });
+ };
+
private onSelectTunnelProtocol = (protocol?: TunnelProtocol) => {
this.props.setTunnelProtocol(protocol);
};
diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
index b2ae12f9b5..6835d37782 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
@@ -210,7 +210,7 @@ export default class ExpiredAccountErrorView extends Component<
<Text style={styles.fieldLabel}>
{messages.pgettext(
'connect-view',
- 'You need to disable “Block when disconnected” in order to access the Internet to add time.',
+ 'You need to disable “Always require VPN” in order to access the Internet to add time.',
)}
</Text>
<Text style={styles.fieldLabel}>
@@ -220,7 +220,7 @@ export default class ExpiredAccountErrorView extends Component<
)}
</Text>
<Cell.Container>
- <Cell.Label>{messages.pgettext('connect-view', 'Block when disconnected')}</Cell.Label>
+ <Cell.Label>{messages.pgettext('connect-view', 'Always require VPN')}</Cell.Label>
<Cell.Switch
isOn={this.props.blockWhenDisconnected}
onChange={this.props.setBlockWhenDisconnected}
diff --git a/gui/src/renderer/components/Modal.tsx b/gui/src/renderer/components/Modal.tsx
index c00a1be466..5a1db8f673 100644
--- a/gui/src/renderer/components/Modal.tsx
+++ b/gui/src/renderer/components/Modal.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import ReactDOM from 'react-dom';
import { Component, Styles, Text, View } from 'reactxp';
import { colors } from '../../config.json';
+import { Scheduler } from '../../shared/scheduler';
import ImageView from './ImageView';
const MODAL_CONTAINER_ID = 'modalContainer';
@@ -10,8 +11,9 @@ const styles = {
modalAlertBackground: Styles.createViewStyle({
flex: 1,
justifyContent: 'center',
- paddingLeft: 14,
- paddingRight: 14,
+ paddingHorizontal: 14,
+ paddingTop: 26,
+ paddingBottom: 14,
}),
modalAlert: Styles.createViewStyle({
backgroundColor: colors.darkBlue,
@@ -20,15 +22,15 @@ const styles = {
}),
modalAlertIcon: Styles.createViewStyle({
alignItems: 'center',
- marginBottom: 12,
- marginTop: 4,
+ marginTop: 8,
}),
modalAlertMessage: Styles.createTextStyle({
fontFamily: 'Open Sans',
- fontSize: 16,
+ fontSize: 14,
fontWeight: '500',
lineHeight: 20,
color: colors.white80,
+ marginTop: 16,
}),
modalAlertButtonContainer: Styles.createViewStyle({
marginTop: 16,
@@ -107,18 +109,27 @@ interface IModalAlertProps {
export class ModalAlert extends Component<IModalAlertProps> {
private element = document.createElement('div');
private modalContainer?: Element;
+ private appendScheduler = new Scheduler();
public componentDidMount() {
const modalContainer = document.getElementById(MODAL_CONTAINER_ID);
if (modalContainer) {
this.modalContainer = modalContainer;
- modalContainer.appendChild(this.element);
+
+ // Mounting the container element immediately results in a graphical issue with the dialog
+ // first rendering with the wrong proportions and then changing to the correct proportions.
+ // Postponing it to the next event cycle solves this issue.
+ this.appendScheduler.schedule(() => {
+ modalContainer.appendChild(this.element);
+ });
} else {
throw Error('Modal container not found when mounting modal');
}
}
public componentWillUnmount() {
+ this.appendScheduler.cancel();
+
if (this.modalContainer) {
this.modalContainer.removeChild(this.element);
}
@@ -136,9 +147,7 @@ export class ModalAlert extends Component<IModalAlertProps> {
{this.props.type && (
<View style={styles.modalAlertIcon}>{this.renderTypeIcon(this.props.type)}</View>
)}
- {this.props.message && (
- <Text style={styles.modalAlertMessage}>{this.props.message}</Text>
- )}
+ {this.props.message && <ModalMessage>{this.props.message}</ModalMessage>}
{this.props.children}
{this.props.buttons.map((button, index) => (
<View key={index} style={styles.modalAlertButtonContainer}>
@@ -167,3 +176,11 @@ export class ModalAlert extends Component<IModalAlertProps> {
return <ImageView height={44} width={44} source={source} tintColor={color} />;
}
}
+
+interface IModalMessageProps {
+ children?: string;
+}
+
+export function ModalMessage(props: IModalMessageProps) {
+ return <Text style={styles.modalAlertMessage}>{props.children}</Text>;
+}
diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx
index 812e686f69..4709a5100d 100644
--- a/gui/src/renderer/components/NotificationArea.tsx
+++ b/gui/src/renderer/components/NotificationArea.tsx
@@ -154,7 +154,7 @@ export default class NotificationArea extends Component<IProps, State> {
return {
visible: true,
type: 'blocking',
- reason: '',
+ reason: messages.pgettext('in-app-notifications', '“Always require VPN” is enabled.'),
};
}
// fallthrough
diff --git a/gui/src/shared/scheduler.ts b/gui/src/shared/scheduler.ts
index 902254c567..103fe9b584 100644
--- a/gui/src/shared/scheduler.ts
+++ b/gui/src/shared/scheduler.ts
@@ -1,7 +1,7 @@
export class Scheduler {
private timer?: NodeJS.Timeout;
- public schedule(action: () => void, delay: number) {
+ public schedule(action: () => void, delay = 0) {
this.cancel();
this.timer = global.setTimeout(action, delay);
}