feat: route pods by URL slug
This commit is contained in:
+63
-3
@@ -42,6 +42,26 @@ import { Skeleton } from '@/components/ui/skeleton';
|
|||||||
const SESSION_KEY = 'podman.session';
|
const SESSION_KEY = 'podman.session';
|
||||||
const fmt = new Intl.NumberFormat('en', { notation: 'compact' });
|
const fmt = new Intl.NumberFormat('en', { notation: 'compact' });
|
||||||
|
|
||||||
|
function pathPodId(): string | null {
|
||||||
|
const [segment] = window.location.pathname.split('/').filter(Boolean);
|
||||||
|
return segment ? decodeURIComponent(segment) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPodPath(podId: string, replace = false): void {
|
||||||
|
const next = `/${encodeURIComponent(podId)}`;
|
||||||
|
if (window.location.pathname === next) return;
|
||||||
|
window.history[replace ? 'replaceState' : 'pushState']({}, '', next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHomePath(): void {
|
||||||
|
if (window.location.pathname === '/') return;
|
||||||
|
window.history.pushState({}, '', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacePath(path: string): void {
|
||||||
|
window.history.replaceState({}, '', path || '/');
|
||||||
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [pods, setPods] = useState<Pod[]>([]);
|
const [pods, setPods] = useState<Pod[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -85,13 +105,20 @@ export default function App() {
|
|||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function connectToPod(podId: string, who: string) {
|
async function connectToPod(podId: string, who: string, replaceRoute = false) {
|
||||||
|
const previousPath = window.location.pathname;
|
||||||
|
setPodPath(podId, replaceRoute);
|
||||||
|
try {
|
||||||
const result = await joinPod(podId, who, who);
|
const result = await joinPod(podId, who, who);
|
||||||
setRoom(result.room);
|
setRoom(result.room);
|
||||||
setDevMode(result.mode === 'dev');
|
setDevMode(result.mode === 'dev');
|
||||||
setMember(who);
|
setMember(who);
|
||||||
setJoinedPodId(podId);
|
setJoinedPodId(podId);
|
||||||
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ podId, member: who }));
|
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ podId, member: who }));
|
||||||
|
} catch (e) {
|
||||||
|
replacePath(previousPath);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -124,8 +151,14 @@ export default function App() {
|
|||||||
}, [joinedPodId]);
|
}, [joinedPodId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const routedPodId = pathPodId();
|
||||||
const raw = sessionStorage.getItem(SESSION_KEY);
|
const raw = sessionStorage.getItem(SESSION_KEY);
|
||||||
if (!raw) return;
|
if (!raw) {
|
||||||
|
if (routedPodId) {
|
||||||
|
setError(`Enter your name to join ${routedPodId}.`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
let saved: { podId: string; member: string };
|
let saved: { podId: string; member: string };
|
||||||
try {
|
try {
|
||||||
saved = JSON.parse(raw);
|
saved = JSON.parse(raw);
|
||||||
@@ -133,10 +166,11 @@ export default function App() {
|
|||||||
sessionStorage.removeItem(SESSION_KEY);
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const podId = routedPodId ?? saved.podId;
|
||||||
setRestoring(true);
|
setRestoring(true);
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
await connectToPod(saved.podId, saved.member);
|
await connectToPod(podId, saved.member, !!routedPodId);
|
||||||
} catch {
|
} catch {
|
||||||
sessionStorage.removeItem(SESSION_KEY);
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -145,6 +179,30 @@ export default function App() {
|
|||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onPopState = () => {
|
||||||
|
const routedPodId = pathPodId();
|
||||||
|
if (!routedPodId) {
|
||||||
|
room?.disconnect();
|
||||||
|
setRoom(null);
|
||||||
|
setJoinedPodId(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const raw = sessionStorage.getItem(SESSION_KEY);
|
||||||
|
if (!raw) return;
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(raw) as { member: string };
|
||||||
|
if (saved.member && routedPodId !== joinedPodId) {
|
||||||
|
void connectToPod(routedPodId, saved.member, true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('popstate', onPopState);
|
||||||
|
return () => window.removeEventListener('popstate', onPopState);
|
||||||
|
}, [joinedPodId, room]);
|
||||||
|
|
||||||
async function run(key: string, fn: () => Promise<void>) {
|
async function run(key: string, fn: () => Promise<void>) {
|
||||||
startPending(key);
|
startPending(key);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -179,6 +237,7 @@ export default function App() {
|
|||||||
run(id, async () => {
|
run(id, async () => {
|
||||||
await api.deletePod(id);
|
await api.deletePod(id);
|
||||||
setPods((cur) => cur.filter((x) => x.id !== id));
|
setPods((cur) => cur.filter((x) => x.id !== id));
|
||||||
|
if (pathPodId() === id) setHomePath();
|
||||||
});
|
});
|
||||||
const handleAddMember = (id: string, name: string) =>
|
const handleAddMember = (id: string, name: string) =>
|
||||||
run(id, async () => upsert(await api.addMember(id, name)));
|
run(id, async () => upsert(await api.addMember(id, name)));
|
||||||
@@ -215,6 +274,7 @@ export default function App() {
|
|||||||
setRoom(null);
|
setRoom(null);
|
||||||
setJoinedPodId(null);
|
setJoinedPodId(null);
|
||||||
sessionStorage.removeItem(SESSION_KEY);
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
|
setHomePath();
|
||||||
void refresh();
|
void refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,6 +262,9 @@ try {
|
|||||||
await podCard.getByPlaceholder('Your name').fill(verifyMember);
|
await podCard.getByPlaceholder('Your name').fill(verifyMember);
|
||||||
await podCard.getByRole('button', { name: 'Add and join' }).click();
|
await podCard.getByRole('button', { name: 'Add and join' }).click();
|
||||||
await page.getByRole('button', { name: 'Share screen' }).waitFor({ timeout: 15_000 });
|
await page.getByRole('button', { name: 'Share screen' }).waitFor({ timeout: 15_000 });
|
||||||
|
if (new URL(page.url()).pathname !== `/${verifyPod.id}`) {
|
||||||
|
throw new Error(`join did not update URL to /${verifyPod.id}: ${page.url()}`);
|
||||||
|
}
|
||||||
await page.getByRole('heading', { name: 'My stream' }).waitFor({ timeout: 15_000 });
|
await page.getByRole('heading', { name: 'My stream' }).waitFor({ timeout: 15_000 });
|
||||||
await page.getByRole('heading', { name: 'Team stream' }).waitFor({ timeout: 15_000 });
|
await page.getByRole('heading', { name: 'Team stream' }).waitFor({ timeout: 15_000 });
|
||||||
|
|
||||||
@@ -316,6 +319,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Leave pod' }).click();
|
await page.getByRole('button', { name: 'Leave pod' }).click();
|
||||||
|
await page.waitForURL((url) => url.pathname === '/', { timeout: 15_000 });
|
||||||
|
|
||||||
if (consoleErrors.length) throw new Error(`console errors: ${consoleErrors.join(' | ')}`);
|
if (consoleErrors.length) throw new Error(`console errors: ${consoleErrors.join(' | ')}`);
|
||||||
if (pageErrors.length) throw new Error(`page errors: ${pageErrors.join(' | ')}`);
|
if (pageErrors.length) throw new Error(`page errors: ${pageErrors.join(' | ')}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user