fix image
fix menu, links add drawer menu
This commit is contained in:
parent
5cfd9155c3
commit
fecd30df70
@ -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 = () => {
|
||||
<AppShell
|
||||
styles={{
|
||||
main: {
|
||||
paddingLeft: !leftSideBar ? "3em" : '',
|
||||
paddingLeft: !leftSideBar ? "1em" : '',
|
||||
paddingRight: !rightSideBar ? '3em' : '',
|
||||
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : undefined,
|
||||
},
|
||||
@ -47,7 +47,7 @@ const AppBody = () => {
|
||||
asideOffsetBreakpoint="sm"
|
||||
|
||||
header={
|
||||
<HeaderAction links={testHeaderLinks.links} />
|
||||
<HeaderAction links={headerLinks} />
|
||||
}
|
||||
aside={
|
||||
<SideBar isHidden={rightSideBarIsHidden} side="right" />
|
||||
|
||||
@ -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
|
||||
@ -46,26 +50,17 @@ const MainPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction='column' h='100%' >
|
||||
<Flex direction='column' h='100%' w='100%' >
|
||||
<Flex justify='space-between' align='center' w='100%'>
|
||||
<Group
|
||||
w='25%'
|
||||
>
|
||||
</Group>
|
||||
<Group
|
||||
w='50%'
|
||||
<Flex w='100%'
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
><HeadSearch /></Group>
|
||||
<Group
|
||||
w='25%'
|
||||
position="right">
|
||||
<ViewSelector state={viewState} onChange={handleToggleState} />
|
||||
</Group>
|
||||
><HeadSearch /></Flex>
|
||||
{/* <ViewSelector state={viewState} onChange={handleToggleState} /> */}
|
||||
</Flex>
|
||||
<Flex justify='center' h='100%' direction='column' >
|
||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||
<Flex justify='center' h='100%' direction='column' w='100%' >
|
||||
<Grid mt='sm' justify="center" mb='sm' align='stretch' mr='0.5rem'>
|
||||
{cards()}
|
||||
</Grid>
|
||||
</Flex>
|
||||
|
||||
@ -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";
|
||||
@ -52,14 +52,12 @@ const AutoUpdatedImage = ({
|
||||
const image = URL.createObjectURL(imageBlob!)
|
||||
|
||||
return (
|
||||
<>
|
||||
{enabled ? <img ref={ref} src={image} alt="Dynamic Content" {...rest}/>
|
||||
:
|
||||
<Flex direction="column" justify="center" h="100%">
|
||||
{enabled ? <Image ref={ref} src={image} alt="Dynamic Content" {...rest} />
|
||||
:
|
||||
<Text align="center">Camera is disabled in config, no stream or snapshot available!</Text>
|
||||
</Flex>
|
||||
}
|
||||
</>)
|
||||
</Flex>)
|
||||
};
|
||||
|
||||
export default AutoUpdatedImage
|
||||
@ -57,9 +57,7 @@ const CameraCard = ({
|
||||
<Grid.Col md={6} lg={3} p='0.2rem'>
|
||||
<Card h='100%' radius="lg" padding='0.5rem' className={classes.mainCard}>
|
||||
<Text align='center' size='md' className={classes.headText} >{camera.name} / {camera.frigateHost?.name}</Text>
|
||||
<Flex direction='column' className={classes.cameraImage}>
|
||||
<AutoUpdatedImage onClick={handleOpenLiveView} enabled={camera.config?.enabled} imageUrl={imageUrl} />
|
||||
</Flex>
|
||||
<Group
|
||||
className={classes.bottomGroup}>
|
||||
<Flex justify='space-evenly' mt='0.5rem' w='100%'>
|
||||
|
||||
85
src/shared/components/DrawerMenu.tsx
Normal file
85
src/shared/components/DrawerMenu.tsx
Normal file
@ -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 => (
|
||||
<UnstyledButton
|
||||
className={classes.drawerButton}
|
||||
key={item.link}
|
||||
onClick={() => handleNavigate(item.link)}
|
||||
>
|
||||
{item.label}
|
||||
</UnstyledButton>
|
||||
))
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Burger opened={drawerOpened} onClick={toggleDrawer} className={classes.burger} size="sm" />
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={closeDrawer}
|
||||
size="100%"
|
||||
padding="md"
|
||||
title="Menu"
|
||||
zIndex={1000000}
|
||||
>
|
||||
<Flex direction='column' w='100%'>
|
||||
{items}
|
||||
</Flex>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default DrawerMenu;
|
||||
@ -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<HTMLImageElement> {
|
||||
// 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<string>("0");
|
||||
// const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||
|
||||
// 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 (
|
||||
// // <AspectRatio ratio={1}>
|
||||
// <Flex direction='column' h='100%'>
|
||||
// {/* <CameraImage
|
||||
// cameraConfig={cameraConfig}
|
||||
// onload={handleLoad}
|
||||
// enabled={cameraConfig?.enabled}
|
||||
// url={url}
|
||||
// {...rest}
|
||||
// /> */}
|
||||
// {showFps ? <span className="text-xs">Displaying at {fps}fps</span> : null}
|
||||
// </Flex>
|
||||
// // </AspectRatio >
|
||||
// );
|
||||
// }
|
||||
@ -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 (
|
||||
// <Fragment>
|
||||
// <Element
|
||||
// role="button"
|
||||
// aria-disabled={disabled ? 'true' : 'false'}
|
||||
// tabindex="0"
|
||||
// className={classes}
|
||||
// href={href}
|
||||
// target={target}
|
||||
// ref={ref}
|
||||
// onmouseenter={handleMousenter}
|
||||
// onmouseleave={handleMouseleave}
|
||||
// {...attrs}
|
||||
// >
|
||||
// {children}
|
||||
// </Element>
|
||||
// {hovered && attrs['aria-label'] ? <Tooltip text={attrs['aria-label']} relativeTo={ref} capitalize={ariaCapitalize} /> : null}
|
||||
// </Fragment>
|
||||
// );
|
||||
// }
|
||||
@ -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 (
|
||||
// <div className={className}>
|
||||
// <AutoUpdatingCameraImage
|
||||
// cameraConfig={cameraConfig}
|
||||
// searchParams={searchParams}
|
||||
// url={url}
|
||||
// />
|
||||
// <Button onClick={handleToggleSettings} variant="link" size="sm">
|
||||
// <span className="w-5 h-5">
|
||||
// <IconSettings />
|
||||
// </span>{" "}
|
||||
// <span>{showSettings ? "Hide" : "Show"} Options</span>
|
||||
// </Button>
|
||||
// {showSettings ? (
|
||||
// <Card>
|
||||
// <CardHeader>
|
||||
// <CardTitle>Options</CardTitle>
|
||||
// </CardHeader>
|
||||
// <CardContent>
|
||||
// <DebugSettings
|
||||
// handleSetOption={handleSetOption}
|
||||
// options={options}
|
||||
// />
|
||||
// </CardContent>
|
||||
// </Card>
|
||||
// ) : null}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// type DebugSettingsProps = {
|
||||
// handleSetOption: (id: string, value: boolean) => void;
|
||||
// options: Options;
|
||||
// };
|
||||
|
||||
// function DebugSettings({ handleSetOption, options }: DebugSettingsProps) {
|
||||
// return (
|
||||
// <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <Switch
|
||||
// id="bbox"
|
||||
// checked={options["bbox"]}
|
||||
// onChange={() => { }}
|
||||
// // onCheckedChange={(isChecked) => {
|
||||
// // handleSetOption("bbox", isChecked);
|
||||
// // }}
|
||||
// />
|
||||
// {/* <Label htmlFor="bbox">Bounding Box</Label> */}
|
||||
// <Text>Bounding Box</Text>
|
||||
// </div>
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <Switch
|
||||
// id="timestamp"
|
||||
// checked={options["timestamp"]}
|
||||
// // onCheckedChange={(isChecked) => {
|
||||
// // handleSetOption("timestamp", isChecked);
|
||||
// // }}
|
||||
// />
|
||||
// {/* <Label htmlFor="timestamp">Timestamp</Label> */}
|
||||
// <Text>Timestamp</Text>
|
||||
// </div>
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <Switch
|
||||
// id="zones"
|
||||
// checked={options["zones"]}
|
||||
// // onCheckedChange={(isChecked) => {
|
||||
// // handleSetOption("zones", isChecked);
|
||||
// // }}
|
||||
// />
|
||||
// {/* <Label htmlFor="zones">Zones</Label> */}
|
||||
// <Text>Zones</Text>
|
||||
// </div>
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <Switch
|
||||
// id="mask"
|
||||
// checked={options["mask"]}
|
||||
// // onCheckedChange={(isChecked) => {
|
||||
// // handleSetOption("mask", isChecked);
|
||||
// // }}
|
||||
// />
|
||||
// {/* <Label htmlFor="mask">Mask</Label> */}
|
||||
// <Text>Mask</Text>
|
||||
// </div>
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <Switch
|
||||
// id="motion"
|
||||
// checked={options["motion"]}
|
||||
// // onCheckedChange={(isChecked) => {
|
||||
// // handleSetOption("motion", isChecked);
|
||||
// // }}
|
||||
// />
|
||||
// {/* <Label htmlFor="motion">Motion</Label> */}
|
||||
// <Text>Motion</Text>
|
||||
// </div>
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <Switch
|
||||
// id="regions"
|
||||
// checked={options["regions"]}
|
||||
// // onCheckedChange={(isChecked) => {
|
||||
// // handleSetOption("regions", isChecked);
|
||||
// // }}
|
||||
// />
|
||||
// {/* <Label htmlFor="regions">Regions</Label> */}
|
||||
// <Text>Regions</Text>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
@ -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 = (
|
||||
// <Fragment>
|
||||
// <div
|
||||
// data-testid="scrim"
|
||||
// key="scrim"
|
||||
// className="fixed bg-fixed inset-0 z-10 flex justify-center items-center bg-black bg-opacity-40"
|
||||
// >
|
||||
// <div
|
||||
// role="modal"
|
||||
// className={`absolute rounded shadow-2xl bg-white dark:bg-gray-700 sm:max-w-sm md:max-w-md lg:max-w-lg text-gray-900 dark:text-white transition-transform transition-opacity duration-75 transform scale-90 opacity-0 ${
|
||||
// show ? 'scale-100 opacity-100' : ''
|
||||
// }`}
|
||||
// >
|
||||
// {children}
|
||||
// </div>
|
||||
// </div>
|
||||
// </Fragment>
|
||||
// );
|
||||
|
||||
// return portalRoot ? createPortal(dialog, portalRoot) : dialog;
|
||||
// }
|
||||
@ -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 (
|
||||
// <RouterLink activeClassName={activeClassName} className={className} href={href} {...props}>
|
||||
// {children}
|
||||
// </RouterLink>
|
||||
// );
|
||||
// }
|
||||
@ -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 ? (
|
||||
// <RelativeModal
|
||||
// children={children}
|
||||
// className={`${className || ''} py-2`}
|
||||
// role="listbox"
|
||||
// onDismiss={onDismiss}
|
||||
// portalRootID="menus"
|
||||
// relativeTo={relativeTo}
|
||||
// widthRelative={widthRelative}
|
||||
// />
|
||||
// ) : 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 (
|
||||
// <Element
|
||||
// className={`flex space-x-2 p-2 px-5 hover:bg-gray-200 dark:hover:bg-gray-800 dark:hover:text-white cursor-pointer ${
|
||||
// focus ? 'bg-gray-200 dark:bg-gray-800 dark:text-white' : ''
|
||||
// }`}
|
||||
// href={href}
|
||||
// onClick={handleClick}
|
||||
// role="option"
|
||||
// {...attrs}
|
||||
// >
|
||||
// {Icon ? (
|
||||
// <div className="w-6 h-6 self-center mr-4 text-gray-500 flex-shrink-0">
|
||||
// <Icon />
|
||||
// </div>
|
||||
// ) : null}
|
||||
// <div className="whitespace-nowrap">{label}</div>
|
||||
// </Element>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export function MenuSeparator() {
|
||||
// return <div className="border-b border-gray-200 dark:border-gray-800 my-2" />;
|
||||
// }
|
||||
@ -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 (
|
||||
// <div className={`${className} p-2`} ref={popupRef}>
|
||||
// <div className="flex justify-between min-w-[120px]" onClick={() => setState({ showMenu: true })}>
|
||||
// <label>{title}</label>
|
||||
// <ArrowDropdown className="w-6" />
|
||||
// </div>
|
||||
// {state.showMenu ? (
|
||||
// <Menu
|
||||
// className={`max-h-[${menuHeight}px] overflow-auto`}
|
||||
// relativeTo={popupRef}
|
||||
// onDismiss={() => setState({ showMenu: false })}
|
||||
// >
|
||||
// <div className="flex flex-wrap justify-between items-center">
|
||||
// <Heading className="p-4 justify-center" size="md">
|
||||
// {title}
|
||||
// </Heading>
|
||||
// <Button tabindex="false" className="mx-4" onClick={() => onShowAll()}>
|
||||
// Show All
|
||||
// </Button>
|
||||
// </div>
|
||||
// {options.map((item) => (
|
||||
// <div className="flex flex-grow" key={item}>
|
||||
// <label
|
||||
// className={`flex flex-shrink space-x-2 p-1 my-1 min-w-[176px] hover:bg-gray-200 dark:hover:bg-gray-800 dark:hover:text-white cursor-pointer capitalize text-sm`}
|
||||
// >
|
||||
// <input
|
||||
// className="mx-4 m-0 align-middle"
|
||||
// type="checkbox"
|
||||
// checked={isOptionSelected(item)}
|
||||
// onChange={() => onToggle(item)}
|
||||
// />
|
||||
// {item.replaceAll('_', ' ')}
|
||||
// </label>
|
||||
// <div className="justify-right">
|
||||
// <Button
|
||||
// color={isOptionSelected(item) ? 'blue' : 'black'}
|
||||
// type="text"
|
||||
// className="max-h-[35px] mx-2"
|
||||
// onClick={() => onSelectSingle(item)}
|
||||
// >
|
||||
// { ( <SelectOnlyIcon /> ) }
|
||||
// </Button>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </Menu>
|
||||
// ) : null}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
@ -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 (
|
||||
// <div className={`flex ${className}`}>
|
||||
// <RenderChildren />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// 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 (
|
||||
// <button onClick={onClick} disabled={disabled} className={`rounded-full px-4 py-2 ${selectedStyle}`}>
|
||||
// <span>{text}</span>
|
||||
// </button>
|
||||
// );
|
||||
// }
|
||||
@ -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<IProp> = ({ refreshInterval = 1000, ...rest }): JSX.Element => {
|
||||
// const [currentTime, setCurrentTime] = useState<Date>(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 <span>{timeAgoValue}</span>;
|
||||
// };
|
||||
// export default TimeAgo;
|
||||
@ -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 (
|
||||
// <Fragment>
|
||||
// <div
|
||||
// className="absolute border-4 border-red-600"
|
||||
// onMouseEnter={() => 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' ? (
|
||||
// <div className="absolute w-2 h-2 bg-yellow-500 left-[50%] -translate-x-1/2 translate-y-3/4 bottom-0" />
|
||||
// ) : null}
|
||||
// </div>
|
||||
// {isHovering && (
|
||||
// <div className="absolute bg-white dark:bg-slate-800 p-4 block text-black dark:text-white text-lg" style={getHoverStyle()}>
|
||||
// <div>{`Area: ${getObjectArea()} px`}</div>
|
||||
// <div>{`Ratio: ${getObjectRatio()}`}</div>
|
||||
// </div>
|
||||
// )}
|
||||
// </Fragment>
|
||||
// );
|
||||
// }
|
||||
@ -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 <ActivityIndicator />;
|
||||
// }
|
||||
|
||||
// if (eventTimeline.length == 0) {
|
||||
// return <div />;
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="flex flex-col">
|
||||
// <div className="h-14 flex justify-center">
|
||||
// <div className="flex flex-row flex-nowrap justify-between overflow-auto">
|
||||
// {eventTimeline.map((item, index) => (
|
||||
// <Button
|
||||
// key={index}
|
||||
// className="rounded-full"
|
||||
// type="iconOnly"
|
||||
// color={index == timeIndex ? 'blue' : 'gray'}
|
||||
// aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
||||
// onClick={() => onSelectMoment(index)}
|
||||
// >
|
||||
// {getTimelineIcon(item)}
|
||||
// </Button>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// {timeIndex >= 0 ? (
|
||||
// <div className="m-2 max-w-md self-center">
|
||||
// <div className="flex justify-start">
|
||||
// <div className="text-lg flex justify-between py-4">Bounding boxes may not align</div>
|
||||
// <Button
|
||||
// className="rounded-full"
|
||||
// type="text"
|
||||
// color="gray"
|
||||
// aria-label=" Disclaimer: This data comes from the detect feed but is shown on the recordings, it is unlikely that the
|
||||
// streams are perfectly in sync so the bounding box and the footage will not line up perfectly. The annotation_offset field can be used to adjust this."
|
||||
// >
|
||||
// <About className="w-4" />
|
||||
// </Button>
|
||||
// </div>
|
||||
// </div>
|
||||
// ) : null}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// function getTimelineIcon(timelineItem) {
|
||||
// switch (timelineItem.class_type) {
|
||||
// case 'visible':
|
||||
// return <PlayIcon className="w-8" />;
|
||||
// case 'gone':
|
||||
// return <ExitIcon className="w-8" />;
|
||||
// case 'active':
|
||||
// return <ActiveObjectIcon className="w-8" />;
|
||||
// case 'stationary':
|
||||
// return <StationaryObjectIcon className="w-8" />;
|
||||
// case 'entered_zone':
|
||||
// return <ZoneIcon className="w-8" />;
|
||||
// case 'attribute':
|
||||
// switch (timelineItem.data.attribute) {
|
||||
// case 'face':
|
||||
// return <FaceIcon className="w-8" />;
|
||||
// case 'license_plate':
|
||||
// return <LicensePlateIcon className="w-8" />;
|
||||
// default:
|
||||
// return <DeliveryTruckIcon className="w-8" />;
|
||||
// }
|
||||
// case 'sub_label':
|
||||
// switch (timelineItem.data.label) {
|
||||
// case 'person':
|
||||
// return <FaceIcon className="w-8" />;
|
||||
// case 'car':
|
||||
// return <LicensePlateIcon className="w-8" />;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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,
|
||||
// })}`;
|
||||
// }
|
||||
// }
|
||||
@ -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 = (
|
||||
// <div
|
||||
// role="tooltip"
|
||||
// className={`shadow max-w-lg absolute pointer-events-none bg-gray-900 dark:bg-gray-200 bg-opacity-80 rounded px-2 py-1 transition-transform transition-opacity duration-75 transform scale-90 opacity-0 text-gray-100 dark:text-gray-900 text-sm ${
|
||||
// capitalize ? 'capitalize' : ''
|
||||
// } ${position.top >= 0 ? 'opacity-100 scale-100' : ''}`}
|
||||
// ref={ref}
|
||||
// style={position}
|
||||
// >
|
||||
// {text}
|
||||
// </div>
|
||||
// );
|
||||
|
||||
// return portalRoot ? createPortal(tooltip, portalRoot) : tooltip;
|
||||
// }
|
||||
@ -1,81 +0,0 @@
|
||||
// import * as React from "react"
|
||||
|
||||
export {}
|
||||
|
||||
// const Card = React.forwardRef<
|
||||
// HTMLDivElement,
|
||||
// React.HTMLAttributes<HTMLDivElement>
|
||||
// >(({ className, ...props }, ref) => (
|
||||
// <div
|
||||
// ref={ref}
|
||||
// // className={cn(
|
||||
// // "rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
// // className
|
||||
// // )}
|
||||
// {...props}
|
||||
// />
|
||||
// ))
|
||||
// Card.displayName = "Card"
|
||||
|
||||
// const CardHeader = React.forwardRef<
|
||||
// HTMLDivElement,
|
||||
// React.HTMLAttributes<HTMLDivElement>
|
||||
// >(({ className, ...props }, ref) => (
|
||||
// <div
|
||||
// ref={ref}
|
||||
// // className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
// {...props}
|
||||
// />
|
||||
// ))
|
||||
// CardHeader.displayName = "CardHeader"
|
||||
|
||||
// const CardTitle = React.forwardRef<
|
||||
// HTMLParagraphElement,
|
||||
// React.HTMLAttributes<HTMLHeadingElement>
|
||||
// >(({ className, ...props }, ref) => (
|
||||
// <h3
|
||||
// ref={ref}
|
||||
// // className={cn(
|
||||
// // "text-2xl font-semibold leading-none tracking-tight",
|
||||
// // className
|
||||
// // )}
|
||||
// {...props}
|
||||
// />
|
||||
// ))
|
||||
// CardTitle.displayName = "CardTitle"
|
||||
|
||||
// const CardDescription = React.forwardRef<
|
||||
// HTMLParagraphElement,
|
||||
// React.HTMLAttributes<HTMLParagraphElement>
|
||||
// >(({ className, ...props }, ref) => (
|
||||
// <p
|
||||
// ref={ref}
|
||||
// // className={cn("text-sm text-muted-foreground", className)}
|
||||
// {...props}
|
||||
// />
|
||||
// ))
|
||||
// CardDescription.displayName = "CardDescription"
|
||||
|
||||
// const CardContent = React.forwardRef<
|
||||
// HTMLDivElement,
|
||||
// React.HTMLAttributes<HTMLDivElement>
|
||||
// >(({ className, ...props }, ref) => (
|
||||
// <div ref={ref}
|
||||
// // className= {cn("p-6 pt-0", className)}
|
||||
// {...props} />
|
||||
// ))
|
||||
// CardContent.displayName = "CardContent"
|
||||
|
||||
// const CardFooter = React.forwardRef<
|
||||
// HTMLDivElement,
|
||||
// React.HTMLAttributes<HTMLDivElement>
|
||||
// >(({ className, ...props }, ref) => (
|
||||
// <div
|
||||
// ref={ref}
|
||||
// // className={cn("flex items-center p-6 pt-0", className)}
|
||||
// {...props}
|
||||
// />
|
||||
// ))
|
||||
// CardFooter.displayName = "CardFooter"
|
||||
|
||||
// export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
@ -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',
|
||||
@ -51,50 +53,42 @@ const useStyles = createStyles((theme) => ({
|
||||
|
||||
}))
|
||||
|
||||
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) => (
|
||||
<Menu.Item key={item.link}>{item.label}</Menu.Item>
|
||||
))
|
||||
|
||||
if (menuItems) {
|
||||
return (
|
||||
<Menu key={link.label} trigger="hover" transitionProps={{ exitDuration: 0 }} withinPortal>
|
||||
const items = links.map(item =>
|
||||
<Menu key={item.label} trigger="hover" transitionProps={{ exitDuration: 0 }} withinPortal>
|
||||
<Menu.Target>
|
||||
<Button variant="subtle" uppercase onClick={() => handleNavigate(link.link)}>
|
||||
{link.label}
|
||||
<Button variant="subtle" uppercase onClick={() => handleNavigate(item.link)}>
|
||||
{item.label}
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Header height={HEADER_HEIGHT} sx={{ borderBottom: 0 }}>
|
||||
<Container className={classes.inner} fluid>
|
||||
<Flex wrap='nowrap' >
|
||||
<Logo onClick={() => handleNavigate(routesPath.MAIN_PATH)} />
|
||||
<Burger opened={opened} onClick={toggle} className={classes.burger} size="sm" />
|
||||
{/* <Burger opened={opened} onClick={toggle} className={classes.burger} size="sm" /> */}
|
||||
<DrawerMenu links={links} />
|
||||
<Flex className={classes.leftLinksMenu}>
|
||||
{items}
|
||||
</Flex>
|
||||
|
||||
@ -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: []},
|
||||
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 },
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user