Search bar fixes and performance updates
This commit is contained in:
24
package-lock.json
generated
24
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<PersonalData>();
|
||||
const [message, setMessage] = useState<Message>();
|
||||
const personal: PersonalData = personalData?.[0];
|
||||
const message: Message = messageData?.[0];
|
||||
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
|
||||
const [current, setCurrent] = useState<CW>({
|
||||
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}
|
||||
/>
|
||||
<Header
|
||||
greet={displayedGreeting}
|
||||
@@ -246,7 +236,7 @@ export function Homepage() {
|
||||
closeWeatherModal={closeWeatherModal}
|
||||
openWeatherModal={openWeatherModal}
|
||||
position='center'
|
||||
personal={personal as PersonalData}
|
||||
personal={personal}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -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<Application[]>([]);
|
||||
|
||||
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 && (
|
||||
<Stack gap={0}>
|
||||
<Text
|
||||
className={textClasses.sectionTitle}
|
||||
@@ -66,7 +57,7 @@ function Applications({ contentType }: { contentType: string[] }) {
|
||||
gridTemplateColumns: `repeat(${appDensity}, 1fr)`,
|
||||
}}
|
||||
>
|
||||
{applications.map((application: Application) => {
|
||||
{applications?.map((application: Application) => {
|
||||
return (
|
||||
<AppItem
|
||||
key={application.name}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Stack, Text } from '@mantine/core';
|
||||
import { useCommonHooks, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { textClasses } from 'styles';
|
||||
import type { Bookmark, BookmarkGroup } from 'types';
|
||||
import BookmarkGroups from './BookmarkGroups';
|
||||
@@ -14,57 +13,41 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
|
||||
});
|
||||
const { bookmarkSort, bookmarkDensity, bookmarkScale, theme, textShadowStyle } =
|
||||
useCommonHooks();
|
||||
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
|
||||
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
|
||||
|
||||
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 (
|
||||
<>
|
||||
|
||||
@@ -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<Provider[]>([]);
|
||||
const [guacamoleHost, setGuacamoleHost] = useState<string | undefined>('');
|
||||
const [guacamoleClients, setGuacamoleClients] = useState<GuacamoleClient[]>([]);
|
||||
const [filteredGuacamoleClients, setFilteredGuacamoleClients] = useState<
|
||||
GuacamoleClient[]
|
||||
>([]);
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
const [filteredApplications, setFilteredApplications] = useState<Application[]>([]);
|
||||
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
|
||||
const searchEndpoint = 'api/searchHistory';
|
||||
const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({
|
||||
endpoint: searchEndpoint,
|
||||
});
|
||||
const [searchHistory, setSearchHistory] = useState<SearchHistoryItem[]>([]);
|
||||
const [filteredHistory, setFilteredHistory] = useState<SearchHistoryItem[]>([]);
|
||||
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<SearchHistoryItem[]>([]);
|
||||
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<string>('');
|
||||
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 && <Overlay blur={5} onClick={closeDropdown} fixed zIndex={200} />}
|
||||
@@ -654,7 +641,6 @@ export default function Search({
|
||||
}
|
||||
rightSection={<div />}
|
||||
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 && (
|
||||
<Card
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function RecommendedBookmarks({
|
||||
backgroundColor: 'var(--mantine-color-search-0)',
|
||||
backdropFilter: 'blur(15px) brightness(1.75)',
|
||||
borderRadius: '4px',
|
||||
opacity: searchValue && dropdownOpened && bookmarks.length > 0 ? 1 : 0,
|
||||
opacity: searchValue && dropdownOpened ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<Stack w={'100%'} mah={'636px'}>
|
||||
@@ -63,6 +63,22 @@ export default function RecommendedBookmarks({
|
||||
))
|
||||
.toSorted((a: any, b: any) => (a.name > b.name ? 1 : -1))
|
||||
.slice(0, 5)}
|
||||
{!bookmarks.length && (
|
||||
<Text
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
style={{
|
||||
backgroundColor: 'var(--mantine-color-search-0)',
|
||||
padding: '.5rem',
|
||||
borderRadius: '10px',
|
||||
overflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
No bookmarks found{' '}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack
|
||||
w={bookmarks.length <= 5 ? '0%' : '48%'}
|
||||
|
||||
@@ -13,13 +13,13 @@ function SettingsButton() {
|
||||
const { data: appCategoryData, isFetched: appCategoryDataIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/appCategories',
|
||||
});
|
||||
const { data: wolClientData, isFetched: wolClientDataIsFetched } = useQueryAPI({
|
||||
const { data: wolClientData } = useQueryAPI({
|
||||
endpoint: 'api/wolClients',
|
||||
});
|
||||
const { data: wolEnabledData, isFetched: wolEnabledDataIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/wolEnabled',
|
||||
});
|
||||
const [wolClients, setWolClients] = useState<WolClient[]>([]);
|
||||
const wolClients = wolClientData || [];
|
||||
const [enabled, setEnabled] = useState<number>(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);
|
||||
|
||||
@@ -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<Theme[]>([]);
|
||||
useEffect(() => {
|
||||
if (allThemeData && allThemeDataIsFetched) {
|
||||
setAllThemes(allThemeData);
|
||||
}
|
||||
}, [allThemeData, allThemeDataIsFetched]);
|
||||
const allThemes: Theme[] = allThemeData || [];
|
||||
|
||||
const {
|
||||
isMobile,
|
||||
currentTheme,
|
||||
|
||||
@@ -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<PersonalData>();
|
||||
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 (
|
||||
<Stack h={'100%'} gap={0}>
|
||||
<Group gap={0} style={{ position: 'relative', paddingBottom: '1rem' }}>
|
||||
|
||||
@@ -38,6 +38,8 @@ const ApplicationSettings = () => {
|
||||
index: 0,
|
||||
enabled: 1,
|
||||
};
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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<Application[]>([]);
|
||||
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<Application[]>([]);
|
||||
const { isMobile, reorder, theme } = useCommonHooks();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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) {
|
||||
|
||||
@@ -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<Bookmark[]>([]);
|
||||
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,
|
||||
|
||||
@@ -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<Bookmark>(bookmarkItem);
|
||||
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
|
||||
const groups: BookmarkGroup[] =
|
||||
groupData.sort(
|
||||
(a: BookmarkGroup, b: BookmarkGroup) =>
|
||||
a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(),
|
||||
) || [];
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
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 (
|
||||
<Card
|
||||
p={isMobile ? 'xl' : undefined}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { isNotEmpty, useForm } from '@mantine/form';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { FaTerminal } from 'react-icons/fa';
|
||||
import { FaComputer, FaDesktop } from 'react-icons/fa6';
|
||||
import { popoverClasses, selectClasses, textInputClasses } from 'styles';
|
||||
@@ -39,20 +39,24 @@ const GuacamoleClients = () => {
|
||||
},
|
||||
});
|
||||
const types: string[] = ['SSH', 'RDP', 'VNC'];
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const endpoint = 'api/guacamoleClients';
|
||||
const {
|
||||
data: guacamoleClientsData,
|
||||
isFetched: guacamoleClientsIsFetched,
|
||||
isFetching: guacamoleClientsIsFetching,
|
||||
} = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const [guacamoleClients, setGuacamoleClients] = useState<GuacamoleClient[]>([]);
|
||||
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<string | number>('');
|
||||
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 (
|
||||
<Stack>
|
||||
<AddItemModal
|
||||
|
||||
@@ -18,11 +18,7 @@ import DebouncedMessage from './DebouncedMessage';
|
||||
|
||||
const MessageSettings = () => {
|
||||
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<Message[]>([]);
|
||||
const [message, setMessage] = useState({});
|
||||
const [debounced] = useDebouncedValue(message, 1000);
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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 });
|
||||
|
||||
@@ -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<SearchHistoryItem[]>([]);
|
||||
const searchHistory: SearchHistoryItem[] = historyData || [];
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
const [searchHistoryItem, setSearchHistoryItem] = useState<SearchHistoryItem>();
|
||||
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) {
|
||||
|
||||
@@ -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<Provider[]>([]);
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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<string | number>('');
|
||||
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 (
|
||||
<Stack>
|
||||
|
||||
@@ -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<ThemePlaylist[]>([]);
|
||||
const { data: themePlaylistData, isFetching: themePlaylistDataIsFetching } =
|
||||
useQueryAPI({
|
||||
endpoint: themePlaylistsEndpoint,
|
||||
});
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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<ThemePlaylist>(defaultPlaylist);
|
||||
const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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 (
|
||||
<Stack>
|
||||
|
||||
@@ -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<Theme[]>([]);
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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<ThemePlaylist[]>([]);
|
||||
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<string[]>(['None']);
|
||||
const [inUseBackgrounds, setInUseBackgrounds] = useState<string[]>([]);
|
||||
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<Theme>({
|
||||
id: undefined,
|
||||
name: 'default',
|
||||
@@ -65,8 +76,6 @@ export function Themes() {
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
});
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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 (
|
||||
<Stack>
|
||||
@@ -195,7 +174,7 @@ export function Themes() {
|
||||
}}
|
||||
pos={'absolute'}
|
||||
/>
|
||||
{allThemes.length > 0 ? (
|
||||
{allThemes?.length > 0 ? (
|
||||
allThemes.map((cardTheme: Theme) => (
|
||||
<Group key={cardTheme.name} pos={'relative'}>
|
||||
{cardTheme.name === currentTheme && (
|
||||
|
||||
@@ -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<WolClient[]>([]);
|
||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||
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<string | number>('');
|
||||
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 (
|
||||
<Stack>
|
||||
|
||||
Reference in New Issue
Block a user