diff options
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/app.tsx | 10 | ||||
| -rw-r--r-- | gui/src/renderer/components/Connect.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountAddTime.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/HeaderBar.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/KeyboardNavigation.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/TooManyDevices.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/TransitionContainer.tsx | 29 | ||||
| -rw-r--r-- | gui/src/renderer/components/select-location/SelectLocation.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/select-location/select-location-hooks.ts | 2 | ||||
| -rw-r--r-- | gui/src/renderer/lib/history.tsx | 75 | ||||
| -rw-r--r-- | gui/src/shared/ipc-types.ts | 3 |
12 files changed, 80 insertions, 63 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index fc6a32aa87..ae7cdec881 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -182,9 +182,7 @@ export default class AppRenderer { this.reduxActions.userInterface.setMacOsScrollbarVisibility(visibility); }); - IpcRendererEventChannel.navigation.listenReset(() => - this.history.dismiss(true, transitions.none), - ); + IpcRendererEventChannel.navigation.listenReset(() => this.history.pop(true)); // Request the initial state from the main process const initialState = IpcRendererEventChannel.state.get(); @@ -342,7 +340,7 @@ export default class AppRenderer { actions.account.loginTooManyDevices(error); this.loginState = 'too many devices'; - this.history.reset(RoutePath.tooManyDevices, transitions.push); + this.history.reset(RoutePath.tooManyDevices, { transition: transitions.push }); } catch (e) { const error = e as Error; log.error('Failed to fetch device list'); @@ -362,7 +360,7 @@ export default class AppRenderer { public logout = async (transition = transitions.dismiss) => { try { - this.history.reset(RoutePath.login, transition); + this.history.reset(RoutePath.login, { transition }); await IpcRendererEventChannel.account.logout(); } catch (e) { const error = e as Error; @@ -651,7 +649,7 @@ export default class AppRenderer { const transition = navigationTransitions[nextPath]?.[pathname] ?? navigationTransitions[nextPath]?.['*']; - this.history.reset(nextPath, transition); + this.history.reset(nextPath, { transition }); } } } diff --git a/gui/src/renderer/components/Connect.tsx b/gui/src/renderer/components/Connect.tsx index 10ee3f209d..492fcc3fc9 100644 --- a/gui/src/renderer/components/Connect.tsx +++ b/gui/src/renderer/components/Connect.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { messages, relayLocations } from '../../shared/gettext'; import log from '../../shared/logging'; import { useAppContext } from '../context'; -import { useHistory } from '../lib/history'; +import { transitions, useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { IRelayLocationRedux, RelaySettingsRedux } from '../redux/settings/reducers'; import { useSelector } from '../redux/store'; @@ -133,8 +133,8 @@ export default function Connect() { }, [mapCenter, showMarkerOrSpinner, markerStyle, zoomLevel]); const onSelectLocation = useCallback(() => { - history.show(RoutePath.selectLocation); - }, [history.show]); + history.push(RoutePath.selectLocation, { transition: transitions.show }); + }, [history.push]); const selectedRelayName = useMemo(() => getRelayName(relaySettings, relayLocations), [ relaySettings, diff --git a/gui/src/renderer/components/ExpiredAccountAddTime.tsx b/gui/src/renderer/components/ExpiredAccountAddTime.tsx index 54d27f75d8..57a3856dce 100644 --- a/gui/src/renderer/components/ExpiredAccountAddTime.tsx +++ b/gui/src/renderer/components/ExpiredAccountAddTime.tsx @@ -280,7 +280,7 @@ function useFinishedCallback() { accountSetupFinished(); } - history.reset(RoutePath.main, transitions.push); + history.reset(RoutePath.main, { transition: transitions.push }); }, [isNewAccount, accountSetupFinished, history]); return callback; diff --git a/gui/src/renderer/components/HeaderBar.tsx b/gui/src/renderer/components/HeaderBar.tsx index 8a9ea40a9d..3cf96df6fc 100644 --- a/gui/src/renderer/components/HeaderBar.tsx +++ b/gui/src/renderer/components/HeaderBar.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; import { TunnelState } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; -import { useHistory } from '../lib/history'; +import { transitions, useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { IReduxState } from '../redux/store'; import { FocusFallback } from './Focus'; @@ -101,7 +101,7 @@ export function HeaderBarSettingsButton(props: IHeaderBarSettingsButtonProps) { const openSettings = useCallback(() => { if (!props.disabled) { - history.show(RoutePath.settings); + history.push(RoutePath.settings, { transition: transitions.show }); } }, [history, props.disabled]); diff --git a/gui/src/renderer/components/KeyboardNavigation.tsx b/gui/src/renderer/components/KeyboardNavigation.tsx index 9f657fb54b..8e8bae5faf 100644 --- a/gui/src/renderer/components/KeyboardNavigation.tsx +++ b/gui/src/renderer/components/KeyboardNavigation.tsx @@ -20,14 +20,14 @@ export default function KeyboardNavigation(props: IKeyboardNavigationProps) { const path = location.pathname as RoutePath; if (!disableDismissForRoutes.includes(path)) { if (event.shiftKey) { - history.dismiss(true); + history.pop(true); } else { backAction?.action(); } } } }, - [history.dismiss, backAction, location.pathname], + [history.pop, backAction, location.pathname], ); useEffect(() => { diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index 14b0fb44d5..76b4d6b4fd 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -38,7 +38,7 @@ export default function Support() { const showSubSettings = loginState.type === 'ok' && connectedToDaemon; return ( - <BackAction icon="close" action={history.dismiss}> + <BackAction icon="close" action={history.pop}> <Layout> <SettingsContainer> <NavigationContainer> diff --git a/gui/src/renderer/components/TooManyDevices.tsx b/gui/src/renderer/components/TooManyDevices.tsx index b23fe3f2df..23ff399edb 100644 --- a/gui/src/renderer/components/TooManyDevices.tsx +++ b/gui/src/renderer/components/TooManyDevices.tsx @@ -93,11 +93,11 @@ export default function TooManyDevices() { const continueLogin = useCallback(() => { void login(accountToken); - history.reset(RoutePath.login, transitions.pop); + history.reset(RoutePath.login, { transition: transitions.pop }); }, [login, accountToken]); const cancel = useCallback(() => { cancelLogin(); - history.reset(RoutePath.login, transitions.pop); + history.reset(RoutePath.login, { transition: transitions.pop }); }, [history.reset, cancelLogin]); const iconSource = getIconSource(devices); diff --git a/gui/src/renderer/components/TransitionContainer.tsx b/gui/src/renderer/components/TransitionContainer.tsx index 5fbfe61a55..1587cce024 100644 --- a/gui/src/renderer/components/TransitionContainer.tsx +++ b/gui/src/renderer/components/TransitionContainer.tsx @@ -91,6 +91,8 @@ export default class TransitionContainer extends React.Component<IProps, IState> private currentContentRef = React.createRef<HTMLDivElement>(); private nextContentRef = React.createRef<HTMLDivElement>(); + // The item that should trigger the cycle to finish in onTransitionEnd + private transitioningItemRef?: React.RefObject<HTMLDivElement>; public static getDerivedStateFromProps(props: IProps, state: IState) { const candidate = props.children; @@ -187,12 +189,11 @@ export default class TransitionContainer extends React.Component<IProps, IState> } private onTransitionEnd = (event: React.TransitionEvent<HTMLDivElement>) => { - if ( - this.isCycling && - (event.target === this.currentContentRef.current || - event.target === this.nextContentRef.current) - ) { - this.continueCycling(); + if (this.isCycling && event.target === this.transitioningItemRef?.current) { + this.transitioningItemRef = undefined; + this.makeNextItemCurrent(() => { + this.onFinishCycle(); + }); } }; @@ -203,15 +204,11 @@ export default class TransitionContainer extends React.Component<IProps, IState> } } - private finishCycling() { - this.isCycling = false; + private onFinishCycle() { this.props.onTransitionEnd(); + this.cycleUnguarded(); } - private continueCycling = () => { - this.makeNextItemCurrent(this.cycleUnguarded); - }; - private cycleUnguarded = () => { const itemQueue = this.state.itemQueue; @@ -237,11 +234,11 @@ export default class TransitionContainer extends React.Component<IProps, IState> break; default: - this.replace(this.cycleUnguarded); + this.replace(() => this.onFinishCycle); break; } } else { - this.finishCycling(); + this.isCycling = false; } }; @@ -270,6 +267,7 @@ export default class TransitionContainer extends React.Component<IProps, IState> } private slideUp(duration: number) { + this.transitioningItemRef = this.nextContentRef; this.setState((state) => ({ nextItem: state.itemQueue[0], itemQueue: state.itemQueue.slice(1), @@ -281,6 +279,7 @@ export default class TransitionContainer extends React.Component<IProps, IState> } private slideDown(duration: number) { + this.transitioningItemRef = this.currentContentRef; this.setState((state) => ({ nextItem: state.itemQueue[0], itemQueue: state.itemQueue.slice(1), @@ -292,6 +291,7 @@ export default class TransitionContainer extends React.Component<IProps, IState> } private push(duration: number) { + this.transitioningItemRef = this.nextContentRef; this.setState((state) => ({ nextItem: state.itemQueue[0], itemQueue: state.itemQueue.slice(1), @@ -303,6 +303,7 @@ export default class TransitionContainer extends React.Component<IProps, IState> } private pop(duration: number) { + this.transitioningItemRef = this.currentContentRef; this.setState((state) => ({ nextItem: state.itemQueue[0], itemQueue: state.itemQueue.slice(1), diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx index c26dacdd33..9e9c60b7a5 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -71,7 +71,7 @@ export default function SelectLocation() { const [searchValue, setSearchValue] = useState(''); - const onClose = useCallback(() => history.dismiss(), [history]); + const onClose = useCallback(() => history.pop(), [history]); const onViewFilter = useCallback(() => history.push(RoutePath.filter), [history]); const tunnelProtocol = relaySettings?.tunnelProtocol ?? 'any'; diff --git a/gui/src/renderer/components/select-location/select-location-hooks.ts b/gui/src/renderer/components/select-location/select-location-hooks.ts index a1d54b435d..0cefdecb4f 100644 --- a/gui/src/renderer/components/select-location/select-location-hooks.ts +++ b/gui/src/renderer/components/select-location/select-location-hooks.ts @@ -27,7 +27,7 @@ export function useOnSelectExitLocation() { throw new Error('relayLocation should never be undefiend'); } - history.dismiss(); + history.pop(); const relayUpdate = RelaySettingsBuilder.normal() .location.fromRaw(relayLocation.value) .build(); diff --git a/gui/src/renderer/lib/history.tsx b/gui/src/renderer/lib/history.tsx index b81aef50fe..68cbbcf482 100644 --- a/gui/src/renderer/lib/history.tsx +++ b/gui/src/renderer/lib/history.tsx @@ -39,6 +39,21 @@ export const transitions: ITransitionMap = { }, }; +const transitionOpposites: Record<string, string> = { + 'slide-up': 'slide-down', + 'slide-down': 'slide-up', + push: 'pop', + pop: 'push', + '': '', +}; + +function oppositeTransition(transition: ITransitionSpecification): ITransitionSpecification { + return { + ...transition, + name: transitionOpposites[transition.name], + }; +} + type LocationDescriptor = RoutePath | GeneratedRoutePath | LocationDescriptorObject<LocationState>; type LocationListener = ( @@ -78,39 +93,26 @@ export default class History { return this.lastAction; } - public push = (nextLocation: LocationDescriptor, nextState?: LocationState) => { - this.pushImpl(nextLocation, nextState); - this.notify(transitions.push); - }; - - public pop = () => { - if (this.popImpl()) { - this.notify(transitions.pop); - } - }; - - public show = (nextLocation: LocationDescriptor, nextState?: LocationState) => { - this.pushImpl(nextLocation, nextState); - this.notify(transitions.show); + public push = (nextLocation: LocationDescriptor, nextState?: Partial<LocationState>) => { + const state = { transition: transitions.push, ...nextState }; + this.pushImpl(nextLocation, state); + this.notify(state.transition); }; - public dismiss = (all?: boolean, transition = transitions.dismiss) => { - if (this.popImpl(all ? this.index : 1)) { + public pop = (all?: boolean) => { + const transition = this.popImpl(all === true ? this.index : 1); + if (transition !== undefined) { this.notify(transition); } }; - public reset = ( - nextLocation: LocationDescriptor, - transition?: ITransitionSpecification, - nextState?: LocationState, - ) => { + public reset = (nextLocation: LocationDescriptor, nextState?: Partial<LocationState>) => { const location = this.createLocation(nextLocation, nextState); this.lastAction = 'REPLACE'; this.index = 0; this.entries = [location]; - this.notify(transition ?? transitions.none); + this.notify(nextState?.transition ?? transitions.none); }; public listen(callback: LocationListener) { @@ -158,22 +160,24 @@ export default class History { throw Error('Not implemented'); } - private pushImpl(nextLocation: LocationDescriptor, nextState?: LocationState) { + private pushImpl(nextLocation: LocationDescriptor, nextState?: Partial<LocationState>) { const location = this.createLocation(nextLocation, nextState); this.lastAction = 'PUSH'; this.index += 1; this.entries.splice(this.index, this.entries.length - this.index, location); } - private popImpl(n = 1): boolean { + private popImpl(n = 1): ITransitionSpecification | undefined { if (this.canGo(-n)) { this.lastAction = 'POP'; this.index -= n; + + const transition = this.entries[this.index + 1].state.transition; this.entries = this.entries.slice(0, this.index + 1); - return true; + return oppositeTransition(transition); } else { - return false; + return undefined; } } @@ -183,7 +187,7 @@ export default class History { private createLocation( location: LocationDescriptor, - state?: LocationState, + state?: Partial<LocationState>, ): Location<LocationState> { if (typeof location === 'string') { return this.createLocationFromString(location, state); @@ -194,22 +198,33 @@ export default class History { pathname: location.pathname ?? this.location.pathname, search: location.search ?? '', hash: location.hash ?? '', - state: location.state ?? { scrollPosition: [0, 0], expandedSections: {} }, + state: this.createState(state), key: location.key ?? this.getRandomKey(), }; } } - private createLocationFromString(path: string, state?: LocationState): Location<LocationState> { + private createLocationFromString( + path: string, + state?: Partial<LocationState>, + ): Location<LocationState> { return { pathname: path, search: '', hash: '', - state: state ?? { scrollPosition: [0, 0], expandedSections: {} }, + state: this.createState(state), key: this.getRandomKey(), }; } + private createState(state?: Partial<LocationState>): LocationState { + return { + scrollPosition: state?.scrollPosition ?? [0, 0], + expandedSections: state?.expandedSections ?? {}, + transition: state?.transition ?? transitions.none, + }; + } + private getRandomKey() { return Math.random().toString(36).substr(8); } diff --git a/gui/src/shared/ipc-types.ts b/gui/src/shared/ipc-types.ts index 81ca28b1ac..c186c9ac83 100644 --- a/gui/src/shared/ipc-types.ts +++ b/gui/src/shared/ipc-types.ts @@ -1,5 +1,7 @@ import { Action, Location } from 'history'; +import { ITransitionSpecification } from '../renderer/lib/history'; + export interface ICurrentAppVersionInfo { gui: string; daemon?: string; @@ -16,6 +18,7 @@ export type IChangelog = Array<string>; export interface LocationState { scrollPosition: [number, number]; expandedSections: Record<string, boolean>; + transition: ITransitionSpecification; } export interface IHistoryObject { |
