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.
This commit is contained in:
Scott Lamb 2022-03-04 11:21:56 -08:00
parent 90c9a31ad0
commit bcc59e9109
6 changed files with 23 additions and 25 deletions

View File

@ -8,7 +8,6 @@ import * as api from "./api";
import MoonfireMenu from "./AppMenu"; import MoonfireMenu from "./AppMenu";
import Login from "./Login"; import Login from "./Login";
import { useSnackbars } from "./snackbars"; import { useSnackbars } from "./snackbars";
import { Camera, Session } from "./types";
import ListActivity from "./List"; import ListActivity from "./List";
import AppBar from "@mui/material/AppBar"; import AppBar from "@mui/material/AppBar";
import { import {
@ -53,8 +52,7 @@ function App() {
const [multiviewLayoutIndex, setMultiviewLayoutIndex] = useState( const [multiviewLayoutIndex, setMultiviewLayoutIndex] = useState(
Number.parseInt(searchParams.get("layout") || "0", 10) Number.parseInt(searchParams.get("layout") || "0", 10)
); );
const [session, setSession] = useState<Session | null>(null); const [toplevel, setToplevel] = useState<api.ToplevelResponse | null>(null);
const [cameras, setCameras] = useState<Camera[] | null>(null);
const [timeZoneName, setTimeZoneName] = useState<string | null>(null); const [timeZoneName, setTimeZoneName] = useState<string | null>(null);
const [fetchSeq, setFetchSeq] = useState(0); const [fetchSeq, setFetchSeq] = useState(0);
const [loginState, setLoginState] = useState<LoginState>("unknown"); const [loginState, setLoginState] = useState<LoginState>("unknown");
@ -75,7 +73,7 @@ function App() {
const logout = async () => { const logout = async () => {
const resp = await api.logout( const resp = await api.logout(
{ {
csrf: session!.csrf, csrf: toplevel!.user!.session!.csrf,
}, },
{} {}
); );
@ -88,21 +86,20 @@ function App() {
}); });
break; break;
case "success": case "success":
setSession(null);
needNewFetch(); needNewFetch();
break; break;
} }
}; };
function fetchedCameras(cameras: Camera[] | null) { function fetchedToplevel(toplevel: api.ToplevelResponse | null) {
if (cameras !== null && cameras.length > 0) { if (toplevel !== null && toplevel.cameras.length > 0) {
return ( return (
<> <>
<Route <Route
path="" path=""
element={ element={
<ListActivity <ListActivity
cameras={cameras} cameras={toplevel.cameras}
showSelectors={showListSelectors} showSelectors={showListSelectors}
timeZoneName={timeZoneName!} timeZoneName={timeZoneName!}
/> />
@ -112,7 +109,7 @@ function App() {
path="live" path="live"
element={ element={
<LiveActivity <LiveActivity
cameras={cameras} cameras={toplevel.cameras}
layoutIndex={multiviewLayoutIndex} layoutIndex={multiviewLayoutIndex}
/> />
} }
@ -143,8 +140,7 @@ function App() {
? "not-logged-in" ? "not-logged-in"
: "logged-in" : "logged-in"
); );
setSession(resp.response.user?.session || null); setToplevel(resp.response);
setCameras(resp.response.cameras);
setTimeZoneName(resp.response.timeZoneName); setTimeZoneName(resp.response.timeZoneName);
} }
}; };
@ -154,7 +150,7 @@ function App() {
}; };
}, [fetchSeq]); }, [fetchSeq]);
let activityMenu = null; let activityMenu = null;
if (error === null && cameras !== null && cameras.length > 0) { if (error === null && toplevel !== null && toplevel.cameras.length > 0) {
switch (activity) { switch (activity) {
case "list": case "list":
activityMenu = ( activityMenu = (
@ -186,7 +182,6 @@ function App() {
<AppBar position="static"> <AppBar position="static">
<MoonfireMenu <MoonfireMenu
loginState={loginState} loginState={loginState}
setSession={setSession}
requestLogin={() => { requestLogin={() => {
setLoginState("user-requested-login"); setLoginState("user-requested-login");
}} }}
@ -252,7 +247,7 @@ function App() {
</p> </p>
</Container> </Container>
)} )}
<Routes>{fetchedCameras(cameras)}</Routes> <Routes>{fetchedToplevel(toplevel)}</Routes>
</> </>
); );
} }

View File

@ -14,7 +14,6 @@ import AccountCircle from "@mui/icons-material/AccountCircle";
import MenuIcon from "@mui/icons-material/Menu"; import MenuIcon from "@mui/icons-material/Menu";
import React from "react"; import React from "react";
import { LoginState } from "./App"; import { LoginState } from "./App";
import { Session } from "./types";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -29,7 +28,6 @@ const useStyles = makeStyles((theme: Theme) =>
interface Props { interface Props {
loginState: LoginState; loginState: LoginState;
setSession: (session: Session | null) => void;
requestLogin: () => void; requestLogin: () => void;
logout: () => void; logout: () => void;
menuClick?: () => void; menuClick?: () => void;

View File

@ -7,9 +7,10 @@ import { Camera, Stream, StreamType } from "../types";
import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox";
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@mui/material/styles";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { ToplevelResponse } from "../api";
interface Props { interface Props {
cameras: Camera[]; toplevel: ToplevelResponse;
selected: Set<Stream>; selected: Set<Stream>;
setSelected: (selected: Set<Stream>) => void; setSelected: (selected: Set<Stream>) => void;
} }
@ -35,7 +36,7 @@ const useStyles = makeStyles({
}); });
/** Returns a table which allows selecting zero or more streams. */ /** 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 theme = useTheme();
const classes = useStyles(); const classes = useStyles();
const setStream = (s: Stream, checked: boolean) => { const setStream = (s: Stream, checked: boolean) => {
@ -57,7 +58,7 @@ const StreamMultiSelector = ({ cameras, selected, setSelected }: Props) => {
} }
} }
if (!foundAny) { if (!foundAny) {
for (const c of cameras) { for (const c of toplevel.cameras) {
if (c.streams[st] !== undefined) { if (c.streams[st] !== undefined) {
updated.add(c.streams[st as StreamType]!); updated.add(c.streams[st as StreamType]!);
} }
@ -83,7 +84,7 @@ const StreamMultiSelector = ({ cameras, selected, setSelected }: Props) => {
setSelected(updated); setSelected(updated);
}; };
const cameraRows = cameras.map((c) => { const cameraRows = toplevel.cameras.map((c) => {
function checkbox(st: StreamType) { function checkbox(st: StreamType) {
const s = c.streams[st]; const s = c.streams[st];
if (s === undefined) { if (s === undefined) {

View File

@ -13,7 +13,7 @@ import utcToZonedTime from "date-fns-tz/utcToZonedTime";
import format from "date-fns/format"; import format from "date-fns/format";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import * as api from "../api"; import * as api from "../api";
import { Camera, Stream } from "../types"; import { Stream } from "../types";
import DisplaySelector, { DEFAULT_DURATION } from "./DisplaySelector"; import DisplaySelector, { DEFAULT_DURATION } from "./DisplaySelector";
import StreamMultiSelector from "./StreamMultiSelector"; import StreamMultiSelector from "./StreamMultiSelector";
import TimerangeSelector from "./TimerangeSelector"; import TimerangeSelector from "./TimerangeSelector";
@ -96,11 +96,11 @@ const FullScreenVideo = ({ src, aspect }: FullScreenVideoProps) => {
interface Props { interface Props {
timeZoneName: string; timeZoneName: string;
cameras: Camera[]; toplevel: api.ToplevelResponse;
showSelectors: boolean; showSelectors: boolean;
} }
const Main = ({ cameras, timeZoneName, showSelectors }: Props) => { const Main = ({ toplevel, timeZoneName, showSelectors }: Props) => {
const classes = useStyles(); const classes = useStyles();
/** /**
@ -161,7 +161,7 @@ const Main = ({ cameras, timeZoneName, showSelectors }: Props) => {
sx={{ display: showSelectors ? "block" : "none" }} sx={{ display: showSelectors ? "block" : "none" }}
> >
<StreamMultiSelector <StreamMultiSelector
cameras={cameras} cameras={toplevel.cameras}
selected={selectedStreams} selected={selectedStreams}
setSelected={setSelectedStreams} setSelected={setSelectedStreams}
/> />

View File

@ -13,7 +13,7 @@
* This seems convenient for ensuring the caller handles all possibilities. * 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"; export type StreamType = "main" | "sub";
@ -157,6 +157,7 @@ async function json<T>(
export interface ToplevelResponse { export interface ToplevelResponse {
timeZoneName: string; timeZoneName: string;
cameras: Camera[]; cameras: Camera[];
streams: Map<number, Stream>;
user: ToplevelUser | undefined; user: ToplevelUser | undefined;
} }
@ -170,11 +171,13 @@ export interface ToplevelUser {
export async function toplevel(init: RequestInit) { export async function toplevel(init: RequestInit) {
const resp = await json<ToplevelResponse>("/api/?days=true", init); const resp = await json<ToplevelResponse>("/api/?days=true", init);
if (resp.status === "success") { if (resp.status === "success") {
resp.response.streams = new Map();
resp.response.cameras.forEach((c) => { resp.response.cameras.forEach((c) => {
for (const key in c.streams) { for (const key in c.streams) {
const s = c.streams[key as StreamType]!; const s = c.streams[key as StreamType]!;
s.camera = c; s.camera = c;
s.streamType = key as StreamType; s.streamType = key as StreamType;
resp.response.streams.set(s.id, s);
} }
}); });
} }

View File

@ -22,6 +22,7 @@ export interface Camera {
export interface Stream { export interface Stream {
camera: Camera; // back-reference added within api.ts. camera: Camera; // back-reference added within api.ts.
id: number;
streamType: StreamType; // likewise. streamType: StreamType; // likewise.
retainBytes: number; retainBytes: number;
minStartTime90k: number; minStartTime90k: number;