summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/app.tsx10
-rw-r--r--gui/src/renderer/components/Connect.tsx6
-rw-r--r--gui/src/renderer/components/ExpiredAccountAddTime.tsx2
-rw-r--r--gui/src/renderer/components/HeaderBar.tsx4
-rw-r--r--gui/src/renderer/components/KeyboardNavigation.tsx4
-rw-r--r--gui/src/renderer/components/Settings.tsx2
-rw-r--r--gui/src/renderer/components/TooManyDevices.tsx4
-rw-r--r--gui/src/renderer/components/TransitionContainer.tsx29
-rw-r--r--gui/src/renderer/components/select-location/SelectLocation.tsx2
-rw-r--r--gui/src/renderer/components/select-location/select-location-hooks.ts2
-rw-r--r--gui/src/renderer/lib/history.tsx75
-rw-r--r--gui/src/shared/ipc-types.ts3
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 {