From fecd30df70ff7e90fe9c049cfc4af342b07cb380 Mon Sep 17 00:00:00 2001 From: NlightN22 Date: Mon, 26 Feb 2024 03:05:19 +0700 Subject: [PATCH] fix image fix menu, links add drawer menu --- src/AppBody.tsx | 6 +- src/pages/MainPage.tsx | 29 +-- src/shared/components/AutoUpdatedImage.tsx | 14 +- src/shared/components/CameraCard.tsx | 4 +- src/shared/components/DrawerMenu.tsx | 85 +++++++ .../frigate/AutoUpdatingCameraImage.tsx | 99 -------- src/shared/components/frigate/Button.jsx | 117 ---------- .../components/frigate/DebugCameraImage.tsx | 161 ------------- src/shared/components/frigate/Dialog.jsx | 37 --- src/shared/components/frigate/Link.jsx | 18 -- src/shared/components/frigate/Menu.jsx | 50 ---- src/shared/components/frigate/MultiSelect.jsx | 72 ------ src/shared/components/frigate/Tabs.jsx | 43 ---- src/shared/components/frigate/TimeAgo.tsx | 85 ------- .../frigate/TimelineEventOverlay.jsx | 67 ------ .../components/frigate/TimelineSummary.jsx | 220 ------------------ src/shared/components/frigate/Tooltip.jsx | 64 ----- src/shared/components/frigate/card.tsx | 81 ------- src/widgets/header/HeaderAction.tsx | 54 ++--- src/widgets/header/header.links.ts | 21 +- 20 files changed, 140 insertions(+), 1187 deletions(-) create mode 100644 src/shared/components/DrawerMenu.tsx delete mode 100644 src/shared/components/frigate/AutoUpdatingCameraImage.tsx delete mode 100644 src/shared/components/frigate/Button.jsx delete mode 100644 src/shared/components/frigate/DebugCameraImage.tsx delete mode 100644 src/shared/components/frigate/Dialog.jsx delete mode 100644 src/shared/components/frigate/Link.jsx delete mode 100644 src/shared/components/frigate/Menu.jsx delete mode 100644 src/shared/components/frigate/MultiSelect.jsx delete mode 100644 src/shared/components/frigate/Tabs.jsx delete mode 100644 src/shared/components/frigate/TimeAgo.tsx delete mode 100644 src/shared/components/frigate/TimelineEventOverlay.jsx delete mode 100644 src/shared/components/frigate/TimelineSummary.jsx delete mode 100644 src/shared/components/frigate/Tooltip.jsx delete mode 100644 src/shared/components/frigate/card.tsx diff --git a/src/AppBody.tsx b/src/AppBody.tsx index d64400f..dbc31f6 100644 --- a/src/AppBody.tsx +++ b/src/AppBody.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { AppShell, useMantineTheme, } from "@mantine/core" import { HeaderAction } from './widgets/header/HeaderAction'; -import { testHeaderLinks } from './widgets/header/header.links'; +import { headerLinks } from './widgets/header/header.links'; import AppRouter from './router/AppRouter'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Context } from '.'; @@ -38,7 +38,7 @@ const AppBody = () => { { asideOffsetBreakpoint="sm" header={ - + } aside={ diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index c5bbb0c..6c26d5c 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -10,9 +10,13 @@ import { useQuery } from '@tanstack/react-query'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import RetryErrorPage from './RetryErrorPage'; import CameraCard from '../shared/components/CameraCard'; +import { useMediaQuery } from '@mantine/hooks'; +import { dimensions } from '../shared/dimensions/dimensions'; const MainPage = () => { const { sideBarsStore } = useContext(Context) + const isMobile = useMediaQuery(dimensions.mobileSize) + useEffect(() => { sideBarsStore.rightVisible = false @@ -36,8 +40,8 @@ const MainPage = () => { if (isError) return const cards = () => { - return cameras.filter(cam => cam.frigateHost?.host.includes('5000')).slice(0,25).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()} diff --git a/src/shared/components/AutoUpdatedImage.tsx b/src/shared/components/AutoUpdatedImage.tsx index 4aa8a96..b685d98 100644 --- a/src/shared/components/AutoUpdatedImage.tsx +++ b/src/shared/components/AutoUpdatedImage.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from "react"; import { CameraConfig } from "../../types/frigateConfig"; -import { Flex, Text } from "@mantine/core"; +import { Flex, Text, Image } from "@mantine/core"; import { useQuery } from "@tanstack/react-query"; import { frigateApi, proxyApi } from "../../services/frigate.proxy/frigate.api"; import { useIntersection } from "@mantine/hooks"; @@ -19,7 +19,7 @@ const AutoUpdatedImage = ({ enabled, ...rest }: AutoUpdatedImageProps) => { - const { ref, entry } = useIntersection({threshold: 0.1,}) + const { ref, entry } = useIntersection({ threshold: 0.1, }) const isVisible = entry?.isIntersecting const { data: imageBlob, refetch, isPending, isError } = useQuery({ @@ -52,14 +52,12 @@ const AutoUpdatedImage = ({ const image = URL.createObjectURL(imageBlob!) return ( - <> - {enabled ? Dynamic Content + + {enabled ? Dynamic Content : - - Camera is disabled in config, no stream or snapshot available! - + Camera is disabled in config, no stream or snapshot available! } - ) + ) }; export default AutoUpdatedImage \ No newline at end of file diff --git a/src/shared/components/CameraCard.tsx b/src/shared/components/CameraCard.tsx index 2e17559..201eccf 100644 --- a/src/shared/components/CameraCard.tsx +++ b/src/shared/components/CameraCard.tsx @@ -57,9 +57,7 @@ const CameraCard = ({ {camera.name} / {camera.frigateHost?.name} - - - + diff --git a/src/shared/components/DrawerMenu.tsx b/src/shared/components/DrawerMenu.tsx new file mode 100644 index 0000000..341f881 --- /dev/null +++ b/src/shared/components/DrawerMenu.tsx @@ -0,0 +1,85 @@ +import { Box, Burger, Button, Center, Collapse, Divider, Drawer, Flex, Group, Menu, ScrollArea, UnstyledButton, createStyles, rem, useMantineTheme } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { IconChevronDown } from '@tabler/icons-react'; +import React from 'react'; +import { LinkItem } from '../../widgets/header/HeaderAction'; +import { useNavigate } from 'react-router-dom'; + +const useStyles = createStyles((theme) => ({ + burger: { + [theme.fn.largerThan('sm')]: { + display: 'none', + }, + }, + drawerButton: { + color: theme.colorScheme === 'dark' ? '#a5d8ff' : '#228be6', + fontWeight: 600, + fontSize: '1.2rem', + margin: '0.3rem', + height: '2.5rem', + textAlign: 'center', + borderRadius: '0.5rem', + transition: 'background-color 0.5s', + '&:hover': { + backgroundColor: theme.colorScheme === 'dark' ? theme.fn.darken(theme.colors.cyan[9], 0.5) : theme.colors.cyan[1], + }, + '&:active': { + backgroundColor: theme.colorScheme === 'dark' ? + theme.fn.darken(theme.colors.cyan[9], 0.6) : + theme.fn.darken(theme.colors.cyan[1], 0.1), + } + }, +})) + +interface DrawerMenuProps { + links: LinkItem[], +} + +const DrawerMenu = ({ + links +}: DrawerMenuProps) => { + const navigate = useNavigate() + + const { classes } = useStyles(); + const [drawerOpened, { toggle: toggleDrawer, close: closeDrawer }] = useDisclosure(false) + const theme = useMantineTheme(); + + + const handleNavigate = (link: string) => { + navigate(link) + closeDrawer() + } + + const items = links.map(item => ( + handleNavigate(item.link)} + > + {item.label} + + )) + + + return ( + <> + + + + {items} + + + + ); +}; + + + +export default DrawerMenu; \ No newline at end of file diff --git a/src/shared/components/frigate/AutoUpdatingCameraImage.tsx b/src/shared/components/frigate/AutoUpdatingCameraImage.tsx deleted file mode 100644 index 75f3e80..0000000 --- a/src/shared/components/frigate/AutoUpdatingCameraImage.tsx +++ /dev/null @@ -1,99 +0,0 @@ -// 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"; - -export {} - -// interface AutoUpdatingCameraImageProps extends React.ImgHTMLAttributes { -// cameraConfig?: CameraConfig -// searchParams?: {}; -// showFps?: boolean; -// className?: string; -// url: string -// }; - -// // 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 -// } - -// // if (liveReady) { -// // return 60000; -// // } - -// // if (cameraActive) { -// // return 200; -// // } - -// return 30000; -// }, [windowVisible]); - -// useEffect(() => { -// if (reloadInterval == -1) { -// return; -// } - -// setKey(Date.now()); - -// return () => { -// if (timeoutId) { -// clearTimeout(timeoutId); -// setTimeoutId(undefined); -// } -// }; -// }, [reloadInterval]); - -// const handleLoad = useCallback(() => { -// if (reloadInterval == -1) { -// return; -// } - -// const loadTime = Date.now() - key; - -// if (showFps) { -// setFps((1000 / Math.max(loadTime, reloadInterval)).toFixed(1)); -// } - -// setTimeoutId( -// setTimeout( -// () => { -// setKey(Date.now()); -// }, -// loadTime > reloadInterval ? 1 : reloadInterval -// ) -// ); -// }, [key, setFps]); - -// return ( -// // -// -// {/* */} -// {showFps ? Displaying at {fps}fps : null} -// -// // -// ); -// } diff --git a/src/shared/components/frigate/Button.jsx b/src/shared/components/frigate/Button.jsx deleted file mode 100644 index 87d6b3d..0000000 --- a/src/shared/components/frigate/Button.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// import Tooltip from './Tooltip'; -// import { Fragment, useCallback, useRef, useState } from 'react'; - -export {} - -// 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: '', -// }, -// }; - -// const ButtonTypes = { -// contained: 'text-white shadow focus:shadow-xl hover:shadow-md', -// outlined: '', -// text: 'transition-opacity', -// iconOnly: 'transition-opacity', -// }; - -// 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(); - -// 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` -// }`; - -// if (disabled) { -// classes = classes.replace(/(?:focus|active|hover):[^ ]+/g, ''); -// } - -// const handleMousenter = useCallback(() => { -// setHovered(true); -// }, []); - -// const handleMouseleave = useCallback(() => { -// setHovered(false); -// }, []); - -// 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 deleted file mode 100644 index 0367a4a..0000000 --- a/src/shared/components/frigate/DebugCameraImage.tsx +++ /dev/null @@ -1,161 +0,0 @@ -// 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"; - - -export {} - -// type Options = { [key: string]: boolean }; - -// const emptyObject = Object.freeze({}); - -// type DebugCameraImageProps = { -// className?: string; -// cameraConfig: CameraConfig -// url: string -// }; - -// 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]); - -// return ( -//
-// -// -// {showSettings ? ( -// -// -// Options -// -// -// -// -// -// ) : null} -//
-// ); -// } - -// 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 deleted file mode 100644 index fc5cc79..0000000 --- a/src/shared/components/frigate/Dialog.jsx +++ /dev/null @@ -1,37 +0,0 @@ -// import { h, Fragment } from 'preact'; -// import { createPortal } from 'preact/compat'; -// import { useState, useEffect } from 'preact/hooks'; - -export {} - -// export default function Dialog({ children, portalRootID = 'dialogs' }) { -// const portalRoot = portalRootID && document.getElementById(portalRootID); -// const [show, setShow] = useState(false); - -// useEffect(() => { -// window.requestAnimationFrame(() => { -// setShow(true); -// }); -// }, []); - -// 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 deleted file mode 100644 index c8aa803..0000000 --- a/src/shared/components/frigate/Link.jsx +++ /dev/null @@ -1,18 +0,0 @@ -// import { h } from 'preact'; -// import { Link as RouterLink } from 'preact-router/match'; - -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 deleted file mode 100644 index e3bb19a..0000000 --- a/src/shared/components/frigate/Menu.jsx +++ /dev/null @@ -1,50 +0,0 @@ -// import { h } from 'preact'; -// import RelativeModal from './RelativeModal'; -// import { useCallback } from 'preact/hooks'; - -export {} - -// export default function Menu({ className, children, onDismiss, relativeTo, widthRelative }) { -// return relativeTo ? ( -// -// ) : null; -// } - -// export function MenuItem({ focus, icon: Icon, label, href, onSelect, value, ...attrs }) { -// const handleClick = useCallback(() => { -// onSelect && onSelect(value, label); -// }, [onSelect, value, label]); - -// const Element = href ? 'a' : 'div'; - -// return ( -// -// {Icon ? ( -//
-// -//
-// ) : null} -//
{label}
-//
-// ); -// } - -// export function MenuSeparator() { -// return
; -// } diff --git a/src/shared/components/frigate/MultiSelect.jsx b/src/shared/components/frigate/MultiSelect.jsx deleted file mode 100644 index 93bb154..0000000 --- a/src/shared/components/frigate/MultiSelect.jsx +++ /dev/null @@ -1,72 +0,0 @@ -// 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 {} - -// export default function MultiSelect({ className, title, options, selection, onToggle, onShowAll, onSelectSingle }) { -// const popupRef = useRef(null); - -// const [state, setState] = useState({ -// showMenu: false, -// }); - -// 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 deleted file mode 100644 index dd7a098..0000000 --- a/src/shared/components/frigate/Tabs.jsx +++ /dev/null @@ -1,43 +0,0 @@ -// import { h } from 'preact'; -// import { useCallback, useState } from 'preact/hooks'; - -export {} - -// export function Tabs({ children, selectedIndex: selectedIndexProp, onChange, className }) { -// const [selectedIndex, setSelectedIndex] = useState(selectedIndexProp); - -// const handleSelected = useCallback( -// (index) => () => { -// setSelectedIndex(index); -// onChange && onChange(index); -// }, -// [onChange] -// ); - -// const RenderChildren = useCallback(() => { -// return children.map((child, i) => { -// child.props.selected = i === selectedIndex; -// child.props.onClick = handleSelected(i); -// return child; -// }); -// }, [selectedIndex, children, handleSelected]); - -// 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 deleted file mode 100644 index 54a5b5e..0000000 --- a/src/shared/components/frigate/TimeAgo.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// 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; -// } - -export {} - -// type TimeUnit = { -// unit: string; -// full: string; -// value: number; -// }; - -// const timeAgo = ({ time, currentTime = new Date(), dense = false }: IProp): string => { -// if (typeof time !== 'number' || time < 0) return 'Invalid Time Provided'; - -// const pastTime: Date = new Date(time); -// const elapsedTime: number = currentTime.getTime() - pastTime.getTime(); - -// 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 elapsed: number = elapsedTime / 1000; -// if (elapsed < 10) { -// return 'just now'; -// } - -// 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(); - -// // get current month and year -// const currentMonth = currentTime.getUTCMonth(); -// const currentYear = currentTime.getUTCFullYear(); - -// let monthDiff = (currentYear - pastYear) * 12 + (currentMonth - pastMonth); - -// // check if the time provided is the previous month but not exceeded 1 month ago. -// if (currentTime.getUTCDate() < pastTime.getUTCDate()) { -// monthDiff--; -// } - -// 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 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]); - -// 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 deleted file mode 100644 index c7458bb..0000000 --- a/src/shared/components/frigate/TimelineEventOverlay.jsx +++ /dev/null @@ -1,67 +0,0 @@ -// import { Fragment, h } from 'preact'; -// import { useState } from 'preact/hooks'; - -export {} - -// 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); - -// 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}%`, -// }; -// } - -// return { -// right: `${boxRightEdge + 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); -// }; - -// 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 deleted file mode 100644 index dff8d9a..0000000 --- a/src/shared/components/frigate/TimelineSummary.jsx +++ /dev/null @@ -1,220 +0,0 @@ -// 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 {} - -// export default function TimelineSummary({ event, onFrameSelected }) { -// const { data: eventTimeline } = useSWR([ -// 'timeline', -// { -// source_id: event.id, -// }, -// ]); - -// const { data: config } = useSWR('config'); - -// const annotationOffset = useMemo(() => { -// if (!config) { -// return 0; -// } - -// return (config.cameras[event.camera]?.detect?.annotation_offset || 0) / 1000; -// }, [config, event]); - -// const [timeIndex, setTimeIndex] = useState(-1); - -// const recordingParams = useMemo(() => { -// if (!event.end_time) { -// return { -// after: event.start_time, -// }; -// } - -// return { -// before: event.end_time, -// after: event.start_time, -// }; -// }, [event]); -// const { data: recordings } = useSWR([`${event.camera}/recordings`, recordingParams], { revalidateOnFocus: 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; -// } - -// let seekSeconds = 0; -// recordings.every((segment) => { -// // if the next segment is past the desired time, stop calculating -// if (segment.start_time > seekUnix) { -// return false; -// } - -// if (segment.end_time < seekUnix) { -// seekSeconds += segment.end_time - segment.start_time; -// return true; -// } - -// seekSeconds += segment.end_time - segment.start_time - (segment.end_time - seekUnix); -// return true; -// }); - -// return seekSeconds; -// }; - -// const onSelectMoment = async (index) => { -// setTimeIndex(index); -// onFrameSelected(eventTimeline[index], getSeekSeconds(eventTimeline[index].timestamp + annotationOffset)); -// }; - -// if (!eventTimeline || !config) { -// return ; -// } - -// if (eventTimeline.length == 0) { -// return
; -// } - -// return ( -//
-//
-//
-// {eventTimeline.map((item, index) => ( -// -// ))} -//
-//
-// {timeIndex >= 0 ? ( -//
-//
-//
Bounding boxes may not align
-// -//
-//
-// ) : null} -//
-// ); -// } - -// 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 deleted file mode 100644 index a7f38ff..0000000 --- a/src/shared/components/frigate/Tooltip.jsx +++ /dev/null @@ -1,64 +0,0 @@ -// import { createPortal } from 'react'; // TODO implement -// import { useLayoutEffect, useRef, useState } from 'react'; - -// const TIP_SPACE = 20; - -export {} - -// export default function Tooltip({ relativeTo, text, capitalize }) { -// const [position, setPosition] = useState({ top: -9999, left: -9999 }); -// const portalRoot = document.getElementById('tooltips'); -// const ref = useRef(); - -// 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; - -// const left = relativeToX + Math.round(relativeToWidth / 2) + window.scrollX; -// const top = relativeToY + Math.round(relativeToHeight / 2) + window.scrollY; - -// 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; -// } - -// setPosition({ left: newLeft, top: newTop }); -// } -// }, [relativeTo, ref]); - -// 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 deleted file mode 100644 index 84e7869..0000000 --- a/src/shared/components/frigate/card.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// import * as React from "react" - -export {} - -// 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 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 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" - -// export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/widgets/header/HeaderAction.tsx b/src/widgets/header/HeaderAction.tsx index e28a9ad..ce37d1b 100644 --- a/src/widgets/header/HeaderAction.tsx +++ b/src/widgets/header/HeaderAction.tsx @@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom'; import ColorSchemeToggle from "../../shared/components/ColorSchemeToggle"; import Logo from "../../shared/components/Logo"; import { routesPath } from "../../router/routes.path"; +import DrawerMenu from "../../shared/components/DrawerMenu"; const HEADER_HEIGHT = rem(60) @@ -39,6 +40,7 @@ const useStyles = createStyles((theme) => ({ }, }, + // TODO delete burger: { [theme.fn.largerThan('sm')]: { display: 'none', @@ -46,62 +48,54 @@ const useStyles = createStyles((theme) => ({ }, colorToggle: { - [theme.fn.smallerThan('md')]: {display:'none'} + [theme.fn.smallerThan('md')]: { display: 'none' } } })) +export interface LinkItem { + label: string + link: string +} + export interface HeaderActionProps { - links: { link: string; label: string; links: { link: string; label: string }[] }[], + links: LinkItem[], logo?: JSX.Element } export const HeaderAction = ({ links }: HeaderActionProps) => { const { classes } = useStyles(); const navigate = useNavigate() - const [opened, { toggle }] = useDisclosure(false) - const isMiddleScreen = useMediaQuery('md') const auth = useAuth() const handleNavigate = (link: string) => { navigate(link) } - const items = links.map((link) => { - const menuItems = link.links?.map((item) => ( - {item.label} - )) - - if (menuItems) { - return ( - - - - - - ) - } - - return ( - null - ) - }) + const items = links.map(item => + + + + + + ) return (
handleNavigate(routesPath.MAIN_PATH)} /> - + {/* */} + - { items } + {items} - - {items} - + + {items} + diff --git a/src/widgets/header/header.links.ts b/src/widgets/header/header.links.ts index 8294765..42f41ac 100644 --- a/src/widgets/header/header.links.ts +++ b/src/widgets/header/header.links.ts @@ -1,15 +1,12 @@ import { routesPath } from "../../router/routes.path"; import { headerMenu } from "../../shared/strings/header.menu.strings"; -import { HeaderActionProps } from "./HeaderAction"; +import { HeaderActionProps, LinkItem } from "./HeaderAction"; -export const testHeaderLinks: HeaderActionProps = - { - links: [ - {link: routesPath.MAIN_PATH, label: headerMenu.home, links: []}, - {link: routesPath.TEST_PATH, label: headerMenu.test, links: []}, - {link: routesPath.SETTINGS_PATH, label: headerMenu.settings, links: []}, - {link: routesPath.RECORDINGS_PATH, label: headerMenu.recordings, links: []}, - {link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig, links: []}, - {link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings, links: []}, - ] - } \ No newline at end of file +export const headerLinks: LinkItem[] = [ + { link: routesPath.MAIN_PATH, label: headerMenu.home }, + { link: routesPath.TEST_PATH, label: headerMenu.test }, + { link: routesPath.SETTINGS_PATH, label: headerMenu.settings }, + { link: routesPath.RECORDINGS_PATH, label: headerMenu.recordings }, + { link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig }, + { link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings }, +] \ No newline at end of file