From 93ea28f968dc4373eae268343e4619a26b21010d Mon Sep 17 00:00:00 2001 From: Sean Edmunds Date: Thu, 8 Jan 2026 12:53:50 -0600 Subject: [PATCH] Search bar fixes and performance updates --- package-lock.json | 24 +- package.json | 6 +- src/pages/homepage/Homepage.tsx | 20 +- .../components/Applications/Applications.tsx | 69 ++-- .../components/Bookmarks/Bookmarks.tsx | 87 +++-- .../homepage/components/Search/Search.tsx | 297 +++++++++--------- .../components/RecommendedBookmarks.tsx | 18 +- .../SettingsButton/SettingsButton.tsx | 10 +- .../components/SettingsButton/ThemingCard.tsx | 10 +- src/pages/settings/Settings.tsx | 10 +- .../components/Applications/Applications.tsx | 34 +- .../components/Bookmarks/Bookmarks.tsx | 62 ++-- .../Bookmarks/DebouncedBookmarkItem.tsx | 19 +- .../GuacamoleClients/GuacamoleClients.tsx | 45 +-- .../settings/components/Messages/Messages.tsx | 32 +- .../components/Search/SearchHistory.tsx | 14 +- .../components/Search/SearchProviders.tsx | 34 +- .../Theming/ThemePlaylists/ThemePlaylists.tsx | 33 +- .../components/Theming/Themes/Themes.tsx | 63 ++-- .../settings/components/WoL/WolClients.tsx | 32 +- 20 files changed, 376 insertions(+), 543 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29e658a..9c84922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0", - "react-router-dom": "^7.11.0", + "react-router-dom": "^7.12.0", "recharts": "^3.6.0" }, "devDependencies": { @@ -35,7 +35,7 @@ "@vitest/coverage-v8": "^4.0.16", "@vitest/ui": "^4.0.16", "typescript": "^5.9.3", - "vite": "^7.3.0", + "vite": "^7.3.1", "vite-plugin-dts": "~4.5.4", "vite-tsconfig-paths": "^6.0.3", "vitest": "^4.0.16" @@ -3486,9 +3486,9 @@ } }, "node_modules/react-router": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", - "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -3507,11 +3507,11 @@ } }, "node_modules/react-router-dom": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz", - "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", + "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", "dependencies": { - "react-router": "7.11.0" + "react-router": "7.12.0" }, "engines": { "node": ">=20.0.0" @@ -4110,9 +4110,9 @@ } }, "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "dependencies": { "esbuild": "^0.27.0", diff --git a/package.json b/package.json index d249d77..36b2056 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "triggerfrontend", "private": true, - "version": "1.0.0", + "version": "1.1.0", "type": "module", "scripts": { "dev": "vite", @@ -26,7 +26,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0", - "react-router-dom": "^7.11.0", + "react-router-dom": "^7.12.0", "recharts": "^3.6.0" }, "devDependencies": { @@ -37,7 +37,7 @@ "@vitest/coverage-v8": "^4.0.16", "@vitest/ui": "^4.0.16", "typescript": "^5.9.3", - "vite": "^7.3.0", + "vite": "^7.3.1", "vite-plugin-dts": "~4.5.4", "vite-tsconfig-paths": "^6.0.3", "vitest": "^4.0.16" diff --git a/src/pages/homepage/Homepage.tsx b/src/pages/homepage/Homepage.tsx index db12367..ac9273d 100644 --- a/src/pages/homepage/Homepage.tsx +++ b/src/pages/homepage/Homepage.tsx @@ -41,11 +41,7 @@ export function Homepage() { const { data: personalData, isFetched: personalIsFetched } = useQueryAPI({ endpoint: 'api/personalData', }); - const { - data: messageData, - isFetching: isMessagesFetching, - isFetched: isMessagesFetched, - } = useQueryAPI({ + const { data: messageData, isFetching: isMessagesFetching } = useQueryAPI({ endpoint: 'api/displayedGreeting', }); const { data: weatherMetadata } = useOSMAPI({ @@ -80,8 +76,8 @@ export function Homepage() { y: points?.properties.gridY ?? currentLocation.y, }); - const [personal, setPersonal] = useState(); - const [message, setMessage] = useState(); + const personal: PersonalData = personalData?.[0]; + const message: Message = messageData?.[0]; const [displayedGreeting, setDisplayedGreeting] = useState(''); const [current, setCurrent] = useState({ icon: '', @@ -110,15 +106,9 @@ export function Homepage() { const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] = useDisclosure(false); - useEffect(() => { - if (messageData && isMessagesFetched) { - setMessage(messageData[0]); - } - }, [messageData, isMessagesFetched]); useEffect(() => { if (personalData && personalIsFetched) { setCurrentUser(personalData[0]); - setPersonal(personalData[0]); } }, [personalData, personalIsFetched]); useEffect(() => { @@ -220,7 +210,7 @@ export function Homepage() { closeWeatherModal={closeWeatherModal} openWeatherModal={openWeatherModal} position='top' - personal={personal as PersonalData} + personal={personal} />
)} diff --git a/src/pages/homepage/components/Applications/Applications.tsx b/src/pages/homepage/components/Applications/Applications.tsx index 5bf44a5..ae6abe1 100644 --- a/src/pages/homepage/components/Applications/Applications.tsx +++ b/src/pages/homepage/components/Applications/Applications.tsx @@ -1,54 +1,45 @@ import { Stack, Text } from '@mantine/core'; import { AppItem } from 'components'; import { useCommonHooks, useQueryAPI } from 'hooks'; -import { useEffect, useState } from 'react'; import { textClasses } from 'styles'; import type { Application } from 'types'; function Applications({ contentType }: { contentType: string[] }) { - const { data: applicationData, isFetching: isApplicationDataFetching } = useQueryAPI({ + const { data: applicationData } = useQueryAPI({ endpoint: 'api/enabledApplications', }); const { appSort, appDensity, appScale, theme, textShadowStyle } = useCommonHooks(); - const [applications, setApplications] = useState([]); - - useEffect(() => { - if (applicationData && !isApplicationDataFetching) { - setApplications( - applicationData - .sort((a: Application, b: Application) => { - switch (appSort) { - case 'A>Z': - return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1; - case 'Z>A': - return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? 1 : -1; - case 'Newest': - if (a.id && b.id) { - return a.id < b.id ? 1 : -1; - } - return -1; - case 'Oldest': - if (a.id && b.id) { - return a.id > b.id ? 1 : -1; - } - return -1; - case 'Custom': - return a.index > b.index ? 1 : -1; - default: - return 0; - } - }) - ?.filter( - (application: Application) => - contentType.includes(application.filterName) || contentType.includes('All'), - ), - ); - } - }, [applicationData, isApplicationDataFetching, appSort, contentType]); + const applications = applicationData + ?.sort((a: Application, b: Application) => { + switch (appSort) { + case 'A>Z': + return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1; + case 'Z>A': + return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? 1 : -1; + case 'Newest': + if (a.id && b.id) { + return a.id < b.id ? 1 : -1; + } + return -1; + case 'Oldest': + if (a.id && b.id) { + return a.id > b.id ? 1 : -1; + } + return -1; + case 'Custom': + return a.index > b.index ? 1 : -1; + default: + return 0; + } + }) + ?.filter( + (application: Application) => + contentType.includes(application.filterName) || contentType.includes('All'), + ); return ( <> - {applications.length > 0 && contentType.length > 0 && ( + {applications?.length > 0 && contentType.length > 0 && ( - {applications.map((application: Application) => { + {applications?.map((application: Application) => { return ( ([]); - const [groups, setGroups] = useState([]); - - useEffect(() => { - if (bookmarkData && bookmarksIsFetched) { - setBookmarks(bookmarkData); - } - }, [bookmarksIsFetched, bookmarkData]); - - useEffect(() => { - if (groupData && groupsIsFetched) { - const groupList = groupData?.map((category: BookmarkGroup) => { - return category; - }); - setGroups( - groupList - .sort((a: BookmarkGroup, b: BookmarkGroup) => { - switch (bookmarkSort) { - case 'A>Z': - return a.categoryName.toLocaleLowerCase() > - b.categoryName.toLocaleLowerCase() - ? 1 - : -1; - case 'Z>A': - return a.categoryName.toLocaleLowerCase() < - b.categoryName.toLocaleLowerCase() - ? 1 - : -1; - case 'Newest': - if (a.id && b.id) { - return a.id < b.id ? 1 : -1; - } - return -1; - case 'Oldest': - if (a.id && b.id) { - return a.id > b.id ? 1 : -1; - } - return -1; - case 'Custom': - return a.categoryIndex > b.categoryIndex ? 1 : -1; - default: - return 0; - } - }) - ?.filter( - (category: BookmarkGroup) => - contentType.includes(category.filterName) || contentType.includes('All'), - ), - ); - } - }, [groupData, groupsIsFetched, bookmarkSort, contentType]); + const groupList = groupData?.map((category: BookmarkGroup) => { + return category; + }); + const bookmarks = bookmarkData || []; + const groups = groupList + ?.sort((a: BookmarkGroup, b: BookmarkGroup) => { + switch (bookmarkSort) { + case 'A>Z': + return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase() + ? 1 + : -1; + case 'Z>A': + return a.categoryName.toLocaleLowerCase() < b.categoryName.toLocaleLowerCase() + ? 1 + : -1; + case 'Newest': + if (a.id && b.id) { + return a.id < b.id ? 1 : -1; + } + return -1; + case 'Oldest': + if (a.id && b.id) { + return a.id > b.id ? 1 : -1; + } + return -1; + case 'Custom': + return a.categoryIndex > b.categoryIndex ? 1 : -1; + default: + return 0; + } + }) + ?.filter( + (category: BookmarkGroup) => + contentType.includes(category.filterName) || contentType.includes('All'), + ); return ( <> diff --git a/src/pages/homepage/components/Search/Search.tsx b/src/pages/homepage/components/Search/Search.tsx index 75c3416..33186fb 100644 --- a/src/pages/homepage/components/Search/Search.tsx +++ b/src/pages/homepage/components/Search/Search.tsx @@ -49,46 +49,7 @@ export default function Search({ }) { const { ref, height } = useElementSize(); const allIcons: any = { ...fa6, ...vsc, ...si }; - const { data: providerData, isFetched: providerDataIsFetched } = useQueryAPI({ - endpoint: 'api/providers', - }); - const { data: applicationData, isFetching: isApplicationDataFetching } = useQueryAPI({ - endpoint: 'api/enabledApplications', - }); - const { data: guacamoleHostData, isFetched: guacamoleHostDataIsFetched } = useQueryAPI({ - endpoint: 'api/guacamoleHost', - }); - const { data: guacamoleClientData, isFetched: guacamoleClientDataIsFetched } = - useQueryAPI({ - endpoint: 'api/guacamoleClients', - }); - const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({ - endpoint: 'api/bookmarks', - }); - const { data: defaultProviderData, isFetched: defaultProviderDataIsFetched } = - useQueryAPI({ - endpoint: 'api/providers/default', - }); - const [providers, setProviders] = useState([]); - const [guacamoleHost, setGuacamoleHost] = useState(''); - const [guacamoleClients, setGuacamoleClients] = useState([]); - const [filteredGuacamoleClients, setFilteredGuacamoleClients] = useState< - GuacamoleClient[] - >([]); - const [applications, setApplications] = useState([]); - const [filteredApplications, setFilteredApplications] = useState([]); - const [bookmarks, setBookmarks] = useState([]); const searchEndpoint = 'api/searchHistory'; - const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({ - endpoint: searchEndpoint, - }); - const [searchHistory, setSearchHistory] = useState([]); - const [filteredHistory, setFilteredHistory] = useState([]); - const { mutateAsync } = useMutationAPI(); - const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] = - useDisclosure(false); - const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure(); - const [listIndex, setListIndex] = useState(-1); const { isMobile, urlPattern, @@ -111,6 +72,64 @@ export default function Search({ defaultProviderIcon, setDefaultProviderIcon, } = useCommonHooks(); + const { data: applicationData } = useQueryAPI({ + endpoint: 'api/enabledApplications', + }); + const { data: bookmarkData } = useQueryAPI({ + endpoint: 'api/bookmarks', + }); + const { data: guacamoleHostData } = useQueryAPI({ + endpoint: 'api/guacamoleHost', + }); + const { data: guacamoleClientData } = useQueryAPI({ + endpoint: 'api/guacamoleClients', + }); + const { data: providerData } = useQueryAPI({ + endpoint: 'api/providers', + }); + const { data: defaultProviderData, isFetched: defaultProviderDataIsFetched } = + useQueryAPI({ + endpoint: 'api/providers/default', + }); + const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({ + endpoint: searchEndpoint, + }); + const applications: Application[] = applicationData || []; + const filteredApplications = applications?.filter((app: Application) => + app.name.toLowerCase().includes((searchValue as string).substring(1).toLowerCase()), + ); + const bookmarks: Bookmark[] = + (searchValue as string).startsWith('>') || + (searchValue as string).startsWith('!') || + (searchValue as string).startsWith('/') + ? bookmarkData?.filter( + (bookmark: Bookmark) => + bookmark.enabled === 1 && + bookmark.name + .toLowerCase() + .includes((searchValue as string).substring(1).toLowerCase()), + ) + : bookmarkData?.filter( + (bookmark: Bookmark) => + bookmark.enabled === 1 && + bookmark.name.toLowerCase().includes((searchValue as string).toLowerCase()), + ) || []; + const providers = providerData || []; + const guacamoleHost = guacamoleHostData?.[0]?.guacamoleHost; + const guacamoleClients = guacamoleClientData || []; + const filteredGuacamoleClients = + guacamoleClients.filter((client: GuacamoleClient) => + client.clientName + .toLowerCase() + .includes((searchValue as string).substring(1).toLowerCase()), + ) || []; + const searchHistory = historyData || []; + const [filteredHistory, setFilteredHistory] = useState([]); + const { mutateAsync } = useMutationAPI(); + const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] = + useDisclosure(false); + const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure(); + const [listIndex, setListIndex] = useState(-1); const [placeholderValue, setPlaceholderValue] = useState(''); const [value, setValue] = useState(0); const [historyMultiplier, setHistoryMultiplier] = useState(1); @@ -155,7 +174,7 @@ export default function Search({ } else { subtext = item.target.value; } - if (item.key === 'Enter') { + if (item.code === 'Enter') { if (providerLabel === 'Show help') { setSearchValue(''); closeDropdown(); @@ -171,9 +190,11 @@ export default function Search({ } else if (providerLabel === 'Show weather') { openWeatherModal(); } else if (providerLabel.startsWith('Launch')) { - const url = applications.find( - (app: Application) => - app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1), + const url = applications?.find((app: Application) => + app.baseUrl + .substring(1) + .toLowerCase() + .startsWith(item.target.value.substr(1).toLowerCase()), )?.url; const validUrl = url?.startsWith('http://') || url?.startsWith('https://') @@ -181,9 +202,10 @@ export default function Search({ : `http://${url}`; open(validUrl, '_self'); } else if (providerLabel.startsWith('Connect to')) { - const client = guacamoleClients?.find( - (client: GuacamoleClient) => - client.clientName.toLowerCase() === item.target.value.toLowerCase().substr(1), + const client = guacamoleClients?.find((client: GuacamoleClient) => + client.clientName + .toLowerCase() + .startsWith(item.target.value.toLowerCase().substr(1)), ); open(`${guacamoleHost}/#/client/${client?.clientIdentifier}`, '_self'); } else if (urlPattern.test(item.target.value)) { @@ -203,7 +225,6 @@ export default function Search({ } } }; - const handleKeyUp = (item: any) => { if (item.code === 'Escape') { setListIndex(-1); @@ -218,7 +239,7 @@ export default function Search({ openDropdown(); setPlaceholderValue( searchHistory.length > 0 - ? `${searchHistory[listIndex].prefix} ${searchHistory[listIndex].searchTerm}` + ? `${searchHistory[listIndex ?? 0]?.prefix} ${searchHistory[listIndex ?? 0]?.searchTerm}` : '', ); } @@ -248,16 +269,21 @@ export default function Search({ setValue(searchHistory[listIndex].id ?? 0); } } else if (searchValue !== '') { + const filteredList: any = (searchValue as string).startsWith('!') + ? filteredApplications + : (searchValue as string).startsWith('>') + ? filteredGuacamoleClients + : filteredHistory; if (item.code === 'ArrowUp') { if (listIndex === 0) { - setListIndex(filteredHistory.length - 1); - } else if (listIndex >= filteredHistory.length) { - setListIndex(filteredHistory.length - 1); + setListIndex(filteredList.length - 1); + } else if (listIndex >= filteredList.length) { + setListIndex(filteredList.length - 1); } else { setListIndex(listIndex - 1); } } else if (item.code === 'ArrowDown') { - if (listIndex >= filteredHistory.length - 1) { + if (listIndex >= filteredList.length - 1) { setListIndex(0); } else if (value === 0) { setListIndex(0); @@ -266,10 +292,17 @@ export default function Search({ setListIndex(listIndex + 1); } } else if (item.code === 'ArrowRight' || item.code === 'Tab') { - setSearchValue( - `${filteredHistory[listIndex].prefix} ${filteredHistory[listIndex].searchTerm}`, - ); - setValue(filteredHistory[listIndex].id ?? 0); + if (Object.keys(filteredList[listIndex]).includes('provider')) { + setSearchValue( + `${filteredList[listIndex].prefix} ${filteredList[listIndex].searchTerm}`, + ); + } else if (Object.keys(filteredList[listIndex]).includes('clientName')) { + setSearchValue(`>${filteredList[listIndex].clientName}`); + } else if (Object.keys(filteredList[listIndex]).includes('name')) { + setSearchValue(`!${filteredList[listIndex].name}`); + } + + setValue(filteredList[listIndex].id ?? 0); } } } @@ -301,26 +334,48 @@ export default function Search({ } else if (item.target.value.startsWith('!')) { if (item.target.value === '!w' || item.target.value === '!weather') { setProviderLabel('Show weather'); + setProviderIcon('FaCloudSun'); } else if ( - applications?.find( - (app: Application) => - app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1), + applications?.find((app: Application) => + app.baseUrl.substring(1).toLowerCase().startsWith(item.target.value.substr(1)), ) ) { - setProviderLabel(`Launch ${item.target.value.substr(1)}`); + setProviderLabel( + `Launch ${ + applications?.find((app: Application) => + app.baseUrl + .substring(1) + .toLowerCase() + .startsWith(item.target.value.substr(1)), + )?.name + }`, + ); + setProviderIcon(''); } else { setProviderLabel('Invalid function'); + setProviderIcon(''); } } else if (item.target.value.startsWith('>') && personal.enableGuacamole === 1) { if ( - guacamoleClients?.find( - (client: GuacamoleClient) => - client.clientName.toLowerCase() === item.target.value.toLowerCase().substr(1), + guacamoleClients?.find((client: GuacamoleClient) => + client.clientName + .toLowerCase() + .startsWith(item.target.value.toLowerCase().substr(1)), ) ) { - setProviderLabel(`Connect to ${item.target.value.substr(1)}`); + setProviderLabel( + `Connect to ${ + guacamoleClients?.find((client: GuacamoleClient) => + client.clientName + .toLowerCase() + .startsWith(item.target.value.toLowerCase().substr(1)), + )?.clientName + }`, + ); + setProviderIcon(''); } else { setProviderLabel('Invalid connection'); + setProviderIcon(''); } } else { setProviderLabel(defaultProviderLabel); @@ -333,32 +388,22 @@ export default function Search({ const optionsFilter: OptionsFilter = ({ options, search }) => { const splittedSearch = search.toLowerCase().trim().split(' '); if (splittedSearch[0].startsWith('!')) { - return applicationData - .filter((app: Application) => - app.name.toLowerCase().includes(splittedSearch[0].substring(1).toLowerCase()), - ) - .map((app: Application) => { - return { - label: app.name, - value: app.baseUrl, - ...app, - }; - }); + return filteredApplications.map((app: Application) => { + return { + label: app.name, + value: app.baseUrl, + ...app, + }; + }); } if (splittedSearch[0].startsWith('>') && personal.enableGuacamole === 1) { - return guacamoleClientData - .filter((guacamoleClient: GuacamoleClient) => - guacamoleClient.clientName - .toLowerCase() - .includes(splittedSearch[0].substring(1).toLowerCase()), - ) - .map((guacamoleClient: GuacamoleClient) => { - return { - label: guacamoleClient.clientName, - value: guacamoleClient.clientIdentifier, - ...guacamoleClient, - }; - }); + return filteredGuacamoleClients.map((guacamoleClient: GuacamoleClient) => { + return { + label: guacamoleClient.clientName, + value: guacamoleClient.clientIdentifier, + ...guacamoleClient, + }; + }); } const historyOptions = options .filter((option: any) => { @@ -495,65 +540,8 @@ export default function Search({ ); }; - useEffect(() => { - if (providerData && providerDataIsFetched) { - setProviders(providerData); - } - }, [providerData, providerDataIsFetched]); - - useEffect(() => { - if (guacamoleHostData && guacamoleHostDataIsFetched) { - setGuacamoleHost(guacamoleHostData[0].guacamoleHost); - } - }, [guacamoleClientData, guacamoleClientDataIsFetched]); - - useEffect(() => { - if (guacamoleClientData && guacamoleClientDataIsFetched) { - setGuacamoleClients(guacamoleClientData); - } - }, [guacamoleClientData, guacamoleClientDataIsFetched]); - - useEffect(() => { - if (guacamoleClients) { - setFilteredGuacamoleClients( - guacamoleClients.filter((client: GuacamoleClient) => - client.clientName.toLowerCase().includes((searchValue as string).toLowerCase()), - ), - ); - } - }, [guacamoleClients, searchValue]); - - useEffect(() => { - if (applicationData && !isApplicationDataFetching) { - setApplications(applicationData); - } - }, [applicationData, isApplicationDataFetching]); - - useEffect(() => { - if (applications) { - setFilteredApplications( - applications.filter((app: Application) => - app.name.toLowerCase().includes((searchValue as string).toLowerCase()), - ), - ); - } - }, [applications, searchValue]); - - useEffect(() => { - if (bookmarkData && bookmarksIsFetched) { - setBookmarks( - bookmarkData.filter( - (bookmark: Bookmark) => - bookmark.enabled === 1 && - bookmark.name.toLowerCase().includes((searchValue as string).toLowerCase()), - ), - ); - } - }, [bookmarksIsFetched, bookmarkData, searchValue]); - useEffect(() => { if (historyData && historyDataIsFetched) { - setSearchHistory(historyData); if (filteredHistory.length === 0) { setFilteredHistory(historyData.slice(0, 10 * historyMultiplier)); } @@ -592,7 +580,6 @@ export default function Search({ setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle'); } }, [defaultProviderData, defaultProviderDataIsFetched]); - return ( <> {dropdownOpened && } @@ -654,7 +641,6 @@ export default function Search({ } rightSection={
} clearable - onClear={handleResetSearch} clearButtonProps={{ 'aria-label': 'Clear search', c: 'var(--mantine-color-accent-0)', @@ -662,25 +648,26 @@ export default function Search({ }} scrollAreaProps={{ viewportRef: ref }} renderOption={(item: any) => renderItem(item)} - onBlur={() => { - setPlaceholderValue(''); - setListIndex(-1); - }} onFocus={() => { if (searchValue !== '') { openDropdown(); } }} - onKeyDown={(e) => { - if (e.key === 'Tab') { - e.preventDefault(); - } + onBlur={() => { + setPlaceholderValue(''); + setListIndex(-1); }} + onClear={handleResetSearch} onDoubleClick={() => { dropdownOpened ? closeDropdown() : openDropdown(); }} onInput={onInput} onKeyUp={handleKeyUp} + onKeyDown={(e) => { + if (e.key === 'Tab') { + e.preventDefault(); + } + }} /> {dropdownOpened && ( 0 ? 1 : 0, + opacity: searchValue && dropdownOpened ? 1 : 0, }} > @@ -63,6 +63,22 @@ export default function RecommendedBookmarks({ )) .toSorted((a: any, b: any) => (a.name > b.name ? 1 : -1)) .slice(0, 5)} + {!bookmarks.length && ( + + {' '} + No bookmarks found{' '} + + )} ([]); + const wolClients = wolClientData || []; const [enabled, setEnabled] = useState(0); const { mutateAsync } = useMutationAPI(); const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]); @@ -64,12 +64,6 @@ function SettingsButton() { } }, [appCategoryData, appCategoryDataIsFetched]); - useEffect(() => { - if (wolClientData && wolClientDataIsFetched) { - setWolClients(wolClientData); - } - }, [wolClientData, wolClientDataIsFetched]); - useEffect(() => { if (wolEnabledData && wolEnabledDataIsFetched) { setEnabled(wolEnabledData[0].enableWol); diff --git a/src/pages/homepage/components/SettingsButton/ThemingCard.tsx b/src/pages/homepage/components/SettingsButton/ThemingCard.tsx index b344df1..31045a6 100644 --- a/src/pages/homepage/components/SettingsButton/ThemingCard.tsx +++ b/src/pages/homepage/components/SettingsButton/ThemingCard.tsx @@ -26,15 +26,11 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) { const { data: themePlaylistData } = useQueryAPI({ endpoint: 'api/themePlaylists', }); - const { data: allThemeData, isFetched: allThemeDataIsFetched } = useQueryAPI({ + const { data: allThemeData } = useQueryAPI({ endpoint: 'api/themes', }); - const [allThemes, setAllThemes] = useState([]); - useEffect(() => { - if (allThemeData && allThemeDataIsFetched) { - setAllThemes(allThemeData); - } - }, [allThemeData, allThemeDataIsFetched]); + const allThemes: Theme[] = allThemeData || []; + const { isMobile, currentTheme, diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index eb94921..dc0cc59 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -1,6 +1,5 @@ import { ActionIcon, Divider, Group, ScrollArea, Stack, Tabs, Text } from '@mantine/core'; import { useCommonHooks, useQueryAPI } from 'hooks'; -import { useEffect, useState } from 'react'; import { FaPaintBrush, FaRegListAlt } from 'react-icons/fa'; import { FiArrowLeftCircle, @@ -27,19 +26,14 @@ import WolClients from './components/WoL/WolClients'; export function Settings() { const endpoint = 'api/personalData'; - const { data: personalData, isFetched: personalDataIsFetched } = useQueryAPI({ + const { data: personalData } = useQueryAPI({ endpoint: endpoint, }); - const [personal, setPersonal] = useState(); + const personal: PersonalData = personalData?.[0]; const { isMobile, theme } = useCommonHooks(); const navigate = useNavigate(); const { tabValue } = useParams(); - useEffect(() => { - if (personalData && personalDataIsFetched) { - setPersonal(personalData[0]); - } - }, [personalData, personalDataIsFetched]); return ( diff --git a/src/pages/settings/components/Applications/Applications.tsx b/src/pages/settings/components/Applications/Applications.tsx index ca81225..109597d 100644 --- a/src/pages/settings/components/Applications/Applications.tsx +++ b/src/pages/settings/components/Applications/Applications.tsx @@ -38,6 +38,8 @@ const ApplicationSettings = () => { index: 0, enabled: 1, }; + const [searchValue, setSearchValue] = useState(''); + const [debouncedSearch] = useDebouncedValue(searchValue, 500); const form = useForm({ initialValues: defaultApplication, validate: { @@ -49,19 +51,21 @@ const ApplicationSettings = () => { }); const appsEndpoint = 'api/applications'; const appsOrderEndpoint = 'api/applicationOrder'; - const { - data: applicationData, - isFetched: applicationDataIsFetched, - isFetching: applicationDataIsFetching, - } = useQueryAPI({ + const { data: applicationData, isFetching: applicationDataIsFetching } = useQueryAPI({ endpoint: appsEndpoint, }); - const [applications, setApplications] = useState([]); + const applications: Application[] = + applicationData + .filter( + (a: Application) => + a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.baseUrl.toLowerCase().includes((debouncedSearch as string).toLowerCase()), + ) + .sort((a: Application, b: Application) => (a.index > b.index ? 1 : -1)) || []; const [applicationList, setApplicationList] = useState([]); const { isMobile, reorder, theme } = useCommonHooks(); const { mutateAsync } = useMutationAPI(); - const [searchValue, setSearchValue] = useState(''); - const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [dragging, setDragging] = useState(false); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -93,22 +97,10 @@ const ApplicationSettings = () => { form.reset(); }; useEffect(() => { - if (applicationData && applicationDataIsFetched) { - setApplications( - applicationData - .filter( - (a: Application) => - a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || - a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || - a.baseUrl.toLowerCase().includes((debouncedSearch as string).toLowerCase()), - ) - .sort((a: Application, b: Application) => (a.index > b.index ? 1 : -1)), - ); - } if (debouncedSearch === searchValue) { setIsLoading(false); } - }, [applicationData, applicationDataIsFetched, debouncedSearch]); + }, [debouncedSearch]); useEffect(() => { if (applicationList.length > 0 && applicationList !== applications) { diff --git a/src/pages/settings/components/Bookmarks/Bookmarks.tsx b/src/pages/settings/components/Bookmarks/Bookmarks.tsx index 5007993..8084302 100644 --- a/src/pages/settings/components/Bookmarks/Bookmarks.tsx +++ b/src/pages/settings/components/Bookmarks/Bookmarks.tsx @@ -33,14 +33,10 @@ const BookmarkSettings = () => { }, }); const bookmarksEndpoint = 'api/bookmarks'; - const { - data: bookmarkData, - isFetched: bookmarkDataIsFetched, - isFetching: bookmarkDataIsFetching, - } = useQueryAPI({ + const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({ endpoint: bookmarksEndpoint, }); - const [bookmarks, setBookmarks] = useState([]); + const bookmarks = bookmarkData || []; const categoriesEndpoint = 'api/bookmarkCategories'; const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({ endpoint: categoriesEndpoint, @@ -54,7 +50,27 @@ const BookmarkSettings = () => { const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [isLoading, setIsLoading] = useState(false); - + const foundBookmarkGroups = [ + ...new Set( + bookmarks + .filter( + (a: Bookmark) => + a.name + .toLowerCase() + .includes((debouncedGroupSearch as string).toLowerCase()) || + a.url.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()), + ) + .map((a: Bookmark) => + groupData.find( + (b: BookmarkGroup) => + b.categoryName.toLowerCase() === a.category.toLowerCase(), + ), + ), + ), + ]; + const foundGroups = groupData.filter((a: BookmarkGroup) => + a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()), + ); const handleClose = () => { closeModal(); form.reset(); @@ -80,40 +96,8 @@ const BookmarkSettings = () => { ); } - useEffect(() => { - if (bookmarkData && bookmarkDataIsFetched) { - setBookmarks(bookmarkData); - } - }, [bookmarkDataIsFetched, bookmarkData]); - useEffect(() => { if (groupData && groupsIsFetched) { - const foundBookmarkGroups = [ - ...new Set( - bookmarks - .filter( - (a: Bookmark) => - a.name - .toLowerCase() - .includes((debouncedGroupSearch as string).toLowerCase()) || - a.url - .toLowerCase() - .includes((debouncedGroupSearch as string).toLowerCase()), - ) - .map((a: Bookmark) => - groupData.find( - (b: BookmarkGroup) => - b.categoryName.toLowerCase() === a.category.toLowerCase(), - ), - ), - ), - ]; - const foundGroups = groupData.filter((a: BookmarkGroup) => - a.categoryName - .toLowerCase() - .includes((debouncedGroupSearch as string).toLowerCase()), - ); - const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort( (a: BookmarkGroup, b: BookmarkGroup) => a.categoryIndex > b.categoryIndex ? 1 : -1, diff --git a/src/pages/settings/components/Bookmarks/DebouncedBookmarkItem.tsx b/src/pages/settings/components/Bookmarks/DebouncedBookmarkItem.tsx index e1297d9..51fc736 100644 --- a/src/pages/settings/components/Bookmarks/DebouncedBookmarkItem.tsx +++ b/src/pages/settings/components/Bookmarks/DebouncedBookmarkItem.tsx @@ -16,11 +16,15 @@ function DebouncedBookmarkItem({ const { handleDelete, isMobile, theme } = useCommonHooks(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const { mutateAsync } = useMutationAPI(); - const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({ + const { data: groupData } = useQueryAPI({ endpoint: 'api/bookmarkCategories', }); const [bookmark, setBookmark] = useState(bookmarkItem); - const [groups, setGroups] = useState([]); + const groups: BookmarkGroup[] = + groupData.sort( + (a: BookmarkGroup, b: BookmarkGroup) => + a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(), + ) || []; const [update, setUpdate] = useState({}); const [debounced] = useDebouncedValue(update, 1000); const handleClose = () => { @@ -39,17 +43,6 @@ function DebouncedBookmarkItem({ } }, [update]); - useEffect(() => { - if (groupData) { - setGroups( - groupData.sort( - (a: BookmarkGroup, b: BookmarkGroup) => - a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(), - ), - ); - } - }, [groupData, groupsIsFetched]); - return ( { }, }); const types: string[] = ['SSH', 'RDP', 'VNC']; + const [searchValue, setSearchValue] = useState(''); + const [debouncedSearch] = useDebouncedValue(searchValue, 500); const endpoint = 'api/guacamoleClients'; - const { - data: guacamoleClientsData, - isFetched: guacamoleClientsIsFetched, - isFetching: guacamoleClientsIsFetching, - } = useQueryAPI({ - endpoint: endpoint, - }); - const [guacamoleClients, setGuacamoleClients] = useState([]); + const { data: guacamoleClientsData, isFetching: guacamoleClientsIsFetching } = + useQueryAPI({ + endpoint: endpoint, + }); + const guacamoleClients = + guacamoleClientsData.filter( + (a: GuacamoleClient) => + a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.clientIdentifier + .toLowerCase() + .includes((debouncedSearch as string).toLowerCase()), + ) || []; const { mutateAsync } = useMutationAPI(); const { isMobile, theme } = useCommonHooks(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); - const [searchValue, setSearchValue] = useState(''); - const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -61,25 +65,6 @@ const GuacamoleClients = () => { form.reset(); }; - useEffect(() => { - if (guacamoleClientsData && guacamoleClientsIsFetched) { - setGuacamoleClients( - guacamoleClientsData.filter( - (a: GuacamoleClient) => - a.clientName - .toLowerCase() - .includes((debouncedSearch as string).toLowerCase()) || - a.clientIdentifier - .toLowerCase() - .includes((debouncedSearch as string).toLowerCase()), - ), - ); - } - if (debouncedSearch === searchValue) { - setIsLoading(false); - } - }, [guacamoleClientsData, guacamoleClientsIsFetched, debouncedSearch]); - return ( { const endpoint = 'api/messages'; - const { - data: messageData, - isFetched: messageDataIsFetched, - isFetching: messageDataIsFetching, - } = useQueryAPI({ + const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({ endpoint: endpoint, }); const form = useForm({ @@ -33,13 +29,16 @@ const MessageSettings = () => { message: isNotEmpty('Message is required'), }, }); - const { isMobile, theme } = useCommonHooks(); - const { mutateAsync } = useMutationAPI(); - const [messages, setMessages] = useState([]); - const [message, setMessage] = useState({}); - const [debounced] = useDebouncedValue(message, 1000); const [searchValue, setSearchValue] = useState(''); const [debouncedSearch] = useDebouncedValue(searchValue, 500); + const { isMobile, theme } = useCommonHooks(); + const { mutateAsync } = useMutationAPI(); + const messages = + messageData.filter((m: Message) => + m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()), + ) || []; + const [message, setMessage] = useState({}); + const [debounced] = useDebouncedValue(message, 1000); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [isLoading, setIsLoading] = useState(false); @@ -48,19 +47,6 @@ const MessageSettings = () => { form.reset(); }; - useEffect(() => { - if (messageData && messageDataIsFetched) { - setMessages( - messageData.filter((m: Message) => - m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()), - ), - ); - } - if (debouncedSearch === searchValue) { - setIsLoading(false); - } - }, [messageData, messageDataIsFetched, debouncedSearch]); - useEffect(() => { if (Object.keys(debounced).length) { mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }); diff --git a/src/pages/settings/components/Search/SearchHistory.tsx b/src/pages/settings/components/Search/SearchHistory.tsx index b4d8ad9..fa9d643 100644 --- a/src/pages/settings/components/Search/SearchHistory.tsx +++ b/src/pages/settings/components/Search/SearchHistory.tsx @@ -9,14 +9,10 @@ import type { SearchHistoryItem } from 'types'; const SearchHistory = () => { const searchEndpoint = 'api/searchHistory'; - const { - data: historyData, - isFetched: historyDataIsFetched, - isFetching: historyDataIsFetching, - } = useQueryAPI({ + const { data: historyData, isFetching: historyDataIsFetching } = useQueryAPI({ endpoint: searchEndpoint, }); - const [searchHistory, setSearchHistory] = useState([]); + const searchHistory: SearchHistoryItem[] = historyData || []; const [searchValue, setSearchValue] = useState(''); const [searchHistoryItem, setSearchHistoryItem] = useState(); const [debouncedSearch] = useDebouncedValue(searchValue, 500); @@ -36,12 +32,6 @@ const SearchHistory = () => { }, ]); - useEffect(() => { - if (historyData && historyDataIsFetched) { - setSearchHistory(historyData); - } - }, [historyData, historyDataIsFetched]); - useEffect(() => { setGlobalFilter(debouncedSearch); if (debouncedSearch === globalFilter) { diff --git a/src/pages/settings/components/Search/SearchProviders.tsx b/src/pages/settings/components/Search/SearchProviders.tsx index a911b3e..a3d69da 100644 --- a/src/pages/settings/components/Search/SearchProviders.tsx +++ b/src/pages/settings/components/Search/SearchProviders.tsx @@ -52,19 +52,23 @@ const SearchProviderSettings = () => { }, }); const endpoint = 'api/providers'; - const { - data: providerData, - isFetched: providerDataIsFetched, - isFetching: providerDataIsFetching, - } = useQueryAPI({ + const { data: providerData, isFetching: providerDataIsFetching } = useQueryAPI({ endpoint: endpoint, }); - const [providers, setProviders] = useState([]); + const [searchValue, setSearchValue] = useState(''); + const [debouncedSearch] = useDebouncedValue(searchValue, 500); + const providers = + providerData + .filter( + (a: Provider) => + a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.prefix.toLowerCase().includes((debouncedSearch as string).toLowerCase()), + ) + .sort((a: Provider, b: Provider) => (a.prefix > b.prefix ? 1 : -1)) ?? []; const { isMobile, theme, iconFilter } = useCommonHooks(); const { mutateAsync } = useMutationAPI(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); - const [searchValue, setSearchValue] = useState(''); - const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -74,22 +78,10 @@ const SearchProviderSettings = () => { }; useEffect(() => { - if (providerData && providerDataIsFetched) { - setProviders( - providerData - .filter( - (a: Provider) => - a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || - a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || - a.prefix.toLowerCase().includes((debouncedSearch as string).toLowerCase()), - ) - .sort((a: Provider, b: Provider) => (a.prefix > b.prefix ? 1 : -1)), - ); - } if (debouncedSearch === searchValue) { setIsLoading(false); } - }, [providerData, providerDataIsFetched, debouncedSearch]); + }, [debouncedSearch]); return ( diff --git a/src/pages/settings/components/Theming/ThemePlaylists/ThemePlaylists.tsx b/src/pages/settings/components/Theming/ThemePlaylists/ThemePlaylists.tsx index d1636bd..1ff13c5 100644 --- a/src/pages/settings/components/Theming/ThemePlaylists/ThemePlaylists.tsx +++ b/src/pages/settings/components/Theming/ThemePlaylists/ThemePlaylists.tsx @@ -15,14 +15,18 @@ export function ThemePlaylists() { endpoint: themesEndpoint, }); const themePlaylistsEndpoint = 'api/themePlaylists'; - const { - data: themePlaylistData, - isFetched: themePlaylistDataIsFetched, - isFetching: themePlaylistDataIsFetching, - } = useQueryAPI({ - endpoint: themePlaylistsEndpoint, - }); - const [themePlaylists, setThemePlaylists] = useState([]); + const { data: themePlaylistData, isFetching: themePlaylistDataIsFetching } = + useQueryAPI({ + endpoint: themePlaylistsEndpoint, + }); + const [searchValue, setSearchValue] = useState(''); + const [debouncedSearch] = useDebouncedValue(searchValue, 500); + const themePlaylists: ThemePlaylist[] = + themePlaylistData?.filter( + (a: ThemePlaylist) => + a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.name.includes('Time of Day'), + ) || []; const defaultPlaylist = { id: undefined, default: 0, @@ -34,8 +38,6 @@ export function ThemePlaylists() { const [selectedPlaylist, setSelectedPlaylist] = useState(defaultPlaylist); const { currentThemePlaylist, theme, isMobile } = useCommonHooks(); - const [searchValue, setSearchValue] = useState(''); - const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [isLoading, setIsLoading] = useState(false); const handleOpen = (playlist: ThemePlaylist) => { @@ -58,19 +60,10 @@ export function ThemePlaylists() { }; useEffect(() => { - if (themePlaylistData && themePlaylistDataIsFetched) { - setThemePlaylists( - themePlaylistData.filter( - (a: ThemePlaylist) => - a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || - a.name.includes('Time of Day'), - ), - ); - } if (debouncedSearch === searchValue) { setIsLoading(false); } - }, [themePlaylistData, themePlaylistDataIsFetched, debouncedSearch]); + }, [debouncedSearch]); return ( diff --git a/src/pages/settings/components/Theming/Themes/Themes.tsx b/src/pages/settings/components/Theming/Themes/Themes.tsx index 9ee91c2..b89b2ca 100644 --- a/src/pages/settings/components/Theming/Themes/Themes.tsx +++ b/src/pages/settings/components/Theming/Themes/Themes.tsx @@ -25,14 +25,14 @@ import { ThemeModal } from './ThemeModal'; export function Themes() { const backgroundsEndpoint = 'api/backgrounds'; - const { - data: allThemeData, - isFetched: allThemeDataIsFetched, - isFetching: allThemeDataIsFetching, - } = useQueryAPI({ + const { data: allThemeData, isFetching: allThemeDataIsFetching } = useQueryAPI({ endpoint: 'api/themes', }); - const [allThemes, setAllThemes] = useState([]); + const [searchValue, setSearchValue] = useState(''); + const [debouncedSearch] = useDebouncedValue(searchValue, 500); + const allThemes: Theme[] = allThemeData?.filter((a: Theme) => + a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()), + ); const { data: backgroundData, isFetched: backgroundDataIsFetched, @@ -43,7 +43,7 @@ export function Themes() { const { data: themePlaylistData } = useQueryAPI({ endpoint: 'api/themePlaylists', }); - const [themePlaylists, setThemePlaylists] = useState([]); + const themePlaylists: ThemePlaylist[] = themePlaylistData || []; const [themeModalOpened, { open: openThemeModal, close: closeThemeModal }] = useDisclosure(false); const [ @@ -51,7 +51,18 @@ export function Themes() { { open: openBackgroundModal, close: closeBackgroundModal }, ] = useDisclosure(false); const [backgroundList, setBackgroundList] = useState(['None']); - const [inUseBackgrounds, setInUseBackgrounds] = useState([]); + const inUseBackgrounds: string[] = [ + ...new Set( + allThemes + ?.filter((t) => { + if (t.backgroundImage !== 'none') { + return t.backgroundImage; + } + return undefined; + }) + .map((t) => t.backgroundImage), + ), + ] as string[]; const [selectedTheme, setSelectedTheme] = useState({ id: undefined, name: 'default', @@ -65,8 +76,6 @@ export function Themes() { textShadowsEnabled: 0, appShadowsEnabled: 0, }); - const [searchValue, setSearchValue] = useState(''); - const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [isLoading, setIsLoading] = useState(false); const [isNew, setIsNew] = useState(false); const navigate = useNavigate(); @@ -94,40 +103,10 @@ export function Themes() { }, [backgroundData, backgroundDataIsFetched]); useEffect(() => { - if (themePlaylistData) { - setThemePlaylists(themePlaylistData); - } - }, [themePlaylistData]); - - useEffect(() => { - if (allThemeData && allThemeDataIsFetched) { - setAllThemes( - allThemeData.filter((a: Theme) => - a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()), - ), - ); - } if (debouncedSearch === searchValue) { setIsLoading(false); } - }, [allThemeData, allThemeDataIsFetched, debouncedSearch]); - - useEffect(() => { - if (allThemes) { - setInUseBackgrounds([ - ...new Set( - allThemes - .filter((t) => { - if (t.backgroundImage !== 'none') { - return t.backgroundImage; - } - return undefined; - }) - .map((t) => t.backgroundImage), - ), - ]); - } - }, [allThemes]); + }, [debouncedSearch]); return ( @@ -195,7 +174,7 @@ export function Themes() { }} pos={'absolute'} /> - {allThemes.length > 0 ? ( + {allThemes?.length > 0 ? ( allThemes.map((cardTheme: Theme) => ( {cardTheme.name === currentTheme && ( diff --git a/src/pages/settings/components/WoL/WolClients.tsx b/src/pages/settings/components/WoL/WolClients.tsx index b4b2df1..855f7dc 100644 --- a/src/pages/settings/components/WoL/WolClients.tsx +++ b/src/pages/settings/components/WoL/WolClients.tsx @@ -35,19 +35,20 @@ const WolClientSettings = () => { }, }); const endpoint = 'api/wolClients'; - const { - data: wolClientsData, - isFetched: wolClientsIsFetched, - isFetching: wolClientsIsFetching, - } = useQueryAPI({ + const { data: wolClientsData, isFetching: wolClientsIsFetching } = useQueryAPI({ endpoint: endpoint, }); - const [wolClients, setWolClients] = useState([]); + const [searchValue, setSearchValue] = useState(''); + const [debouncedSearch] = useDebouncedValue(searchValue, 500); + const wolClients: WolClient[] = + wolClientsData.filter( + (a: WolClient) => + a.hostname.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || + a.macAddress.toLowerCase().includes((debouncedSearch as string).toLowerCase()), + ) || []; const { mutateAsync } = useMutationAPI(); const { isMobile, theme } = useCommonHooks(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); - const [searchValue, setSearchValue] = useState(''); - const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -57,23 +58,10 @@ const WolClientSettings = () => { }; useEffect(() => { - if (wolClientsData && wolClientsIsFetched) { - setWolClients( - wolClientsData.filter( - (a: WolClient) => - a.hostname - .toLowerCase() - .includes((debouncedSearch as string).toLowerCase()) || - a.macAddress - .toLowerCase() - .includes((debouncedSearch as string).toLowerCase()), - ), - ); - } if (debouncedSearch === searchValue) { setIsLoading(false); } - }, [wolClientsData, wolClientsIsFetched, debouncedSearch]); + }, [debouncedSearch]); return (