From bcc59e91093655839666ede31665bd7feb1d8470 Mon Sep 17 00:00:00 2001 From: Scott Lamb Date: Fri, 4 Mar 2022 11:21:56 -0800 Subject: [PATCH] plumb more api response through to list view This keeps a coarser-grained `toplevel` property rather than `user` and `session`. It also synthesizes a `streams` field within it with ids. This makes it easier to put the streams in the URL by id. --- ui/src/App.tsx | 23 +++++++++-------------- ui/src/AppMenu.tsx | 2 -- ui/src/List/StreamMultiSelector.tsx | 9 +++++---- ui/src/List/index.tsx | 8 ++++---- ui/src/api.ts | 5 ++++- ui/src/types.ts | 1 + 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ui/src/App.tsx b/ui/src/App.tsx index db85979..13bed3c 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -8,7 +8,6 @@ import * as api from "./api"; import MoonfireMenu from "./AppMenu"; import Login from "./Login"; import { useSnackbars } from "./snackbars"; -import { Camera, Session } from "./types"; import ListActivity from "./List"; import AppBar from "@mui/material/AppBar"; import { @@ -53,8 +52,7 @@ function App() { const [multiviewLayoutIndex, setMultiviewLayoutIndex] = useState( Number.parseInt(searchParams.get("layout") || "0", 10) ); - const [session, setSession] = useState(null); - const [cameras, setCameras] = useState(null); + const [toplevel, setToplevel] = useState(null); const [timeZoneName, setTimeZoneName] = useState(null); const [fetchSeq, setFetchSeq] = useState(0); const [loginState, setLoginState] = useState("unknown"); @@ -75,7 +73,7 @@ function App() { const logout = async () => { const resp = await api.logout( { - csrf: session!.csrf, + csrf: toplevel!.user!.session!.csrf, }, {} ); @@ -88,21 +86,20 @@ function App() { }); break; case "success": - setSession(null); needNewFetch(); break; } }; - function fetchedCameras(cameras: Camera[] | null) { - if (cameras !== null && cameras.length > 0) { + function fetchedToplevel(toplevel: api.ToplevelResponse | null) { + if (toplevel !== null && toplevel.cameras.length > 0) { return ( <> @@ -112,7 +109,7 @@ function App() { path="live" element={ } @@ -143,8 +140,7 @@ function App() { ? "not-logged-in" : "logged-in" ); - setSession(resp.response.user?.session || null); - setCameras(resp.response.cameras); + setToplevel(resp.response); setTimeZoneName(resp.response.timeZoneName); } }; @@ -154,7 +150,7 @@ function App() { }; }, [fetchSeq]); let activityMenu = null; - if (error === null && cameras !== null && cameras.length > 0) { + if (error === null && toplevel !== null && toplevel.cameras.length > 0) { switch (activity) { case "list": activityMenu = ( @@ -186,7 +182,6 @@ function App() { { setLoginState("user-requested-login"); }} @@ -252,7 +247,7 @@ function App() {

)} - {fetchedCameras(cameras)} + {fetchedToplevel(toplevel)} ); } diff --git a/ui/src/AppMenu.tsx b/ui/src/AppMenu.tsx index 4a82e46..a94143f 100644 --- a/ui/src/AppMenu.tsx +++ b/ui/src/AppMenu.tsx @@ -14,7 +14,6 @@ import AccountCircle from "@mui/icons-material/AccountCircle"; import MenuIcon from "@mui/icons-material/Menu"; import React from "react"; import { LoginState } from "./App"; -import { Session } from "./types"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -29,7 +28,6 @@ const useStyles = makeStyles((theme: Theme) => interface Props { loginState: LoginState; - setSession: (session: Session | null) => void; requestLogin: () => void; logout: () => void; menuClick?: () => void; diff --git a/ui/src/List/StreamMultiSelector.tsx b/ui/src/List/StreamMultiSelector.tsx index 9da2427..27d95ef 100644 --- a/ui/src/List/StreamMultiSelector.tsx +++ b/ui/src/List/StreamMultiSelector.tsx @@ -7,9 +7,10 @@ import { Camera, Stream, StreamType } from "../types"; import Checkbox from "@mui/material/Checkbox"; import { useTheme } from "@mui/material/styles"; import { makeStyles } from "@mui/styles"; +import { ToplevelResponse } from "../api"; interface Props { - cameras: Camera[]; + toplevel: ToplevelResponse; selected: Set; setSelected: (selected: Set) => void; } @@ -35,7 +36,7 @@ const useStyles = makeStyles({ }); /** Returns a table which allows selecting zero or more streams. */ -const StreamMultiSelector = ({ cameras, selected, setSelected }: Props) => { +const StreamMultiSelector = ({ toplevel, selected, setSelected }: Props) => { const theme = useTheme(); const classes = useStyles(); const setStream = (s: Stream, checked: boolean) => { @@ -57,7 +58,7 @@ const StreamMultiSelector = ({ cameras, selected, setSelected }: Props) => { } } if (!foundAny) { - for (const c of cameras) { + for (const c of toplevel.cameras) { if (c.streams[st] !== undefined) { updated.add(c.streams[st as StreamType]!); } @@ -83,7 +84,7 @@ const StreamMultiSelector = ({ cameras, selected, setSelected }: Props) => { setSelected(updated); }; - const cameraRows = cameras.map((c) => { + const cameraRows = toplevel.cameras.map((c) => { function checkbox(st: StreamType) { const s = c.streams[st]; if (s === undefined) { diff --git a/ui/src/List/index.tsx b/ui/src/List/index.tsx index fedc4a9..d54163a 100644 --- a/ui/src/List/index.tsx +++ b/ui/src/List/index.tsx @@ -13,7 +13,7 @@ import utcToZonedTime from "date-fns-tz/utcToZonedTime"; import format from "date-fns/format"; import React, { useMemo, useState } from "react"; import * as api from "../api"; -import { Camera, Stream } from "../types"; +import { Stream } from "../types"; import DisplaySelector, { DEFAULT_DURATION } from "./DisplaySelector"; import StreamMultiSelector from "./StreamMultiSelector"; import TimerangeSelector from "./TimerangeSelector"; @@ -96,11 +96,11 @@ const FullScreenVideo = ({ src, aspect }: FullScreenVideoProps) => { interface Props { timeZoneName: string; - cameras: Camera[]; + toplevel: api.ToplevelResponse; showSelectors: boolean; } -const Main = ({ cameras, timeZoneName, showSelectors }: Props) => { +const Main = ({ toplevel, timeZoneName, showSelectors }: Props) => { const classes = useStyles(); /** @@ -161,7 +161,7 @@ const Main = ({ cameras, timeZoneName, showSelectors }: Props) => { sx={{ display: showSelectors ? "block" : "none" }} > diff --git a/ui/src/api.ts b/ui/src/api.ts index 25e4457..8c57b56 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -13,7 +13,7 @@ * This seems convenient for ensuring the caller handles all possibilities. */ -import { Camera, Session } from "./types"; +import { Camera, Session, Stream } from "./types"; export type StreamType = "main" | "sub"; @@ -157,6 +157,7 @@ async function json( export interface ToplevelResponse { timeZoneName: string; cameras: Camera[]; + streams: Map; user: ToplevelUser | undefined; } @@ -170,11 +171,13 @@ export interface ToplevelUser { export async function toplevel(init: RequestInit) { const resp = await json("/api/?days=true", init); if (resp.status === "success") { + resp.response.streams = new Map(); resp.response.cameras.forEach((c) => { for (const key in c.streams) { const s = c.streams[key as StreamType]!; s.camera = c; s.streamType = key as StreamType; + resp.response.streams.set(s.id, s); } }); } diff --git a/ui/src/types.ts b/ui/src/types.ts index 464e909..c6272ed 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -22,6 +22,7 @@ export interface Camera { export interface Stream { camera: Camera; // back-reference added within api.ts. + id: number; streamType: StreamType; // likewise. retainBytes: number; minStartTime90k: number;