fix frigate hosts infinity rendering after save
This commit is contained in:
parent
67c27ff614
commit
ea48a538a5
@ -51,7 +51,7 @@
|
|||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"dev": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
import FrigateHostsTable from '../widgets/FrigateHostsTable';
|
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
import { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { Button, Flex } from '@mantine/core';
|
|
||||||
import { observer } from 'mobx-react-lite'
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
|
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
||||||
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
import { strings } from '../shared/strings/strings';
|
||||||
|
import FrigateHostsTable from '../widgets/FrigateHostsTable';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
import { IconAlertCircle } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
|
||||||
const FrigateHostsPage = () => {
|
const FrigateHostsPage = () => {
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
@ -36,6 +40,10 @@ const FrigateHostsPage = () => {
|
|||||||
if (data) setPageData(data)
|
if (data) setPageData(data)
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isProduction) console.log('pageData', pageData)
|
||||||
|
}, [pageData])
|
||||||
|
|
||||||
const { mutate } = useMutation({
|
const { mutate } = useMutation({
|
||||||
mutationFn: (tableData: GetFrigateHost[]) => {
|
mutationFn: (tableData: GetFrigateHost[]) => {
|
||||||
let fetchPromises = []
|
let fetchPromises = []
|
||||||
@ -48,16 +56,29 @@ const FrigateHostsPage = () => {
|
|||||||
const parsedChanged = putFrigateHostSchema.array().parse(tableData)
|
const parsedChanged = putFrigateHostSchema.array().parse(tableData)
|
||||||
fetchPromises.push(frigateApi.putHosts(parsedChanged))
|
fetchPromises.push(frigateApi.putHosts(parsedChanged))
|
||||||
}
|
}
|
||||||
return Promise.all(fetchPromises)
|
return Promise.all(fetchPromises).catch(error => {
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
return Promise.reject(error.response.data);
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
||||||
},
|
},
|
||||||
onError: () => {
|
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] })
|
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
||||||
},
|
|
||||||
onSettled: () => {
|
|
||||||
if (data) setPageData([...data])
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -79,18 +100,16 @@ const FrigateHostsPage = () => {
|
|||||||
if (hostsPending || adminLoading) return <CenterLoader />
|
if (hostsPending || adminLoading) return <CenterLoader />
|
||||||
if (!isAdmin) return <Forbidden />
|
if (!isAdmin) return <Forbidden />
|
||||||
if (hostsError) return <RetryErrorPage />
|
if (hostsError) return <RetryErrorPage />
|
||||||
|
if (!pageData) return <Text>Empty server response</Text>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Flex w='100%' h='100%' direction='column'>
|
||||||
{
|
|
||||||
!pageData ? <></> :
|
|
||||||
<FrigateHostsTable data={pageData} showAddButton changedCallback={handleChange} />
|
<FrigateHostsTable data={pageData} showAddButton changedCallback={handleChange} />
|
||||||
}
|
|
||||||
<Flex justify='center'>
|
<Flex justify='center'>
|
||||||
<Button m='0.5rem' onClick={handleDiscard}>{strings.discard}</Button>
|
<Button m='0.5rem' onClick={handleDiscard}>{strings.discard}</Button>
|
||||||
<Button m='0.5rem' onClick={handleSave}>{strings.save}</Button>
|
<Button m='0.5rem' onClick={handleSave}>{strings.save}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,16 @@
|
|||||||
import { Button, Flex, Table } from '@mantine/core';
|
import { Button, Flex, Table } from '@mantine/core';
|
||||||
import { IconPlus, IconTrash } from '@tabler/icons-react';
|
import { IconPlus, IconTrash } from '@tabler/icons-react';
|
||||||
import ObjectId from 'bson-objectid';
|
import ObjectId from 'bson-objectid';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||||
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
|
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
|
||||||
import SortedTh from '../shared/components/table.aps/SortedTh';
|
import SortedTh from '../shared/components/table.aps/SortedTh';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
import { strings } from '../shared/strings/strings';
|
import { strings } from '../shared/strings/strings';
|
||||||
import { debounce } from '../shared/utils/debounce';
|
|
||||||
import StateCell from './hosts.table/StateCell';
|
import StateCell from './hosts.table/StateCell';
|
||||||
import SwitchCell from './hosts.table/SwitchCell';
|
import SwitchCell from './hosts.table/SwitchCell';
|
||||||
import TextInputCell from './hosts.table/TextInputCell';
|
import TextInputCell from './hosts.table/TextInputCell';
|
||||||
import { isProduction } from '../shared/env.const';
|
|
||||||
|
|
||||||
interface TableProps<T> {
|
interface TableProps<T> {
|
||||||
data: T[],
|
data: T[],
|
||||||
@ -21,22 +20,22 @@ interface TableProps<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
|
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
|
||||||
if (!isProduction) console.log('FrigateHostsTable rendered')
|
|
||||||
const [tableData, setTableData] = useState(data)
|
const [tableData, setTableData] = useState(data)
|
||||||
const [reversed, setReversed] = useState(false)
|
const [reversed, setReversed] = useState(false)
|
||||||
const [sortedName, setSortedName] = useState<string | null>(null)
|
const [sortedName, setSortedName] = useState<string | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
console.log('data', data)
|
||||||
setTableData(data)
|
console.log('tableData', tableData)
|
||||||
}, [data])
|
|
||||||
|
|
||||||
const debouncedChanged = useCallback(debounce((tableData: GetFrigateHost[]) => {
|
|
||||||
if (changedCallback) changedCallback(tableData)
|
|
||||||
}, 200), [tableData])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedChanged(tableData)
|
setTableData(data);
|
||||||
}, [tableData, debouncedChanged])
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isProduction) console.log('TableData', tableData)
|
||||||
|
if (changedCallback)
|
||||||
|
changedCallback(tableData)
|
||||||
|
}, [tableData])
|
||||||
|
|
||||||
function sortByKey<T, K extends keyof T>(array: T[], key: K): T[] {
|
function sortByKey<T, K extends keyof T>(array: T[], key: K): T[] {
|
||||||
return array.sort((a, b) => {
|
return array.sort((a, b) => {
|
||||||
@ -80,7 +79,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleTextChange = (id: string | number, propertyName: string, value: string,) => {
|
const handleTextChange = (id: string | number, propertyName: string, value: string | number | boolean | undefined,) => {
|
||||||
setTableData(tableData.map(item => {
|
setTableData(tableData.map(item => {
|
||||||
if (item.id === id) {
|
if (item.id === id) {
|
||||||
return {
|
return {
|
||||||
@ -116,7 +115,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
|
|||||||
name: '',
|
name: '',
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
setTableData(prevTableData => [...prevTableData, newHost])
|
setTableData([...tableData, newHost])
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = tableData.map(item => {
|
const rows = tableData.map(item => {
|
||||||
@ -135,7 +134,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
|
|||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
if (!isProduction) console.log('FrigateHostsTable rendered')
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table >
|
<Table >
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Flex, Switch, useMantineTheme } from '@mantine/core';
|
import { Flex, Switch, useMantineTheme } from '@mantine/core';
|
||||||
import { IconBulbFilled, IconBulbOff } from '@tabler/icons-react';
|
import { IconBulbFilled, IconBulbOff } from '@tabler/icons-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { boolean } from 'zod';
|
||||||
|
|
||||||
interface SwithCellProps {
|
interface SwithCellProps {
|
||||||
value?: boolean,
|
value?: boolean,
|
||||||
@ -14,7 +15,7 @@ interface SwithCellProps {
|
|||||||
export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => {
|
export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => {
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
if (!value && !defaultValue) value = defaultValue
|
if (typeof value !== 'boolean') value = defaultValue
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (id && toggle && propertyName) toggle(id, propertyName, event.target.value)
|
if (id && toggle && propertyName) toggle(id, propertyName, event.target.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { TextInput } from '@mantine/core';
|
import { TextInput } from '@mantine/core';
|
||||||
import React from 'react';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface TextImputCellProps {
|
interface TextImputCellProps {
|
||||||
text?: string | number | boolean,
|
text?: string | number | boolean,
|
||||||
@ -9,18 +10,25 @@ interface TextImputCellProps {
|
|||||||
onChange?: (
|
onChange?: (
|
||||||
id: string | number,
|
id: string | number,
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
value: string,
|
value?: string | number | boolean,
|
||||||
) => void,
|
) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextInputCell = ({ text, width, id, propertyName, onChange }: TextImputCellProps) => {
|
const TextInputCell = ({ text, width, id, propertyName, onChange }: TextImputCellProps) => {
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const [value, setValue] = useState(text);
|
||||||
|
const [debouncedValue] = useDebouncedValue(value, 300)
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedValue !== text) {
|
||||||
if (id && propertyName && onChange)
|
if (id && propertyName && onChange)
|
||||||
onChange(id, propertyName, event.currentTarget.value)
|
onChange(id, propertyName, debouncedValue)
|
||||||
|
}
|
||||||
|
}, [debouncedValue, id, propertyName, onChange, text])
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setValue(event.currentTarget.value)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<td style={{ width: width, textAlign: 'center' }}>
|
<td style={{ width: width, textAlign: 'center' }}>
|
||||||
<TextInput onChange={handleChange} size='sm' value={String(text)} />
|
<TextInput onChange={handleChange} size='sm' value={String(value)} />
|
||||||
</td>
|
</td>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user