diff options
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/src/renderer/components/AdvancedSettings.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/Changelog.tsx | 14 | ||||
| -rw-r--r-- | gui/src/renderer/components/CustomDnsSettings.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountErrorView.tsx | 3 | ||||
| -rw-r--r-- | gui/src/renderer/components/Modal.tsx | 98 | ||||
| -rw-r--r-- | gui/src/renderer/components/OpenVPNSettings.tsx | 3 | ||||
| -rw-r--r-- | gui/src/renderer/components/Preferences.tsx | 29 | ||||
| -rw-r--r-- | gui/src/renderer/components/RedeemVoucher.tsx | 11 | ||||
| -rw-r--r-- | gui/src/renderer/components/SplitTunnelingSettings.tsx | 56 | ||||
| -rw-r--r-- | gui/src/renderer/components/Support.tsx | 15 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardSettings.tsx | 3 |
11 files changed, 160 insertions, 84 deletions
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx index 7d873ffca5..d811f04a06 100644 --- a/gui/src/renderer/components/AdvancedSettings.tsx +++ b/gui/src/renderer/components/AdvancedSettings.tsx @@ -187,8 +187,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> { </NavigationContainer> </SettingsContainer> - {this.state.showConfirmBlockWhenDisconnectedAlert && - this.renderConfirmBlockWhenDisconnectedAlert()} + {this.renderConfirmBlockWhenDisconnectedAlert()} </Layout> ); } @@ -221,6 +220,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> { private renderConfirmBlockWhenDisconnectedAlert = () => { return ( <ModalAlert + isOpen={this.state.showConfirmBlockWhenDisconnectedAlert} type={ModalAlertType.caution} buttons={[ <AppButton.RedButton key="confirm" onClick={this.confirmEnableBlockWhenDisconnected}> diff --git a/gui/src/renderer/components/Changelog.tsx b/gui/src/renderer/components/Changelog.tsx index 545ed6753b..039fe9bbc4 100644 --- a/gui/src/renderer/components/Changelog.tsx +++ b/gui/src/renderer/components/Changelog.tsx @@ -37,17 +37,15 @@ export function Changelog() { const { setDisplayedChangelog } = useAppContext(); - if ( - changelogDisplayedForVersion === currentVersion || - changelog.length === 0 || - window.env.development || - /-dev-[0-9a-f]{6}$/.test(currentVersion) - ) { - return null; - } + const visible = + changelogDisplayedForVersion !== currentVersion && + changelog.length > 0 && + !window.env.development && + !/-dev-[0-9a-f]{6}$/.test(currentVersion); return ( <ModalAlert + isOpen={visible} buttons={[ <AppButton.BlueButton key="close" onClick={setDisplayedChangelog}> { diff --git a/gui/src/renderer/components/CustomDnsSettings.tsx b/gui/src/renderer/components/CustomDnsSettings.tsx index bbecd4b3f2..287ed58a4b 100644 --- a/gui/src/renderer/components/CustomDnsSettings.tsx +++ b/gui/src/renderer/components/CustomDnsSettings.tsx @@ -270,7 +270,11 @@ export default function CustomDnsSettings() { </Cell.FooterText> </StyledCustomDnsFooter> - {confirmAction && <ConfirmationDialog confirm={confirm} abort={abortConfirmation} />} + <ConfirmationDialog + isOpen={confirmAction !== undefined} + confirm={confirm} + abort={abortConfirmation} + /> </> ); } @@ -391,6 +395,7 @@ function CellListItem(props: ICellListItemProps) { } interface IConfirmationDialogProps { + isOpen: boolean; confirm: () => void; abort: () => void; } @@ -398,6 +403,7 @@ interface IConfirmationDialogProps { function ConfirmationDialog(props: IConfirmationDialogProps) { return ( <ModalAlert + isOpen={props.isOpen} type={ModalAlertType.caution} buttons={[ <AppButton.RedButton key="confirm" onClick={props.confirm}> diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx index 6f29b3dad2..8b2439e406 100644 --- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx +++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx @@ -87,7 +87,7 @@ export default class ExpiredAccountErrorView extends React.Component< </AppButton.GreenButton> </StyledFooter> - {this.state.showBlockWhenDisconnectedAlert && this.renderBlockWhenDisconnectedAlert()} + {this.renderBlockWhenDisconnectedAlert()} </StyledContainer> </StyledCustomScrollbars> </Layout> @@ -192,6 +192,7 @@ export default class ExpiredAccountErrorView extends React.Component< private renderBlockWhenDisconnectedAlert() { return ( <ModalAlert + isOpen={this.state.showBlockWhenDisconnectedAlert} type={ModalAlertType.caution} buttons={[ <AppButton.BlueButton diff --git a/gui/src/renderer/components/Modal.tsx b/gui/src/renderer/components/Modal.tsx index 2713144e93..6b8a9a67dc 100644 --- a/gui/src/renderer/components/Modal.tsx +++ b/gui/src/renderer/components/Modal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import styled from 'styled-components'; import { colors } from '../../config.json'; @@ -20,8 +20,9 @@ const ModalContent = styled.div({ bottom: 0, }); -const ModalBackground = styled.div({ - backgroundColor: 'rgba(0,0,0,0.5)', +const ModalBackground = styled.div({}, (props: { visible: boolean }) => ({ + backgroundColor: props.visible ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)', + backdropFilter: props.visible ? 'blur(1.5px)' : '', position: 'absolute', display: 'flex', flexDirection: 'column', @@ -30,7 +31,10 @@ const ModalBackground = styled.div({ left: 0, right: 0, bottom: 0, -}); + transition: 'all 150ms ease-out', + pointerEvents: props.visible ? 'auto' : 'none', + zIndex: 2, +})); export const StyledModalContainer = styled.div({ position: 'relative', @@ -102,13 +106,26 @@ const ModalAlertContainer = styled.div({ padding: '26px 14px 14px', }); -const StyledModalAlert = styled.div({ - display: 'flex', - flexDirection: 'column', - backgroundColor: colors.darkBlue, - borderRadius: '11px', - padding: '16px 0 16px 16px', - maxHeight: '80vh', +const StyledModalAlert = styled.div({}, (props: { visible: boolean; closing: boolean }) => { + let transform = ''; + if (props.visible && props.closing) { + transform = 'scale(80%)'; + } else if (!props.visible) { + transform = 'translateY(10px) scale(98%)'; + } + + return { + display: 'flex', + flexDirection: 'column', + backgroundColor: colors.darkBlue, + borderRadius: '11px', + padding: '16px 0 16px 16px', + maxHeight: '80vh', + opacity: props.visible && !props.closing ? 1 : 0, + transform, + boxShadow: ' 0px 15px 35px 5px rgba(0,0,0,0.5)', + transition: 'all 150ms ease-out', + }; }); const StyledCustomScrollbars = styled(CustomScrollbars)({ @@ -137,16 +154,48 @@ interface IModalAlertProps { close?: () => void; } -export function ModalAlert(props: IModalAlertProps) { +export function ModalAlert(props: IModalAlertProps & { isOpen: boolean }) { + const { isOpen, ...otherProps } = props; const activeModalContext = useContext(ActiveModalContext); - return <ModalAlertWithContext {...activeModalContext} {...props} />; + const [closing, setClosing] = useState(false); + const prevIsOpen = useRef(isOpen); + + const onTransitionEnd = useCallback(() => setClosing(false), []); + useEffect(() => { + setClosing((closing) => closing || (prevIsOpen.current && !isOpen)); + prevIsOpen.current = isOpen; + }, [isOpen]); + + if (!prevIsOpen.current && !isOpen && !closing) { + return null; + } + + return ( + <ModalAlertImpl + {...activeModalContext} + {...otherProps} + closing={closing} + onTransitionEnd={onTransitionEnd} + /> + ); +} + +interface IModalAlertState { + visible: boolean; +} + +interface IModalAlertImplProps extends IModalAlertProps, IModalContext { + closing: boolean; + onTransitionEnd: () => void; } -class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalContext> { +class ModalAlertImpl extends React.Component<IModalAlertImplProps, IModalAlertState> { + public state = { visible: false }; + private element = document.createElement('div'); private modalRef = React.createRef<HTMLDivElement>(); - constructor(props: IModalAlertProps & IModalContext) { + constructor(props: IModalAlertImplProps) { super(props); if (document.activeElement) { @@ -164,6 +213,8 @@ class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalCon if (modalContainer) { modalContainer.appendChild(this.element); this.modalRef.current?.focus(); + + this.setState({ visible: true }); } else { log.error('Modal container not found when mounting modal'); } @@ -183,9 +234,16 @@ class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalCon private renderModal() { return ( - <ModalBackground> + <ModalBackground visible={this.state.visible && !this.props.closing}> <ModalAlertContainer> - <StyledModalAlert ref={this.modalRef} tabIndex={-1} role="dialog" aria-modal> + <StyledModalAlert + ref={this.modalRef} + tabIndex={-1} + role="dialog" + aria-modal + visible={this.state.visible} + closing={this.props.closing} + onTransitionEnd={this.onTransitionEnd}> <StyledCustomScrollbars> {this.props.type && ( <ModalAlertIcon>{this.renderTypeIcon(this.props.type)}</ModalAlertIcon> @@ -231,6 +289,12 @@ class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalCon this.props.close?.(); } }; + + private onTransitionEnd = (event: React.TransitionEvent<HTMLDivElement>) => { + if (event.target === this.modalRef.current) { + this.props.onTransitionEnd(); + } + }; } export const ModalMessage = styled.span(tinyText, { diff --git a/gui/src/renderer/components/OpenVPNSettings.tsx b/gui/src/renderer/components/OpenVPNSettings.tsx index b554eb2372..55157a9bfe 100644 --- a/gui/src/renderer/components/OpenVPNSettings.tsx +++ b/gui/src/renderer/components/OpenVPNSettings.tsx @@ -224,7 +224,7 @@ export default class OpenVpnSettings extends React.Component<IProps, IState> { </NavigationContainer> </SettingsContainer> - {this.state.showBridgeStateConfirmationDialog && this.renderBridgeStateConfirmation()} + {this.renderBridgeStateConfirmation()} </Layout> ); } @@ -326,6 +326,7 @@ export default class OpenVpnSettings extends React.Component<IProps, IState> { private renderBridgeStateConfirmation = () => { return ( <ModalAlert + isOpen={this.state.showBridgeStateConfirmationDialog} type={ModalAlertType.info} message={messages.gettext('This setting increases latency. Use only if needed.')} buttons={[ diff --git a/gui/src/renderer/components/Preferences.tsx b/gui/src/renderer/components/Preferences.tsx index f1c349c664..e3e4ccd270 100644 --- a/gui/src/renderer/components/Preferences.tsx +++ b/gui/src/renderer/components/Preferences.tsx @@ -351,21 +351,20 @@ export default class Preferences extends React.Component<IProps, IState> { </NavigationContainer> </StyledContainer> - {this.state.showKillSwitchInfo && ( - <ModalAlert - message={messages.pgettext( - 'preferences-view', - 'The app has a built in kill switch that is enabled by default and cannot be disabled. This is to prevent your traffic from leaking outside of the VPN tunnel if your network suddenly stops working or if the tunnel fails for any reason. Mullvad automatically protects your data until your connection is reestablished.', - )} - type={ModalAlertType.info} - buttons={[ - <AppButton.BlueButton key="back" onClick={this.hideKillSwitchInfo}> - {messages.gettext('Got it!')} - </AppButton.BlueButton>, - ]} - close={this.hideKillSwitchInfo} - /> - )} + <ModalAlert + isOpen={this.state.showKillSwitchInfo} + message={messages.pgettext( + 'preferences-view', + 'The app has a built in kill switch that is enabled by default and cannot be disabled. This is to prevent your traffic from leaking outside of the VPN tunnel if your network suddenly stops working or if the tunnel fails for any reason. Mullvad automatically protects your data until your connection is reestablished.', + )} + type={ModalAlertType.info} + buttons={[ + <AppButton.BlueButton key="back" onClick={this.hideKillSwitchInfo}> + {messages.gettext('Got it!')} + </AppButton.BlueButton>, + ]} + close={this.hideKillSwitchInfo} + /> </Layout> ); } diff --git a/gui/src/renderer/components/RedeemVoucher.tsx b/gui/src/renderer/components/RedeemVoucher.tsx index 7810112082..ef14ef77a9 100644 --- a/gui/src/renderer/components/RedeemVoucher.tsx +++ b/gui/src/renderer/components/RedeemVoucher.tsx @@ -207,6 +207,7 @@ export function RedeemVoucherSubmitButton() { } interface IRedeemVoucherAlertProps { + show: boolean; onClose?: () => void; } @@ -220,6 +221,7 @@ export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) { return ( <ModalAlert + isOpen={props.show} buttons={[ <AppButton.BlueButton key="gotit" onClick={props.onClose}> {messages.gettext('Got it!')} @@ -243,6 +245,7 @@ export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) { } else { return ( <ModalAlert + isOpen={props.show} buttons={[ <RedeemVoucherSubmitButton key="submit" />, <AppButton.BlueButton key="cancel" disabled={submitting} onClick={props.onClose}> @@ -273,11 +276,9 @@ export function RedeemVoucherButton(props: IRedeemVoucherButtonProps) { <AppButton.GreenButton onClick={onClick} className={props.className}> {messages.pgettext('redeem-voucher-alert', 'Redeem voucher')} </AppButton.GreenButton> - {showAlert && ( - <RedeemVoucherContainer> - <RedeemVoucherAlert onClose={onClose} /> - </RedeemVoucherContainer> - )} + <RedeemVoucherContainer> + <RedeemVoucherAlert show={showAlert} onClose={onClose} /> + </RedeemVoucherContainer> </> ); } diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx index 8f173cab2f..ace08cd694 100644 --- a/gui/src/renderer/components/SplitTunnelingSettings.tsx +++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx @@ -183,26 +183,25 @@ function LinuxSplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsProps {messages.pgettext('split-tunneling-view', 'Find another app')} </StyledBrowseButton> - {browseError && ( - <ModalAlert - type={ModalAlertType.warning} - iconColor={colors.red} - message={sprintf( - // TRANSLATORS: Error message showed in a dialog when an application failes to launch. - messages.pgettext( - 'split-tunneling-view', - 'Unable to launch selection. %(detailedErrorMessage)s', - ), - { detailedErrorMessage: browseError }, - )} - buttons={[ - <AppButton.BlueButton key="close" onClick={hideBrowseFailureDialog}> - {messages.gettext('Close')} - </AppButton.BlueButton>, - ]} - close={hideBrowseFailureDialog} - /> - )} + <ModalAlert + isOpen={browseError !== undefined} + type={ModalAlertType.warning} + iconColor={colors.red} + message={sprintf( + // TRANSLATORS: Error message showed in a dialog when an application failes to launch. + messages.pgettext( + 'split-tunneling-view', + 'Unable to launch selection. %(detailedErrorMessage)s', + ), + { detailedErrorMessage: browseError }, + )} + buttons={[ + <AppButton.BlueButton key="close" onClick={hideBrowseFailureDialog}> + {messages.gettext('Close')} + </AppButton.BlueButton>, + ]} + close={hideBrowseFailureDialog} + /> </> ); } @@ -279,15 +278,14 @@ function LinuxApplicationRow(props: ILinuxApplicationRowProps) { <StyledCellWarningIcon source="icon-alert" tintColor={warningColor} width={18} /> )} </StyledCellButton> - {showWarning && ( - <ModalAlert - type={ModalAlertType.warning} - iconColor={warningColor} - message={warningMessage} - buttons={warningDialogButtons} - close={hideWarningDialog} - /> - )} + <ModalAlert + isOpen={showWarning} + type={ModalAlertType.warning} + iconColor={warningColor} + message={warningMessage} + buttons={warningDialogButtons} + close={hideWarningDialog} + /> </> ); } diff --git a/gui/src/renderer/components/Support.tsx b/gui/src/renderer/components/Support.tsx index 16070a1531..b3b7a3e6cf 100644 --- a/gui/src/renderer/components/Support.tsx +++ b/gui/src/renderer/components/Support.tsx @@ -132,7 +132,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat }; public render() { - const { sendState, showOutdatedVersionWarning } = this.state; + const { sendState } = this.state; const header = ( <SettingsHeader> <HeaderTitle>{messages.pgettext('support-view', 'Report a problem')}</HeaderTitle> @@ -168,8 +168,8 @@ export default class Support extends React.Component<ISupportProps, ISupportStat {content} </StyledContentContainer> - {sendState === SendState.confirm && this.renderNoEmailDialog()} - {showOutdatedVersionWarning && this.renderOutdateVersionWarningDialog()} + {this.renderNoEmailDialog()} + {this.renderOutdateVersionWarningDialog()} </StyledContainer> </Layout> ); @@ -247,6 +247,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat ); return ( <ModalAlert + isOpen={this.state.sendState === SendState.confirm} type={ModalAlertType.warning} message={message} buttons={[ @@ -276,6 +277,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat ); return ( <ModalAlert + isOpen={this.state.showOutdatedVersionWarning} type={ModalAlertType.warning} message={message} buttons={[ @@ -301,7 +303,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat <AppButton.RedButton key="proceed" onClick={this.acknowledgeOutdateVersion}> {messages.pgettext('support-view', 'Continue anyway')} </AppButton.RedButton>, - <AppButton.BlueButton key="cancel" onClick={this.props.onClose}> + <AppButton.BlueButton key="cancel" onClick={this.outdatedVersionCancel}> {messages.gettext('Cancel')} </AppButton.BlueButton>, ]} @@ -310,6 +312,11 @@ export default class Support extends React.Component<ISupportProps, ISupportStat ); } + private outdatedVersionCancel = () => { + this.acknowledgeOutdateVersion(); + this.props.onClose(); + }; + private renderForm() { return ( <StyledContent> diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 123f4b3b42..7456db290d 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -256,7 +256,7 @@ export default class WireguardSettings extends React.Component<IProps, IState> { </NavigationContainer> </SettingsContainer> - {this.state.showMultihopConfirmationDialog && this.renderMultihopConfirmation()} + {this.renderMultihopConfirmation()} </Layout> ); } @@ -283,6 +283,7 @@ export default class WireguardSettings extends React.Component<IProps, IState> { private renderMultihopConfirmation = () => { return ( <ModalAlert + isOpen={this.state.showMultihopConfirmationDialog} type={ModalAlertType.info} message={ // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled. |
