mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-24 13:13:16 -05:00
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:
parent
90c9a31ad0
commit
bcc59e9109
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user