From 7304fe231e76994198359e2f4bca8a030ffd6bd8 Mon Sep 17 00:00:00 2001 From: NlightN22 Date: Sun, 25 Feb 2024 21:15:31 +0700 Subject: [PATCH] refactoring add translates finish recordings --- src/pages/403.tsx | 2 +- src/pages/404.tsx | 2 +- src/pages/FrigateHostsPage.tsx | 4 +- src/pages/HostConfigPage.tsx | 4 +- src/pages/LiveCameraPage.tsx | 6 +- src/pages/{MainBody.tsx => MainPage.tsx} | 19 +- src/pages/RecordingsPage.tsx | 82 +++- .../{RetryError.tsx => RetryErrorPage.tsx} | 6 +- src/pages/SettingsPage.tsx | 4 +- src/pages/Test.tsx | 30 -- src/pages/TestItem.tsx | 18 + src/pages/TestPage.tsx | 59 +++ src/router/routes.tsx | 10 +- .../CameraImage.tsx => AutoUpdatedImage.tsx} | 40 +- src/shared/components/CameraCard.tsx | 7 +- src/shared/components/FullProductModal.tsx | 2 +- src/shared/components/RetryError.tsx | 25 ++ .../components/accordion/CameraAccordion.tsx | 52 ++- .../components/accordion/DayAccordion.tsx | 57 ++- .../accordion/DayEventsAccordion.tsx | 3 +- .../components/accordion/EventsAccordion.tsx | 63 ++- .../components/accordion/PlayControl.tsx | 52 ++- .../components/accordion/TestAccordion.tsx | 11 - .../filters.aps/CameraSelectFilter.tsx | 14 +- .../filters.aps/DateRangeSelectFilter.tsx | 59 +++ .../filters.aps/HostSelectFilter.tsx | 63 +++ .../frigate/AutoUpdatingCameraImage.tsx | 164 +++---- src/shared/components/frigate/Button.jsx | 214 +++++----- .../components/frigate/DebugCameraImage.tsx | 304 ++++++------- src/shared/components/frigate/Dialog.jsx | 64 +-- src/shared/components/frigate/Link.jsx | 32 +- src/shared/components/frigate/Menu.jsx | 88 ++-- src/shared/components/frigate/MultiSelect.jsx | 134 +++--- src/shared/components/frigate/Tabs.jsx | 74 ++-- src/shared/components/frigate/TimeAgo.tsx | 140 +++--- .../frigate/TimelineEventOverlay.jsx | 120 +++--- .../components/frigate/TimelineSummary.jsx | 402 +++++++++--------- src/shared/components/frigate/Tooltip.jsx | 110 ++--- src/shared/components/frigate/card.tsx | 145 +++---- src/shared/components/frigate/icons/About.jsx | 19 - .../components/frigate/icons/CalendarIcon.jsx | 24 -- .../components/frigate/icons/Camera.jsx | 24 -- src/shared/components/frigate/icons/Clip.jsx | 24 -- src/shared/components/frigate/icons/Clock.jsx | 24 -- .../components/frigate/icons/Delete.jsx | 24 -- .../components/frigate/icons/Download.jsx | 26 -- src/shared/components/frigate/icons/Menu.jsx | 13 - .../components/frigate/icons/MenuOpen.jsx | 13 - src/shared/components/frigate/icons/Score.jsx | 20 - .../components/frigate/icons/Snapshot.jsx | 24 -- .../frigate/icons/StarRecording.jsx | 24 -- .../components/frigate/icons/Submitted.jsx | 19 - .../components/frigate/icons/UploadPlus.jsx | 23 - src/shared/components/frigate/icons/Zone.jsx | 25 -- .../{ => loaders}/CogWheelWithText.tsx | 0 .../{ => loaders}/CogwheelLoader.tsx | 2 +- .../{frigate => players}/JSMpegPlayer.tsx | 0 .../{frigate => players}/MsePlayer.tsx | 0 .../{frigate => players}/VideoPlayer.tsx | 0 .../{frigate => players}/WebRTCPlayer.tsx | 0 src/shared/stores/recordings.store.ts | 13 +- src/shared/strings/strings.ts | 35 +- .../{components/frigate => utils}/dateUtil.ts | 33 +- src/widgets/FrigateHostsTable.tsx | 6 +- .../components/frigate => widgets}/Player.tsx | 20 +- src/widgets/RecordingsFiltersRightSide.tsx | 61 +-- src/widgets/SelecteDayList.tsx | 53 +++ src/widgets/SelectedCameraList.tsx | 23 +- src/widgets/SelectedHostList.tsx | 9 +- 69 files changed, 1682 insertions(+), 1584 deletions(-) rename src/pages/{MainBody.tsx => MainPage.tsx} (84%) rename src/pages/{RetryError.tsx => RetryErrorPage.tsx} (92%) delete mode 100644 src/pages/Test.tsx create mode 100644 src/pages/TestItem.tsx create mode 100644 src/pages/TestPage.tsx rename src/shared/components/{frigate/CameraImage.tsx => AutoUpdatedImage.tsx} (51%) create mode 100644 src/shared/components/RetryError.tsx delete mode 100644 src/shared/components/accordion/TestAccordion.tsx create mode 100644 src/shared/components/filters.aps/DateRangeSelectFilter.tsx create mode 100644 src/shared/components/filters.aps/HostSelectFilter.tsx delete mode 100644 src/shared/components/frigate/icons/About.jsx delete mode 100644 src/shared/components/frigate/icons/CalendarIcon.jsx delete mode 100644 src/shared/components/frigate/icons/Camera.jsx delete mode 100644 src/shared/components/frigate/icons/Clip.jsx delete mode 100644 src/shared/components/frigate/icons/Clock.jsx delete mode 100644 src/shared/components/frigate/icons/Delete.jsx delete mode 100644 src/shared/components/frigate/icons/Download.jsx delete mode 100644 src/shared/components/frigate/icons/Menu.jsx delete mode 100644 src/shared/components/frigate/icons/MenuOpen.jsx delete mode 100644 src/shared/components/frigate/icons/Score.jsx delete mode 100644 src/shared/components/frigate/icons/Snapshot.jsx delete mode 100644 src/shared/components/frigate/icons/StarRecording.jsx delete mode 100644 src/shared/components/frigate/icons/Submitted.jsx delete mode 100644 src/shared/components/frigate/icons/UploadPlus.jsx delete mode 100644 src/shared/components/frigate/icons/Zone.jsx rename src/shared/components/{ => loaders}/CogWheelWithText.tsx (100%) rename src/shared/components/{ => loaders}/CogwheelLoader.tsx (72%) rename src/shared/components/{frigate => players}/JSMpegPlayer.tsx (100%) rename src/shared/components/{frigate => players}/MsePlayer.tsx (100%) rename src/shared/components/{frigate => players}/VideoPlayer.tsx (100%) rename src/shared/components/{frigate => players}/WebRTCPlayer.tsx (100%) rename src/shared/{components/frigate => utils}/dateUtil.ts (93%) rename src/{shared/components/frigate => widgets}/Player.tsx (77%) create mode 100644 src/widgets/SelecteDayList.tsx diff --git a/src/pages/403.tsx b/src/pages/403.tsx index 9bbf3d4..5f1e2b3 100644 --- a/src/pages/403.tsx +++ b/src/pages/403.tsx @@ -1,6 +1,6 @@ import { Button, Flex, Text } from '@mantine/core'; import React, { useContext, useEffect, useState } from 'react'; -import CogWheelWithText from '../shared/components/CogWheelWithText'; +import CogWheelWithText from '../shared/components/loaders/CogWheelWithText'; import { strings } from '../shared/strings/strings'; import { redirect, useNavigate } from 'react-router-dom'; import { routesPath } from '../router/routes.path'; diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 98c7c13..dcf3e64 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,6 +1,6 @@ import { Button, Flex, Text } from '@mantine/core'; import React, { useContext, useEffect, useState } from 'react'; -import CogWheelWithText from '../shared/components/CogWheelWithText'; +import CogWheelWithText from '../shared/components/loaders/CogWheelWithText'; import { strings } from '../shared/strings/strings'; import { redirect, useNavigate } from 'react-router-dom'; import { routesPath } from '../router/routes.path'; diff --git a/src/pages/FrigateHostsPage.tsx b/src/pages/FrigateHostsPage.tsx index 777d60b..31a2684 100644 --- a/src/pages/FrigateHostsPage.tsx +++ b/src/pages/FrigateHostsPage.tsx @@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema} from '../services/frigate.proxy/frigate.schema'; import CenterLoader from '../shared/components/CenterLoader'; -import RetryError from './RetryError'; +import RetryErrorPage from './RetryErrorPage'; import { Context } from '..'; import { strings } from '../shared/strings/strings'; import { Button, Flex } from '@mantine/core'; @@ -71,7 +71,7 @@ const FrigateHostsPage = observer(() => { } if (hostsPending) return - if (hostsError) return + if (hostsError) return return (
{ diff --git a/src/pages/HostConfigPage.tsx b/src/pages/HostConfigPage.tsx index 153ad9f..18ce3d7 100644 --- a/src/pages/HostConfigPage.tsx +++ b/src/pages/HostConfigPage.tsx @@ -9,7 +9,7 @@ import { configureMonacoYaml } from "monaco-yaml"; import Editor, { DiffEditor, useMonaco, loader, Monaco } from '@monaco-editor/react' import * as monaco from "monaco-editor"; import CenterLoader from '../shared/components/CenterLoader'; -import RetryError from './RetryError'; +import RetryErrorPage from './RetryErrorPage'; const HostConfigPage = () => { @@ -74,7 +74,7 @@ const HostConfigPage = () => { if (configPending) return - if (configError) return + if (configError) return return ( diff --git a/src/pages/LiveCameraPage.tsx b/src/pages/LiveCameraPage.tsx index 5aaa799..a6b5c36 100644 --- a/src/pages/LiveCameraPage.tsx +++ b/src/pages/LiveCameraPage.tsx @@ -5,8 +5,8 @@ import { useParams } from 'react-router-dom'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { useQuery } from '@tanstack/react-query'; import CenterLoader from '../shared/components/CenterLoader'; -import RetryError from './RetryError'; -import Player from '../shared/components/frigate/Player'; +import RetryErrorPage from './RetryErrorPage'; +import Player from '../widgets/Player'; import { Flex } from '@mantine/core'; const LiveCameraPage = observer(() => { @@ -28,7 +28,7 @@ const LiveCameraPage = observer(() => { if (isPending) return - if (isError) return + if (isError) return return ( diff --git a/src/pages/MainBody.tsx b/src/pages/MainPage.tsx similarity index 84% rename from src/pages/MainBody.tsx rename to src/pages/MainPage.tsx index ae450bc..c5bbb0c 100644 --- a/src/pages/MainBody.tsx +++ b/src/pages/MainPage.tsx @@ -8,11 +8,12 @@ import { observer } from 'mobx-react-lite' import CenterLoader from '../shared/components/CenterLoader'; import { useQuery } from '@tanstack/react-query'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; -import RetryError from './RetryError'; +import RetryErrorPage from './RetryErrorPage'; import CameraCard from '../shared/components/CameraCard'; -const MainBody = observer(() => { +const MainPage = () => { const { sideBarsStore } = useContext(Context) + useEffect(() => { sideBarsStore.rightVisible = false sideBarsStore.setLeftChildren(null) @@ -32,11 +33,11 @@ const MainBody = observer(() => { if (isPending) return - if (isError) return + if (isError) return const cards = () => { - // return cameras.filter(cam => cam.frigateHost?.host.includes('5001')).slice(0,1).map(camera => ( - return cameras.map(camera => ( + return cameras.filter(cam => cam.frigateHost?.host.includes('5000')).slice(0,25).map(camera => ( + // return cameras.map(camera => ( { } return ( - + { - + {cards()} ); -}) +} -export default MainBody; \ No newline at end of file +export default observer(MainPage); \ No newline at end of file diff --git a/src/pages/RecordingsPage.tsx b/src/pages/RecordingsPage.tsx index 2573105..ee47501 100644 --- a/src/pages/RecordingsPage.tsx +++ b/src/pages/RecordingsPage.tsx @@ -9,12 +9,18 @@ import SelectedCameraList from '../widgets/SelectedCameraList'; import SelectedHostList from '../widgets/SelectedHostList'; import { useQuery } from '@tanstack/react-query'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; +import { dateToQueryString, parseQueryDateToDate } from '../shared/utils/dateUtil'; +import SelecteDayList from '../widgets/SelecteDayList'; +import { useDebouncedValue } from '@mantine/hooks'; +import CogwheelLoader from '../shared/components/loaders/CogwheelLoader'; +import CenterLoader from '../shared/components/CenterLoader'; -const recordingsQuery = { +export const recordingsPageQuery = { hostId: 'hostId', cameraId: 'cameraId', - date: 'date', + startDay: 'startDay', + endDay: 'endDay', hour: 'hour', } @@ -24,13 +30,16 @@ const RecordingsPage = observer(() => { const location = useLocation() const navigate = useNavigate() const queryParams = new URLSearchParams(location.search) - const paramHostId = queryParams.get(recordingsQuery.hostId) - const paramCameraId = queryParams.get(recordingsQuery.cameraId); - const paramDate = queryParams.get(recordingsQuery.date); - const paramTime = queryParams.get(recordingsQuery.hour); + const paramHostId = queryParams.get(recordingsPageQuery.hostId) + const paramCameraId = queryParams.get(recordingsPageQuery.cameraId); + const paramStartDay = queryParams.get(recordingsPageQuery.startDay); + const paramEndDay = queryParams.get(recordingsPageQuery.endDay); + const paramTime = queryParams.get(recordingsPageQuery.hour); const [hostId, setHostId] = useState('') const [cameraId, setCameraId] = useState('') + const [period, setPeriod] = useState<[Date | null, Date | null]>([null, null]) + const [firstRender, setFirstRender] = useState(false) useEffect(() => { sideBarsStore.rightVisible = true @@ -39,15 +48,21 @@ const RecordingsPage = observer(() => { ) if (paramHostId) recStore.hostIdParam = paramHostId if (paramCameraId) recStore.cameraIdParam = paramCameraId + if (paramStartDay && paramEndDay) { + const parsedStartDay = parseQueryDateToDate(paramStartDay) + const parsedEndDay = parseQueryDateToDate(paramEndDay) + recStore.selectedRange = [parsedStartDay, parsedEndDay] + } + setFirstRender(true) return () => sideBarsStore.setRightChildren(null) }, []) useEffect(() => { setHostId(recStore.selectedHost?.id || '') if (recStore.selectedHost) { - queryParams.set(recordingsQuery.hostId, recStore.selectedHost.id) + queryParams.set(recordingsPageQuery.hostId, recStore.selectedHost.id) } else { - queryParams.delete(recordingsQuery.hostId) + queryParams.delete(recordingsPageQuery.hostId) } navigate({ pathname: location.pathname, search: queryParams.toString() }); }, [recStore.selectedHost]) @@ -55,26 +70,61 @@ const RecordingsPage = observer(() => { useEffect(() => { setCameraId(recStore.selectedCamera?.id || '') if (recStore.selectedCamera) { - queryParams.set(recordingsQuery.cameraId, recStore.selectedCamera?.id) + queryParams.set(recordingsPageQuery.cameraId, recStore.selectedCamera?.id) } else { - console.log('delete recordingsQuery.cameraId') - queryParams.delete(recordingsQuery.cameraId) + queryParams.delete(recordingsPageQuery.cameraId) } navigate({ pathname: location.pathname, search: queryParams.toString() }); }, [recStore.selectedCamera]) - if (cameraId) { - return + useEffect(() => { + setPeriod(recStore.selectedRange) + const [startDay, endDay] = recStore.selectedRange + if (startDay && endDay) { + const startQuery = dateToQueryString(startDay) + const endQuery = dateToQueryString(endDay) + queryParams.set(recordingsPageQuery.startDay, startQuery) + queryParams.set(recordingsPageQuery.endDay, endQuery) + } else { + queryParams.delete(recordingsPageQuery.startDay) + queryParams.delete(recordingsPageQuery.endDay) + } + navigate({ pathname: location.pathname, search: queryParams.toString() }); + }, [recStore.selectedRange]) + + console.log('RecordingsPage rendered') + + if (!firstRender) return + + const [startDay, endDay] = period + if (startDay && endDay) { + if (startDay.getDate() === endDay.getDate()) { // if select only one day + return + } } - if (hostId) { + if (cameraId && paramCameraId) { + // console.log('cameraId', cameraId) + // console.log('paramCameraId', paramCameraId) + if ((startDay && endDay) || (!startDay && !endDay)) { + return + // return + } + } + + if (hostId && paramHostId && !cameraId) { return } - console.log('RecordingsPage rendered') return ( - Please select host + {!hostId ? + Please select host + : <>} + {hostId && !(startDay && endDay) ? + Please select date + : <> + } ) }) diff --git a/src/pages/RetryError.tsx b/src/pages/RetryErrorPage.tsx similarity index 92% rename from src/pages/RetryError.tsx rename to src/pages/RetryErrorPage.tsx index 9a081c3..5967487 100644 --- a/src/pages/RetryError.tsx +++ b/src/pages/RetryErrorPage.tsx @@ -6,11 +6,11 @@ import { useNavigate } from 'react-router-dom'; import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel'; import { Context } from '..'; -interface RetryErrorProps { +interface RetryErrorPageProps { onRetry?: () => void } -const RetryError = ({ onRetry }: RetryErrorProps) => { +const RetryErrorPage = ({ onRetry }: RetryErrorPageProps) => { const navigate = useNavigate() const { sideBarsStore } = useContext(Context) @@ -48,4 +48,4 @@ const RetryError = ({ onRetry }: RetryErrorProps) => { ); }; -export default RetryError; \ No newline at end of file +export default RetryErrorPage; \ No newline at end of file diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 568da83..10246e0 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -6,7 +6,7 @@ import { } from '@tanstack/react-query' import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import CenterLoader from '../shared/components/CenterLoader'; -import RetryError from './RetryError'; +import RetryErrorPage from './RetryErrorPage'; import { Button, Flex, Space } from '@mantine/core'; import { FloatingLabelInput } from '../shared/components/FloatingLabelInput'; import { strings } from '../shared/strings/strings'; @@ -83,7 +83,7 @@ const SettingsPage = () => { if (configPending) return - if (configError) return + if (configError) return return ( diff --git a/src/pages/Test.tsx b/src/pages/Test.tsx deleted file mode 100644 index c90a119..0000000 --- a/src/pages/Test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React, { Fragment, useContext, useEffect } from 'react'; -import { observer } from 'mobx-react-lite'; -import JSMpegPlayer from '../shared/components/frigate/JSMpegPlayer'; -import { frigateApi } from '../services/frigate.proxy/frigate.api'; -import { Flex } from '@mantine/core'; -import AutoUpdatingCameraImage from '../shared/components/frigate/AutoUpdatingCameraImage'; - -const Test = observer(() => { - // const test = { - // camera: 'Buhgalteria', - // host: 'localhost:5000', - // width: 800, - // height: 600, - // url : function() { return frigateApi.cameraWsURL(this.host, this.camera)}, - // } - - // return ( - // - // - // - // ); - - return ( - - - - ); -}) - -export default Test; \ No newline at end of file diff --git a/src/pages/TestItem.tsx b/src/pages/TestItem.tsx new file mode 100644 index 0000000..cc6ae19 --- /dev/null +++ b/src/pages/TestItem.tsx @@ -0,0 +1,18 @@ +import { Paper } from '@mantine/core'; +import { useIntersection } from '@mantine/hooks'; +import React from 'react'; + +const TestItem = () => { + + const { ref, entry } = useIntersection({threshold: 0.1,}) + + return ( ({ + backgroundColor: entry?.isIntersecting ? theme.colors.green[9] : theme.colors.red[9], + })} + />) +}; + +export default TestItem; \ No newline at end of file diff --git a/src/pages/TestPage.tsx b/src/pages/TestPage.tsx new file mode 100644 index 0000000..741bbd1 --- /dev/null +++ b/src/pages/TestPage.tsx @@ -0,0 +1,59 @@ +import React, { Fragment, useContext, useEffect, useRef, useState } from 'react'; +import { observer } from 'mobx-react-lite'; +import { Button, Flex, Grid, Group, Indicator, Paper, Skeleton } from '@mantine/core'; +import RetryError from '../shared/components/RetryError'; +import { DatePickerInput } from '@mantine/dates'; +import HeadSearch from '../shared/components/HeadSearch'; +import ViewSelector from '../shared/components/ViewSelector'; +import { useIntersection } from '@mantine/hooks'; +import TestItem from './TestItem'; + +const Test = observer(() => { + const [value, setValue] = useState<[Date | null, Date | null]>([null, null]) + + useEffect(() => { + console.log('value', value) + }, [value]) + + const handleClick = () => { + const startOfDay = new Date(); + startOfDay.setHours(0, 0, 0, 0); + setValue([startOfDay, startOfDay]) + } + + const cards = (qty: number) => { + let items = [] + for (let i = 0; i < qty; i++) { + items.push() + } + return items + } + + return ( + + + + + + + + + + + {cards(60)} + + + + ); +}) + +export default Test; \ No newline at end of file diff --git a/src/router/routes.tsx b/src/router/routes.tsx index 43cca09..ca2ece1 100644 --- a/src/router/routes.tsx +++ b/src/router/routes.tsx @@ -1,8 +1,8 @@ import {JSX} from "react"; -import Test from "../pages/Test" -import MainBody from "../pages/MainBody"; +import Test from "../pages/TestPage" +import MainPage from "../pages/MainPage"; import {routesPath} from "./routes.path"; -import RetryError from "../pages/RetryError"; +import RetryErrorPage from "../pages/RetryErrorPage"; import Forbidden from "../pages/403"; import NotFound from "../pages/404"; import SettingsPage from "../pages/SettingsPage"; @@ -53,11 +53,11 @@ export const routes: IRoute[] = [ }, { path: routesPath.MAIN_PATH, - component: , + component: , }, { path: routesPath.RETRY_ERROR_PATH, - component: , + component: , }, { path: routesPath.FORBIDDEN_ERROR_PATH, diff --git a/src/shared/components/frigate/CameraImage.tsx b/src/shared/components/AutoUpdatedImage.tsx similarity index 51% rename from src/shared/components/frigate/CameraImage.tsx rename to src/shared/components/AutoUpdatedImage.tsx index 956d426..8258949 100644 --- a/src/shared/components/frigate/CameraImage.tsx +++ b/src/shared/components/AutoUpdatedImage.tsx @@ -1,12 +1,13 @@ import { useEffect, useRef } from "react"; -import { CameraConfig } from "../../../types/frigateConfig"; -import { AspectRatio, Flex, createStyles, Text } from "@mantine/core"; +import { CameraConfig } from "../../types/frigateConfig"; +import { Flex, Text } from "@mantine/core"; import { useQuery } from "@tanstack/react-query"; -import CenterLoader from "../CenterLoader"; -import axios from "axios"; -import { frigateApi, proxyApi } from "../../../services/frigate.proxy/frigate.api"; +import CenterLoader from "./CenterLoader"; +import { frigateApi, proxyApi } from "../../services/frigate.proxy/frigate.api"; +import { useIntersection } from "@mantine/hooks"; +import CogwheelLoader from "./loaders/CogwheelLoader"; -interface CameraImageProps extends React.ImgHTMLAttributes { +interface AutoUpdatedImageProps extends React.ImgHTMLAttributes { className?: string; cameraConfig?: CameraConfig; onload?: () => void; @@ -18,26 +19,29 @@ const AutoUpdatedImage = ({ imageUrl, enabled, ...rest -}: CameraImageProps) => { +}: AutoUpdatedImageProps) => { + const { ref, entry } = useIntersection({threshold: 0.1,}) + const isVisible = entry?.isIntersecting + const { data: imageBlob, refetch, isPending, isError } = useQuery({ queryKey: ['image', imageUrl], queryFn: () => proxyApi.getImageFrigate(imageUrl), staleTime: 60 * 1000, gcTime: Infinity, - refetchInterval: 60 * 1000, + refetchInterval: isVisible ? 30 * 1000 : undefined, }); useEffect(() => { - const intervalId = setInterval(() => { - refetch(); - }, 60 * 1000); + if (isVisible) { + console.log('imageUrl is visible') + const intervalId = setInterval(() => { + refetch(); + }, 60 * 1000); + return () => clearInterval(intervalId); + } + }, [refetch, isVisible]); - return () => clearInterval(intervalId); - }, [refetch]); - - - - if (isPending) return + if (isPending) return if (isError) return ( @@ -51,7 +55,7 @@ const AutoUpdatedImage = ({ return ( <> - {enabled ? Dynamic Content + {enabled ? Dynamic Content : Camera is disabled in config, no stream or snapshot available! diff --git a/src/shared/components/CameraCard.tsx b/src/shared/components/CameraCard.tsx index acbc99a..2e17559 100644 --- a/src/shared/components/CameraCard.tsx +++ b/src/shared/components/CameraCard.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { CameraConfig } from '../../types/frigateConfig'; import { AspectRatio, Button, Card, Flex, Grid, Group, Space, Text, createStyles, useMantineTheme } from '@mantine/core'; -import AutoUpdatingCameraImage from './frigate/AutoUpdatingCameraImage'; import { useNavigate } from 'react-router-dom'; import { routesPath } from '../../router/routes.path'; import { GetCameraWHostWConfig, GetFrigateHost } from '../../services/frigate.proxy/frigate.schema'; import { frigateApi, mapHostToHostname, proxyApi } from '../../services/frigate.proxy/frigate.api'; -import AutoUpdatedImage from './frigate/CameraImage'; +import AutoUpdatedImage from './AutoUpdatedImage'; +import { recordingsPageQuery } from '../../pages/RecordingsPage'; const useStyles = createStyles((theme) => ({ @@ -50,7 +50,8 @@ const CameraCard = ({ navigate(url) } const handleOpenRecordings = () => { - throw Error('Not yet implemented') + const url = `${routesPath.RECORDINGS_PATH}?${recordingsPageQuery.hostId}=${camera.frigateHost?.id}&${recordingsPageQuery.cameraId}=${camera.id}` + navigate(url) } return ( diff --git a/src/shared/components/FullProductModal.tsx b/src/shared/components/FullProductModal.tsx index 9851c66..cd8aec0 100644 --- a/src/shared/components/FullProductModal.tsx +++ b/src/shared/components/FullProductModal.tsx @@ -7,7 +7,7 @@ import { Modal, createStyles, getStylesRef, rem, Text, Box, Flex, Grid, Divider, import { useMediaQuery } from '@mantine/hooks'; import { dimensions } from '../dimensions/dimensions'; import { observer } from 'mobx-react-lite'; -import KomponentLoader from './CogwheelLoader'; +import KomponentLoader from './loaders/CogwheelLoader'; import ProductParameter from './ProductParameter'; import { productString } from '../strings/product.strings'; import { IconArrowBadgeLeft, IconArrowBadgeRight } from '@tabler/icons-react'; diff --git a/src/shared/components/RetryError.tsx b/src/shared/components/RetryError.tsx new file mode 100644 index 0000000..79438be --- /dev/null +++ b/src/shared/components/RetryError.tsx @@ -0,0 +1,25 @@ +import { Center, Text, ActionIcon } from '@mantine/core'; +import { IconRotateClockwise } from '@tabler/icons-react'; +import React from 'react'; + +interface RetryErrorProps { + onRetry?: () => void +} + +const RetryError = ({ + onRetry +}: RetryErrorProps) => { + const handleClick = () => { + if (onRetry) onRetry() + } + return ( +
+ Loading error + + + +
+ ); +}; + +export default RetryError; \ No newline at end of file diff --git a/src/shared/components/accordion/CameraAccordion.tsx b/src/shared/components/accordion/CameraAccordion.tsx index 8f1ca5a..dd95b8d 100644 --- a/src/shared/components/accordion/CameraAccordion.tsx +++ b/src/shared/components/accordion/CameraAccordion.tsx @@ -1,4 +1,4 @@ -import { Accordion, Center, Text } from '@mantine/core'; +import { Accordion, Center, Loader, Text } from '@mantine/core'; import React, { useContext, useEffect, useState } from 'react'; import { GetCameraWHostWConfig, GetFrigateHost } from '../../../services/frigate.proxy/frigate.schema'; import { useQuery } from '@tanstack/react-query'; @@ -6,20 +6,23 @@ import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services import DayAccordion from './DayAccordion'; import { observer } from 'mobx-react-lite'; import { Context } from '../../..'; -import { getResolvedTimeZone } from '../frigate/dateUtil'; +import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil'; +import RetryError from '../RetryError'; +import { strings } from '../../strings/strings'; +import { RecordSummary } from '../../../types/record'; interface CameraAccordionProps { camera: GetCameraWHostWConfig, host: GetFrigateHost } -const CameraAccordion = observer(({ +const CameraAccordion = ({ camera, host }: CameraAccordionProps) => { const { recordingsStore: recStore } = useContext(Context) - const { data, isPending, isError } = useQuery({ + const { data, isPending, isError, refetch } = useQuery({ queryKey: [frigateQueryKeys.getRecordingsSummary, camera?.id], queryFn: () => { if (camera && host) { @@ -44,29 +47,46 @@ const CameraAccordion = observer(({ setOpenedDay(value) } - if (isPending) return
Loading...
- if (isError) return
Loading error
+ if (isPending) return
+ if (isError) return if (!data || !camera) return null - - const days = data.slice(0, 2).map(rec => ( - - {rec.day} - - + const recodItem = (record: RecordSummary) => ( + + {strings.day}: {record.day} + + + ) - )) + const days = () => { + const [startDate, endDate] = recStore.selectedRange + if (startDate && endDate) { + return data + .filter(rec => { + const parsedRecDate = parseQueryDateToDate(rec.day) + if (parsedRecDate) { + return parsedRecDate >= startDate && parsedRecDate <= endDate + } + return false + }) + .map(rec => recodItem(rec)) + } + if ((startDate && endDate) || (!startDate && !endDate)) { + return data.map(rec => recodItem(rec)) + } + return [] + } console.log('CameraAccordion rendered') return ( - {days} + {days()} ) -}) +} -export default CameraAccordion; \ No newline at end of file +export default observer(CameraAccordion); \ No newline at end of file diff --git a/src/shared/components/accordion/DayAccordion.tsx b/src/shared/components/accordion/DayAccordion.tsx index c29c330..edc14fb 100644 --- a/src/shared/components/accordion/DayAccordion.tsx +++ b/src/shared/components/accordion/DayAccordion.tsx @@ -1,25 +1,23 @@ -import { Accordion, Center, Flex, Group, Text } from '@mantine/core'; +import { Accordion, Center, Group, Text } from '@mantine/core'; import React, { useContext, useEffect, useState } from 'react'; -import { RecordHour, RecordSummary, Recording } from '../../../types/record'; -import Button from '../frigate/Button'; -import { IconPlayerPause, IconPlayerPlay, IconPlayerStop } from '@tabler/icons-react'; +import { RecordSummary } from '../../../types/record'; import { observer } from 'mobx-react-lite'; import PlayControl from './PlayControl'; -import { frigateApi, proxyApi } from '../../../services/frigate.proxy/frigate.api'; +import { proxyApi } from '../../../services/frigate.proxy/frigate.api'; import { Context } from '../../..'; -import VideoPlayer from '../frigate/VideoPlayer'; -import { getResolvedTimeZone } from '../frigate/dateUtil'; -import EventsAccordion from './EventsAccordion'; +import VideoPlayer from '../players/VideoPlayer'; +import { getResolvedTimeZone } from '../../utils/dateUtil'; import DayEventsAccordion from './DayEventsAccordion'; +import { strings } from '../../strings/strings'; interface RecordingAccordionProps { recordSummary?: RecordSummary } -const DayAccordion = observer(({ +const DayAccordion = ({ recordSummary }: RecordingAccordionProps) => { - const { recordingsStore } = useContext(Context) + const { recordingsStore: recStore } = useContext(Context) const [openVideoPlayer, setOpenVideoPlayer] = useState() const [openedValue, setOpenedValue] = useState() const [playerUrl, setPlayerUrl] = useState() @@ -28,11 +26,11 @@ const DayAccordion = observer(({ if (openVideoPlayer) { console.log('openVideoPlayer', openVideoPlayer) if (openVideoPlayer) { - recordingsStore.recordToPlay.day = recordSummary?.day - recordingsStore.recordToPlay.hour = openVideoPlayer - recordingsStore.recordToPlay.timezone = getResolvedTimeZone().replace('/', ',') - const parsed = recordingsStore.getFullRecordForPlay(recordingsStore.recordToPlay) - console.log('recordingsStore.playedRecord: ', recordingsStore.recordToPlay) + recStore.recordToPlay.day = recordSummary?.day + recStore.recordToPlay.hour = openVideoPlayer + recStore.recordToPlay.timezone = getResolvedTimeZone().replace('/', ',') + const parsed = recStore.getFullRecordForPlay(recStore.recordToPlay) + console.log('recordingsStore.playedRecord: ', recStore.recordToPlay) if (parsed.success) { const url = proxyApi.recordingURL( parsed.data.hostName, @@ -50,10 +48,10 @@ const DayAccordion = observer(({ } }, [openVideoPlayer]) - if (!recordSummary || recordSummary.hours.length < 1) return (Not have record at that day) + if (!recordSummary ) return Not have record at that day + if (recordSummary.hours.length < 1) return Not have record at that day const handleOpenPlayer = (hour: string) => { - // console.log(`openVideoPlayer day:${recordSummary.day} hour:${hour}`) if (openVideoPlayer !== hour) { setOpenedValue(hour) setOpenVideoPlayer(hour) @@ -73,6 +71,17 @@ const DayAccordion = observer(({ console.log('DayAccordion rendered') + const hourLabel = (hour: string, eventsQty: number) => ( + + {strings.hour}: {hour}:00 + {eventsQty > 0 ? + {strings.events}: {eventsQty} + : + {strings.notHaveEvents} + } + + ) + return ( - {recordSummary.hours.slice(0, 5).map(hour => ( + {recordSummary.hours.map(hour => ( - + {openVideoPlayer === hour.hour && playerUrl ? : <>} {hour.events > 0 ? : -
Not have events
+
{strings.notHaveEvents}
}
))}
) -}) +} -export default DayAccordion; \ No newline at end of file +export default observer(DayAccordion); \ No newline at end of file diff --git a/src/shared/components/accordion/DayEventsAccordion.tsx b/src/shared/components/accordion/DayEventsAccordion.tsx index 05ea739..c673e4b 100644 --- a/src/shared/components/accordion/DayEventsAccordion.tsx +++ b/src/shared/components/accordion/DayEventsAccordion.tsx @@ -1,5 +1,6 @@ import { Accordion, Text } from '@mantine/core'; import React, { Suspense, lazy, useState } from 'react'; +import { strings } from '../../strings/strings'; const EventsAccordion = lazy(() => import('./EventsAccordion')) interface DayEventsAccordionProps { @@ -21,7 +22,7 @@ const DayEventsAccordion = ({ return ( - Events {qty} + {strings.events}: {qty} {openedItem === hour ? diff --git a/src/shared/components/accordion/EventsAccordion.tsx b/src/shared/components/accordion/EventsAccordion.tsx index 0e94e65..fc714c7 100644 --- a/src/shared/components/accordion/EventsAccordion.tsx +++ b/src/shared/components/accordion/EventsAccordion.tsx @@ -1,13 +1,16 @@ -import { Accordion, Center, Text } from '@mantine/core'; +import { Accordion, Center, Group, Loader, Text } from '@mantine/core'; import { observer } from 'mobx-react-lite'; import React, { useContext, useEffect, useState } from 'react'; import { Context } from '../../..'; import { useQuery } from '@tanstack/react-query'; -import { frigateQueryKeys, proxyApi } from '../../../services/frigate.proxy/frigate.api'; +import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api'; import { getEventsQuerySchema } from '../../../services/frigate.proxy/frigate.schema'; import PlayControl from './PlayControl'; -import VideoPlayer from '../frigate/VideoPlayer'; -import { formatUnixTimestampToDateTime, getDurationFromTimestamps, getUnixTime, unixTimeToDate } from '../frigate/dateUtil'; +import VideoPlayer from '../players/VideoPlayer'; +import { getDurationFromTimestamps, getUnixTime, unixTimeToDate } from '../../utils/dateUtil'; +import RetryError from '../RetryError'; +import { strings } from '../../strings/strings'; +import { EventFrigate } from '../../../types/event'; /** * @param day frigate format, e.g day: 2024-02-23 @@ -31,8 +34,6 @@ interface EventsAccordionProps { const EventsAccordion = observer(({ day, hour, - cameraName, - hostName, // TODO labels, score }: EventsAccordionProps) => { const { recordingsStore: recStore } = useContext(Context) @@ -40,17 +41,17 @@ const EventsAccordion = observer(({ const [openedValue, setOpenedValue] = useState() const [playerUrl, setPlayerUrl] = useState() - const inHostName = hostName || recStore.recordToPlay.hostName - const inCameraName = cameraName || recStore.recordToPlay.cameraName - const isRequiredParams = inCameraName && inHostName + const inHost = recStore.selectedHost + const inCamera = recStore.selectedCamera + const isRequiredParams = inHost && inCamera const { data, isPending, isError, refetch } = useQuery({ - queryKey: [frigateQueryKeys.getEvents, day, hour, inCameraName, inHostName], + queryKey: [frigateQueryKeys.getEvents, inHost, inCamera, day, hour], queryFn: () => { if (!isRequiredParams) return null const [startTime, endTime] = getUnixTime(day, hour) const parsed = getEventsQuerySchema.safeParse({ - hostName: inHostName, - camerasName: [inCameraName], + hostName: mapHostToHostname(inHost), + camerasName: [inCamera.name], after: startTime, before: endTime, hasClip: true, @@ -78,8 +79,8 @@ const EventsAccordion = observer(({ useEffect(() => { if (openVideoPlayer) { console.log('openVideoPlayer', openVideoPlayer) - if (openVideoPlayer && inHostName) { - const url = proxyApi.eventURL(inHostName, openVideoPlayer) + if (openVideoPlayer && inHost) { + const url = proxyApi.eventURL(mapHostToHostname(inHost), openVideoPlayer) console.log('GET EVENT URL: ', url) setPlayerUrl(url) } @@ -88,8 +89,8 @@ const EventsAccordion = observer(({ } }, [openVideoPlayer]) - if (isPending) return
Loading...
- if (isError) return
Loading error
+ if (isPending) return
+ if (isError) return if (!data || data.length < 1) return
Not have events at that period
const handleOpenPlayer = (eventId: string) => { @@ -111,6 +112,20 @@ const EventsAccordion = observer(({ setOpenVideoPlayer(undefined) } + const eventLabel = (event: EventFrigate) => { + const time = unixTimeToDate(event.start_time) + const duration = getDurationFromTimestamps(event.start_time, event.end_time) + return ( + + {strings.player.object}: {event.label} + {time} + {duration ? + {duration} + : <>} + + ) + } + return ( - {data.slice(0, 5).map(event => ( + {data.map(event => ( {openVideoPlayer === event.id && playerUrl ? : <>} - Camera: {event.camera} - Label: {event.label} - Start: {unixTimeToDate(event.start_time)} - Duration: {getDurationFromTimestamps(event.start_time, event.end_time)} + + {strings.camera}: {event.camera} + {strings.player.object}: {event.label} + + + {strings.player.startTime}: {unixTimeToDate(event.start_time)} + {strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)} + ))} diff --git a/src/shared/components/accordion/PlayControl.tsx b/src/shared/components/accordion/PlayControl.tsx index 0208329..bad8324 100644 --- a/src/shared/components/accordion/PlayControl.tsx +++ b/src/shared/components/accordion/PlayControl.tsx @@ -1,9 +1,33 @@ -import { Flex, Group, Text } from '@mantine/core'; -import { IconPlayerPlay, IconPlayerStop } from '@tabler/icons-react'; import React from 'react'; +import { Flex, Group, Text, createStyles } from '@mantine/core'; +import { IconPlayerPlay, IconPlayerPlayFilled, IconPlayerStop, IconPlayerStopFilled } from '@tabler/icons-react'; +import { strings } from '../../strings/strings'; + + +const useStyles = createStyles((theme) => ({ + group: { + backgroundColor: theme.colors.blue[7], + borderRadius: '1rem', + paddingLeft: '0.5rem', + paddingRight: '0.3rem', + '&:hover': { + backgroundColor: theme.fn.darken(theme.colors.blue[7], 0.2), + }, + }, + text: { + color: theme.white, + fontWeight: 'normal' + }, + iconStop: { + color: theme.colors.red[5] + }, + iconPlay: { + color: theme.colors.green[5] + } +})) interface PlayControlProps { - label: string, + label: string | JSX.Element, value: string, openVideoPlayer?: string, onClick?: (value: string) => void @@ -15,26 +39,38 @@ const PlayControl = ({ openVideoPlayer, onClick }: PlayControlProps) => { + const { classes } = useStyles(); + const handleClick = (value: string) => { if (onClick) onClick(value) } return ( {label} - - { + { + event.stopPropagation() + handleClick(value) + }} + > + { event.stopPropagation() handleClick(value) }}> - {openVideoPlayer === value ? 'Stop Video' : 'Play Video'} + {openVideoPlayer === value ? strings.player.stopVideo : strings.player.startVideo} {openVideoPlayer === value ? - { + { event.stopPropagation() handleClick(value) }} /> : - { + { event.stopPropagation() handleClick(value) }} /> diff --git a/src/shared/components/accordion/TestAccordion.tsx b/src/shared/components/accordion/TestAccordion.tsx deleted file mode 100644 index a690bda..0000000 --- a/src/shared/components/accordion/TestAccordion.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const TestAccordion = ({ camera }: { camera: any }) => { - return ( -
- TEST ACCORDION {camera.name} -
- ); -}; - -export default TestAccordion; \ No newline at end of file diff --git a/src/shared/components/filters.aps/CameraSelectFilter.tsx b/src/shared/components/filters.aps/CameraSelectFilter.tsx index 08c75db..768dcc6 100644 --- a/src/shared/components/filters.aps/CameraSelectFilter.tsx +++ b/src/shared/components/filters.aps/CameraSelectFilter.tsx @@ -3,9 +3,11 @@ import React, { useContext, useEffect } from 'react'; import { Context } from '../../..'; import { useQuery } from '@tanstack/react-query'; import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api'; -import CogwheelLoader from '../CogwheelLoader'; -import { Center, Text } from '@mantine/core'; +import CogwheelLoader from '../loaders/CogwheelLoader'; +import { Center, Loader, Text } from '@mantine/core'; import OneSelectFilter, { OneSelectItem } from './OneSelectFilter'; +import { strings } from '../../strings/strings'; +import RetryError from '../RetryError'; interface CameraSelectFilterProps { selectedHostId: string, @@ -16,7 +18,7 @@ const CameraSelectFilter = ({ }: CameraSelectFilterProps) => { const { recordingsStore: recStore } = useContext(Context) - const { data, isError, isPending, isSuccess } = useQuery({ + const { data, isError, isPending, isSuccess, refetch } = useQuery({ queryKey: [frigateQueryKeys.getFrigateHost, selectedHostId], queryFn: () => frigateApi.getHost(selectedHostId) }) @@ -30,8 +32,8 @@ const CameraSelectFilter = ({ } }, [isSuccess]) - if (isPending) return - if (isError) return
Loading error!
+ if (isPending) return + if (isError) return if (!data) return null const camerasItems: OneSelectItem[] = data.cameras.map(camera => ({ value: camera.id, label: camera.name })) @@ -51,7 +53,7 @@ const CameraSelectFilter = ({ return ( { + const { recordingsStore: recStore } = useContext(Context) + + const handlePick = (value: [Date | null, Date | null]) => { + console.log('handlePick',value) + recStore.selectedRange = value + } + + console.log('DateRangeSelectFilter rendered') + return ( + + + {strings.selectRange} + + { + const day = date.getDate(); + const now = new Date().getDate() + return ( + +
{day}
+
+ ); + }} + /> +
+ ); +}; + + + +export default observer(DateRangeSelectFilter); \ No newline at end of file diff --git a/src/shared/components/filters.aps/HostSelectFilter.tsx b/src/shared/components/filters.aps/HostSelectFilter.tsx new file mode 100644 index 0000000..03d1402 --- /dev/null +++ b/src/shared/components/filters.aps/HostSelectFilter.tsx @@ -0,0 +1,63 @@ +import { Center, Text } from '@mantine/core'; +import { useQuery } from '@tanstack/react-query'; +import { observer } from 'mobx-react-lite'; +import React, { useContext, useEffect } from 'react'; +import { Context } from '../../..'; +import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api'; +import { strings } from '../../strings/strings'; +import CogwheelLoader from '../loaders/CogwheelLoader'; +import OneSelectFilter, { OneSelectItem } from './OneSelectFilter'; +import RetryError from '../RetryError'; + +const HostSelectFilter = () => { + const { recordingsStore: recStore } = useContext(Context) + + const { data: hosts, isError, isPending, isSuccess, refetch } = useQuery({ + queryKey: [frigateQueryKeys.getFrigateHosts], + queryFn: frigateApi.getHosts + }) + + useEffect(() => { + if (!hosts) return + if (recStore.hostIdParam) { + recStore.selectedHost = hosts.find(host => host.id === recStore.hostIdParam) + recStore.hostIdParam = undefined + } + }, [isSuccess]) + + if (isPending) return + if (isError) return + + if (!hosts || hosts.length < 1) return null + + const hostItems: OneSelectItem[] = hosts + .filter(host => host.enabled) + .map(host => ({ value: host.id, label: host.name })) + + const handleSelect = (id: string, value: string) => { + const host = hosts?.find(host => host.id === value) + if (!host) { + recStore.selectedHost = undefined + recStore.selectedCamera = undefined + return + } + if (recStore.selectedHost?.id !== host.id) { + recStore.selectedCamera = undefined + } + recStore.selectedHost = host + } + + return ( + + ); +}; + +export default observer(HostSelectFilter); \ No newline at end of file diff --git a/src/shared/components/frigate/AutoUpdatingCameraImage.tsx b/src/shared/components/frigate/AutoUpdatingCameraImage.tsx index 5630c54..75f3e80 100644 --- a/src/shared/components/frigate/AutoUpdatingCameraImage.tsx +++ b/src/shared/components/frigate/AutoUpdatingCameraImage.tsx @@ -1,97 +1,99 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import CameraImage from "./CameraImage"; -import { CameraConfig } from "../../../types/frigateConfig"; -import { useDocumentVisibility } from "@mantine/hooks"; -import { AspectRatio, Flex } from "@mantine/core"; +// import { useCallback, useEffect, useMemo, useState } from "react"; +// import CameraImage from "./CameraImage"; +// import { CameraConfig } from "../../../types/frigateConfig"; +// import { useDocumentVisibility } from "@mantine/hooks"; +// import { AspectRatio, Flex } from "@mantine/core"; -interface AutoUpdatingCameraImageProps extends React.ImgHTMLAttributes { - cameraConfig?: CameraConfig - searchParams?: {}; - showFps?: boolean; - className?: string; - url: string -}; +export {} -// TODO Delete -export default function AutoUpdatingCameraImage({ - cameraConfig, - searchParams = "", - showFps = true, - className, - url, - ...rest -}: AutoUpdatingCameraImageProps) { - const [key, setKey] = useState(Date.now()); - const [fps, setFps] = useState("0"); - const [timeoutId, setTimeoutId] = useState(); +// interface AutoUpdatingCameraImageProps extends React.ImgHTMLAttributes { +// cameraConfig?: CameraConfig +// searchParams?: {}; +// showFps?: boolean; +// className?: string; +// url: string +// }; - const windowVisible = useDocumentVisibility() +// // TODO Delete +// export default function AutoUpdatingCameraImage({ +// cameraConfig, +// searchParams = "", +// showFps = true, +// className, +// url, +// ...rest +// }: AutoUpdatingCameraImageProps) { +// const [key, setKey] = useState(Date.now()); +// const [fps, setFps] = useState("0"); +// const [timeoutId, setTimeoutId] = useState(); + +// const windowVisible = useDocumentVisibility() - const reloadInterval = useMemo(() => { - if (windowVisible === "hidden") { - return -1; // no reason to update the image when the window is not visible - } +// const reloadInterval = useMemo(() => { +// if (windowVisible === "hidden") { +// return -1; // no reason to update the image when the window is not visible +// } - // if (liveReady) { - // return 60000; - // } +// // if (liveReady) { +// // return 60000; +// // } - // if (cameraActive) { - // return 200; - // } +// // if (cameraActive) { +// // return 200; +// // } - return 30000; - }, [windowVisible]); +// return 30000; +// }, [windowVisible]); - useEffect(() => { - if (reloadInterval == -1) { - return; - } +// useEffect(() => { +// if (reloadInterval == -1) { +// return; +// } - setKey(Date.now()); +// setKey(Date.now()); - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - setTimeoutId(undefined); - } - }; - }, [reloadInterval]); +// return () => { +// if (timeoutId) { +// clearTimeout(timeoutId); +// setTimeoutId(undefined); +// } +// }; +// }, [reloadInterval]); - const handleLoad = useCallback(() => { - if (reloadInterval == -1) { - return; - } +// const handleLoad = useCallback(() => { +// if (reloadInterval == -1) { +// return; +// } - const loadTime = Date.now() - key; +// const loadTime = Date.now() - key; - if (showFps) { - setFps((1000 / Math.max(loadTime, reloadInterval)).toFixed(1)); - } +// if (showFps) { +// setFps((1000 / Math.max(loadTime, reloadInterval)).toFixed(1)); +// } - setTimeoutId( - setTimeout( - () => { - setKey(Date.now()); - }, - loadTime > reloadInterval ? 1 : reloadInterval - ) - ); - }, [key, setFps]); +// setTimeoutId( +// setTimeout( +// () => { +// setKey(Date.now()); +// }, +// loadTime > reloadInterval ? 1 : reloadInterval +// ) +// ); +// }, [key, setFps]); - return ( - // - - {/* */} - {showFps ? Displaying at {fps}fps : null} - - // - ); -} +// return ( +// // +// +// {/* */} +// {showFps ? Displaying at {fps}fps : null} +// +// // +// ); +// } diff --git a/src/shared/components/frigate/Button.jsx b/src/shared/components/frigate/Button.jsx index f422e40..87d6b3d 100644 --- a/src/shared/components/frigate/Button.jsx +++ b/src/shared/components/frigate/Button.jsx @@ -1,115 +1,117 @@ -import Tooltip from './Tooltip'; -import { Fragment, useCallback, useRef, useState } from 'react'; +// import Tooltip from './Tooltip'; +// import { Fragment, useCallback, useRef, useState } from 'react'; -const ButtonColors = { - blue: { - contained: 'bg-blue-500 focus:bg-blue-400 active:bg-blue-600 ring-blue-300', - outlined: - 'text-blue-500 border-2 border-blue-500 hover:bg-blue-500 hover:bg-opacity-20 focus:bg-blue-500 focus:bg-opacity-40 active:bg-blue-500 active:bg-opacity-40', - text: 'text-blue-500 hover:bg-blue-500 hover:bg-opacity-20 focus:bg-blue-500 focus:bg-opacity-40 active:bg-blue-500 active:bg-opacity-40', - iconOnly: 'text-blue-500 hover:text-blue-200', - }, - red: { - contained: 'bg-red-500 focus:bg-red-400 active:bg-red-600 ring-red-300', - outlined: - 'text-red-500 border-2 border-red-500 hover:bg-red-500 hover:bg-opacity-20 focus:bg-red-500 focus:bg-opacity-40 active:bg-red-500 active:bg-opacity-40', - text: 'text-red-500 hover:bg-red-500 hover:bg-opacity-20 focus:bg-red-500 focus:bg-opacity-40 active:bg-red-500 active:bg-opacity-40', - iconOnly: 'text-red-500 hover:text-red-200', - }, - yellow: { - contained: 'bg-yellow-500 focus:bg-yellow-400 active:bg-yellow-600 ring-yellow-300', - outlined: - 'text-yellow-500 border-2 border-yellow-500 hover:bg-yellow-500 hover:bg-opacity-20 focus:bg-yellow-500 focus:bg-opacity-40 active:bg-yellow-500 active:bg-opacity-40', - text: 'text-yellow-500 hover:bg-yellow-500 hover:bg-opacity-20 focus:bg-yellow-500 focus:bg-opacity-40 active:bg-yellow-500 active:bg-opacity-40', - iconOnly: 'text-yellow-500 hover:text-yellow-200', - }, - green: { - contained: 'bg-green-500 focus:bg-green-400 active:bg-green-600 ring-green-300', - outlined: - 'text-green-500 border-2 border-green-500 hover:bg-green-500 hover:bg-opacity-20 focus:bg-green-500 focus:bg-opacity-40 active:bg-green-500 active:bg-opacity-40', - text: 'text-green-500 hover:bg-green-500 hover:bg-opacity-20 focus:bg-green-500 focus:bg-opacity-40 active:bg-green-500 active:bg-opacity-40', - iconOnly: 'text-green-500 hover:text-green-200', - }, - gray: { - contained: 'bg-gray-500 focus:bg-gray-400 active:bg-gray-600 ring-gray-300', - outlined: - 'text-gray-500 border-2 border-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', - text: 'text-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', - iconOnly: 'text-gray-500 hover:text-gray-200', - }, - disabled: { - contained: 'bg-gray-400', - outlined: - 'text-gray-500 border-2 border-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', - text: 'text-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', - iconOnly: 'text-gray-500 hover:text-gray-200', - }, - black: { - contained: '', - outlined: '', - text: 'text-black dark:text-white', - iconOnly: '', - }, -}; +export {} -const ButtonTypes = { - contained: 'text-white shadow focus:shadow-xl hover:shadow-md', - outlined: '', - text: 'transition-opacity', - iconOnly: 'transition-opacity', -}; +// const ButtonColors = { +// blue: { +// contained: 'bg-blue-500 focus:bg-blue-400 active:bg-blue-600 ring-blue-300', +// outlined: +// 'text-blue-500 border-2 border-blue-500 hover:bg-blue-500 hover:bg-opacity-20 focus:bg-blue-500 focus:bg-opacity-40 active:bg-blue-500 active:bg-opacity-40', +// text: 'text-blue-500 hover:bg-blue-500 hover:bg-opacity-20 focus:bg-blue-500 focus:bg-opacity-40 active:bg-blue-500 active:bg-opacity-40', +// iconOnly: 'text-blue-500 hover:text-blue-200', +// }, +// red: { +// contained: 'bg-red-500 focus:bg-red-400 active:bg-red-600 ring-red-300', +// outlined: +// 'text-red-500 border-2 border-red-500 hover:bg-red-500 hover:bg-opacity-20 focus:bg-red-500 focus:bg-opacity-40 active:bg-red-500 active:bg-opacity-40', +// text: 'text-red-500 hover:bg-red-500 hover:bg-opacity-20 focus:bg-red-500 focus:bg-opacity-40 active:bg-red-500 active:bg-opacity-40', +// iconOnly: 'text-red-500 hover:text-red-200', +// }, +// yellow: { +// contained: 'bg-yellow-500 focus:bg-yellow-400 active:bg-yellow-600 ring-yellow-300', +// outlined: +// 'text-yellow-500 border-2 border-yellow-500 hover:bg-yellow-500 hover:bg-opacity-20 focus:bg-yellow-500 focus:bg-opacity-40 active:bg-yellow-500 active:bg-opacity-40', +// text: 'text-yellow-500 hover:bg-yellow-500 hover:bg-opacity-20 focus:bg-yellow-500 focus:bg-opacity-40 active:bg-yellow-500 active:bg-opacity-40', +// iconOnly: 'text-yellow-500 hover:text-yellow-200', +// }, +// green: { +// contained: 'bg-green-500 focus:bg-green-400 active:bg-green-600 ring-green-300', +// outlined: +// 'text-green-500 border-2 border-green-500 hover:bg-green-500 hover:bg-opacity-20 focus:bg-green-500 focus:bg-opacity-40 active:bg-green-500 active:bg-opacity-40', +// text: 'text-green-500 hover:bg-green-500 hover:bg-opacity-20 focus:bg-green-500 focus:bg-opacity-40 active:bg-green-500 active:bg-opacity-40', +// iconOnly: 'text-green-500 hover:text-green-200', +// }, +// gray: { +// contained: 'bg-gray-500 focus:bg-gray-400 active:bg-gray-600 ring-gray-300', +// outlined: +// 'text-gray-500 border-2 border-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', +// text: 'text-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', +// iconOnly: 'text-gray-500 hover:text-gray-200', +// }, +// disabled: { +// contained: 'bg-gray-400', +// outlined: +// 'text-gray-500 border-2 border-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', +// text: 'text-gray-500 hover:bg-gray-500 hover:bg-opacity-20 focus:bg-gray-500 focus:bg-opacity-40 active:bg-gray-500 active:bg-opacity-40', +// iconOnly: 'text-gray-500 hover:text-gray-200', +// }, +// black: { +// contained: '', +// outlined: '', +// text: 'text-black dark:text-white', +// iconOnly: '', +// }, +// }; -export default function Button({ - children, - className = '', - color = 'blue', - disabled = false, - ariaCapitalize = false, - href, - target, - type = 'contained', - ...attrs -}) { - const [hovered, setHovered] = useState(false); - const ref = useRef(); +// const ButtonTypes = { +// contained: 'text-white shadow focus:shadow-xl hover:shadow-md', +// outlined: '', +// text: 'transition-opacity', +// iconOnly: 'transition-opacity', +// }; - let classes = `whitespace-nowrap flex items-center space-x-1 ${className} ${ButtonTypes[type]} ${ - ButtonColors[disabled ? 'disabled' : color][type] - } font-sans inline-flex font-bold uppercase text-xs px-1.5 md:px-2 py-2 rounded outline-none focus:outline-none ring-opacity-50 transition-shadow transition-colors ${ - disabled ? 'cursor-not-allowed' : `${type == 'iconOnly' ? '' : 'focus:ring-2'} cursor-pointer` - }`; +// export default function Button({ +// children, +// className = '', +// color = 'blue', +// disabled = false, +// ariaCapitalize = false, +// href, +// target, +// type = 'contained', +// ...attrs +// }) { +// const [hovered, setHovered] = useState(false); +// const ref = useRef(); - if (disabled) { - classes = classes.replace(/(?:focus|active|hover):[^ ]+/g, ''); - } +// let classes = `whitespace-nowrap flex items-center space-x-1 ${className} ${ButtonTypes[type]} ${ +// ButtonColors[disabled ? 'disabled' : color][type] +// } font-sans inline-flex font-bold uppercase text-xs px-1.5 md:px-2 py-2 rounded outline-none focus:outline-none ring-opacity-50 transition-shadow transition-colors ${ +// disabled ? 'cursor-not-allowed' : `${type == 'iconOnly' ? '' : 'focus:ring-2'} cursor-pointer` +// }`; - const handleMousenter = useCallback(() => { - setHovered(true); - }, []); +// if (disabled) { +// classes = classes.replace(/(?:focus|active|hover):[^ ]+/g, ''); +// } - const handleMouseleave = useCallback(() => { - setHovered(false); - }, []); +// const handleMousenter = useCallback(() => { +// setHovered(true); +// }, []); - const Element = href ? 'a' : 'div'; +// const handleMouseleave = useCallback(() => { +// setHovered(false); +// }, []); - return ( - - - {children} - - {hovered && attrs['aria-label'] ? : null} - - ); -} +// const Element = href ? 'a' : 'div'; + +// return ( +// +// +// {children} +// +// {hovered && attrs['aria-label'] ? : null} +// +// ); +// } diff --git a/src/shared/components/frigate/DebugCameraImage.tsx b/src/shared/components/frigate/DebugCameraImage.tsx index e129d06..0367a4a 100644 --- a/src/shared/components/frigate/DebugCameraImage.tsx +++ b/src/shared/components/frigate/DebugCameraImage.tsx @@ -1,159 +1,161 @@ -import { useCallback, useMemo, useState } from "react"; -import AutoUpdatingCameraImage from "./AutoUpdatingCameraImage"; -import { CameraConfig } from "../../../types/frigateConfig"; -import { usePersistence } from "../../../hooks/use-persistence"; -import { Button, Switch, Text } from "@mantine/core"; -import { Card, CardContent, CardHeader, CardTitle } from "./card"; -import { IconSettings } from "@tabler/icons-react"; +// import { useCallback, useMemo, useState } from "react"; +// import AutoUpdatingCameraImage from "./AutoUpdatingCameraImage"; +// import { CameraConfig } from "../../../types/frigateConfig"; +// import { usePersistence } from "../../../hooks/use-persistence"; +// import { Button, Switch, Text } from "@mantine/core"; +// import { Card, CardContent, CardHeader, CardTitle } from "./card"; +// import { IconSettings } from "@tabler/icons-react"; -type Options = { [key: string]: boolean }; +export {} -const emptyObject = Object.freeze({}); +// type Options = { [key: string]: boolean }; -type DebugCameraImageProps = { - className?: string; - cameraConfig: CameraConfig - url: string -}; +// const emptyObject = Object.freeze({}); -export default function DebugCameraImage({ - className, - cameraConfig, - url, -}: DebugCameraImageProps) { - const [showSettings, setShowSettings] = useState(false); - const [options, setOptions] = usePersistence( - `${cameraConfig?.name}-feed`, - emptyObject - ); - const handleSetOption = useCallback( - (id: string, value: boolean) => { - const newOptions = { ...options, [id]: value }; - setOptions(newOptions); - }, - [options] - ); - const searchParams = useMemo( - () => - new URLSearchParams( - Object.keys(options).reduce((memo, key) => { - //@ts-ignore we know this is correct - memo.push([key, options[key] === true ? "1" : "0"]); - return memo; - }, []) - ), - [options] - ); - const handleToggleSettings = useCallback(() => { - setShowSettings(!showSettings); - }, [showSettings]); +// type DebugCameraImageProps = { +// className?: string; +// cameraConfig: CameraConfig +// url: string +// }; - return ( -
- - - {showSettings ? ( - - - Options - - - - - - ) : null} -
- ); -} +// export default function DebugCameraImage({ +// className, +// cameraConfig, +// url, +// }: DebugCameraImageProps) { +// const [showSettings, setShowSettings] = useState(false); +// const [options, setOptions] = usePersistence( +// `${cameraConfig?.name}-feed`, +// emptyObject +// ); +// const handleSetOption = useCallback( +// (id: string, value: boolean) => { +// const newOptions = { ...options, [id]: value }; +// setOptions(newOptions); +// }, +// [options] +// ); +// const searchParams = useMemo( +// () => +// new URLSearchParams( +// Object.keys(options).reduce((memo, key) => { +// //@ts-ignore we know this is correct +// memo.push([key, options[key] === true ? "1" : "0"]); +// return memo; +// }, []) +// ), +// [options] +// ); +// const handleToggleSettings = useCallback(() => { +// setShowSettings(!showSettings); +// }, [showSettings]); -type DebugSettingsProps = { - handleSetOption: (id: string, value: boolean) => void; - options: Options; -}; +// return ( +//
+// +// +// {showSettings ? ( +// +// +// Options +// +// +// +// +// +// ) : null} +//
+// ); +// } -function DebugSettings({ handleSetOption, options }: DebugSettingsProps) { - return ( -
-
- { }} - // onCheckedChange={(isChecked) => { - // handleSetOption("bbox", isChecked); - // }} - /> - {/* */} - Bounding Box -
-
- { - // handleSetOption("timestamp", isChecked); - // }} - /> - {/* */} - Timestamp -
-
- { - // handleSetOption("zones", isChecked); - // }} - /> - {/* */} - Zones -
-
- { - // handleSetOption("mask", isChecked); - // }} - /> - {/* */} - Mask -
-
- { - // handleSetOption("motion", isChecked); - // }} - /> - {/* */} - Motion -
-
- { - // handleSetOption("regions", isChecked); - // }} - /> - {/* */} - Regions -
-
- ); -} +// type DebugSettingsProps = { +// handleSetOption: (id: string, value: boolean) => void; +// options: Options; +// }; + +// function DebugSettings({ handleSetOption, options }: DebugSettingsProps) { +// return ( +//
+//
+// { }} +// // onCheckedChange={(isChecked) => { +// // handleSetOption("bbox", isChecked); +// // }} +// /> +// {/* */} +// Bounding Box +//
+//
+// { +// // handleSetOption("timestamp", isChecked); +// // }} +// /> +// {/* */} +// Timestamp +//
+//
+// { +// // handleSetOption("zones", isChecked); +// // }} +// /> +// {/* */} +// Zones +//
+//
+// { +// // handleSetOption("mask", isChecked); +// // }} +// /> +// {/* */} +// Mask +//
+//
+// { +// // handleSetOption("motion", isChecked); +// // }} +// /> +// {/* */} +// Motion +//
+//
+// { +// // handleSetOption("regions", isChecked); +// // }} +// /> +// {/* */} +// Regions +//
+//
+// ); +// } diff --git a/src/shared/components/frigate/Dialog.jsx b/src/shared/components/frigate/Dialog.jsx index 6bf9e31..fc5cc79 100644 --- a/src/shared/components/frigate/Dialog.jsx +++ b/src/shared/components/frigate/Dialog.jsx @@ -1,35 +1,37 @@ -import { h, Fragment } from 'preact'; -import { createPortal } from 'preact/compat'; -import { useState, useEffect } from 'preact/hooks'; +// import { h, Fragment } from 'preact'; +// import { createPortal } from 'preact/compat'; +// import { useState, useEffect } from 'preact/hooks'; -export default function Dialog({ children, portalRootID = 'dialogs' }) { - const portalRoot = portalRootID && document.getElementById(portalRootID); - const [show, setShow] = useState(false); +export {} - useEffect(() => { - window.requestAnimationFrame(() => { - setShow(true); - }); - }, []); +// export default function Dialog({ children, portalRootID = 'dialogs' }) { +// const portalRoot = portalRootID && document.getElementById(portalRootID); +// const [show, setShow] = useState(false); - const dialog = ( - -
-
- {children} -
-
-
- ); +// useEffect(() => { +// window.requestAnimationFrame(() => { +// setShow(true); +// }); +// }, []); - return portalRoot ? createPortal(dialog, portalRoot) : dialog; -} +// const dialog = ( +// +//
+//
+// {children} +//
+//
+//
+// ); + +// return portalRoot ? createPortal(dialog, portalRoot) : dialog; +// } diff --git a/src/shared/components/frigate/Link.jsx b/src/shared/components/frigate/Link.jsx index 3547996..c8aa803 100644 --- a/src/shared/components/frigate/Link.jsx +++ b/src/shared/components/frigate/Link.jsx @@ -1,16 +1,18 @@ -import { h } from 'preact'; -import { Link as RouterLink } from 'preact-router/match'; +// import { h } from 'preact'; +// import { Link as RouterLink } from 'preact-router/match'; -export default function Link({ - activeClassName = '', - className = 'text-blue-500 hover:underline', - children, - href, - ...props -}) { - return ( - - {children} - - ); -} +export {} + +// export default function Link({ +// activeClassName = '', +// className = 'text-blue-500 hover:underline', +// children, +// href, +// ...props +// }) { +// return ( +// +// {children} +// +// ); +// } diff --git a/src/shared/components/frigate/Menu.jsx b/src/shared/components/frigate/Menu.jsx index 34ff203..e3bb19a 100644 --- a/src/shared/components/frigate/Menu.jsx +++ b/src/shared/components/frigate/Menu.jsx @@ -1,48 +1,50 @@ -import { h } from 'preact'; -import RelativeModal from './RelativeModal'; -import { useCallback } from 'preact/hooks'; +// import { h } from 'preact'; +// import RelativeModal from './RelativeModal'; +// import { useCallback } from 'preact/hooks'; -export default function Menu({ className, children, onDismiss, relativeTo, widthRelative }) { - return relativeTo ? ( - - ) : null; -} +export {} -export function MenuItem({ focus, icon: Icon, label, href, onSelect, value, ...attrs }) { - const handleClick = useCallback(() => { - onSelect && onSelect(value, label); - }, [onSelect, value, label]); +// export default function Menu({ className, children, onDismiss, relativeTo, widthRelative }) { +// return relativeTo ? ( +// +// ) : null; +// } - const Element = href ? 'a' : 'div'; +// export function MenuItem({ focus, icon: Icon, label, href, onSelect, value, ...attrs }) { +// const handleClick = useCallback(() => { +// onSelect && onSelect(value, label); +// }, [onSelect, value, label]); - return ( - - {Icon ? ( -
- -
- ) : null} -
{label}
-
- ); -} +// const Element = href ? 'a' : 'div'; -export function MenuSeparator() { - return
; -} +// return ( +// +// {Icon ? ( +//
+// +//
+// ) : null} +//
{label}
+//
+// ); +// } + +// export function MenuSeparator() { +// return
; +// } diff --git a/src/shared/components/frigate/MultiSelect.jsx b/src/shared/components/frigate/MultiSelect.jsx index 5c706fd..93bb154 100644 --- a/src/shared/components/frigate/MultiSelect.jsx +++ b/src/shared/components/frigate/MultiSelect.jsx @@ -1,70 +1,72 @@ -import { h } from 'preact'; -import { useRef, useState } from 'preact/hooks'; -import Menu from './Menu'; -import { ArrowDropdown } from '../icons/ArrowDropdown'; -import Heading from './Heading'; -import Button from './Button'; -import SelectOnlyIcon from '../icons/SelectOnly'; +// import { h } from 'preact'; +// import { useRef, useState } from 'preact/hooks'; +// import Menu from './Menu'; +// import { ArrowDropdown } from '../icons/ArrowDropdown'; +// import Heading from './Heading'; +// import Button from './Button'; +// import SelectOnlyIcon from '../icons/SelectOnly'; -export default function MultiSelect({ className, title, options, selection, onToggle, onShowAll, onSelectSingle }) { - const popupRef = useRef(null); +export {} - const [state, setState] = useState({ - showMenu: false, - }); +// export default function MultiSelect({ className, title, options, selection, onToggle, onShowAll, onSelectSingle }) { +// const popupRef = useRef(null); - const isOptionSelected = (item) => { - return selection == 'all' || selection.split(',').indexOf(item) > -1; - }; +// const [state, setState] = useState({ +// showMenu: false, +// }); - const menuHeight = Math.round(window.innerHeight * 0.55); - return ( -
-
setState({ showMenu: true })}> - - -
- {state.showMenu ? ( - setState({ showMenu: false })} - > -
- - {title} - - -
- {options.map((item) => ( -
- -
- -
-
- ))} -
- ) : null} -
- ); -} +// const isOptionSelected = (item) => { +// return selection == 'all' || selection.split(',').indexOf(item) > -1; +// }; + +// const menuHeight = Math.round(window.innerHeight * 0.55); +// return ( +//
+//
setState({ showMenu: true })}> +// +// +//
+// {state.showMenu ? ( +// setState({ showMenu: false })} +// > +//
+// +// {title} +// +// +//
+// {options.map((item) => ( +//
+// +//
+// +//
+//
+// ))} +//
+// ) : null} +//
+// ); +// } diff --git a/src/shared/components/frigate/Tabs.jsx b/src/shared/components/frigate/Tabs.jsx index 2e14227..dd7a098 100644 --- a/src/shared/components/frigate/Tabs.jsx +++ b/src/shared/components/frigate/Tabs.jsx @@ -1,41 +1,43 @@ -import { h } from 'preact'; -import { useCallback, useState } from 'preact/hooks'; +// import { h } from 'preact'; +// import { useCallback, useState } from 'preact/hooks'; -export function Tabs({ children, selectedIndex: selectedIndexProp, onChange, className }) { - const [selectedIndex, setSelectedIndex] = useState(selectedIndexProp); +export {} - const handleSelected = useCallback( - (index) => () => { - setSelectedIndex(index); - onChange && onChange(index); - }, - [onChange] - ); +// export function Tabs({ children, selectedIndex: selectedIndexProp, onChange, className }) { +// const [selectedIndex, setSelectedIndex] = useState(selectedIndexProp); - const RenderChildren = useCallback(() => { - return children.map((child, i) => { - child.props.selected = i === selectedIndex; - child.props.onClick = handleSelected(i); - return child; - }); - }, [selectedIndex, children, handleSelected]); +// const handleSelected = useCallback( +// (index) => () => { +// setSelectedIndex(index); +// onChange && onChange(index); +// }, +// [onChange] +// ); - return ( -
- -
- ); -} +// const RenderChildren = useCallback(() => { +// return children.map((child, i) => { +// child.props.selected = i === selectedIndex; +// child.props.onClick = handleSelected(i); +// return child; +// }); +// }, [selectedIndex, children, handleSelected]); -export function TextTab({ selected, text, onClick, disabled }) { - const selectedStyle = disabled - ? 'text-gray-400 dark:text-gray-600 bg-transparent' - : selected - ? 'text-white bg-blue-500 dark:text-black dark:bg-white' - : 'text-black dark:text-white bg-transparent'; - return ( - - ); -} +// return ( +//
+// +//
+// ); +// } + +// export function TextTab({ selected, text, onClick, disabled }) { +// const selectedStyle = disabled +// ? 'text-gray-400 dark:text-gray-600 bg-transparent' +// : selected +// ? 'text-white bg-blue-500 dark:text-black dark:bg-white' +// : 'text-black dark:text-white bg-transparent'; +// return ( +// +// ); +// } diff --git a/src/shared/components/frigate/TimeAgo.tsx b/src/shared/components/frigate/TimeAgo.tsx index 38c80c7..54a5b5e 100644 --- a/src/shared/components/frigate/TimeAgo.tsx +++ b/src/shared/components/frigate/TimeAgo.tsx @@ -1,83 +1,85 @@ -import { FunctionComponent, useEffect, useMemo, useState } from 'react'; +// import { FunctionComponent, useEffect, useMemo, useState } from 'react'; -interface IProp { - /** The time to calculate time-ago from */ - time: Date; - /** OPTIONAL: overwrite current time */ - currentTime?: Date; - /** OPTIONAL: boolean that determines whether to show the time-ago text in dense format */ - dense?: boolean; - /** OPTIONAL: set custom refresh interval in milliseconds, default 1000 (1 sec) */ - refreshInterval?: number; -} +// interface IProp { +// /** The time to calculate time-ago from */ +// time: Date; +// /** OPTIONAL: overwrite current time */ +// currentTime?: Date; +// /** OPTIONAL: boolean that determines whether to show the time-ago text in dense format */ +// dense?: boolean; +// /** OPTIONAL: set custom refresh interval in milliseconds, default 1000 (1 sec) */ +// refreshInterval?: number; +// } -type TimeUnit = { - unit: string; - full: string; - value: number; -}; +export {} -const timeAgo = ({ time, currentTime = new Date(), dense = false }: IProp): string => { - if (typeof time !== 'number' || time < 0) return 'Invalid Time Provided'; +// type TimeUnit = { +// unit: string; +// full: string; +// value: number; +// }; - const pastTime: Date = new Date(time); - const elapsedTime: number = currentTime.getTime() - pastTime.getTime(); +// const timeAgo = ({ time, currentTime = new Date(), dense = false }: IProp): string => { +// if (typeof time !== 'number' || time < 0) return 'Invalid Time Provided'; - const timeUnits: TimeUnit[] = [ - { unit: 'yr', full: 'year', value: 31536000 }, - { unit: 'mo', full: 'month', value: 0 }, - { unit: 'd', full: 'day', value: 86400 }, - { unit: 'h', full: 'hour', value: 3600 }, - { unit: 'm', full: 'minute', value: 60 }, - { unit: 's', full: 'second', value: 1 }, - ]; +// const pastTime: Date = new Date(time); +// const elapsedTime: number = currentTime.getTime() - pastTime.getTime(); - const elapsed: number = elapsedTime / 1000; - if (elapsed < 10) { - return 'just now'; - } +// const timeUnits: TimeUnit[] = [ +// { unit: 'yr', full: 'year', value: 31536000 }, +// { unit: 'mo', full: 'month', value: 0 }, +// { unit: 'd', full: 'day', value: 86400 }, +// { unit: 'h', full: 'hour', value: 3600 }, +// { unit: 'm', full: 'minute', value: 60 }, +// { unit: 's', full: 'second', value: 1 }, +// ]; - for (let i = 0; i < timeUnits.length; i++) { - // if months - if (i === 1) { - // Get the month and year for the time provided - const pastMonth = pastTime.getUTCMonth(); - const pastYear = pastTime.getUTCFullYear(); +// const elapsed: number = elapsedTime / 1000; +// if (elapsed < 10) { +// return 'just now'; +// } - // get current month and year - const currentMonth = currentTime.getUTCMonth(); - const currentYear = currentTime.getUTCFullYear(); +// for (let i = 0; i < timeUnits.length; i++) { +// // if months +// if (i === 1) { +// // Get the month and year for the time provided +// const pastMonth = pastTime.getUTCMonth(); +// const pastYear = pastTime.getUTCFullYear(); - let monthDiff = (currentYear - pastYear) * 12 + (currentMonth - pastMonth); +// // get current month and year +// const currentMonth = currentTime.getUTCMonth(); +// const currentYear = currentTime.getUTCFullYear(); - // check if the time provided is the previous month but not exceeded 1 month ago. - if (currentTime.getUTCDate() < pastTime.getUTCDate()) { - monthDiff--; - } +// let monthDiff = (currentYear - pastYear) * 12 + (currentMonth - pastMonth); - if (monthDiff > 0) { - const unitAmount = monthDiff; - return `${unitAmount}${dense ? timeUnits[i].unit : ` ${timeUnits[i].full}`}${dense ? '' : 's'} ago`; - } - } else if (elapsed >= timeUnits[i].value) { - const unitAmount: number = Math.floor(elapsed / timeUnits[i].value); - return `${unitAmount}${dense ? timeUnits[i].unit : ` ${timeUnits[i].full}`}${dense ? '' : 's'} ago`; - } - } - return 'Invalid Time'; -}; +// // check if the time provided is the previous month but not exceeded 1 month ago. +// if (currentTime.getUTCDate() < pastTime.getUTCDate()) { +// monthDiff--; +// } -const TimeAgo: FunctionComponent = ({ refreshInterval = 1000, ...rest }): JSX.Element => { - const [currentTime, setCurrentTime] = useState(new Date()); - useEffect(() => { - const intervalId: NodeJS.Timeout = setInterval(() => { - setCurrentTime(new Date()); - }, refreshInterval); - return () => clearInterval(intervalId); - }, [refreshInterval]); +// if (monthDiff > 0) { +// const unitAmount = monthDiff; +// return `${unitAmount}${dense ? timeUnits[i].unit : ` ${timeUnits[i].full}`}${dense ? '' : 's'} ago`; +// } +// } else if (elapsed >= timeUnits[i].value) { +// const unitAmount: number = Math.floor(elapsed / timeUnits[i].value); +// return `${unitAmount}${dense ? timeUnits[i].unit : ` ${timeUnits[i].full}`}${dense ? '' : 's'} ago`; +// } +// } +// return 'Invalid Time'; +// }; - const timeAgoValue = useMemo(() => timeAgo({ currentTime, ...rest }), [currentTime, rest]); +// const TimeAgo: FunctionComponent = ({ refreshInterval = 1000, ...rest }): JSX.Element => { +// const [currentTime, setCurrentTime] = useState(new Date()); +// useEffect(() => { +// const intervalId: NodeJS.Timeout = setInterval(() => { +// setCurrentTime(new Date()); +// }, refreshInterval); +// return () => clearInterval(intervalId); +// }, [refreshInterval]); - return {timeAgoValue}; -}; -export default TimeAgo; +// const timeAgoValue = useMemo(() => timeAgo({ currentTime, ...rest }), [currentTime, rest]); + +// return {timeAgoValue}; +// }; +// export default TimeAgo; diff --git a/src/shared/components/frigate/TimelineEventOverlay.jsx b/src/shared/components/frigate/TimelineEventOverlay.jsx index 1cff550..c7458bb 100644 --- a/src/shared/components/frigate/TimelineEventOverlay.jsx +++ b/src/shared/components/frigate/TimelineEventOverlay.jsx @@ -1,65 +1,67 @@ -import { Fragment, h } from 'preact'; -import { useState } from 'preact/hooks'; +// import { Fragment, h } from 'preact'; +// import { useState } from 'preact/hooks'; -export default function TimelineEventOverlay({ eventOverlay, cameraConfig }) { - const boxLeftEdge = Math.round(eventOverlay.data.box[0] * 100); - const boxTopEdge = Math.round(eventOverlay.data.box[1] * 100); - const boxRightEdge = Math.round((1 - eventOverlay.data.box[2] - eventOverlay.data.box[0]) * 100); - const boxBottomEdge = Math.round((1 - eventOverlay.data.box[3] - eventOverlay.data.box[1]) * 100); +export {} - const [isHovering, setIsHovering] = useState(false); - const getHoverStyle = () => { - if (boxLeftEdge < 15) { - // show object stats on right side - return { - left: `${boxLeftEdge + eventOverlay.data.box[2] * 100 + 1}%`, - top: `${boxTopEdge}%`, - }; - } +// export default function TimelineEventOverlay({ eventOverlay, cameraConfig }) { +// const boxLeftEdge = Math.round(eventOverlay.data.box[0] * 100); +// const boxTopEdge = Math.round(eventOverlay.data.box[1] * 100); +// const boxRightEdge = Math.round((1 - eventOverlay.data.box[2] - eventOverlay.data.box[0]) * 100); +// const boxBottomEdge = Math.round((1 - eventOverlay.data.box[3] - eventOverlay.data.box[1]) * 100); - return { - right: `${boxRightEdge + eventOverlay.data.box[2] * 100 + 1}%`, - top: `${boxTopEdge}%`, - }; - }; +// const [isHovering, setIsHovering] = useState(false); +// const getHoverStyle = () => { +// if (boxLeftEdge < 15) { +// // show object stats on right side +// return { +// left: `${boxLeftEdge + eventOverlay.data.box[2] * 100 + 1}%`, +// top: `${boxTopEdge}%`, +// }; +// } - const getObjectArea = () => { - const width = eventOverlay.data.box[2] * cameraConfig.detect.width; - const height = eventOverlay.data.box[3] * cameraConfig.detect.height; - return Math.round(width * height); - }; +// return { +// right: `${boxRightEdge + eventOverlay.data.box[2] * 100 + 1}%`, +// top: `${boxTopEdge}%`, +// }; +// }; - const getObjectRatio = () => { - const width = eventOverlay.data.box[2] * cameraConfig.detect.width; - const height = eventOverlay.data.box[3] * cameraConfig.detect.height; - return Math.round(100 * (width / height)) / 100; - }; +// const getObjectArea = () => { +// const width = eventOverlay.data.box[2] * cameraConfig.detect.width; +// const height = eventOverlay.data.box[3] * cameraConfig.detect.height; +// return Math.round(width * height); +// }; - return ( - -
setIsHovering(true)} - onMouseLeave={() => setIsHovering(false)} - onTouchStart={() => setIsHovering(true)} - onTouchEnd={() => setIsHovering(false)} - style={{ - left: `${boxLeftEdge}%`, - top: `${boxTopEdge}%`, - right: `${boxRightEdge}%`, - bottom: `${boxBottomEdge}%`, - }} - > - {eventOverlay.class_type == 'entered_zone' ? ( -
- ) : null} -
- {isHovering && ( -
-
{`Area: ${getObjectArea()} px`}
-
{`Ratio: ${getObjectRatio()}`}
-
- )} - - ); -} +// const getObjectRatio = () => { +// const width = eventOverlay.data.box[2] * cameraConfig.detect.width; +// const height = eventOverlay.data.box[3] * cameraConfig.detect.height; +// return Math.round(100 * (width / height)) / 100; +// }; + +// return ( +// +//
setIsHovering(true)} +// onMouseLeave={() => setIsHovering(false)} +// onTouchStart={() => setIsHovering(true)} +// onTouchEnd={() => setIsHovering(false)} +// style={{ +// left: `${boxLeftEdge}%`, +// top: `${boxTopEdge}%`, +// right: `${boxRightEdge}%`, +// bottom: `${boxBottomEdge}%`, +// }} +// > +// {eventOverlay.class_type == 'entered_zone' ? ( +//
+// ) : null} +//
+// {isHovering && ( +//
+//
{`Area: ${getObjectArea()} px`}
+//
{`Ratio: ${getObjectRatio()}`}
+//
+// )} +// +// ); +// } diff --git a/src/shared/components/frigate/TimelineSummary.jsx b/src/shared/components/frigate/TimelineSummary.jsx index 62e3b4c..dff8d9a 100644 --- a/src/shared/components/frigate/TimelineSummary.jsx +++ b/src/shared/components/frigate/TimelineSummary.jsx @@ -1,218 +1,220 @@ -import { h } from 'preact'; -import useSWR from 'swr'; -import ActivityIndicator from './ActivityIndicator'; -import { formatUnixTimestampToDateTime } from '../utils/dateUtil'; -import About from '../icons/About'; -import ActiveObjectIcon from '../icons/ActiveObject'; -import PlayIcon from '../icons/Play'; -import ExitIcon from '../icons/Exit'; -import StationaryObjectIcon from '../icons/StationaryObject'; -import FaceIcon from '../icons/Face'; -import LicensePlateIcon from '../icons/LicensePlate'; -import DeliveryTruckIcon from '../icons/DeliveryTruck'; -import ZoneIcon from '../icons/Zone'; -import { useMemo, useState } from 'preact/hooks'; -import Button from './Button'; +// import { h } from 'preact'; +// import useSWR from 'swr'; +// import ActivityIndicator from './ActivityIndicator'; +// import { formatUnixTimestampToDateTime } from '../utils/dateUtil'; +// import About from '../icons/About'; +// import ActiveObjectIcon from '../icons/ActiveObject'; +// import PlayIcon from '../icons/Play'; +// import ExitIcon from '../icons/Exit'; +// import StationaryObjectIcon from '../icons/StationaryObject'; +// import FaceIcon from '../icons/Face'; +// import LicensePlateIcon from '../icons/LicensePlate'; +// import DeliveryTruckIcon from '../icons/DeliveryTruck'; +// import ZoneIcon from '../icons/Zone'; +// import { useMemo, useState } from 'preact/hooks'; +// import Button from './Button'; -export default function TimelineSummary({ event, onFrameSelected }) { - const { data: eventTimeline } = useSWR([ - 'timeline', - { - source_id: event.id, - }, - ]); +export {} - const { data: config } = useSWR('config'); +// export default function TimelineSummary({ event, onFrameSelected }) { +// const { data: eventTimeline } = useSWR([ +// 'timeline', +// { +// source_id: event.id, +// }, +// ]); - const annotationOffset = useMemo(() => { - if (!config) { - return 0; - } +// const { data: config } = useSWR('config'); - return (config.cameras[event.camera]?.detect?.annotation_offset || 0) / 1000; - }, [config, event]); +// const annotationOffset = useMemo(() => { +// if (!config) { +// return 0; +// } - const [timeIndex, setTimeIndex] = useState(-1); +// return (config.cameras[event.camera]?.detect?.annotation_offset || 0) / 1000; +// }, [config, event]); - const recordingParams = useMemo(() => { - if (!event.end_time) { - return { - after: event.start_time, - }; - } +// const [timeIndex, setTimeIndex] = useState(-1); - return { - before: event.end_time, - after: event.start_time, - }; - }, [event]); - const { data: recordings } = useSWR([`${event.camera}/recordings`, recordingParams], { revalidateOnFocus: false }); +// const recordingParams = useMemo(() => { +// if (!event.end_time) { +// return { +// after: event.start_time, +// }; +// } - // calculates the seek seconds by adding up all the seconds in the segments prior to the playback time - const getSeekSeconds = (seekUnix) => { - if (!recordings) { - return 0; - } +// return { +// before: event.end_time, +// after: event.start_time, +// }; +// }, [event]); +// const { data: recordings } = useSWR([`${event.camera}/recordings`, recordingParams], { revalidateOnFocus: false }); - let seekSeconds = 0; - recordings.every((segment) => { - // if the next segment is past the desired time, stop calculating - if (segment.start_time > seekUnix) { - return false; - } +// // calculates the seek seconds by adding up all the seconds in the segments prior to the playback time +// const getSeekSeconds = (seekUnix) => { +// if (!recordings) { +// return 0; +// } - if (segment.end_time < seekUnix) { - seekSeconds += segment.end_time - segment.start_time; - return true; - } +// let seekSeconds = 0; +// recordings.every((segment) => { +// // if the next segment is past the desired time, stop calculating +// if (segment.start_time > seekUnix) { +// return false; +// } - seekSeconds += segment.end_time - segment.start_time - (segment.end_time - seekUnix); - return true; - }); +// if (segment.end_time < seekUnix) { +// seekSeconds += segment.end_time - segment.start_time; +// return true; +// } - return seekSeconds; - }; +// seekSeconds += segment.end_time - segment.start_time - (segment.end_time - seekUnix); +// return true; +// }); - const onSelectMoment = async (index) => { - setTimeIndex(index); - onFrameSelected(eventTimeline[index], getSeekSeconds(eventTimeline[index].timestamp + annotationOffset)); - }; +// return seekSeconds; +// }; - if (!eventTimeline || !config) { - return ; - } +// const onSelectMoment = async (index) => { +// setTimeIndex(index); +// onFrameSelected(eventTimeline[index], getSeekSeconds(eventTimeline[index].timestamp + annotationOffset)); +// }; - if (eventTimeline.length == 0) { - return
; - } +// if (!eventTimeline || !config) { +// return ; +// } - return ( -
-
-
- {eventTimeline.map((item, index) => ( - - ))} -
-
- {timeIndex >= 0 ? ( -
-
-
Bounding boxes may not align
- -
-
- ) : null} -
- ); -} +// if (eventTimeline.length == 0) { +// return
; +// } -function getTimelineIcon(timelineItem) { - switch (timelineItem.class_type) { - case 'visible': - return ; - case 'gone': - return ; - case 'active': - return ; - case 'stationary': - return ; - case 'entered_zone': - return ; - case 'attribute': - switch (timelineItem.data.attribute) { - case 'face': - return ; - case 'license_plate': - return ; - default: - return ; - } - case 'sub_label': - switch (timelineItem.data.label) { - case 'person': - return ; - case 'car': - return ; - } - } -} +// return ( +//
+//
+//
+// {eventTimeline.map((item, index) => ( +// +// ))} +//
+//
+// {timeIndex >= 0 ? ( +//
+//
+//
Bounding boxes may not align
+// +//
+//
+// ) : null} +//
+// ); +// } -function getTimelineItemDescription(config, timelineItem, event) { - switch (timelineItem.class_type) { - case 'visible': - return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; - case 'entered_zone': - return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones - .join(' and ') - .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; - case 'active': - return `${event.label} became active at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; - case 'stationary': - return `${event.label} became stationary at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; - case 'attribute': { - let title = ""; - if (timelineItem.data.attribute == 'face' || timelineItem.data.attribute == 'license_plate') { - title = `${timelineItem.data.attribute.replaceAll("_", " ")} detected for ${event.label}`; - } else { - title = `${event.label} recognized as ${timelineItem.data.attribute.replaceAll("_", " ")}` - } - return `${title} at ${formatUnixTimestampToDateTime( - timelineItem.timestamp, - { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - } - )}`; - } - case 'sub_label': - return `${event.label} recognized as ${timelineItem.data.sub_label} at ${formatUnixTimestampToDateTime( - timelineItem.timestamp, - { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - } - )}`; - case 'gone': - return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; - } -} +// function getTimelineIcon(timelineItem) { +// switch (timelineItem.class_type) { +// case 'visible': +// return ; +// case 'gone': +// return ; +// case 'active': +// return ; +// case 'stationary': +// return ; +// case 'entered_zone': +// return ; +// case 'attribute': +// switch (timelineItem.data.attribute) { +// case 'face': +// return ; +// case 'license_plate': +// return ; +// default: +// return ; +// } +// case 'sub_label': +// switch (timelineItem.data.label) { +// case 'person': +// return ; +// case 'car': +// return ; +// } +// } +// } + +// function getTimelineItemDescription(config, timelineItem, event) { +// switch (timelineItem.class_type) { +// case 'visible': +// return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// })}`; +// case 'entered_zone': +// return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones +// .join(' and ') +// .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// })}`; +// case 'active': +// return `${event.label} became active at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// })}`; +// case 'stationary': +// return `${event.label} became stationary at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// })}`; +// case 'attribute': { +// let title = ""; +// if (timelineItem.data.attribute == 'face' || timelineItem.data.attribute == 'license_plate') { +// title = `${timelineItem.data.attribute.replaceAll("_", " ")} detected for ${event.label}`; +// } else { +// title = `${event.label} recognized as ${timelineItem.data.attribute.replaceAll("_", " ")}` +// } +// return `${title} at ${formatUnixTimestampToDateTime( +// timelineItem.timestamp, +// { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// } +// )}`; +// } +// case 'sub_label': +// return `${event.label} recognized as ${timelineItem.data.sub_label} at ${formatUnixTimestampToDateTime( +// timelineItem.timestamp, +// { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// } +// )}`; +// case 'gone': +// return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { +// date_style: 'short', +// time_style: 'medium', +// time_format: config.ui.time_format, +// })}`; +// } +// } diff --git a/src/shared/components/frigate/Tooltip.jsx b/src/shared/components/frigate/Tooltip.jsx index 626239b..a7f38ff 100644 --- a/src/shared/components/frigate/Tooltip.jsx +++ b/src/shared/components/frigate/Tooltip.jsx @@ -1,62 +1,64 @@ -import { createPortal } from 'react'; // TODO implement -import { useLayoutEffect, useRef, useState } from 'react'; +// import { createPortal } from 'react'; // TODO implement +// import { useLayoutEffect, useRef, useState } from 'react'; -const TIP_SPACE = 20; +// const TIP_SPACE = 20; -export default function Tooltip({ relativeTo, text, capitalize }) { - const [position, setPosition] = useState({ top: -9999, left: -9999 }); - const portalRoot = document.getElementById('tooltips'); - const ref = useRef(); +export {} - useLayoutEffect(() => { - if (ref && ref.current && relativeTo && relativeTo.current) { - const windowWidth = window.innerWidth; - const { - x: relativeToX, - y: relativeToY, - width: relativeToWidth, - height: relativeToHeight, - } = relativeTo.current.getBoundingClientRect(); - const { width: _tipWidth, height: _tipHeight } = ref.current.getBoundingClientRect(); - const tipWidth = _tipWidth * 1.1; - const tipHeight = _tipHeight * 1.1; +// export default function Tooltip({ relativeTo, text, capitalize }) { +// const [position, setPosition] = useState({ top: -9999, left: -9999 }); +// const portalRoot = document.getElementById('tooltips'); +// const ref = useRef(); - const left = relativeToX + Math.round(relativeToWidth / 2) + window.scrollX; - const top = relativeToY + Math.round(relativeToHeight / 2) + window.scrollY; +// useLayoutEffect(() => { +// if (ref && ref.current && relativeTo && relativeTo.current) { +// const windowWidth = window.innerWidth; +// const { +// x: relativeToX, +// y: relativeToY, +// width: relativeToWidth, +// height: relativeToHeight, +// } = relativeTo.current.getBoundingClientRect(); +// const { width: _tipWidth, height: _tipHeight } = ref.current.getBoundingClientRect(); +// const tipWidth = _tipWidth * 1.1; +// const tipHeight = _tipHeight * 1.1; - let newTop = top - TIP_SPACE - tipHeight; - let newLeft = left - Math.round(tipWidth / 2); - // too far right - if (newLeft + tipWidth + TIP_SPACE > windowWidth - window.scrollX) { - newLeft = Math.max(0, left - tipWidth - TIP_SPACE); - newTop = top - Math.round(tipHeight / 2); - } - // too far left - else if (newLeft < TIP_SPACE + window.scrollX) { - newLeft = left + TIP_SPACE; - newTop = top - Math.round(tipHeight / 2); - } - // too close to top - else if (newTop <= TIP_SPACE + window.scrollY) { - newTop = top + tipHeight + TIP_SPACE; - } +// const left = relativeToX + Math.round(relativeToWidth / 2) + window.scrollX; +// const top = relativeToY + Math.round(relativeToHeight / 2) + window.scrollY; - setPosition({ left: newLeft, top: newTop }); - } - }, [relativeTo, ref]); +// let newTop = top - TIP_SPACE - tipHeight; +// let newLeft = left - Math.round(tipWidth / 2); +// // too far right +// if (newLeft + tipWidth + TIP_SPACE > windowWidth - window.scrollX) { +// newLeft = Math.max(0, left - tipWidth - TIP_SPACE); +// newTop = top - Math.round(tipHeight / 2); +// } +// // too far left +// else if (newLeft < TIP_SPACE + window.scrollX) { +// newLeft = left + TIP_SPACE; +// newTop = top - Math.round(tipHeight / 2); +// } +// // too close to top +// else if (newTop <= TIP_SPACE + window.scrollY) { +// newTop = top + tipHeight + TIP_SPACE; +// } - const tooltip = ( -
= 0 ? 'opacity-100 scale-100' : ''}`} - ref={ref} - style={position} - > - {text} -
- ); +// setPosition({ left: newLeft, top: newTop }); +// } +// }, [relativeTo, ref]); - return portalRoot ? createPortal(tooltip, portalRoot) : tooltip; -} +// const tooltip = ( +//
= 0 ? 'opacity-100 scale-100' : ''}`} +// ref={ref} +// style={position} +// > +// {text} +//
+// ); + +// return portalRoot ? createPortal(tooltip, portalRoot) : tooltip; +// } diff --git a/src/shared/components/frigate/card.tsx b/src/shared/components/frigate/card.tsx index e0d00ae..84e7869 100644 --- a/src/shared/components/frigate/card.tsx +++ b/src/shared/components/frigate/card.tsx @@ -1,80 +1,81 @@ -import * as React from "react" +// import * as React from "react" +export {} -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -Card.displayName = "Card" +// const Card = React.forwardRef< +// HTMLDivElement, +// React.HTMLAttributes +// >(({ className, ...props }, ref) => ( +//
+// )) +// Card.displayName = "Card" -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardHeader.displayName = "CardHeader" +// const CardHeader = React.forwardRef< +// HTMLDivElement, +// React.HTMLAttributes +// >(({ className, ...props }, ref) => ( +//
+// )) +// CardHeader.displayName = "CardHeader" -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardTitle.displayName = "CardTitle" +// const CardTitle = React.forwardRef< +// HTMLParagraphElement, +// React.HTMLAttributes +// >(({ className, ...props }, ref) => ( +//

+// )) +// CardTitle.displayName = "CardTitle" -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardDescription.displayName = "CardDescription" +// const CardDescription = React.forwardRef< +// HTMLParagraphElement, +// React.HTMLAttributes +// >(({ className, ...props }, ref) => ( +//

+// )) +// CardDescription.displayName = "CardDescription" -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardContent.displayName = "CardContent" +// const CardContent = React.forwardRef< +// HTMLDivElement, +// React.HTMLAttributes +// >(({ className, ...props }, ref) => ( +//
+// )) +// CardContent.displayName = "CardContent" -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardFooter.displayName = "CardFooter" +// const CardFooter = React.forwardRef< +// HTMLDivElement, +// React.HTMLAttributes +// >(({ className, ...props }, ref) => ( +//
+// )) +// CardFooter.displayName = "CardFooter" -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +// export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/shared/components/frigate/icons/About.jsx b/src/shared/components/frigate/icons/About.jsx deleted file mode 100644 index 6271b2d..0000000 --- a/src/shared/components/frigate/icons/About.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function About({ className = '' }) { - return ( - - - - ); -} - -export default memo(About); diff --git a/src/shared/components/frigate/icons/CalendarIcon.jsx b/src/shared/components/frigate/icons/CalendarIcon.jsx deleted file mode 100644 index 6558f1a..0000000 --- a/src/shared/components/frigate/icons/CalendarIcon.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function CalendarIcon({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(CalendarIcon); diff --git a/src/shared/components/frigate/icons/Camera.jsx b/src/shared/components/frigate/icons/Camera.jsx deleted file mode 100644 index fd0fcd1..0000000 --- a/src/shared/components/frigate/icons/Camera.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Camera({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(Camera); diff --git a/src/shared/components/frigate/icons/Clip.jsx b/src/shared/components/frigate/icons/Clip.jsx deleted file mode 100644 index a7fd815..0000000 --- a/src/shared/components/frigate/icons/Clip.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Clip({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(Clip); diff --git a/src/shared/components/frigate/icons/Clock.jsx b/src/shared/components/frigate/icons/Clock.jsx deleted file mode 100644 index e813e00..0000000 --- a/src/shared/components/frigate/icons/Clock.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Clock({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(Clock); diff --git a/src/shared/components/frigate/icons/Delete.jsx b/src/shared/components/frigate/icons/Delete.jsx deleted file mode 100644 index f00da15..0000000 --- a/src/shared/components/frigate/icons/Delete.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Delete({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(Delete); diff --git a/src/shared/components/frigate/icons/Download.jsx b/src/shared/components/frigate/icons/Download.jsx deleted file mode 100644 index 7ed2c57..0000000 --- a/src/shared/components/frigate/icons/Download.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { memo } from 'react'; - - - - -export function Download({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => void }) { - return ( - - - - ); -} - -export default memo(Download); diff --git a/src/shared/components/frigate/icons/Menu.jsx b/src/shared/components/frigate/icons/Menu.jsx deleted file mode 100644 index 76ea9dd..0000000 --- a/src/shared/components/frigate/icons/Menu.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Menu({ className = '' }) { - return ( - - - - - ); -} - -export default memo(Menu); diff --git a/src/shared/components/frigate/icons/MenuOpen.jsx b/src/shared/components/frigate/icons/MenuOpen.jsx deleted file mode 100644 index c3b2830..0000000 --- a/src/shared/components/frigate/icons/MenuOpen.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function MenuOpen({ className = '' }) { - return ( - - - - - ); -} - -export default memo(MenuOpen); diff --git a/src/shared/components/frigate/icons/Score.jsx b/src/shared/components/frigate/icons/Score.jsx deleted file mode 100644 index 2abed4b..0000000 --- a/src/shared/components/frigate/icons/Score.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Score({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'currentColor', onClick = () => {} }) { - return ( - - percent - - - ); -} - -export default memo(Score); diff --git a/src/shared/components/frigate/icons/Snapshot.jsx b/src/shared/components/frigate/icons/Snapshot.jsx deleted file mode 100644 index 696b080..0000000 --- a/src/shared/components/frigate/icons/Snapshot.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Snapshot({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(Snapshot); diff --git a/src/shared/components/frigate/icons/StarRecording.jsx b/src/shared/components/frigate/icons/StarRecording.jsx deleted file mode 100644 index e4923b6..0000000 --- a/src/shared/components/frigate/icons/StarRecording.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function StarRecording({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(StarRecording); diff --git a/src/shared/components/frigate/icons/Submitted.jsx b/src/shared/components/frigate/icons/Submitted.jsx deleted file mode 100644 index e7612a7..0000000 --- a/src/shared/components/frigate/icons/Submitted.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Submitted({ className = 'h-6 w-6', inner_fill = 'none', outer_stroke = 'currentColor', onClick = () => {} }) { - return ( - - - - - - ); -} - -export default memo(Submitted); diff --git a/src/shared/components/frigate/icons/UploadPlus.jsx b/src/shared/components/frigate/icons/UploadPlus.jsx deleted file mode 100644 index 7fd2f88..0000000 --- a/src/shared/components/frigate/icons/UploadPlus.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function UploadPlus({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) { - return ( - - - - ); -} - -export default memo(UploadPlus); diff --git a/src/shared/components/frigate/icons/Zone.jsx b/src/shared/components/frigate/icons/Zone.jsx deleted file mode 100644 index b19205d..0000000 --- a/src/shared/components/frigate/icons/Zone.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { h } from 'preact'; -import { memo } from 'preact/compat'; - -export function Zone({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) { - return ( - - - - - ); -} - -export default memo(Zone); diff --git a/src/shared/components/CogWheelWithText.tsx b/src/shared/components/loaders/CogWheelWithText.tsx similarity index 100% rename from src/shared/components/CogWheelWithText.tsx rename to src/shared/components/loaders/CogWheelWithText.tsx diff --git a/src/shared/components/CogwheelLoader.tsx b/src/shared/components/loaders/CogwheelLoader.tsx similarity index 72% rename from src/shared/components/CogwheelLoader.tsx rename to src/shared/components/loaders/CogwheelLoader.tsx index fe2ea4f..e008c5e 100644 --- a/src/shared/components/CogwheelLoader.tsx +++ b/src/shared/components/loaders/CogwheelLoader.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Center, DEFAULT_THEME } from '@mantine/core'; -import CogwheelSVG from './svg/CogwheelSVG'; +import CogwheelSVG from '../svg/CogwheelSVG'; const CogwheelLoader = () => { diff --git a/src/shared/components/frigate/JSMpegPlayer.tsx b/src/shared/components/players/JSMpegPlayer.tsx similarity index 100% rename from src/shared/components/frigate/JSMpegPlayer.tsx rename to src/shared/components/players/JSMpegPlayer.tsx diff --git a/src/shared/components/frigate/MsePlayer.tsx b/src/shared/components/players/MsePlayer.tsx similarity index 100% rename from src/shared/components/frigate/MsePlayer.tsx rename to src/shared/components/players/MsePlayer.tsx diff --git a/src/shared/components/frigate/VideoPlayer.tsx b/src/shared/components/players/VideoPlayer.tsx similarity index 100% rename from src/shared/components/frigate/VideoPlayer.tsx rename to src/shared/components/players/VideoPlayer.tsx diff --git a/src/shared/components/frigate/WebRTCPlayer.tsx b/src/shared/components/players/WebRTCPlayer.tsx similarity index 100% rename from src/shared/components/frigate/WebRTCPlayer.tsx rename to src/shared/components/players/WebRTCPlayer.tsx diff --git a/src/shared/stores/recordings.store.ts b/src/shared/stores/recordings.store.ts index ed5760b..0bbd0f9 100644 --- a/src/shared/stores/recordings.store.ts +++ b/src/shared/stores/recordings.store.ts @@ -15,7 +15,7 @@ export class RecordingsStore { makeAutoObservable(this) } - recordingSchema = z.object({ + private _recordingSchema = z.object({ hostName: z.string(), cameraName: z.string(), hour: z.string(), @@ -31,7 +31,7 @@ export class RecordingsStore { this._recordToPlay = value } getFullRecordForPlay(value: RecordForPlay) { - return this.recordingSchema.safeParse(value) + return this._recordingSchema.safeParse(value) } private _hostIdParam: string | undefined @@ -63,6 +63,11 @@ export class RecordingsStore { public set selectedCamera(value: GetCameraWHostWConfig | undefined) { this._selectedCamera = value } - selectedStartDay: string = '' - selectedEndDay: string = '' + private _selectedRange: [Date | null, Date | null] = [null, null] + public get selectedRange(): [Date | null, Date | null] { + return this._selectedRange + } + public set selectedRange(value: [Date | null, Date | null]) { + this._selectedRange = value + } } \ No newline at end of file diff --git a/src/shared/strings/strings.ts b/src/shared/strings/strings.ts index 2aff05d..e0c06d5 100644 --- a/src/shared/strings/strings.ts +++ b/src/shared/strings/strings.ts @@ -1,10 +1,31 @@ export const strings = { - host: { + host: 'Хост', + hostArr: { name: 'Имя хоста', url: 'Адрес', enabled: 'Включен', }, - // user section + player: { + startVideo: 'Вкл Видео', + stopVideo: 'Выкл Видео', + object: 'Объект', + duration: 'Длительность', + startTime: 'Начало', + endTime: 'Конец' + }, + camera: 'Камера', + hour: 'Час', + minute: 'Минута', + second: 'Секунда', + events: 'События', + event: 'Событие', + notHaveEvents: 'Нет событий', + date: 'Дата', + day: 'День', + selectHost:'Выбери хост', + selectCamera: 'Выбери камеру', + selectDate: 'Выбери дату', + selectRange: 'Выбери период', aboutMe: "Обо мне", settings: "Настройки", changeTheme: "Изменить тему", @@ -32,14 +53,14 @@ export const strings = { selectDeliveryMethod: "Выберите метод доставки:", pickUpByMyself: "Я заберу самостоятельно", courierDelivery: "Доставка курьером", - deliveryPoint:"Адрес доставки", + deliveryPoint: "Адрес доставки", selectYourDeliveryAddress: "Выберите свой адрес доставки:", - deliveryDate:"Дата доставки", + deliveryDate: "Дата доставки", selectDeliveryDate: "Выберите дату доставки:", enterQuantity: "Введите количество:", - quantity:"Количество", - tooltip_close:"нажмите Enter", - currency:"₽", + quantity: "Количество", + tooltip_close: "нажмите Enter", + currency: "₽", category: "Категории:", collapse: "Свернуть", hide: "Скрыть", diff --git a/src/shared/components/frigate/dateUtil.ts b/src/shared/utils/dateUtil.ts similarity index 93% rename from src/shared/components/frigate/dateUtil.ts rename to src/shared/utils/dateUtil.ts index ee7cd69..61a6761 100644 --- a/src/shared/components/frigate/dateUtil.ts +++ b/src/shared/utils/dateUtil.ts @@ -30,6 +30,33 @@ export const unixTimeToDate = (unixTime: number) => { return formattedDate; } +/** + * @param date + * @returns string '2024-02-25' + */ +export const dateToQueryString = (date: Date): string => { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const formattedMonth = month < 10 ? `0${month}` : month; + const day = date.getDate(); + const formattedDay = day < 10 ? `0${day}` : day; + + return `${year}-${formattedMonth}-${formattedDay}`; +} + +export const parseQueryDateToDate = (dateQuery: string): Date | null => { + const match = dateQuery.match(/(\d{4})-(\d{2})-(\d{2})/); + + if (match) { + const year = parseInt(match[1], 10); + const month = parseInt(match[2], 10) - 1; + const day = parseInt(match[3], 10); + return new Date(year, month, day); + } + + return null; +} + /** * @param day frigate format, e.g day: 2024-02-23 * @param hour frigate format, e.g hour: 22 @@ -210,14 +237,14 @@ interface DurationToken { * @param end_time: number|null - Unix timestamp for end time * @returns string - duration or 'In Progress' if end time is not provided */ -export const getDurationFromTimestamps = (start_time: number, end_time: number | undefined): string => { +export const getDurationFromTimestamps = (start_time: number, end_time: number | undefined): string | undefined => { if (isNaN(start_time)) { - return 'Invalid start time'; + return } let duration = 'In Progress'; if (end_time) { if (isNaN(end_time)) { - return 'Invalid end time'; + return } const start = fromUnixTime(start_time); const end = fromUnixTime(end_time); diff --git a/src/widgets/FrigateHostsTable.tsx b/src/widgets/FrigateHostsTable.tsx index 5b7b03c..16df897 100644 --- a/src/widgets/FrigateHostsTable.tsx +++ b/src/widgets/FrigateHostsTable.tsx @@ -61,9 +61,9 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC } const headTitle = [ - { propertyName: 'name', title: strings.host.name }, - { propertyName: 'host', title: strings.host.url }, - { propertyName: 'enabled', title: strings.host.enabled }, + { propertyName: 'name', title: strings.hostArr.name }, + { propertyName: 'host', title: strings.hostArr.url }, + { propertyName: 'enabled', title: strings.hostArr.enabled }, { title: '', sorting: false }, ] diff --git a/src/shared/components/frigate/Player.tsx b/src/widgets/Player.tsx similarity index 77% rename from src/shared/components/frigate/Player.tsx rename to src/widgets/Player.tsx index f187883..ba09575 100644 --- a/src/shared/components/frigate/Player.tsx +++ b/src/widgets/Player.tsx @@ -1,14 +1,14 @@ import React, { useEffect, useMemo, useState } from 'react'; -import JSMpegPlayer from './JSMpegPlayer'; -import MSEPlayer from './MsePlayer'; -import { CameraConfig } from '../../../types/frigateConfig'; -import { LivePlayerMode } from '../../../types/live'; -import useCameraActivity from '../../../hooks/use-camera-activity'; -import useCameraLiveMode from '../../../hooks/use-camera-live-mode'; -import WebRtcPlayer from './WebRTCPlayer'; +import JSMpegPlayer from '../shared/components/players/JSMpegPlayer'; +import MSEPlayer from '../shared/components/players/MsePlayer'; +import { CameraConfig } from '../types/frigateConfig'; +import { LivePlayerMode } from '../types/live'; +import useCameraActivity from '../hooks/use-camera-activity'; +import useCameraLiveMode from '../hooks/use-camera-live-mode'; +import WebRtcPlayer from '../shared/components/players/WebRTCPlayer'; import { Flex } from '@mantine/core'; -import { frigateApi, proxyApi } from '../../../services/frigate.proxy/frigate.api'; -import { GetCameraWHostWConfig } from '../../../services/frigate.proxy/frigate.schema'; +import { frigateApi, proxyApi } from '../services/frigate.proxy/frigate.api'; +import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema'; type LivePlayerProps = { camera: GetCameraWHostWConfig; @@ -70,7 +70,7 @@ const Player = ({ player = ( setLiveReady(true)} wsUrl={wsUrl} diff --git a/src/widgets/RecordingsFiltersRightSide.tsx b/src/widgets/RecordingsFiltersRightSide.tsx index b0a8199..625cc5a 100644 --- a/src/widgets/RecordingsFiltersRightSide.tsx +++ b/src/widgets/RecordingsFiltersRightSide.tsx @@ -1,12 +1,9 @@ -import React, { useContext, useEffect } from 'react'; -import OneSelectFilter, { OneSelectItem } from '../shared/components/filters.aps/OneSelectFilter'; +import React, { useContext } from 'react'; import { observer } from 'mobx-react-lite'; import { Context } from '..'; -import { useQuery } from '@tanstack/react-query'; -import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; -import { Center, Text } from '@mantine/core'; -import CogwheelLoader from '../shared/components/CogwheelLoader'; import CameraSelectFilter from '../shared/components/filters.aps/CameraSelectFilter'; +import DateRangeSelectFilter from '../shared/components/filters.aps/DateRangeSelectFilter'; +import HostSelectFilter from '../shared/components/filters.aps/HostSelectFilter'; interface RecordingsFiltersRightSideProps { } @@ -15,58 +12,18 @@ const RecordingsFiltersRightSide = ({ }: RecordingsFiltersRightSideProps) => { const { recordingsStore: recStore } = useContext(Context) - const { data: hosts, isError, isPending, isSuccess } = useQuery({ - queryKey: [frigateQueryKeys.getFrigateHosts], - queryFn: frigateApi.getHosts - }) - - useEffect(() => { - if (!hosts) return - if (recStore.hostIdParam) { - recStore.selectedHost = hosts.find(host => host.id === recStore.hostIdParam) - recStore.hostIdParam = undefined - } - }, [isSuccess]) - - if (isPending) return - if (isError) return
Loading error!
- - if (!hosts || hosts.length < 1) return null - - const hostItems: OneSelectItem[] = hosts - .filter(host => host.enabled) - .map(host => ({ value: host.id, label: host.name })) - - const handleSelect = (id: string, value: string) => { - const host = hosts?.find(host => host.id === value) - if (!host) { - recStore.selectedHost = undefined - recStore.selectedCamera = undefined - return - } - if (recStore.selectedHost?.id !== host.id) { - recStore.selectedCamera = undefined - } - recStore.selectedHost = host - } - console.log('RecordingsFiltersRightSide rendered') return ( <> - + {recStore.selectedHost ? - : - <> + : <> + } + {recStore.selectedCamera ? + + : <> } diff --git a/src/widgets/SelecteDayList.tsx b/src/widgets/SelecteDayList.tsx new file mode 100644 index 0000000..f3f609c --- /dev/null +++ b/src/widgets/SelecteDayList.tsx @@ -0,0 +1,53 @@ +import { useQuery } from '@tanstack/react-query'; +import React, { useContext } from 'react'; +import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api'; +import { dateToQueryString, getResolvedTimeZone } from '../shared/utils/dateUtil'; +import { Context } from '..'; +import { Flex, Text } from '@mantine/core'; +import RetryErrorPage from '../pages/RetryErrorPage'; +import CenterLoader from '../shared/components/CenterLoader'; +import { observer } from 'mobx-react-lite'; +import DayAccordion from '../shared/components/accordion/DayAccordion'; + +interface SelecteDayListProps { + day: Date + +} + +const SelecteDayList = ({ + day +}: SelecteDayListProps) => { + const { recordingsStore: recStore } = useContext(Context) + const camera = recStore.selectedCamera + const host = recStore.selectedHost + + const { data, isPending, isError, refetch } = useQuery({ + queryKey: [frigateQueryKeys.getRecordingsSummary, recStore.selectedCamera?.id, day], + queryFn: async () => { + if (camera && host) { + const stringDay = dateToQueryString(day) + const hostName = mapHostToHostname(host) + const res = await proxyApi.getRecordingsSummary(hostName, camera.name, getResolvedTimeZone()) + return res.find(record => record.day === stringDay) + } + return null + } + }) + + const handleRetry = () => { + if (recStore.selectedHost) refetch() + } + + if (isPending) return + if (isError) return + if (!camera || !host || !data) return + + return ( + + {host.name} / {camera.name} / {data.day} + + + ); +}; + +export default observer(SelecteDayList); \ No newline at end of file diff --git a/src/widgets/SelectedCameraList.tsx b/src/widgets/SelectedCameraList.tsx index fd2beec..4ad279a 100644 --- a/src/widgets/SelectedCameraList.tsx +++ b/src/widgets/SelectedCameraList.tsx @@ -6,24 +6,25 @@ import { useQuery } from '@tanstack/react-query'; import { Context } from '..'; import { frigateQueryKeys, frigateApi } from '../services/frigate.proxy/frigate.api'; import { host } from '../shared/env.const'; -import CogwheelLoader from '../shared/components/CogwheelLoader'; -import RetryError from '../pages/RetryError'; +import CogwheelLoader from '../shared/components/loaders/CogwheelLoader'; +import RetryErrorPage from '../pages/RetryErrorPage'; +import CenterLoader from '../shared/components/CenterLoader'; interface SelectedCameraListProps { - cameraId: string, + // cameraId: string, } const SelectedCameraList = ({ - cameraId, + // cameraId, }: SelectedCameraListProps) => { const { recordingsStore: recStore } = useContext(Context) const { data: camera, isPending: cameraPending, isError: cameraError, refetch: cameraRefetch } = useQuery({ - queryKey: [frigateQueryKeys.getCameraWHost, cameraId], + queryKey: [frigateQueryKeys.getCameraWHost, recStore.selectedCamera?.id], queryFn: async () => { - if (cameraId) { - return frigateApi.getCameraWHost(cameraId) + if (recStore.selectedCamera) { + return frigateApi.getCameraWHost(recStore.selectedCamera.id) } return null } @@ -33,14 +34,18 @@ const SelectedCameraList = ({ cameraRefetch() } - if (cameraPending) return - if (cameraError) return + if (cameraPending) return + if (cameraError) return if (!camera?.frigateHost) return null return ( {camera.frigateHost.name} / {camera.name} + { + + + } diff --git a/src/widgets/SelectedHostList.tsx b/src/widgets/SelectedHostList.tsx index c8021e5..cdd359b 100644 --- a/src/widgets/SelectedHostList.tsx +++ b/src/widgets/SelectedHostList.tsx @@ -5,7 +5,8 @@ import { useQuery } from '@tanstack/react-query'; import { frigateQueryKeys, frigateApi } from '../services/frigate.proxy/frigate.api'; import { Context } from '..'; import CenterLoader from '../shared/components/CenterLoader'; -import RetryError from '../pages/RetryError'; +import RetryErrorPage from '../pages/RetryErrorPage'; +import { strings } from '../shared/strings/strings'; const CameraAccordion = lazy(() => import('../shared/components/accordion/CameraAccordion')); @@ -39,14 +40,14 @@ const SelectedHostList = ({ } if (hostPending) return - if (hostError) return + if (hostError) return if (!host || host.cameras.length < 1) return null const cameras = host.cameras.slice(0, 2).map(camera => { return ( - {camera.name} + {strings.camera}: {camera.name} {openCameraId === camera.id && ( @@ -60,7 +61,7 @@ const SelectedHostList = ({ return ( - {host.name} + {strings.host}: {host.name}