diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-03-10 17:31:23 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2023-03-10 17:31:23 +0100 |
| commit | 67f13cc44286432274c320360529ed28f6358a37 (patch) | |
| tree | aba4873436bc4cab25bf08125430ac98d96c02cc /gui/src/renderer | |
| parent | 8be326a45b53dae1e16bd62a57ac257ed1e9cf93 (diff) | |
| parent | 25fa6c63766f6da254ebc6d252214244ca116e0e (diff) | |
| download | mullvadvpn-67f13cc44286432274c320360529ed28f6358a37.tar.xz mullvadvpn-67f13cc44286432274c320360529ed28f6358a37.zip | |
Merge branch 'disconnect-daemon-on-suspen-DES-11'
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/components/AppRouter.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/TransitionContainer.tsx | 167 |
2 files changed, 103 insertions, 66 deletions
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index e31066337a..cd6e07fbbd 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -59,7 +59,7 @@ export default function AppRouter() { return ( <Focus ref={focusRef}> <TransitionContainer onTransitionEnd={onNavigation} {...transition}> - <TransitionView viewId={currentLocation.key || ''} routePath={history.location.pathname}> + <TransitionView routePath={history.location.pathname}> <Switch key={currentLocation.key} location={currentLocation}> <Route exact path={RoutePath.launch} component={Launch} /> <Route exact path={RoutePath.login} component={LoginPage} /> diff --git a/gui/src/renderer/components/TransitionContainer.tsx b/gui/src/renderer/components/TransitionContainer.tsx index 45e46c37e0..ce9900927f 100644 --- a/gui/src/renderer/components/TransitionContainer.tsx +++ b/gui/src/renderer/components/TransitionContainer.tsx @@ -5,7 +5,6 @@ import { ITransitionSpecification } from '../lib/history'; import { WillExit } from '../lib/will-exit'; interface ITransitioningViewProps { - viewId: string; routePath: string; children?: React.ReactNode; } @@ -33,7 +32,7 @@ interface IItemStyle { interface IState { currentItem?: ITransitionQueueItem; nextItem?: ITransitionQueueItem; - itemQueue: ITransitionQueueItem[]; + queuedItem?: ITransitionQueueItem; currentItemStyle?: IItemStyle; nextItemStyle?: IItemStyle; currentItemTransition?: Partial<IItemStyle>; @@ -91,59 +90,22 @@ export class TransitionView extends React.Component<ITransitioningViewProps> { export default class TransitionContainer extends React.Component<IProps, IState> { public state: IState = { - itemQueue: [], currentItem: TransitionContainer.makeItem(this.props), }; private isCycling = false; + private isTransitioning = false; - private currentContentRef = React.createRef<HTMLDivElement>(); - private nextContentRef = React.createRef<HTMLDivElement>(); + private currentContentRef: React.MutableRefObject<HTMLDivElement | null> = React.createRef<HTMLDivElement>(); + private nextContentRef: React.MutableRefObject<HTMLDivElement | null> = 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; - - if (candidate && state.currentItem) { - // Synchronize updates to the last added child. Although the queue doesn't change, the child - // itself might need to change. That's why the queue-/next item is replaced by it again after - // calling `makeItem`. - const itemQueueCount = state.itemQueue.length; - const lastItemInQueue = itemQueueCount > 0 ? state.itemQueue[itemQueueCount - 1] : undefined; - - if (lastItemInQueue && lastItemInQueue.view.props.viewId === candidate.props.viewId) { - // Child is last item in queue. No change to the queue needed. - return { - itemQueue: [...state.itemQueue.slice(0, -1), TransitionContainer.makeItem(props)], - }; - } else if ( - itemQueueCount === 0 && - state.nextItem && - state.nextItem.view.props.viewId === candidate.props.viewId - ) { - // Child is next item, no change to the queue needed. - return { nextItem: TransitionContainer.makeItem(props) }; - } else if ( - itemQueueCount === 0 && - !state.nextItem && - state.currentItem.view.props.viewId === candidate.props.viewId - ) { - // Child is current item and there's no new child, no change to the queue needed. - return { currentItem: TransitionContainer.makeItem(props) }; - } else { - // Child is a new item and is added to the queue. - return { itemQueue: [...state.itemQueue, TransitionContainer.makeItem(props)] }; - } - } else if (candidate && !state.currentItem) { - // Child is set as current item if there's no item already. - return { currentItem: TransitionContainer.makeItem(props) }; - } else { - return null; + public componentDidUpdate(prevProps: IProps) { + if (this.props.children !== prevProps.children) { + this.updateStateFromProps(); } - } - public componentDidUpdate() { if ( this.state.currentItemStyle && this.state.currentItemTransition && @@ -167,14 +129,14 @@ export default class TransitionContainer extends React.Component<IProps, IState> } public render() { - const willExit = this.state.itemQueue.length > 0 || this.state.nextItem !== undefined; + const willExit = this.state.queuedItem !== undefined || this.state.nextItem !== undefined; return ( <StyledTransitionContainer disableUserInteraction={willExit}> {this.state.currentItem && ( - <WillExit key={this.state.currentItem.view.props.viewId} value={willExit}> + <WillExit key={this.state.currentItem.view.props.routePath} value={willExit}> <StyledTransitionContent - ref={this.currentContentRef} + ref={this.setCurrentContentRef} transition={this.state.currentItemStyle} onTransitionEnd={this.onTransitionEnd}> {this.state.currentItem.view} @@ -183,9 +145,9 @@ export default class TransitionContainer extends React.Component<IProps, IState> )} {this.state.nextItem && ( - <WillExit key={this.state.nextItem.view.props.viewId} value={false}> + <WillExit key={this.state.nextItem.view.props.routePath} value={false}> <StyledTransitionContent - ref={this.nextContentRef} + ref={this.setNextContentRef} transition={this.state.nextItemStyle} onTransitionEnd={this.onTransitionEnd}> {this.state.nextItem.view} @@ -196,8 +158,86 @@ export default class TransitionContainer extends React.Component<IProps, IState> ); } + private setCurrentContentRef = (element: HTMLDivElement) => { + this.currentContentRef.current?.removeEventListener('transitionstart', this.onTransitionStart); + this.currentContentRef.current = element; + this.currentContentRef.current?.addEventListener('transitionstart', this.onTransitionStart); + }; + + private setNextContentRef = (element: HTMLDivElement) => { + this.nextContentRef.current?.removeEventListener('transitionstart', this.onTransitionStart); + this.nextContentRef.current = element; + this.nextContentRef.current?.addEventListener('transitionstart', this.onTransitionStart); + }; + + private updateStateFromProps() { + const candidate = this.props.children; + + if (candidate && this.state.currentItem) { + // Update currentItem, nextItem, queuedItem depending on which the candidate matches. + if ( + !this.isTransitioning && + this.state.currentItem.view.props.routePath === candidate.props.routePath + ) { + // There's no transition in progress and the newest candidate has the same path as the + // current. In this sitation the app should just remain in the same view. + this.setState( + { + currentItem: TransitionContainer.makeItem(this.props), + nextItem: undefined, + queuedItem: undefined, + currentItemStyle: undefined, + nextItemStyle: undefined, + currentItemTransition: undefined, + nextItemTransition: undefined, + }, + () => (this.isCycling = false), + ); + } else if (!this.isTransitioning && this.state.nextItem) { + // There's no transition in progress but there is a next item. Abort the transition and add + // the candidate to the queue. The app shouldn't start a transition if there is another view + // to queue. + this.setState( + { + nextItem: undefined, + queuedItem: TransitionContainer.makeItem(this.props), + currentItemStyle: undefined, + nextItemStyle: undefined, + currentItemTransition: undefined, + nextItemTransition: undefined, + }, + () => (this.isCycling = false), + ); + } else if (this.state.nextItem?.view.props.routePath === candidate.props.routePath) { + // There's an update to the item that is currently being transitioned to. Update that item + // and continue the transition. + this.setState({ + nextItem: TransitionContainer.makeItem(this.props), + queuedItem: undefined, + }); + } else { + // If none of the above, initiate a transition to the new item. + this.setState({ queuedItem: TransitionContainer.makeItem(this.props) }); + } + } else if (candidate) { + // Child is set as current item if there's no item already. + this.setState({ currentItem: TransitionContainer.makeItem(this.props) }); + } + } + + private onTransitionStart = (event: TransitionEvent) => { + if ( + this.isCycling && + !this.isTransitioning && + event.target === this.transitioningItemRef?.current + ) { + this.isTransitioning = true; + } + }; + private onTransitionEnd = (event: React.TransitionEvent<HTMLDivElement>) => { if (this.isCycling && event.target === this.transitioningItemRef?.current) { + this.isTransitioning = false; this.transitioningItemRef = undefined; this.makeNextItemCurrent(() => { this.onFinishCycle(); @@ -218,11 +258,8 @@ export default class TransitionContainer extends React.Component<IProps, IState> } private cycleUnguarded = () => { - const itemQueue = this.state.itemQueue; - - if (itemQueue.length > 0) { - const nextItem = itemQueue[0]; - const transition = nextItem.transition; + if (this.state.queuedItem) { + const transition = this.state.queuedItem.transition; switch (transition.name) { case 'slide-up': @@ -277,8 +314,8 @@ 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), + nextItem: state.queuedItem, + queuedItem: undefined, currentItemStyle: { x: 0, y: 0, inFront: false }, nextItemStyle: { x: 0, y: 100, inFront: true }, currentItemTransition: { duration }, @@ -289,8 +326,8 @@ 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), + nextItem: state.queuedItem, + queuedItem: undefined, currentItemStyle: { x: 0, y: 0, inFront: true }, nextItemStyle: { x: 0, y: 0, inFront: false }, currentItemTransition: { y: 100, duration }, @@ -301,8 +338,8 @@ 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), + nextItem: state.queuedItem, + queuedItem: undefined, currentItemStyle: { x: 0, y: 0, inFront: false }, nextItemStyle: { x: 100, y: 0, inFront: true }, currentItemTransition: { x: -50, duration }, @@ -313,8 +350,8 @@ 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), + nextItem: state.queuedItem, + queuedItem: undefined, currentItemStyle: { x: 0, y: 0, inFront: true }, nextItemStyle: { x: -50, y: 0, inFront: false }, currentItemTransition: { x: 100, duration }, @@ -325,9 +362,9 @@ export default class TransitionContainer extends React.Component<IProps, IState> private replace(completion: () => void) { this.setState( (state) => ({ - currentItem: state.itemQueue[0], + currentItem: state.queuedItem, nextItem: undefined, - itemQueue: state.itemQueue.slice(1), + queuedItem: undefined, currentItemStyle: { x: 0, y: 0, inFront: false, duration: 0 }, nextItemStyle: { x: 0, y: 0, inFront: true, duration: 0 }, currentItemTransition: undefined, |
