Skip to content

Commit

Permalink
Show message while reconnecting
Browse files Browse the repository at this point in the history
  • Loading branch information
davidje13 committed Sep 25, 2024
1 parent d723e37 commit cad1b6a
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 23 deletions.
8 changes: 7 additions & 1 deletion frontend/src/api/RetroTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export class RetroTracker {
url: `${wsBase}/retros/${retroId}`,
token: retroToken,
}));
// TODO: show reconnection UI
s.addEventListener('connected', () => console.info('connected'));
s.addEventListener('disconnected', (e) =>
console.info('disconnected', e.detail.code, e.detail.reason),
Expand All @@ -51,14 +50,21 @@ export class RetroTracker {
retroId: string,
retroToken: string,
retroStateCallback: (state: Retro) => void,
connectivityCallback: (connected: boolean) => void,
): RetroSubscription {
const sub = this.subscriptionTracker.subscribe({ retroId, retroToken });
sub.service.addStateListener(retroStateCallback);
const onConnect = () => connectivityCallback(true);
const onDisconnect = () => connectivityCallback(false);
sub.service.addEventListener('connected', onConnect);
sub.service.addEventListener('disconnected', onDisconnect);

return {
dispatch: sub.service.dispatch,
unsubscribe: () => {
sub.service.removeStateListener(retroStateCallback);
sub.service.removeEventListener('connected', onConnect);
sub.service.removeEventListener('disconnected', onDisconnect);
sub.unsubscribe().catch((e) => {
console.warn(`Failed to unsubscribe from retro ${retroId}`, e);
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const App: FC = () => (
<RetroListPage />
</Route>
<Route path="/retros/:slug/:rest*">
{({ slug }): ReactNode => <RetroRouter slug={slug ?? ''} />}
{({ slug }) => <RetroRouter slug={slug ?? ''} />}
</Route>

<RedirectRoute path="/retro/:slug" to="/retros/:slug" replace />
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/components/RetroRouter.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@import (reference) './colours.less';

.connectionMessage {
position: fixed;
left: 50%;
transform: translateX(-50%);
padding: 8px 16px;
border-radius: 4px;
color: @notification-text;
background: rgba(@notification-back, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
z-index: 3;

@media (prefers-reduced-transparency: reduce) {
background: @notification-back;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}

.connectionMessage svg {
fill: currentColor;
width: 0.9em;
height: 0.9em;
}

.connectionMessage.connected {
top: -80px;
opacity: 0;
visibility: hidden;
transition:
top 1.5s step-end,
visibility 1.5s step-end,
opacity 0.5s ease 1s;
animation: pulse 0.2s;
user-select: none;
pointer-events: none;
}

@keyframes pulse {
from {
transform: translateX(-50%);
animation-timing-function: ease-out;
}

50% {
transform: translateX(-50%) scale(1.1);
animation-timing-function: ease-in;
}

to {
transform: translateX(-50%);
}
}

.connectionMessage.disconnected {
top: 8px;
opacity: 1;
visibility: visible;
transition: top 0.2s cubic-bezier(0.1, 0.5, 0.6, 1.3) 1.5s;
}
52 changes: 31 additions & 21 deletions frontend/src/components/RetroRouter.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import {
type FC,
useState,
useLayoutEffect,
useEffect,
type ReactNode,
} from 'react';
import { type FC, useState, useLayoutEffect, useEffect } from 'react';
import { Route, Switch, useLocation } from 'wouter';
import { type Retro } from '../shared/api-entities';
import { type RetroDispatch } from '../api/RetroTracker';
import { retroTracker, slugTracker } from '../api/api';
import { useNonce } from '../hooks/useNonce';
import { RetroCreatePage } from './retro-create/RetroCreatePage';
import { PasswordPage } from './password/PasswordPage';
import { RetroPage } from './retro/RetroPage';
Expand All @@ -21,8 +14,10 @@ import { useRetroToken } from '../hooks/data/useRetroToken';
import { StateMapProvider } from '../hooks/useStateMap';
import { RedirectRoute } from './RedirectRoute';
import { useEvent } from '../hooks/useEvent';
import TickBold from '../../resources/tick-bold.svg';
import './RetroRouter.less';

type RetroReducerState = [Retro | null, RetroDispatch | null];
type RetroReducerState = [Retro | null, RetroDispatch | null, boolean];

const RETRO_SLUG_PATH = /^\/retros\/([^/]+)($|\/.*)/;

Expand All @@ -32,16 +27,14 @@ function useRetroReducer(
): RetroReducerState {
const [location, setLocation] = useLocation();
const [retroState, setRetroState] = useState<Retro | null>(null);
const [connected, setConnected] = useState<boolean>(false);
const [retroDispatch, setRetroDispatch] = useState<RetroDispatch | null>(
null,
);
const nonce = useNonce();

// This cannot be useEffect; the websocket would be closed & reopened
// when switching between pages within the retro
useLayoutEffect(() => {
const myNonce = nonce.next();

setRetroState(null);
setRetroDispatch(null);
if (!retroId || !retroToken) {
Expand All @@ -51,7 +44,8 @@ function useRetroReducer(
const subscription = retroTracker.subscribe(
retroId,
retroToken,
(data) => nonce.check(myNonce) && setRetroState(data),
(data) => setRetroState(data),
setConnected,
);
setRetroDispatch(() => subscription.dispatch);
return () => subscription.unsubscribe();
Expand All @@ -77,7 +71,7 @@ function useRetroReducer(
}
}, [slug, updateSlug]);

return [retroState, retroDispatch];
return [retroState, retroDispatch, connected];
}

interface PropsT {
Expand All @@ -93,7 +87,10 @@ export interface RetroPagePropsT {
export const RetroRouter: FC<PropsT> = ({ slug }) => {
const [retroId, slugError] = useSlug(slug);
const [retroToken, retroTokenError] = useRetroToken(retroId);
const [retro, retroDispatch] = useRetroReducer(retroId, retroToken);
const [retro, retroDispatch, connected] = useRetroReducer(
retroId,
retroToken,
);

if (slugError === 'not found') {
return <RetroCreatePage defaultSlug={slug} />;
Expand All @@ -119,27 +116,25 @@ export const RetroRouter: FC<PropsT> = ({ slug }) => {
retroDispatch,
};

return (
const routes = (
<StateMapProvider scope={slug}>
<Switch>
<Route path="/retros/:slug">
<RetroPage {...retroParams} />
</Route>
<Route path="/retros/:slug/groups/:group">
{({ group }): ReactNode => (
<RetroPage {...retroParams} group={group} />
)}
{({ group }) => <RetroPage {...retroParams} group={group} />}
</Route>
<Route path="/retros/:slug/archives">
<ArchiveListPage {...retroParams} />
</Route>
<Route path="/retros/:slug/archives/:archiveId">
{({ archiveId }): ReactNode => (
{({ archiveId }) => (
<ArchivePage {...retroParams} archiveId={archiveId ?? ''} />
)}
</Route>
<Route path="/retros/:slug/archives/:archiveId/groups/:group">
{({ archiveId, group }): ReactNode => (
{({ archiveId, group }) => (
<ArchivePage
{...retroParams}
archiveId={archiveId ?? ''}
Expand All @@ -155,4 +150,19 @@ export const RetroRouter: FC<PropsT> = ({ slug }) => {
</Switch>
</StateMapProvider>
);

return (
<>
{routes}
{connected ? (
<div className="connectionMessage connected" aria-hidden>
<TickBold /> Connected
</div>
) : (
<div className="connectionMessage disconnected" role="status">
Reconnecting&hellip;
</div>
)}
</>
);
};
3 changes: 3 additions & 0 deletions frontend/src/components/colours.less
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@

@link-text: #2b9e8e;
@link-text-hover: darken(@link-text, 5%);

@notification-back: #000000;
@notification-text: #eeeeee;

0 comments on commit cad1b6a

Please sign in to comment.