diff --git a/Dockerfile b/Dockerfile index 8c5de47..fd12b82 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 # Build commands: -# - $VERSION=0.5 +# - $VERSION=0.6 # - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build # - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "." # - docker image push --all-tags oncharterliz/multi-frigate diff --git a/src/index.tsx b/src/index.tsx index e4ea18b..b3f0426 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,19 +20,12 @@ export const keycloakConfig: AuthProviderProps = { onSigninCallback: () => { const currentUrl = new URL(window.location.href); const params = currentUrl.searchParams; - console.log('params', params.toString()) - params.delete('state'); params.delete('session_state'); params.delete('code'); - const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}` - console.log('newUrl', newUrl) - window.history.replaceState({}, document.title, newUrl) } - - } const rootStore = new RootStore() diff --git a/src/pages/AccessSettings.tsx b/src/pages/AccessSettingsPage.tsx similarity index 95% rename from src/pages/AccessSettings.tsx rename to src/pages/AccessSettingsPage.tsx index 5c7cd32..37a9029 100644 --- a/src/pages/AccessSettings.tsx +++ b/src/pages/AccessSettingsPage.tsx @@ -4,7 +4,7 @@ import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate. import CenterLoader from '../shared/components/loaders/CenterLoader'; import RetryErrorPage from './RetryErrorPage'; import { Flex, Group, Select, Text } from '@mantine/core'; -import { OneSelectItem } from '../shared/components/filters.aps/OneSelectFilter'; +import { OneSelectItem } from '../shared/components/filters/OneSelectFilter'; import { useMediaQuery } from '@mantine/hooks'; import { dimensions } from '../shared/dimensions/dimensions'; import CamerasTransferList from '../shared/components/CamerasTransferList'; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 01c4a0d..8f10d50 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -6,7 +6,9 @@ import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Context } from '..'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema'; +import HostSelect from '../shared/components/filters/HostSelect'; import CenterLoader from '../shared/components/loaders/CenterLoader'; +import { strings } from '../shared/strings/strings'; import CameraCard from '../widgets/CameraCard'; import RetryErrorPage from './RetryErrorPage'; @@ -14,6 +16,7 @@ const MainPage = () => { const executed = useRef(false) const { sideBarsStore } = useContext(Context) const [searchQuery, setSearchQuery] = useState() + const [selectedHostId, setSelectedHostId] = useState() const [filteredCameras, setFilteredCameras] = useState() const { data: cameras, isPending, isError, refetch } = useQuery({ @@ -22,12 +25,19 @@ const MainPage = () => { }) useEffect(() => { - if (searchQuery && cameras) { - setFilteredCameras(cameras.filter(camera => camera.name.toLowerCase().includes(searchQuery.toLowerCase()))) - } else { + if (!cameras) { setFilteredCameras(undefined) + return } - }, [searchQuery, cameras]) + + const filterCameras = (camera: GetCameraWHostWConfig) => { + const matchesHostId = selectedHostId ? camera.frigateHost?.id === selectedHostId : true + const matchesSearchQuery = searchQuery ? camera.name.toLowerCase().includes(searchQuery.toLowerCase()) : true + return matchesHostId && matchesSearchQuery + } + + setFilteredCameras(cameras.filter(filterCameras)) + }, [searchQuery, cameras, selectedHostId]) useEffect(() => { if (!executed.current) { @@ -66,6 +76,10 @@ const MainPage = () => { if (isError) return + const handleSelectHost = (hostId: string) => { + setSelectedHostId(hostId) + } + return ( @@ -77,11 +91,18 @@ const MainPage = () => { } value={searchQuery} onChange={(event) => setSearchQuery(event.currentTarget.value)} /> + diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index a1c8eef..e84f5ec 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -100,9 +100,9 @@ const SettingsPage = () => { mutation.mutate(configsToUpdate); } + if (!isAdmin) return if (configPending || adminLoading) return if (configError) return - if (!isAdmin) return return ( diff --git a/src/router/routes.tsx b/src/router/routes.tsx index db1157d..3842bc4 100644 --- a/src/router/routes.tsx +++ b/src/router/routes.tsx @@ -12,7 +12,7 @@ import HostSystemPage from "../pages/HostSystemPage"; import HostStoragePage from "../pages/HostStoragePage"; import LiveCameraPage from "../pages/LiveCameraPage"; import RecordingsPage from "../pages/RecordingsPage"; -import AccessSettings from "../pages/AccessSettings"; +import AccessSettings from "../pages/AccessSettingsPage"; import PlayRecordPage from "../pages/PlayRecordPage"; interface IRoute { diff --git a/src/shared/components/CamerasTransferList.tsx b/src/shared/components/CamerasTransferList.tsx index 53d243c..b35e8d3 100644 --- a/src/shared/components/CamerasTransferList.tsx +++ b/src/shared/components/CamerasTransferList.tsx @@ -4,7 +4,7 @@ import { frigateApi, frigateQueryKeys } from '../../services/frigate.proxy/friga import CogwheelLoader from './loaders/CogwheelLoader'; import RetryError from './RetryError'; import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core'; -import { OneSelectItem } from './filters.aps/OneSelectFilter'; +import { OneSelectItem } from './filters/OneSelectFilter'; import { strings } from '../strings/strings'; import { isProduction } from '../env.const'; @@ -65,8 +65,8 @@ const CamerasTransferList = ({ return ( <> - - + + { const hideSizePx = useMantineSize(dimensions.hideSidebarsSize) - const [visible, { open, close }] = useDisclosure(window.innerWidth > hideSizePx); - const manualVisible: React.MutableRefObject = useRef(null) + 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 handleClickVisible = (state: boolean) => { - manualVisible.current = state + localStorage.setItem(`sidebarVisible_${side}`, String(state)) if (state) open() else close() } @@ -73,23 +79,14 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => { isHidden(!visible) }, [visible]) - // resize controller useEffect(() => { - const checkWindowSize = () => { - if (window.innerWidth <= hideSizePx && visible) { - close() - } - if (window.innerWidth > hideSizePx && !visible && manualVisible.current === null) { - open() - } + const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`); + if (savedVisibility === null && window.innerWidth < hideSizePx) { + open() + } else if (savedVisibility) { + savedVisibility === 'true' ? open() : close() } - window.addEventListener('resize', checkWindowSize); - - // Cleanup function to remove event listener - return () => { - window.removeEventListener('resize', checkWindowSize); - } - }, [visible]) + }, []) return (
@@ -112,7 +109,6 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => { {rightChildren} } - handleClickVisible(true)} />
) diff --git a/src/shared/components/filters.aps/HostSelectFilter.tsx b/src/shared/components/filters.aps/HostSelectFilter.tsx deleted file mode 100644 index 4749959..0000000 --- a/src/shared/components/filters.aps/HostSelectFilter.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Center, Text } from '@mantine/core'; -import { useQuery } from '@tanstack/react-query'; -import { observer } from 'mobx-react-lite'; -import React, { useContext, useEffect } from 'react'; -import { Context } from '../../..'; -import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api'; -import { strings } from '../../strings/strings'; -import CogwheelLoader from '../loaders/CogwheelLoader'; -import OneSelectFilter, { OneSelectItem } from './OneSelectFilter'; -import RetryError from '../RetryError'; - -const HostSelectFilter = () => { - const { recordingsStore: recStore } = useContext(Context) - - const { data: hosts, isError, isPending, isSuccess, refetch } = useQuery({ - queryKey: [frigateQueryKeys.getFrigateHosts], - queryFn: frigateApi.getHosts - }) - - useEffect(() => { - if (!hosts) return - if (recStore.hostIdParam) { - recStore.filteredHost = hosts.find(host => host.id === recStore.hostIdParam) - recStore.hostIdParam = undefined - } - }, [isSuccess]) - - if (isPending) return - if (isError) return - - if (!hosts || hosts.length < 1) return null - - const hostItems: OneSelectItem[] = hosts - .filter(host => host.enabled) - .map(host => ({ value: host.id, label: host.name })) - - const handleSelect = (value: string) => { - const host = hosts?.find(host => host.id === value) - if (!host) { - recStore.filteredHost = undefined - recStore.filteredCamera = undefined - return - } - if (recStore.filteredHost?.id !== host.id) { - recStore.filteredCamera = undefined - } - recStore.filteredHost = host - } - - return ( - - ); -}; - -export default observer(HostSelectFilter); \ No newline at end of file diff --git a/src/shared/components/filters.aps/CameraSelectFilter.tsx b/src/shared/components/filters/CameraSelectFilter.tsx similarity index 100% rename from src/shared/components/filters.aps/CameraSelectFilter.tsx rename to src/shared/components/filters/CameraSelectFilter.tsx diff --git a/src/shared/components/filters.aps/DateRangeSelectFilter.tsx b/src/shared/components/filters/DateRangeSelectFilter.tsx similarity index 91% rename from src/shared/components/filters.aps/DateRangeSelectFilter.tsx rename to src/shared/components/filters/DateRangeSelectFilter.tsx index 3d53a61..e8bc847 100644 --- a/src/shared/components/filters.aps/DateRangeSelectFilter.tsx +++ b/src/shared/components/filters/DateRangeSelectFilter.tsx @@ -1,11 +1,10 @@ +import { Box, Flex, Indicator, Text } from '@mantine/core'; import { DatePickerInput } from '@mantine/dates'; import { observer } from 'mobx-react-lite'; -import React, { useContext } from 'react'; -import { strings } from '../../strings/strings'; -import { Box, Flex, Indicator, Text } from '@mantine/core'; -import CloseWithTooltip from '../buttons/CloseWithTooltip'; +import { useContext } from 'react'; import { Context } from '../../..'; import { isProduction } from '../../env.const'; +import { strings } from '../../strings/strings'; interface DateRangeSelectFilterProps {} diff --git a/src/shared/components/filters/HostSelect.tsx b/src/shared/components/filters/HostSelect.tsx new file mode 100644 index 0000000..44535c7 --- /dev/null +++ b/src/shared/components/filters/HostSelect.tsx @@ -0,0 +1,67 @@ +import { useQuery } from '@tanstack/react-query'; +import React, { useEffect } from 'react'; +import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api'; +import { strings } from '../../strings/strings'; +import RetryError from '../RetryError'; +import CogwheelLoader from '../loaders/CogwheelLoader'; +import OneSelectFilter, { OneSelectItem } from './OneSelectFilter'; +import { SystemProp, SpacingValue, MantineStyleSystemProps, Loader, Center } from '@mantine/core'; + +interface HostSelectProps extends MantineStyleSystemProps { + label?: string + valueId?: string + defaultId?: string + spaceBetween?: SystemProp + placeholder?: string + onChange?: (value: string) => void + onSuccess?: () => void +} + +const HostSelect = ({ + label, + valueId, + defaultId, + spaceBetween, + placeholder, + onChange, + onSuccess, + ...styleProps +}: HostSelectProps) => { + const { data: hosts, isError, isPending, isSuccess, refetch } = useQuery({ + queryKey: [frigateQueryKeys.getFrigateHosts], + queryFn: frigateApi.getHosts + }) + + useEffect(() => { + if (onSuccess) onSuccess() + }, [isSuccess]) + + if (isPending) return
+ if (isError) return + + if (!hosts || hosts.length < 1) return null + + const hostItems: OneSelectItem[] = hosts + .filter(host => host.enabled) + .map(host => ({ value: host.id, label: host.name })) + + const handleSelect = (value: string) => { + if (onChange) onChange(value) + } + + return ( + + ); +}; + +export default HostSelect; \ No newline at end of file diff --git a/src/shared/components/filters.aps/MultiSelectFilter.tsx b/src/shared/components/filters/MultiSelectFilter.tsx similarity index 100% rename from src/shared/components/filters.aps/MultiSelectFilter.tsx rename to src/shared/components/filters/MultiSelectFilter.tsx diff --git a/src/shared/components/filters.aps/OneSelectFilter.tsx b/src/shared/components/filters/OneSelectFilter.tsx similarity index 74% rename from src/shared/components/filters.aps/OneSelectFilter.tsx rename to src/shared/components/filters/OneSelectFilter.tsx index 8d8fda6..e777ffc 100644 --- a/src/shared/components/filters.aps/OneSelectFilter.tsx +++ b/src/shared/components/filters/OneSelectFilter.tsx @@ -11,15 +11,15 @@ export interface OneSelectItem { disabled?: boolean; } -interface OneSelectFilterProps extends SelectProps { +export interface OneSelectFilterProps extends SelectProps { id?: string data: OneSelectItem[] spaceBetween?: SystemProp label?: string - defaultValue?: string + defaultValue?: string | null textClassName?: string showClose?: boolean, - value?: string, + value?: string | null, onChange?: (value: string, id?: string,) => void onClose?: () => void } @@ -41,11 +41,13 @@ const OneSelectFilter = ({ return ( - - {label} - {showClose ? - : null} - + {!label ? null : + + {label} + {showClose ? + : null} + + }