Skip to content

Commit

Permalink
Merge branch 'add-spinner-while-fda-check-is-being-performed-des-1554'
Browse files Browse the repository at this point in the history
  • Loading branch information
hulthe committed Dec 18, 2024
2 parents 7cb8b8c + 64828f6 commit 2c47294
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 66 deletions.
4 changes: 4 additions & 0 deletions desktop/packages/mullvad-vpn/locales/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,10 @@ msgctxt "tray-icon-tooltip"
msgid "Connecting. %(location)s"
msgstr ""

msgctxt "troubleshoot"
msgid "Disable split tunneling"
msgstr ""

msgctxt "troubleshoot"
msgid "Enable “Full Disk Access” for “Mullvad VPN” in the macOS system settings."
msgstr ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
ErrorNotificationProvider,
InAppNotificationAction,
InAppNotificationProvider,
InAppNotificationTroubleshootInfo,
InconsistentVersionNotificationProvider,
ReconnectingNotificationProvider,
UnsupportedVersionNotificationProvider,
Expand Down Expand Up @@ -47,7 +46,7 @@ interface IProps {
}

export default function NotificationArea(props: IProps) {
const { showFullDiskAccessSettings } = useAppContext();
const { showFullDiskAccessSettings, reconnectTunnel } = useAppContext();

const account = useSelector((state: IReduxState) => state.account);
const locale = useSelector((state: IReduxState) => state.userInterface.locale);
Expand Down Expand Up @@ -75,6 +74,15 @@ export default function NotificationArea(props: IProps) {
setDisplayedChangelog();
}, [setDisplayedChangelog]);

const [isModalOpen, setIsModalOpen] = useState(false);

const { setSplitTunnelingState } = useAppContext();
const disableSplitTunneling = useCallback(async () => {
setIsModalOpen(false);
await setSplitTunnelingState(false);
await reconnectTunnel();
}, [reconnectTunnel, setSplitTunnelingState]);

const notificationProviders: InAppNotificationProvider[] = [
new ConnectingNotificationProvider({ tunnelState }),
new ReconnectingNotificationProvider(tunnelState),
Expand All @@ -83,7 +91,13 @@ export default function NotificationArea(props: IProps) {
blockWhenDisconnected,
hasExcludedApps,
}),
new ErrorNotificationProvider({ tunnelState, hasExcludedApps, showFullDiskAccessSettings }),

new ErrorNotificationProvider({
tunnelState,
hasExcludedApps,
showFullDiskAccessSettings,
disableSplitTunneling,
}),
new InconsistentVersionNotificationProvider({ consistent: version.consistent }),
new UnsupportedVersionNotificationProvider(version),
];
Expand Down Expand Up @@ -140,7 +154,13 @@ export default function NotificationArea(props: IProps) {
)}
</NotificationSubtitle>
</NotificationContent>
{notification.action && <NotificationActionWrapper action={notification.action} />}
{notification.action && (
<NotificationActionWrapper
action={notification.action}
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
/>
)}
</NotificationBanner>
);
} else {
Expand All @@ -153,46 +173,51 @@ export default function NotificationArea(props: IProps) {
return <NotificationBanner className={props.className} aria-hidden={true} />;
}

interface INotificationActionWrapperProps {
interface NotificationActionWrapperProps {
action: InAppNotificationAction;
isModalOpen: boolean;
setIsModalOpen: (isOpen: boolean) => void;
}

function NotificationActionWrapper(props: INotificationActionWrapperProps) {
function NotificationActionWrapper({
action,
isModalOpen,
setIsModalOpen,
}: NotificationActionWrapperProps) {
const { push } = useHistory();
const { openLinkWithAuth, openUrl } = useAppContext();
const [troubleshootInfo, setTroubleshootInfo] = useState<InAppNotificationTroubleshootInfo>();

const closeTroubleshootModal = useCallback(() => setIsModalOpen(false), [setIsModalOpen]);

const handleClick = useCallback(() => {
if (props.action) {
switch (props.action.type) {
if (action) {
switch (action.type) {
case 'open-url':
if (props.action.withAuth) {
return openLinkWithAuth(props.action.url);
if (action.withAuth) {
return openLinkWithAuth(action.url);
} else {
return openUrl(props.action.url);
return openUrl(action.url);
}
case 'troubleshoot-dialog':
setTroubleshootInfo(props.action.troubleshoot);
setIsModalOpen(true);
break;
case 'close':
props.action.close();
action.close();
break;
}
}

return Promise.resolve();
}, [openLinkWithAuth, openUrl, props.action]);
}, [action, setIsModalOpen, openLinkWithAuth, openUrl]);

const goToProblemReport = useCallback(() => {
setTroubleshootInfo(undefined);
closeTroubleshootModal();
push(RoutePath.problemReport, { transition: transitions.show });
}, [push]);

const closeTroubleshootInfo = useCallback(() => setTroubleshootInfo(undefined), []);
}, [closeTroubleshootModal, push]);

let actionComponent: React.ReactElement | undefined;
if (props.action) {
switch (props.action.type) {
if (action) {
switch (action.type) {
case 'open-url':
actionComponent = <NotificationOpenLinkAction onClick={handleClick} />;
break;
Expand All @@ -208,7 +233,11 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) {
}
}

const problemReportButton = troubleshootInfo?.buttons ? (
if (action.type !== 'troubleshoot-dialog') {
return <NotificationActions>{actionComponent}</NotificationActions>;
}

const problemReportButton = action.troubleshoot?.buttons ? (
<AppButton.BlueButton key="problem-report" onClick={goToProblemReport}>
{messages.pgettext('in-app-notifications', 'Send problem report')}
</AppButton.BlueButton>
Expand All @@ -220,17 +249,32 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) {

let buttons = [
problemReportButton,
<AppButton.BlueButton key="back" onClick={closeTroubleshootInfo}>
<AppButton.BlueButton key="back" onClick={closeTroubleshootModal}>
{messages.gettext('Back')}
</AppButton.BlueButton>,
];

if (troubleshootInfo?.buttons) {
const actionButtons = troubleshootInfo.buttons.map((button) => (
<AppButton.GreenButton key={button.label} onClick={button.action}>
{button.label}
</AppButton.GreenButton>
));
if (action.troubleshoot?.buttons) {
const actionButtons = action.troubleshoot.buttons.map(({ variant, label, action }) => {
if (variant === 'success')
return (
<AppButton.GreenButton key={label} onClick={action}>
{label}
</AppButton.GreenButton>
);
else if (variant === 'destructive')
return (
<AppButton.RedButton key={label} onClick={action}>
{label}
</AppButton.RedButton>
);
else
return (
<AppButton.BlueButton key={label} onClick={action}>
{label}
</AppButton.BlueButton>
);
});

buttons = actionButtons.concat(buttons);
}
Expand All @@ -239,14 +283,14 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) {
<>
<NotificationActions>{actionComponent}</NotificationActions>
<ModalAlert
isOpen={troubleshootInfo !== undefined}
isOpen={isModalOpen}
type={ModalAlertType.info}
buttons={buttons}
close={closeTroubleshootInfo}>
<ModalMessage>{troubleshootInfo?.details}</ModalMessage>
close={closeTroubleshootModal}>
<ModalMessage>{action.troubleshoot?.details}</ModalMessage>
<ModalMessage>
<ModalMessageList>
{troubleshootInfo?.steps.map((step) => <li key={step}>{step}</li>)}
{action.troubleshoot?.steps.map((step) => <li key={step}>{step}</li>)}
</ModalMessageList>
</ModalMessage>
<ModalMessage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,10 @@ export default function SplitTunneling() {
</NavigationBar>

<StyledNavigationScrollbars ref={scrollbarsRef}>
<Flex $flexDirection="column" $flex={1}>
<PlatformSpecificSplitTunnelingSettings
setBrowsing={setBrowsing}
scrollToTop={scrollToTop}
/>
</Flex>
<PlatformSpecificSplitTunnelingSettings
setBrowsing={setBrowsing}
scrollToTop={scrollToTop}
/>
</StyledNavigationScrollbars>
</NavigationContainer>
</SettingsContainer>
Expand Down Expand Up @@ -315,25 +313,26 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
needFullDiskPermissions,
setSplitTunnelingState,
} = useAppContext();
const splitTunnelingEnabledValue = useSelector(
(state: IReduxState) => state.settings.splitTunneling,
);
const splitTunnelingEnabled = useSelector((state: IReduxState) => state.settings.splitTunneling);
const splitTunnelingApplications = useSelector(
(state: IReduxState) => state.settings.splitTunnelingApplications,
);

const [searchTerm, setSearchTerm] = useState('');
const [applications, setApplications] = useState<ISplitTunnelingApplication[]>();

const [loadingDiskPermissions, setLoadingDiskPermissions] = useState(false);
const [splitTunnelingAvailable, setSplitTunnelingAvailable] = useState(
window.env.platform === 'darwin' ? undefined : true,
);

const splitTunnelingEnabled = splitTunnelingEnabledValue && (splitTunnelingAvailable ?? false);
const canEditSplitTunneling = splitTunnelingEnabled && (splitTunnelingAvailable ?? false);

const fetchNeedFullDiskPermissions = useCallback(async () => {
setLoadingDiskPermissions(true);
const needPermissions = await needFullDiskPermissions();
setSplitTunnelingAvailable(!needPermissions);
setLoadingDiskPermissions(false);
}, [needFullDiskPermissions]);

useEffect((): void | (() => void) => {
Expand Down Expand Up @@ -375,12 +374,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro

const addApplication = useCallback(
async (application: ISplitTunnelingApplication | string) => {
if (!splitTunnelingEnabled) {
if (!canEditSplitTunneling) {
await setSplitTunnelingState(true);
}
await addSplitTunnelingApplication(application);
},
[addSplitTunnelingApplication, splitTunnelingEnabled, setSplitTunnelingState],
[addSplitTunnelingApplication, canEditSplitTunneling, setSplitTunnelingState],
);

const addBrowsedForApplication = useCallback(
Expand All @@ -403,12 +402,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro

const removeApplication = useCallback(
async (application: ISplitTunnelingApplication) => {
if (!splitTunnelingEnabled) {
if (!canEditSplitTunneling) {
await setSplitTunnelingState(true);
}
removeSplitTunnelingApplication(application);
},
[removeSplitTunnelingApplication, setSplitTunnelingState, splitTunnelingEnabled],
[removeSplitTunnelingApplication, setSplitTunnelingState, canEditSplitTunneling],
);

const filePickerCallback = useFilePicker(
Expand Down Expand Up @@ -440,9 +439,9 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
[addApplication, forgetManuallyAddedApplicationAndUpdate],
);

const showSplitSection = splitTunnelingEnabled && filteredSplitApplications.length > 0;
const showSplitSection = canEditSplitTunneling && filteredSplitApplications.length > 0;
const showNonSplitSection =
splitTunnelingEnabled &&
canEditSplitTunneling &&
(!filteredNonSplitApplications || filteredNonSplitApplications.length > 0);

const excludedTitle = (
Expand All @@ -462,26 +461,37 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
<HeaderTitle>{strings.splitTunneling}</HeaderTitle>
<Switch
isOn={splitTunnelingEnabled}
disabled={!splitTunnelingAvailable}
disabled={
!splitTunnelingEnabled && (!splitTunnelingAvailable || loadingDiskPermissions)
}
onChange={setSplitTunnelingState}
/>
</Flex>
<MacOsSplitTunnelingAvailability
needFullDiskPermissions={
window.env.platform === 'darwin' && splitTunnelingAvailable === false
}
/>
{splitTunnelingAvailable ? (
<HeaderSubTitle>
{messages.pgettext(
'split-tunneling-view',
'Choose the apps you want to exclude from the VPN tunnel.',
{!loadingDiskPermissions && (
<>
<MacOsSplitTunnelingAvailability
needFullDiskPermissions={
window.env.platform === 'darwin' && splitTunnelingAvailable === false
}
/>
{splitTunnelingAvailable && (
<HeaderSubTitle>
{messages.pgettext(
'split-tunneling-view',
'Choose the apps you want to exclude from the VPN tunnel.',
)}
</HeaderSubTitle>
)}
</HeaderSubTitle>
) : null}
</>
)}
</SettingsHeader>
{loadingDiskPermissions && (
<Flex $justifyContent="center" $margin={{ top: Spacings.spacing6 }}>
<ImageView source="icon-spinner" height={48} />
</Flex>
)}

{splitTunnelingEnabled && (
{canEditSplitTunneling && (
<StyledSearchBar searchTerm={searchTerm} onSearch={setSearchTerm} />
)}

Expand All @@ -505,7 +515,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
</Cell.Section>
</Accordion>

{splitTunnelingEnabled && searchTerm !== '' && !showSplitSection && !showNonSplitSection && (
{canEditSplitTunneling && searchTerm !== '' && !showSplitSection && !showNonSplitSection && (
<StyledNoResult>
<StyledNoResultText>
{formatHtml(
Expand All @@ -516,7 +526,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
</StyledNoResult>
)}

{splitTunnelingEnabled && (
{canEditSplitTunneling && (
<StyledBrowseButton onClick={addWithFilePicker}>
{messages.pgettext('split-tunneling-view', 'Find another app')}
</StyledBrowseButton>
Expand Down
Loading

0 comments on commit 2c47294

Please sign in to comment.