diff --git a/package.json b/package.json index c2fc1ec..8875000 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@emotion/styled": "^11.1.5", "@material-ui/core": "^5.0.0-alpha.21", "@material-ui/lab": "^5.0.0-alpha.21", + "@tanstack/react-query": "^4.36.1", "@use-gesture/react": "^10.2.23", "chokidar": "^3.5.1", "cors": "^2.8.5", diff --git a/src/components/Controls/Controls.tsx b/src/components/Controls/Controls.tsx index 13a936b..7aaa4d7 100644 --- a/src/components/Controls/Controls.tsx +++ b/src/components/Controls/Controls.tsx @@ -6,7 +6,8 @@ import { useStreamStore } from "utils/stream" import { css } from "@emotion/react" import { theme } from "utils/theme" import { ScrobberRange } from "components/Scrobber/ScrobberRange" -import { MutableRefObject } from "react" +import { MutableRefObject, useEffect } from "react" +import { useQuery } from "@tanstack/react-query" function ControlsHeader(props: { stream: { diff --git a/src/components/Scrobber/ScrobberShift.utils.ts b/src/components/Scrobber/ScrobberShift.utils.ts index 5925776..3c81b34 100644 --- a/src/components/Scrobber/ScrobberShift.utils.ts +++ b/src/components/Scrobber/ScrobberShift.utils.ts @@ -3,7 +3,7 @@ import { useEffect, useLayoutEffect, useState } from "react" async function getServerTimeDiff() { const clientStart = Date.now(), timeBefore = performance.now() - const serverReq = await fetch("http://192.168.2.152:3000/api/time") + const serverReq = await fetch("/api/time") const timeAfter = performance.now() const roundTripTime = timeAfter - timeBefore diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 97b4800..34bbeb2 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -12,9 +12,12 @@ import NoSleep from "nosleep.js" import dayjs from "dayjs" import minMax from "dayjs/plugin/minMax" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" dayjs.extend(minMax) +const queryClient = new QueryClient() + export default function App(props: AppProps) { const { Component, pageProps } = props @@ -35,68 +38,70 @@ export default function App(props: AppProps) { ) return ( - - - - - - - - - - - - - + + + + + + + + + + + + + + - + - - - - + + + + overflow-x: hidden; + } + `} + /> - - - - + + + + + ) } diff --git a/src/pages/api/data/[folder]/log/stream.ts b/src/pages/api/data/[folder]/log/stream.ts index 77f88e6..d9f9a91 100644 --- a/src/pages/api/data/[folder]/log/stream.ts +++ b/src/pages/api/data/[folder]/log/stream.ts @@ -6,7 +6,7 @@ import cors from "cors" const dbRef = createPersistentDatabase() -const RANGE = 10 * 2 // 20s +const RANGE = 10 * 60 // 20s export default async function handler( req: NextApiRequest, diff --git a/src/pages/camera/[cameraKey].tsx b/src/pages/camera/[cameraKey].tsx index 8a6c791..61f65c1 100644 --- a/src/pages/camera/[cameraKey].tsx +++ b/src/pages/camera/[cameraKey].tsx @@ -11,6 +11,8 @@ import { useStreamStore, useStreamPeriodicRefreshNow, generateUrl, + fitDateInBounds, + getDateBounds, } from "utils/stream" import { NextSeo } from "next-seo" import Head from "next/head" @@ -18,6 +20,122 @@ import { GetServerSideProps } from "next" import { loadServerConfig } from "shared/config" import { theme } from "utils/theme" import { useVisibleTimer } from "components/Controls/Controls.utils" +import { useQuery } from "@tanstack/react-query" +import { encodeQuery } from "utils/query" +import { z } from "zod" +import { useServerTimeDiff } from "components/Scrobber/ScrobberShift.utils" +import dayjs from "dayjs" + +const schema = z.object({ + cart: z.object({ + items: z.array( + z.object({ name: z.string(), price: z.number(), qty: z.number() }) + ), + }), +}) + +function LogStream(props: { + stream: { + key: string + } +}) { + const stream = useStreamStore((state) => state.stream) + const log = useQuery( + [props.stream.key, stream] as const, + async ({ queryKey: [camera, args] }) => { + let url: string + + if ("from" in args && "to" in args) { + url = encodeQuery(`/api/data/${camera}/log/stream`, { + from: Math.floor(args.from / 1000), + to: Math.floor(args.to / 1000), + }) + } else { + url = encodeQuery(`/api/data/${camera}/log/stream`, { + shift: Math.floor(args.shift / 1000), + }) + } + + const json: Array<{ + json: string + timestamp: number + }> = await fetch(url).then((res) => res.json()) + + return json + .map(({ json, timestamp }) => ({ + timestamp, + data: schema.parse(JSON.parse(json)), + })) + .reverse() + }, + { refetchInterval: 1000 } + ) + + const serverDiff = useServerTimeDiff() + const now = useStreamStore((state) => state.now) + const playback = useStreamStore((state) => state.playback) + + const displayDate = ( + "playing" in playback + ? now.add(playback.playing, "millisecond") + : playback.paused + ).add(serverDiff, "millisecond") + + const currentItem = log.data?.find( + ({ timestamp }) => dayjs(timestamp).valueOf() <= displayDate.valueOf() + ) + + return ( +
+
+
+ {currentItem?.data.cart.items.map((item, idx) => { + const name = item.name || "Zboží" + return ( +
+ {item.qty} x {name} ... {item.price * item.qty} Kč +
+ ) + })} +
+
+
+ ) +} export default function Page() { useStreamPeriodicRefreshNow() @@ -98,6 +216,8 @@ export default function Page() { }} /> )} + + {meta && } ) diff --git a/src/utils/stream.ts b/src/utils/stream.ts index ba90277..9a27915 100644 --- a/src/utils/stream.ts +++ b/src/utils/stream.ts @@ -21,16 +21,13 @@ export type PlaybackType = { playing: number } | { paused: dayjs.Dayjs } export function generateUrl(name: string, args: StreamType): string | null { if ("from" in args && "to" in args) { - return encodeQuery( - `http://192.168.2.152:3000/api/data/${name}/slice.m3u8`, - { - from: Math.floor(args.from / 1000), - to: Math.floor(args.to / 1000), - } - ) + return encodeQuery(`/api/data/${name}/slice.m3u8`, { + from: Math.floor(args.from / 1000), + to: Math.floor(args.to / 1000), + }) } - return encodeQuery(`http://192.168.2.152:3000/api/data/${name}/stream.m3u8`, { + return encodeQuery(`/api/data/${name}/stream.m3u8`, { shift: Math.floor(args.shift / 1000), }) } diff --git a/yarn.lock b/yarn.lock index c362981..99f1284 100644 --- a/yarn.lock +++ b/yarn.lock @@ -954,6 +954,19 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@tanstack/query-core@4.36.1": + version "4.36.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.36.1.tgz#79f8c1a539d47c83104210be2388813a7af2e524" + integrity sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA== + +"@tanstack/react-query@^4.36.1": + version "4.36.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.36.1.tgz#acb589fab4085060e2e78013164868c9c785e5d2" + integrity sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw== + dependencies: + "@tanstack/query-core" "4.36.1" + use-sync-external-store "^1.2.0" + "@types/babel__core@^7.1.14": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" @@ -4642,7 +4655,7 @@ use-subscription@1.5.1: dependencies: object-assign "^4.1.1" -use-sync-external-store@1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==