Add user tags filter at main page
This commit is contained in:
parent
d39e976053
commit
90114ac7a6
@ -8,6 +8,7 @@ import { useEffect, useState } from 'react';
|
||||
import AppBody from './AppBody';
|
||||
import { FfprobeModal } from './shared/components/modal.windows/FfprobeModal';
|
||||
import { VaInfoModal } from './shared/components/modal.windows/VaInfoModal';
|
||||
import { SideBarProvider } from './widgets/sidebars/SideBarContext';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@ -67,8 +68,10 @@ function App() {
|
||||
}}
|
||||
>
|
||||
<ModalsProvider modals={modals}>
|
||||
<Notifications />
|
||||
<AppBody />
|
||||
<SideBarProvider>
|
||||
<Notifications />
|
||||
<AppBody />
|
||||
</SideBarProvider>
|
||||
</ModalsProvider>
|
||||
</MantineProvider >
|
||||
</ColorSchemeProvider>
|
||||
|
||||
@ -5,9 +5,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Context } from '.';
|
||||
import AppRouter from './router/AppRouter';
|
||||
import { routesPath } from './router/routes.path';
|
||||
import SideBar from './shared/components/SideBar';
|
||||
import { isProduction } from './shared/env.const';
|
||||
import { HeaderAction } from './widgets/header/HeaderAction';
|
||||
import RightSideBar from "./shared/components/RightSideBar";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const AppBody = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -20,20 +21,22 @@ const AppBody = () => {
|
||||
{ link: routesPath.ACCESS_PATH, label: t('header.acessSettings'), admin: true },
|
||||
]
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
const [leftSideBar, setLeftSidebar] = useState(false)
|
||||
const [rightSideBar, setRightSidebar] = useState(false)
|
||||
const location = useLocation()
|
||||
|
||||
const leftSideBarIsHidden = (isHidden: boolean) => {
|
||||
setLeftSidebar(!isHidden)
|
||||
}
|
||||
const rightSideBarIsHidden = (isHidden: boolean) => {
|
||||
setRightSidebar(!isHidden)
|
||||
}
|
||||
const pathsWithLeftSidebar: string[] = []
|
||||
const pathsWithRightSidebar: string[] = [routesPath.MAIN_PATH, routesPath.RECORDINGS_PATH]
|
||||
|
||||
const [leftSideBar, setLeftSidebar] = useState(pathsWithLeftSidebar.includes(location.pathname))
|
||||
const [rightSideBar, setRightSidebar] = useState(pathsWithRightSidebar.includes(location.pathname))
|
||||
|
||||
const handleRightSidebarChange = (isVisible: boolean) => {
|
||||
setRightSidebar(isVisible);
|
||||
};
|
||||
|
||||
const theme = useMantineTheme();
|
||||
|
||||
|
||||
if (!isProduction) console.log("render Main")
|
||||
return (
|
||||
<AppShell
|
||||
@ -50,9 +53,10 @@ const AppBody = () => {
|
||||
header={
|
||||
<HeaderAction links={headerLinks} />
|
||||
}
|
||||
|
||||
aside={
|
||||
!sideBarsStore.rightVisible ? <></> :
|
||||
<SideBar isHidden={rightSideBarIsHidden} side="right" />
|
||||
!pathsWithRightSidebar.includes(location.pathname) ? <></> :
|
||||
<RightSideBar onChangeHidden={handleRightSidebarChange}/>
|
||||
}
|
||||
>
|
||||
<AppRouter />
|
||||
|
||||
@ -2,7 +2,8 @@ import { error } from "console"
|
||||
|
||||
const en = {
|
||||
mainPage: {
|
||||
createSelectTags: 'Create\\Select tags'
|
||||
createSelectTags: 'Create\\Select tags',
|
||||
tagsError: 'Cannot be more than 10 symbols',
|
||||
},
|
||||
editCameraPage: {
|
||||
notFrigateCamera: 'Not frigate camera',
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const ru = {
|
||||
mainPage: {
|
||||
createSelectTags: 'Создать\\Выбрать тэги'
|
||||
createSelectTags: 'Создай\\Выбери тэги',
|
||||
tagsError: 'Не может быть более 10 символов',
|
||||
},
|
||||
editCameraPage: {
|
||||
notFrigateCamera: 'Не камера Фригата',
|
||||
|
||||
@ -10,17 +10,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
const Forbidden = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
const handleGoToMain = () => {
|
||||
navigate(routesPath.MAIN_PATH)
|
||||
|
||||
@ -1,24 +1,11 @@
|
||||
import { Button, Flex, Text } from '@mantine/core';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
||||
import { routesPath } from '../router/routes.path';
|
||||
import { Context } from '..';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { routesPath } from '../router/routes.path';
|
||||
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
||||
|
||||
const NotFound = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
const handleGoToMain = () => {
|
||||
window.location.replace(routesPath.MAIN_PATH)
|
||||
|
||||
@ -2,9 +2,8 @@ import { Flex, Group, Text } from '@mantine/core';
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
||||
@ -17,22 +16,13 @@ import RetryErrorPage from './RetryErrorPage';
|
||||
|
||||
const AccessSettings = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const { data, isPending, isError, refetch } = useQuery({
|
||||
queryKey: [frigateQueryKeys.getRoles],
|
||||
queryFn: frigateApi.getRoles
|
||||
})
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
|
||||
const isMobile = useMediaQuery(dimensions.mobileSize)
|
||||
const [roleId, setRoleId] = useState<string>()
|
||||
|
||||
@ -3,10 +3,9 @@ import { notifications } from '@mantine/notifications';
|
||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||
import MaskSelect, { MaskItem, MaskType } from '../shared/components/filters/MaskSelect';
|
||||
@ -19,7 +18,6 @@ import RetryErrorPage from './RetryErrorPage';
|
||||
|
||||
const EditCameraPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
let { id: cameraId } = useParams<'id'>()
|
||||
if (!cameraId) throw Error(t('editCameraPage.cameraIdNotExist'))
|
||||
const [selectedMask, setSelectedMask] = useState<MaskItem>()
|
||||
@ -105,16 +103,6 @@ const EditCameraPage = () => {
|
||||
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
if (isPending || adminLoading) return <CenterLoader />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||
|
||||
@ -3,9 +3,8 @@ import { notifications } from '@mantine/notifications';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
||||
@ -19,22 +18,12 @@ import RetryErrorPage from './RetryErrorPage';
|
||||
const FrigateHostsPage = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const executed = useRef(false)
|
||||
const queryClient = useQueryClient()
|
||||
const { isPending: hostsPending, error: hostsError, data } = useQuery({
|
||||
queryKey: [frigateQueryKeys.getFrigateHosts],
|
||||
queryFn: frigateApi.getHosts,
|
||||
})
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
const [pageData, setPageData] = useState(data)
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { Button, Flex, Text, useMantineTheme } from '@mantine/core';
|
||||
import { Button, Flex, useMantineTheme } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import Editor, { Monaco } from '@monaco-editor/react';
|
||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import * as monaco from "monaco-editor";
|
||||
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||
@ -16,9 +18,6 @@ import { isProduction } from '../shared/env.const';
|
||||
import { SaveOption } from '../types/saveConfig';
|
||||
import Forbidden from './403';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
@ -42,9 +41,7 @@ const HostConfigPage = () => {
|
||||
const queryParams = useMemo(() => {
|
||||
return new URLSearchParams(location.search);
|
||||
}, [location.search])
|
||||
const executed = useRef(false)
|
||||
const host = useRef<GetFrigateHost | undefined>()
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
|
||||
let { id } = useParams<'id'>()
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
@ -96,15 +93,6 @@ const HostConfigPage = () => {
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
const clipboard = useClipboard({ timeout: 500 })
|
||||
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||
|
||||
@ -2,10 +2,9 @@ import { Flex, Grid, SegmentedControl, Text } from '@mantine/core';
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||
@ -16,9 +15,9 @@ import StorageRingStat from '../shared/components/stats/StorageRingStat';
|
||||
import { isProduction } from '../shared/env.const';
|
||||
import { formatUptime } from '../shared/utils/dateUtil';
|
||||
import FrigateCamerasStateTable, { CameraItem, ProcessType } from '../widgets/camera.stat.table/FrigateCameraStateTable';
|
||||
import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable';
|
||||
import Forbidden from './403';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable';
|
||||
|
||||
export const hostSystemPageQuery = {
|
||||
hostId: 'hostId',
|
||||
@ -31,21 +30,10 @@ enum SelectorItems {
|
||||
|
||||
const HostSystemPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
const { isAdmin } = useAdminRole()
|
||||
const host = useRef<GetFrigateHost | undefined>()
|
||||
const [selector, setSelector] = useState(SelectorItems.Cameras)
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
let { id: paramHostId } = useParams<'id'>()
|
||||
|
||||
const { data, isError, isPending, refetch } = useQuery({
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { Flex } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Context } from '..';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
import Player from '../widgets/Player';
|
||||
@ -13,7 +11,6 @@ import RetryErrorPage from './RetryErrorPage';
|
||||
|
||||
const LiveCameraPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
let { id: cameraId } = useParams<'id'>()
|
||||
if (!cameraId) throw Error('Camera id does not exist')
|
||||
|
||||
@ -22,17 +19,6 @@ const LiveCameraPage = () => {
|
||||
queryFn: () => frigateApi.getCameraWHost(cameraId!)
|
||||
})
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
|
||||
if (isPending) return <CenterLoader />
|
||||
|
||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import { Flex, Grid, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { Flex, Grid } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Context } from '..';
|
||||
import { useRealmUser } from '../hooks/useRealmUser';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
import CameraCard from '../widgets/CameraCard';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import ClearableTextInput from '../shared/components/inputs/ClearableTextInput';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide';
|
||||
import { isProduction } from '../shared/env.const';
|
||||
import { useKeycloak } from '@react-keycloak/web';
|
||||
import { useRealmUser } from '../hooks/useRealmUser';
|
||||
import CameraCard from '../widgets/CameraCard';
|
||||
import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide';
|
||||
import { SideBarContext } from '../widgets/sidebars/SideBarContext';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import ClearableTextInput from '../shared/components/inputs/ClearableTextInput';
|
||||
|
||||
const MainPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore, mainStore } = useContext(Context)
|
||||
const { mainStore } = useContext(Context)
|
||||
const { setChildrenComponent } = useContext(SideBarContext)
|
||||
const { selectedHostId } = mainStore
|
||||
const [searchQuery, setSearchQuery] = useState<string>()
|
||||
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
||||
@ -32,6 +32,11 @@ const MainPage = () => {
|
||||
queryFn: frigateApi.getCamerasWHost
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setChildrenComponent(<MainFiltersRightSide />);
|
||||
return () => setChildrenComponent(null);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cameras) {
|
||||
setFilteredCameras(undefined)
|
||||
@ -48,26 +53,6 @@ const MainPage = () => {
|
||||
}, [searchQuery, cameras, selectedHostId])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.leftVisible = false
|
||||
executed.current = true
|
||||
if (!isProduction) console.log('MainPage rendered first time')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
sideBarsStore.setRightChildren(<MainFiltersRightSide />)
|
||||
sideBarsStore.rightVisible = true
|
||||
|
||||
return () => {
|
||||
sideBarsStore.setRightChildren(null)
|
||||
sideBarsStore.rightVisible = false
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
//test change
|
||||
|
||||
const cards = useMemo(() => {
|
||||
if (filteredCameras)
|
||||
return filteredCameras.filter(camera => {
|
||||
@ -115,7 +100,8 @@ const MainPage = () => {
|
||||
</Flex>
|
||||
<Flex justify='center' h='100%' direction='column' w='100%' >
|
||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||
{cards}
|
||||
{/* TODO DELETE SLICE TO WORK */}
|
||||
{cards.slice(0, 5)}
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@ -1,26 +1,14 @@
|
||||
import { Flex } from '@mantine/core';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import VideoPlayer from '../shared/components/players/VideoPlayer';
|
||||
import NotFound from './404';
|
||||
import { Context } from '..';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
export const playRecordPageQuery = {
|
||||
link: 'link',
|
||||
}
|
||||
|
||||
const PlayRecordPage = () => {
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
const location = useLocation()
|
||||
const queryParams = new URLSearchParams(location.search)
|
||||
|
||||
@ -11,6 +11,7 @@ import SelectedCameraList from '../widgets/SelectedCameraList';
|
||||
import SelectedDayList from '../widgets/SelectedDayList';
|
||||
import SelectedHostList from '../widgets/SelectedHostList';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SideBarContext } from '../widgets/sidebars/SideBarContext';
|
||||
|
||||
|
||||
export const recordingsPageQuery = {
|
||||
@ -24,7 +25,7 @@ export const recordingsPageQuery = {
|
||||
const RecordingsPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore, recordingsStore: recStore } = useContext(Context)
|
||||
const { recordingsStore: recStore } = useContext(Context)
|
||||
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
@ -40,12 +41,17 @@ const RecordingsPage = () => {
|
||||
const [cameraId, setCameraId] = useState<string>('')
|
||||
const [period, setPeriod] = useState<[Date | null, Date | null]>([null, null])
|
||||
|
||||
const { setChildrenComponent } = useContext(SideBarContext)
|
||||
|
||||
useEffect(() => {
|
||||
setChildrenComponent(<RecordingsFiltersRightSide />);
|
||||
|
||||
return () => setChildrenComponent(null);
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = true
|
||||
sideBarsStore.setRightChildren(
|
||||
<RecordingsFiltersRightSide />
|
||||
)
|
||||
if (paramHostId) recStore.hostIdParam = paramHostId
|
||||
if (paramCameraId) recStore.cameraIdParam = paramCameraId
|
||||
if (paramStartDay && paramEndDay) {
|
||||
@ -55,10 +61,6 @@ const RecordingsPage = () => {
|
||||
}
|
||||
executed.current = true
|
||||
if (!isProduction) console.log('RecordingsPage rendered first time')
|
||||
return () => {
|
||||
sideBarsStore.setRightChildren(null)
|
||||
sideBarsStore.rightVisible = false
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Flex, Button, Text } from '@mantine/core';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import { routesPath } from '../router/routes.path';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
|
||||
import { Context } from '..';
|
||||
import { Button, Flex, Text } from '@mantine/core';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { routesPath } from '../router/routes.path';
|
||||
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
|
||||
|
||||
interface RetryErrorPageProps {
|
||||
repeatVisible?: boolean
|
||||
@ -21,20 +20,9 @@ const RetryErrorPage = ({
|
||||
onRetry
|
||||
}: RetryErrorPageProps) => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
const handleGoToMain = () => {
|
||||
navigate(routesPath.MAIN_PATH)
|
||||
}
|
||||
|
||||
@ -2,9 +2,8 @@ import { Flex, Space } from '@mantine/core';
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Context } from '..';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetRole } from '../services/frigate.proxy/frigate.schema';
|
||||
@ -16,21 +15,10 @@ import Forbidden from './403';
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
|
||||
const [showRoles, setShowRoles] = useState<boolean>(false)
|
||||
const [allRoles, setAllRoles] = useState<GetRole[]>()
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
|
||||
const isMobile = useMediaQuery(dimensions.mobileSize)
|
||||
|
||||
@ -3,29 +3,13 @@ import { Editor, Monaco } from '@monaco-editor/react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
||||
import { useContext, useEffect, useRef } from 'react';
|
||||
import { Context } from '..';
|
||||
import { useRef } from 'react';
|
||||
import HeadSearch from '../shared/components/inputs/HeadSearch';
|
||||
|
||||
const Test = () => {
|
||||
const executed = useRef(false)
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||
const theme = useMantineTheme();
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
sideBarsStore.rightVisible = true
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
|
||||
|
||||
|
||||
|
||||
const value = `
|
||||
# Property descriptions are displayed when hovering over properties using your cursor
|
||||
|
||||
@ -21,10 +21,6 @@ interface IRoute {
|
||||
}
|
||||
|
||||
export const routes: IRoute[] = [
|
||||
{ //todo delete
|
||||
path: routesPath.TEST_PATH,
|
||||
component: <Test />,
|
||||
},
|
||||
{
|
||||
path: routesPath.SETTINGS_PATH,
|
||||
component: <SettingsPage />,
|
||||
|
||||
@ -15,6 +15,7 @@ import { FrigateStats, GetFfprobe, GetHostStorage, GetVaInfo } from "../../types
|
||||
import { PostSaveConfig, SaveOption } from "../../types/saveConfig";
|
||||
import keycloak from "../keycloak-config";
|
||||
import { PutMask } from "../../types/mask";
|
||||
import { GetUserTag, PutUserTag } from "../../types/tags";
|
||||
|
||||
const instanceApi = axios.create({
|
||||
baseURL: proxyURL.toString(),
|
||||
@ -61,6 +62,9 @@ export const frigateApi = {
|
||||
cameraIDs: cameraIDs
|
||||
}).then(res => res.data),
|
||||
getAdminRole: () => instanceApi.get<GetConfig>('apiv1/config/admin').then(res => res.data),
|
||||
getUserTags: () => instanceApi.get<GetUserTag[]>('apiv1/tags').then(res => res.data),
|
||||
putUserTag: (tag: PutUserTag) => instanceApi.put<GetUserTag>('apiv1/tags', tag).then(res => res.data),
|
||||
delUserTag: (tagId: string) => instanceApi.delete<GetUserTag>(`apiv1/tags/${tagId}`).then(res => res.data)
|
||||
}
|
||||
|
||||
export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`
|
||||
@ -242,4 +246,6 @@ export const frigateQueryKeys = {
|
||||
getRoleWCameras: 'roles-cameras',
|
||||
getUsersByRole: 'users-role',
|
||||
getAdminRole: 'admin-role',
|
||||
getUserTags: 'users-tags',
|
||||
putUserTag: 'put-user-tag',
|
||||
}
|
||||
|
||||
62
src/shared/components/RightSideBar.tsx
Normal file
62
src/shared/components/RightSideBar.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { Aside, Button, createStyles } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { t } from 'i18next';
|
||||
import React, { useContext } from 'react';
|
||||
import { SideBarContext } from '../../widgets/sidebars/SideBarContext';
|
||||
import { dimensions } from '../dimensions/dimensions';
|
||||
import { useMantineSize } from '../utils/mantine.size.convertor';
|
||||
import { SideButton } from './SideButton';
|
||||
|
||||
|
||||
interface RightSideBarProps {
|
||||
onChangeHidden?: (isHidden: boolean) => void,
|
||||
children?: React.ReactNode,
|
||||
}
|
||||
|
||||
|
||||
const useStyles = createStyles((theme,
|
||||
{ visible }: { visible: boolean }) => ({
|
||||
navbar: {
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
transform: visible ? 'translateX(0)' : 'translateX(-100%)',
|
||||
},
|
||||
|
||||
aside: {
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
transform: visible ? 'translateX(0)' : 'translateX(+100%)',
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
const RightSideBar = ({ onChangeHidden, children }: RightSideBarProps) => {
|
||||
|
||||
const side = 'right'
|
||||
|
||||
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||
|
||||
const [visible, { open, close }] = useDisclosure(true);
|
||||
|
||||
const { classes } = useStyles({ visible })
|
||||
|
||||
const { childrenComponent } = useContext(SideBarContext);
|
||||
|
||||
const handleClickVisible = (state: boolean) => {
|
||||
if (state) open()
|
||||
else close()
|
||||
if (onChangeHidden) onChangeHidden(state)
|
||||
}
|
||||
|
||||
return (<div>
|
||||
<Aside
|
||||
className={classes.aside}
|
||||
p={dimensions.hideSidebarsSize}
|
||||
width={{ sm: 200, lg: 300 }}>
|
||||
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||
{childrenComponent}
|
||||
</Aside>
|
||||
<SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightSideBar;
|
||||
@ -8,111 +8,113 @@ import { dimensions } from '../dimensions/dimensions';
|
||||
import { useMantineSize } from '../utils/mantine.size.convertor';
|
||||
import { SideButton } from './SideButton';
|
||||
|
||||
export interface SideBarProps {
|
||||
isHidden: (isHidden: boolean) => void,
|
||||
side: 'left' | 'right',
|
||||
children?: React.ReactNode,
|
||||
}
|
||||
// export interface SideBarProps {
|
||||
// onChangeHidden: (isHidden: boolean) => void,
|
||||
// side: 'left' | 'right',
|
||||
// children?: React.ReactNode,
|
||||
// }
|
||||
|
||||
const useStyles = createStyles((theme,
|
||||
{ visible }: { visible: boolean }) => ({
|
||||
navbar: {
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
transform: visible ? 'translateX(0)' : 'translateX(-100%)',
|
||||
},
|
||||
// const useStyles = createStyles((theme,
|
||||
// { visible }: { visible: boolean }) => ({
|
||||
// navbar: {
|
||||
// transition: 'transform 0.3s ease-in-out',
|
||||
// transform: visible ? 'translateX(0)' : 'translateX(-100%)',
|
||||
// },
|
||||
|
||||
aside: {
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
transform: visible ? 'translateX(0)' : 'translateX(+100%)',
|
||||
},
|
||||
}))
|
||||
// aside: {
|
||||
// transition: 'transform 0.3s ease-in-out',
|
||||
// transform: visible ? 'translateX(0)' : 'translateX(+100%)',
|
||||
// },
|
||||
// }))
|
||||
|
||||
|
||||
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
||||
const { t } = useTranslation()
|
||||
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||
const initialVisible = () => {
|
||||
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||
if (savedVisibility === null) {
|
||||
return window.innerWidth < hideSizePx;
|
||||
}
|
||||
return savedVisibility === 'true';
|
||||
}
|
||||
const [visible, { open, close }] = useDisclosure(initialVisible());
|
||||
// const SideBar = ({ onChangeHidden: isHidden, side, children }: SideBarProps) => {
|
||||
// const { t } = useTranslation()
|
||||
// const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||
// const initialVisible = () => {
|
||||
// const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||
// if (savedVisibility === null) {
|
||||
// return window.innerWidth < hideSizePx;
|
||||
// }
|
||||
// return savedVisibility === 'true';
|
||||
// }
|
||||
// const [visible, { open, close }] = useDisclosure(initialVisible());
|
||||
|
||||
const { classes } = useStyles({ visible })
|
||||
// const { classes } = useStyles({ visible })
|
||||
|
||||
const handleClickVisible = (state: boolean) => {
|
||||
localStorage.setItem(`sidebarVisible_${side}`, String(state))
|
||||
if (state) open()
|
||||
else close()
|
||||
}
|
||||
// const handleClickVisible = (state: boolean) => {
|
||||
// localStorage.setItem(`sidebarVisible_${side}`, String(state))
|
||||
// if (state) open()
|
||||
// else close()
|
||||
// }
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
useEffect(() => {
|
||||
if (sideBarsStore.rightVisible && side === 'right' && !visible) {
|
||||
open()
|
||||
} else if (!sideBarsStore.rightVisible && side === 'right' && visible) {
|
||||
close()
|
||||
}
|
||||
}, [sideBarsStore.rightVisible])
|
||||
// const { sideBarsStore } = useContext(Context)
|
||||
// useEffect(() => {
|
||||
// if (sideBarsStore.rightVisible && side === 'right' && !visible) {
|
||||
// open()
|
||||
// } else if (!sideBarsStore.rightVisible && side === 'right' && visible) {
|
||||
// close()
|
||||
// }
|
||||
// }, [sideBarsStore.rightVisible])
|
||||
|
||||
const [leftChildren, setLeftChildren] = useState<React.ReactNode>(() => {
|
||||
if (children && side === 'left') return children
|
||||
else if (sideBarsStore.leftChildren) return sideBarsStore.leftChildren
|
||||
return null
|
||||
})
|
||||
const [rightChildren, setRightChildren] = useState<React.ReactNode>(() => {
|
||||
if (children && side === 'right') return children
|
||||
else if (sideBarsStore.rightChildren) return sideBarsStore.rightChildren
|
||||
return null
|
||||
})
|
||||
// const [leftChildren, setLeftChildren] = useState<React.ReactNode>(() => {
|
||||
// if (children && side === 'left') return children
|
||||
// else if (sideBarsStore.leftChildren) return sideBarsStore.leftChildren
|
||||
// return null
|
||||
// })
|
||||
// const [rightChildren, setRightChildren] = useState<React.ReactNode>(() => {
|
||||
// if (children && side === 'right') return children
|
||||
// else if (sideBarsStore.rightChildren) return sideBarsStore.rightChildren
|
||||
// return null
|
||||
// })
|
||||
|
||||
useEffect(() => {
|
||||
setLeftChildren(sideBarsStore.leftChildren)
|
||||
}, [sideBarsStore.leftChildren])
|
||||
// useEffect(() => {
|
||||
// setLeftChildren(sideBarsStore.leftChildren)
|
||||
// }, [sideBarsStore.leftChildren])
|
||||
|
||||
useEffect(() => {
|
||||
setRightChildren(sideBarsStore.rightChildren)
|
||||
}, [sideBarsStore.rightChildren])
|
||||
// useEffect(() => {
|
||||
// setRightChildren(sideBarsStore.rightChildren)
|
||||
// }, [sideBarsStore.rightChildren])
|
||||
|
||||
useEffect(() => {
|
||||
isHidden(!visible)
|
||||
}, [visible])
|
||||
// useEffect(() => {
|
||||
// isHidden(!visible)
|
||||
// }, [visible])
|
||||
|
||||
useEffect(() => {
|
||||
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||
if (savedVisibility === null && window.innerWidth < hideSizePx) {
|
||||
open()
|
||||
} else if (savedVisibility) {
|
||||
savedVisibility === 'true' ? open() : close()
|
||||
}
|
||||
}, [])
|
||||
// useEffect(() => {
|
||||
// const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||
// if (savedVisibility === null && window.innerWidth < hideSizePx) {
|
||||
// open()
|
||||
// } else if (savedVisibility) {
|
||||
// savedVisibility === 'true' ? open() : close()
|
||||
// }
|
||||
// }, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
side === 'left' ?
|
||||
<Navbar
|
||||
className={classes.navbar}
|
||||
p={dimensions.hideSidebarsSize}
|
||||
width={{ sm: 200, lg: 300, }}
|
||||
>
|
||||
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||
{leftChildren}
|
||||
</Navbar>
|
||||
:
|
||||
<Aside
|
||||
className={classes.aside}
|
||||
p={dimensions.hideSidebarsSize}
|
||||
width={{ sm: 200, lg: 300 }}>
|
||||
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||
{rightChildren}
|
||||
</Aside>
|
||||
}
|
||||
<SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// return (
|
||||
// <div>
|
||||
// {
|
||||
// side === 'left' ?
|
||||
// <Navbar
|
||||
// className={classes.navbar}
|
||||
// p={dimensions.hideSidebarsSize}
|
||||
// width={{ sm: 200, lg: 300, }}
|
||||
// >
|
||||
// <Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||
// {leftChildren}
|
||||
// </Navbar>
|
||||
// :
|
||||
// <Aside
|
||||
// className={classes.aside}
|
||||
// p={dimensions.hideSidebarsSize}
|
||||
// width={{ sm: 200, lg: 300 }}>
|
||||
// <Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||
// {rightChildren}
|
||||
// </Aside>
|
||||
// }
|
||||
// <SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
export default observer(SideBar)
|
||||
export const toDoDelete = () => ( <></> )
|
||||
|
||||
// export default observer(SideBar)
|
||||
@ -1,53 +1,78 @@
|
||||
import { Box, Flex, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||
import { t } from 'i18next';
|
||||
import React, { CSSProperties, FC } from 'react';
|
||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||
import { Box, Flex, Group, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||
import React, { CSSProperties, forwardRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
|
||||
interface CreatableMultiSelectProps {
|
||||
id?: string
|
||||
value?: string[]
|
||||
data: SelectItem[]
|
||||
spaceBetween?: SystemProp<SpacingValue>
|
||||
label?: string
|
||||
defaultValue?: string[]
|
||||
textClassName?: string
|
||||
selectProps?: MultiSelectProps,
|
||||
display?: SystemProp<CSSProperties['display']>
|
||||
showClose?: boolean,
|
||||
changedState?(value: string[], id?: string): void
|
||||
onChange?(value: string[]): void
|
||||
onClose?(): void
|
||||
onCreate?(value: string): void
|
||||
onCreate?(query: string | SelectItem | null | undefined): SelectItem | string | null | undefined
|
||||
error?: string
|
||||
onTrashClick?(value: string): void
|
||||
}
|
||||
|
||||
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||
label: string,
|
||||
value: string,
|
||||
onTrashClick?: (value: string) => void
|
||||
}
|
||||
|
||||
const DeletableItem = forwardRef<HTMLDivElement, ItemProps>(
|
||||
({ label, value, onTrashClick, ...others }, ref) => (
|
||||
<div {...others} ref={ref}>
|
||||
<Flex justify='space-between'>
|
||||
<Text>{label}</Text>
|
||||
<IconTrash
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (onTrashClick) onTrashClick(value);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const CreatableMultiSelect: React.FC<CreatableMultiSelectProps> = ({
|
||||
id,
|
||||
value,
|
||||
data,
|
||||
spaceBetween,
|
||||
label,
|
||||
defaultValue,
|
||||
textClassName,
|
||||
selectProps,
|
||||
display,
|
||||
showClose,
|
||||
changedState,
|
||||
onChange,
|
||||
onClose,
|
||||
onCreate
|
||||
onCreate,
|
||||
onTrashClick,
|
||||
error,
|
||||
}) => {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleOnChange = (value: string[]) => {
|
||||
if (changedState) {
|
||||
changedState(value, id)
|
||||
}
|
||||
if (onChange) onChange(value)
|
||||
}
|
||||
|
||||
const handleOnCreate = (query: string | SelectItem | null | undefined) => {
|
||||
const item = { value: query, label: query } as SelectItem
|
||||
if (onCreate && typeof query === 'string') {
|
||||
onCreate(query)
|
||||
}
|
||||
return item
|
||||
if (onCreate) return onCreate(query)
|
||||
}
|
||||
|
||||
const handleTrashClick = (value: string) => {
|
||||
if (onTrashClick) onTrashClick(value)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -59,17 +84,25 @@ const CreatableMultiSelect: React.FC<CreatableMultiSelectProps> = ({
|
||||
: null}
|
||||
</Flex>
|
||||
<MultiSelect
|
||||
{...selectProps}
|
||||
value={value}
|
||||
mt={spaceBetween}
|
||||
data={data}
|
||||
disableSelectedItemFiltering
|
||||
defaultValue={defaultValue}
|
||||
itemComponent={forwardRef((itemProps, ref) => (
|
||||
<DeletableItem
|
||||
{...itemProps}
|
||||
ref={ref} // передаем ref в DeletableItem
|
||||
onTrashClick={handleTrashClick} // передаем коллбэк сюда напрямую
|
||||
/>
|
||||
))}
|
||||
onChange={handleOnChange}
|
||||
searchable
|
||||
clearable
|
||||
creatable
|
||||
getCreateLabel={(query) => `+ ${t('create') + ' ' + query}`}
|
||||
onCreate={handleOnCreate}
|
||||
{...selectProps}
|
||||
error={error}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -3,7 +3,7 @@ import { CSSProperties } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||
|
||||
interface MultiSelectFilterProps {
|
||||
interface MultiSelectFilterProps extends MultiSelectProps {
|
||||
id?: string
|
||||
data: SelectItem[]
|
||||
spaceBetween?: SystemProp<SpacingValue>
|
||||
|
||||
157
src/shared/components/filters/UserTagsFilter.tsx
Normal file
157
src/shared/components/filters/UserTagsFilter.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { t } from 'i18next';
|
||||
import { useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
import CreatableMultiSelect from './CreatableMultiSelect';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api';
|
||||
import RetryError from '../RetryError';
|
||||
import CogwheelLoader from '../loaders/CogwheelLoader';
|
||||
import { mapUserTagsToSelectItems, PutUserTag } from '../../../types/tags';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
|
||||
|
||||
|
||||
const UserTagsFilter = () => {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const [selectedList, setSelectedList] = useState<string[]>([])
|
||||
|
||||
const { data, isPending, isError, refetch } = useQuery({
|
||||
queryKey: [frigateQueryKeys.getUserTags],
|
||||
queryFn: frigateApi.getUserTags
|
||||
})
|
||||
|
||||
const SelectItemSchema = z.object({
|
||||
value: z.string(),
|
||||
label: z.string().optional(),
|
||||
selected: z.boolean().optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
group: z.string().optional(),
|
||||
}).passthrough();
|
||||
|
||||
const [tagsError, setTagsError] = useState<string>('')
|
||||
|
||||
const validateNewTag = (query: string): boolean => {
|
||||
if (query.length > 10) {
|
||||
setTagsError(t('mainPage.tagsError'))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const { mutate } = useMutation({
|
||||
mutationFn: (newTag: PutUserTag) => frigateApi.putUserTag(newTag)
|
||||
.catch(error => {
|
||||
if (error.response && error.response.data) {
|
||||
return Promise.reject(error.response.data)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}),
|
||||
onSuccess: (data) => {
|
||||
setSelectedList([...selectedList, data.id])
|
||||
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getUserTags] })
|
||||
},
|
||||
onError: (e) => {
|
||||
if (e && e.message) {
|
||||
notifications.show({
|
||||
id: e.message,
|
||||
withCloseButton: true,
|
||||
autoClose: 5000,
|
||||
title: "Error",
|
||||
message: e.message,
|
||||
color: 'red',
|
||||
icon: <IconAlertCircle />,
|
||||
})
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
||||
}
|
||||
})
|
||||
|
||||
const { mutate: deleteTag } = useMutation({
|
||||
mutationFn: (tagId: string) => frigateApi.delUserTag(tagId)
|
||||
.catch(error => {
|
||||
if (error.response && error.response.data) {
|
||||
return Promise.reject(error.response.data)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getUserTags] })
|
||||
const updatedList = selectedList.filter(item => data.id === item)
|
||||
setSelectedList(updatedList)
|
||||
},
|
||||
onError: (e) => {
|
||||
if (e && e.message) {
|
||||
notifications.show({
|
||||
id: e.message,
|
||||
withCloseButton: true,
|
||||
autoClose: 5000,
|
||||
title: "Error",
|
||||
message: e.message,
|
||||
color: 'red',
|
||||
icon: <IconAlertCircle />,
|
||||
})
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
||||
}
|
||||
})
|
||||
|
||||
const saveNewTag = (value: string) => {
|
||||
const newTag: PutUserTag = {
|
||||
value,
|
||||
cameraIds: []
|
||||
}
|
||||
mutate(newTag)
|
||||
}
|
||||
|
||||
const onCreate = (query: string | SelectItem | null | undefined) => {
|
||||
setTagsError('')
|
||||
const parseQuery = SelectItemSchema.safeParse(query)
|
||||
if (typeof query === 'string') {
|
||||
if (!validateNewTag(query)) return undefined
|
||||
saveNewTag(query)
|
||||
// return query
|
||||
}
|
||||
else if (parseQuery.success) {
|
||||
const parsedQuery = parseQuery.data as SelectItem
|
||||
if (!validateNewTag(parsedQuery.value)) return undefined
|
||||
saveNewTag(parsedQuery.value)
|
||||
// return parsedQuery
|
||||
}
|
||||
}
|
||||
|
||||
if (isPending) return <CogwheelLoader />
|
||||
if (isError) return <RetryError onRetry={refetch} />
|
||||
|
||||
const handleOnChange = (value: string[]) => {
|
||||
console.log('cahnged:', value)
|
||||
const updatedList = selectedList.filter(item => value.includes(item))
|
||||
const newItems = value.filter(item => !selectedList.includes(item))
|
||||
setSelectedList([...updatedList, ...newItems])
|
||||
}
|
||||
|
||||
const handleTrashClick = (value: string) => {
|
||||
if (value) {
|
||||
deleteTag(value)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CreatableMultiSelect
|
||||
value={selectedList}
|
||||
label={t('mainPage.createSelectTags')}
|
||||
onChange={handleOnChange}
|
||||
spaceBetween='1rem'
|
||||
data={mapUserTagsToSelectItems(data)}
|
||||
onCreate={onCreate}
|
||||
error={tagsError}
|
||||
onTrashClick={handleTrashClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTagsFilter;
|
||||
@ -1,20 +1,17 @@
|
||||
import { MainStore } from "./main.store";
|
||||
import { ModalStore } from "./modal.store";
|
||||
import { RecordingsStore } from "./recordings.store";
|
||||
import { SideBarsStore } from "./sidebars.store";
|
||||
import { UserStore } from "./user.store";
|
||||
|
||||
class RootStore {
|
||||
mainStore: MainStore
|
||||
userStore: UserStore
|
||||
modalStore: ModalStore
|
||||
sideBarsStore: SideBarsStore
|
||||
recordingsStore: RecordingsStore
|
||||
constructor() {
|
||||
this.mainStore = new MainStore()
|
||||
this.userStore = new UserStore()
|
||||
this.modalStore = new ModalStore(this)
|
||||
this.sideBarsStore = new SideBarsStore()
|
||||
this.recordingsStore = new RecordingsStore()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,45 +1,47 @@
|
||||
import { action, makeAutoObservable } from "mobx"
|
||||
|
||||
|
||||
// TODO Delete
|
||||
export class SideBarsStore {
|
||||
|
||||
private _rightVisible: boolean = true
|
||||
public get rightVisible(): boolean {
|
||||
return this._rightVisible
|
||||
}
|
||||
public set rightVisible(visible: boolean) {
|
||||
this._rightVisible = visible
|
||||
}
|
||||
private _leftVisible: boolean = true
|
||||
public get leftVisible(): boolean {
|
||||
return this._leftVisible
|
||||
}
|
||||
public set leftVisible(visible: boolean) {
|
||||
this._leftVisible = visible
|
||||
}
|
||||
// private _rightVisible: boolean = true
|
||||
// public get rightVisible(): boolean {
|
||||
// return this._rightVisible
|
||||
// }
|
||||
// public set rightVisible(visible: boolean) {
|
||||
// this._rightVisible = visible
|
||||
// }
|
||||
// private _leftVisible: boolean = true
|
||||
// public get leftVisible(): boolean {
|
||||
// return this._leftVisible
|
||||
// }
|
||||
// public set leftVisible(visible: boolean) {
|
||||
// this._leftVisible = visible
|
||||
// }
|
||||
|
||||
private _leftChildren: React.ReactNode = null
|
||||
public get leftChildren(): React.ReactNode {
|
||||
return this._leftChildren
|
||||
}
|
||||
// private _leftChildren: React.ReactNode = null
|
||||
// public get leftChildren(): React.ReactNode {
|
||||
// return this._leftChildren
|
||||
// }
|
||||
|
||||
|
||||
private _rightChildren: React.ReactNode = null
|
||||
public get rightChildren(): React.ReactNode {
|
||||
return this._rightChildren
|
||||
}
|
||||
// private _rightChildren: React.ReactNode = null
|
||||
// public get rightChildren(): React.ReactNode {
|
||||
// return this._rightChildren
|
||||
// }
|
||||
|
||||
constructor () {
|
||||
makeAutoObservable(this, {
|
||||
setRightChildren: action,
|
||||
setLeftChildren: action,
|
||||
})
|
||||
}
|
||||
// constructor () {
|
||||
// makeAutoObservable(this, {
|
||||
// setRightChildren: action,
|
||||
// setLeftChildren: action,
|
||||
// })
|
||||
// }
|
||||
|
||||
setRightChildren = (value: React.ReactNode) => {
|
||||
this._rightChildren = value
|
||||
}
|
||||
// setRightChildren = (value: React.ReactNode) => {
|
||||
// this._rightChildren = value
|
||||
// }
|
||||
|
||||
setLeftChildren = (value: React.ReactNode) => {
|
||||
this._leftChildren = value
|
||||
}
|
||||
// setLeftChildren = (value: React.ReactNode) => {
|
||||
// this._leftChildren = value
|
||||
// }
|
||||
}
|
||||
26
src/types/tags.ts
Normal file
26
src/types/tags.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { SelectItem } from "@mantine/core";
|
||||
import { z } from "zod";
|
||||
|
||||
export const putUserTag = z.object({
|
||||
value: z.string(),
|
||||
cameraIds: z.string().array()
|
||||
})
|
||||
|
||||
export const getUserTag = z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.string().datetime(),
|
||||
updatedAt: z.string().datetime(),
|
||||
value: z.string(),
|
||||
userId: z.string(),
|
||||
cameraIds: z.string().array(),
|
||||
})
|
||||
|
||||
export type GetUserTag = z.infer<typeof getUserTag>
|
||||
export type PutUserTag = z.infer<typeof putUserTag>
|
||||
|
||||
export const mapUserTagsToSelectItems = (tags: GetUserTag[]): SelectItem[] => {
|
||||
return tags.map(tag => ({
|
||||
value: tag.id,
|
||||
label: tag.value
|
||||
}))
|
||||
}
|
||||
@ -3,10 +3,13 @@ import { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Context } from '../..';
|
||||
import HostSelect from '../../shared/components/filters/HostSelect';
|
||||
import UserTagsFilter from '../../shared/components/filters/UserTagsFilter';
|
||||
import { isProduction } from '../../shared/env.const';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const MainFiltersRightSide = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -26,16 +29,11 @@ const MainFiltersRightSide = () => {
|
||||
defaultId={selectedHostId}
|
||||
onChange={handleSelect}
|
||||
/>
|
||||
{/* TODO Add tags select */}
|
||||
{/* <CreatableMultiSelect
|
||||
label={t('mainPage.createSelectTags')}
|
||||
spaceBetween='1rem'
|
||||
data={[]}
|
||||
/> */}
|
||||
|
||||
<UserTagsFilter />
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default observer(MainFiltersRightSide);
|
||||
21
src/widgets/sidebars/SideBarContext.tsx
Normal file
21
src/widgets/sidebars/SideBarContext.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { createContext, ReactNode, useState } from 'react';
|
||||
|
||||
interface SideBarContextProps {
|
||||
childrenComponent: ReactNode;
|
||||
setChildrenComponent: (component: ReactNode) => void;
|
||||
}
|
||||
|
||||
export const SideBarContext = createContext<SideBarContextProps>({
|
||||
childrenComponent: null,
|
||||
setChildrenComponent: () => {},
|
||||
});
|
||||
|
||||
export const SideBarProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [childrenComponent, setChildrenComponent] = useState<ReactNode>(null);
|
||||
|
||||
return (
|
||||
<SideBarContext.Provider value={{ childrenComponent, setChildrenComponent }}>
|
||||
{children}
|
||||
</SideBarContext.Provider>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user