diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-04-20 10:53:07 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-04-20 10:53:07 +0200 |
| commit | 4c93bdcf07af75c8ab3a91ddb0ab2641d277b7c9 (patch) | |
| tree | b0af816c5202d64b54d3f3b91b2da700399c019e | |
| parent | fbdf3932b450ab65940f9f006db05cd35ff10704 (diff) | |
| parent | 87a7d264343b356d1acc746635346021fe7d3c6a (diff) | |
| download | mullvadvpn-4c93bdcf07af75c8ab3a91ddb0ab2641d277b7c9.tar.xz mullvadvpn-4c93bdcf07af75c8ab3a91ddb0ab2641d277b7c9.zip | |
Merge branch 'remember-location'
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | gui/src/main/index.ts | 21 | ||||
| -rw-r--r-- | gui/src/main/window-controller.ts | 6 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 41 | ||||
| -rw-r--r-- | gui/src/renderer/components/AppRouter.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/components/NavigationBar.tsx | 23 | ||||
| -rw-r--r-- | gui/src/renderer/lib/history.tsx | 18 | ||||
| -rw-r--r-- | gui/src/renderer/redux/userinterface/actions.ts | 16 | ||||
| -rw-r--r-- | gui/src/renderer/redux/userinterface/reducers.ts | 3 | ||||
| -rw-r--r-- | gui/src/shared/ipc-schema.ts | 12 | ||||
| -rw-r--r-- | gui/src/shared/ipc-types.ts | 10 |
11 files changed, 140 insertions, 19 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8fc35ee4..ee84941061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Line wrap the file at 100 chars. Th - Fix "Open Mullvad VPN" tray context menu item not working after toggling unpinned window setting. - Fix apps not always visible in split tunneling view after browsing for an app and then removing it from the excluded applications. +- Fix navigation resetting to main view when toggling the unpinned window setting. ### Security #### Android diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index c39ccdbc5c..8acdac16a0 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -43,7 +43,12 @@ import { import { messages, relayLocations } from '../shared/gettext'; import { SYSTEM_PREFERRED_LOCALE_KEY } from '../shared/gui-settings-state'; import { ITranslations, MacOsScrollbarVisibility } from '../shared/ipc-schema'; -import { IChangelog, ICurrentAppVersionInfo } from '../shared/ipc-types'; +import { + IChangelog, + ICurrentAppVersionInfo, + IHistoryObject, + ScrollPositions, +} from '../shared/ipc-types'; import log, { ConsoleOutput, Logger } from '../shared/logging'; import { LogLevel } from '../shared/logging-types'; import { @@ -252,6 +257,9 @@ class ApplicationMain { private changelog?: IChangelog; + private navigationHistory?: IHistoryObject; + private scrollPositions: ScrollPositions = {}; + public run() { // Remove window animations to combat window flickering when opening window. Can be removed when // this issue has been resolved: https://github.com/electron/electron/issues/12130 @@ -1273,6 +1281,8 @@ class ApplicationMain { windowsSplitTunnelingApplications: this.windowsSplitTunnelingApplications, macOsScrollbarVisibility: this.macOsScrollbarVisibility, changelog: this.changelog ?? [], + navigationHistory: this.navigationHistory, + scrollPositions: this.scrollPositions, })); IpcMainEventChannel.settings.handleSetAllowLan((allowLan: boolean) => @@ -1486,6 +1496,13 @@ class ApplicationMain { this.guiSettings.changelogDisplayedForVersion = this.currentVersion.gui; }); + IpcMainEventChannel.navigation.handleSetHistory((history) => { + this.navigationHistory = history; + }); + IpcMainEventChannel.navigation.handleSetScrollPositions((scrollPositions) => { + this.scrollPositions = scrollPositions; + }); + if (windowsSplitTunneling) { this.guiSettings.browsedForSplitTunnelingApplications.forEach( windowsSplitTunneling.addApplicationPathToCache, @@ -1650,7 +1667,7 @@ class ApplicationMain { const window = await this.createWindow(); - this.windowController.destroy(); + this.windowController.close(); this.windowController = new WindowController( window, this.tray, diff --git a/gui/src/main/window-controller.ts b/gui/src/main/window-controller.ts index 4676dbbecb..b737f4da96 100644 --- a/gui/src/main/window-controller.ts +++ b/gui/src/main/window-controller.ts @@ -192,9 +192,11 @@ export default class WindowController { this.notifyUpdateWindowShape(); } - public destroy() { + public close() { if (this.window && !this.window.isDestroyed()) { - this.window.destroy(); + this.window.webContents.closeDevTools(); + this.window.closable = true; + this.window.close(); } } diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index a0fb5f3266..d4672066f2 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -25,7 +25,12 @@ import { import { messages, relayLocations } from '../shared/gettext'; import { IGuiSettingsState, SYSTEM_PREFERRED_LOCALE_KEY } from '../shared/gui-settings-state'; import { IRelayListPair, LaunchApplicationResult } from '../shared/ipc-schema'; -import { IChangelog, ICurrentAppVersionInfo } from '../shared/ipc-types'; +import { + IChangelog, + ICurrentAppVersionInfo, + IHistoryObject, + ScrollPositions, +} from '../shared/ipc-types'; import log, { ConsoleOutput } from '../shared/logging'; import { LogLevel } from '../shared/logging-types'; import { Scheduler } from '../shared/scheduler'; @@ -208,7 +213,11 @@ export default class AppRenderer { this.setAccountExpiry(initialState.accountData?.expiry); this.setSettings(initialState.settings); this.setIsPerformingPostUpgrade(initialState.isPerformingPostUpgrade); - this.handleAccountChange({ deviceConfig: initialState.deviceConfig }, undefined); + this.handleAccountChange( + { deviceConfig: initialState.deviceConfig }, + undefined, + initialState.navigationHistory !== undefined, + ); this.hasReceivedDeviceConfig = initialState.hasReceivedDeviceConfig; this.setAccountHistory(initialState.accountHistory); this.setTunnelState(initialState.tunnelState); @@ -244,8 +253,16 @@ export default class AppRenderer { void this.updateLocation(); - const navigationBase = this.getNavigationBase(); - this.history = new History(navigationBase); + this.reduxActions.userInterface.setScrollPositions(initialState.scrollPositions); + + if (initialState.navigationHistory) { + // Set last action to POP to trigger automatic scrolling to saved coordinates. + initialState.navigationHistory.lastAction = 'POP'; + this.history = History.fromSavedHistory(initialState.navigationHistory); + } else { + const navigationBase = this.getNavigationBase(); + this.history = new History(navigationBase); + } } public renderView() { @@ -555,6 +572,14 @@ export default class AppRenderer { IpcRendererEventChannel.currentVersion.displayedChangelog(); }; + public setNavigationHistory(history: IHistoryObject) { + IpcRendererEventChannel.navigation.setHistory(history); + } + + public setScrollPositions(scrollPositions: ScrollPositions) { + IpcRendererEventChannel.navigation.setScrollPositions(scrollPositions); + } + // Make sure that the content height is correct and log if it isn't. This is mostly for debugging // purposes since there's a bug in Electron that causes the app height to be another value than // the one we have set. @@ -804,7 +829,11 @@ export default class AppRenderer { } } - private handleAccountChange(newDeviceEvent: IDeviceEvent, oldAccount?: string) { + private handleAccountChange( + newDeviceEvent: IDeviceEvent, + oldAccount?: string, + preventRedirectToConnect?: boolean, + ) { const reduxAccount = this.reduxActions.account; this.deviceConfig = newDeviceEvent.deviceConfig; @@ -828,7 +857,7 @@ export default class AppRenderer { if (this.previousLoginState === 'too many devices') { this.resetNavigation(); - } else { + } else if (!preventRedirectToConnect) { this.redirectToConnect(); } break; diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index d061c7b9d5..18a8b51063 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -12,6 +12,7 @@ import SelectLocationPage from '../containers/SelectLocationPage'; import SettingsPage from '../containers/SettingsPage'; import SupportPage from '../containers/SupportPage'; import WireguardSettingsPage from '../containers/WireguardSettingsPage'; +import withAppContext, { IAppContext } from '../context'; import { IHistoryProps, ITransitionSpecification, transitions, withHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { DeviceRevokedView } from './DeviceRevokedView'; @@ -36,12 +37,12 @@ interface IAppRoutesState { action?: Action; } -class AppRouter extends React.Component<IHistoryProps, IAppRoutesState> { +class AppRouter extends React.Component<IHistoryProps & IAppContext, IAppRoutesState> { private unobserveHistory?: () => void; private focusRef = React.createRef<IFocusHandle>(); - constructor(props: IHistoryProps) { + constructor(props: IHistoryProps & IAppContext) { super(props); this.state = { @@ -54,6 +55,7 @@ class AppRouter extends React.Component<IHistoryProps, IAppRoutesState> { // React throttles updates, so it's impossible to capture the intermediate navigation without // listening to the history directly. this.unobserveHistory = this.props.history.listen((location, action, transition) => { + this.props.app.setNavigationHistory(this.props.history.asObject); this.setState({ currentLocation: location, transition, @@ -114,6 +116,6 @@ class AppRouter extends React.Component<IHistoryProps, IAppRoutesState> { }; } -const AppRoutesWithRouter = withHistory(AppRouter); +const AppRoutesWithRouter = withAppContext(withHistory(AppRouter)); export default AppRoutesWithRouter; diff --git a/gui/src/renderer/components/NavigationBar.tsx b/gui/src/renderer/components/NavigationBar.tsx index 2628f484eb..7e231646ce 100644 --- a/gui/src/renderer/components/NavigationBar.tsx +++ b/gui/src/renderer/components/NavigationBar.tsx @@ -1,7 +1,8 @@ -import React, { useCallback, useContext, useLayoutEffect, useRef } from 'react'; +import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef } from 'react'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; +import { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import { useHistory } from '../lib/history'; import { useCombinedRefs } from '../lib/utilityHooks'; @@ -114,18 +115,32 @@ export const NavigationScrollbars = React.forwardRef(function NavigationScrollba ) { const history = useHistory(); const { onScroll } = useContext(NavigationScrollContext); + const { setScrollPositions } = useAppContext(); const ref = useRef<CustomScrollbarsRef>(); const combinedRefs = useCombinedRefs(forwardedRef, ref); const { addScrollPosition, removeScrollPosition } = useActions(userInterface); - const scrollPosition = useSelector( - (state) => state.userInterface.scrollPosition[history.location.pathname], - ); + const scrollPositions = useSelector((state) => state.userInterface.scrollPosition); + + useEffect(() => { + const path = history.location.pathname; + const beforeunload = () => { + if (ref.current) { + const scrollPosition = ref.current.getScrollPosition(); + setScrollPositions({ ...scrollPositions, [path]: scrollPosition }); + } + }; + + window.addEventListener('beforeunload', beforeunload); + + return () => window.removeEventListener('beforeunload', beforeunload); + }, [scrollPositions]); useLayoutEffect(() => { const path = history.location.pathname; + const scrollPosition = scrollPositions[history.location.pathname]; if (history.action === 'POP' && scrollPosition) { ref.current?.scrollTo(...scrollPosition); removeScrollPosition(path); diff --git a/gui/src/renderer/lib/history.tsx b/gui/src/renderer/lib/history.tsx index 2eb08d001d..9a0205b382 100644 --- a/gui/src/renderer/lib/history.tsx +++ b/gui/src/renderer/lib/history.tsx @@ -2,6 +2,7 @@ import { Action, History as OriginalHistory, Location, LocationDescriptorObject import React from 'react'; import { RouteComponentProps, useHistory as useReactRouterHistory, withRouter } from 'react-router'; +import { IHistoryObject } from '../../shared/ipc-types'; import { GeneratedRoutePath, RoutePath } from './routes'; export interface ITransitionSpecification { @@ -61,6 +62,15 @@ export default class History { this.entries = [this.createLocation(location, state)]; } + public static fromSavedHistory(savedHistory: IHistoryObject): History { + const history = new History(RoutePath.launch); + history.entries = savedHistory.entries; + history.index = savedHistory.index; + history.lastAction = savedHistory.lastAction; + + return history; + } + public get location(): Location<S> { return this.entries[this.index]; } @@ -126,6 +136,14 @@ export default class History { return this as OriginalHistory; } + public get asObject(): IHistoryObject { + return { + entries: this.entries, + index: this.index, + lastAction: this.lastAction, + }; + } + public block(): never { throw Error('Not implemented'); } diff --git a/gui/src/renderer/redux/userinterface/actions.ts b/gui/src/renderer/redux/userinterface/actions.ts index ce0cd5401b..cc8929934d 100644 --- a/gui/src/renderer/redux/userinterface/actions.ts +++ b/gui/src/renderer/redux/userinterface/actions.ts @@ -1,5 +1,5 @@ import { MacOsScrollbarVisibility } from '../../../shared/ipc-schema'; -import { IChangelog } from '../../../shared/ipc-types'; +import { IChangelog, ScrollPositions } from '../../../shared/ipc-types'; export interface IUpdateLocaleAction { type: 'UPDATE_LOCALE'; @@ -20,6 +20,11 @@ export interface ISetWindowFocusedAction { focused: boolean; } +export interface ISetScrollPositions { + type: 'SET_SCROLL_POSITIONS'; + scrollPositions: ScrollPositions; +} + export interface IAddScrollPosition { type: 'ADD_SCROLL_POSITION'; path: string; @@ -56,6 +61,7 @@ export type UserInterfaceAction = | IUpdateWindowArrowPositionAction | IUpdateConnectionInfoOpenAction | ISetWindowFocusedAction + | ISetScrollPositions | IAddScrollPosition | IRemoveScrollPosition | ISetMacOsScrollbarVisibility @@ -90,6 +96,13 @@ function setWindowFocused(focused: boolean): ISetWindowFocusedAction { }; } +function setScrollPositions(scrollPositions: ScrollPositions): ISetScrollPositions { + return { + type: 'SET_SCROLL_POSITIONS', + scrollPositions, + }; +} + function addScrollPosition(path: string, scrollPosition: [number, number]): IAddScrollPosition { return { type: 'ADD_SCROLL_POSITION', @@ -140,6 +153,7 @@ export default { updateWindowArrowPosition, toggleConnectionPanel, setWindowFocused, + setScrollPositions, addScrollPosition, removeScrollPosition, setMacOsScrollbarVisibility, diff --git a/gui/src/renderer/redux/userinterface/reducers.ts b/gui/src/renderer/redux/userinterface/reducers.ts index ca470a8cba..1850d553ab 100644 --- a/gui/src/renderer/redux/userinterface/reducers.ts +++ b/gui/src/renderer/redux/userinterface/reducers.ts @@ -42,6 +42,9 @@ export default function ( case 'SET_WINDOW_FOCUSED': return { ...state, windowFocused: action.focused }; + case 'SET_SCROLL_POSITIONS': + return { ...state, scrollPosition: action.scrollPositions }; + case 'ADD_SCROLL_POSITION': return { ...state, diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts index ce517c8f21..e09d552c25 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -27,7 +27,13 @@ interface ILogEntry { message: string; } import { invoke, invokeSync, notifyRenderer, send } from './ipc-helpers'; -import { IChangelog, ICurrentAppVersionInfo, IWindowShapeParameters } from './ipc-types'; +import { + IChangelog, + ICurrentAppVersionInfo, + IHistoryObject, + IWindowShapeParameters, + ScrollPositions, +} from './ipc-types'; export interface ITranslations { locale: string; @@ -66,6 +72,8 @@ export interface IAppStateSnapshot { windowsSplitTunnelingApplications?: IWindowsApplication[]; macOsScrollbarVisibility?: MacOsScrollbarVisibility; changelog: IChangelog; + navigationHistory?: IHistoryObject; + scrollPositions: ScrollPositions; } // The different types of requests are: @@ -118,6 +126,8 @@ export const ipcSchema = { }, navigation: { reset: notifyRenderer<void>(), + setHistory: send<IHistoryObject>(), + setScrollPositions: send<ScrollPositions>(), }, daemon: { isPerformingPostUpgrade: notifyRenderer<boolean>(), diff --git a/gui/src/shared/ipc-types.ts b/gui/src/shared/ipc-types.ts index 0624ac95cd..2b42f42b0e 100644 --- a/gui/src/shared/ipc-types.ts +++ b/gui/src/shared/ipc-types.ts @@ -1,3 +1,5 @@ +import { Action, Location } from 'history'; + export interface ICurrentAppVersionInfo { gui: string; daemon?: string; @@ -10,3 +12,11 @@ export interface IWindowShapeParameters { } export type IChangelog = Array<string>; + +export interface IHistoryObject { + entries: Location<unknown>[]; + index: number; + lastAction: Action; +} + +export type ScrollPositions = Record<string, [number, number]>; |
