Type fixes

This commit is contained in:
Sean Edmunds
2026-01-08 16:12:13 -06:00
parent fd160ede88
commit 3307bcefc9
10 changed files with 212 additions and 165 deletions

View File

@@ -139,8 +139,6 @@ export function useMutationAPI() {
queryClient.refetchQueries({ queryKey: ['GET', 'api/providers/default'] }); queryClient.refetchQueries({ queryKey: ['GET', 'api/providers/default'] });
endpoint === 'api/personalData' && endpoint === 'api/personalData' &&
queryClient.refetchQueries({ queryKey: ['GET', 'api/personalData'] }); queryClient.refetchQueries({ queryKey: ['GET', 'api/personalData'] });
queryClient.refetchQueries({ queryKey: ['GET', 'api/wolEnabled'] });
queryClient.refetchQueries({ queryKey: ['GET', 'api/guacamoleEnabled'] });
}, },
}); });
} }

View File

@@ -9,7 +9,7 @@ function Applications({ contentType }: { contentType: string[] }) {
endpoint: 'api/enabledApplications', endpoint: 'api/enabledApplications',
}); });
const { appSort, appDensity, appScale, theme, textShadowStyle } = useCommonHooks(); const { appSort, appDensity, appScale, theme, textShadowStyle } = useCommonHooks();
const applications = applicationData const applications: Application[] = applicationData
?.sort((a: Application, b: Application) => { ?.sort((a: Application, b: Application) => {
switch (appSort) { switch (appSort) {
case 'A>Z': case 'A>Z':

View File

@@ -13,12 +13,12 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
}); });
const { bookmarkSort, bookmarkDensity, bookmarkScale, theme, textShadowStyle } = const { bookmarkSort, bookmarkDensity, bookmarkScale, theme, textShadowStyle } =
useCommonHooks(); useCommonHooks();
const groupList = groupData?.map((category: BookmarkGroup) => { const groupList: BookmarkGroup[] = groupData?.map((category: BookmarkGroup) => {
return category; return category;
}); });
const bookmarks = bookmarkData || []; const bookmarks: Bookmark[] = bookmarkData || [];
const groups = groupList const groups: BookmarkGroup[] = groupList
?.sort((a: BookmarkGroup, b: BookmarkGroup) => { ?.toSorted((a: BookmarkGroup, b: BookmarkGroup) => {
switch (bookmarkSort) { switch (bookmarkSort) {
case 'A>Z': case 'A>Z':
return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase() return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase()

View File

@@ -28,7 +28,7 @@ import type {
import RecommendedApplications from './components/RecommendedApplications'; import RecommendedApplications from './components/RecommendedApplications';
import RecommendedBookmarks from './components/RecommendedBookmarks'; import RecommendedBookmarks from './components/RecommendedBookmarks';
import RecommendedClients from './components/RecommendedClients'; import RecommendedClients from './components/RecommendedClients';
import SearchSelectItem from './components/SearchSelectItem'; import RenderItem from './components/RenderItem';
export default function Search({ export default function Search({
current, current,
@@ -47,9 +47,6 @@ export default function Search({
position: string; position: string;
personal: PersonalData; personal: PersonalData;
}) { }) {
const { ref, height } = useElementSize();
const allIcons: any = { ...fa6, ...vsc, ...si };
const searchEndpoint = 'api/searchHistory';
const { const {
isMobile, isMobile,
urlPattern, urlPattern,
@@ -72,6 +69,12 @@ export default function Search({
defaultProviderIcon, defaultProviderIcon,
setDefaultProviderIcon, setDefaultProviderIcon,
} = useCommonHooks(); } = useCommonHooks();
const { ref, height } = useElementSize();
const { mutateAsync } = useMutationAPI();
const allIcons: any = { ...fa6, ...vsc, ...si };
const Icon = allIcons[providerIcon];
const searchEndpoint = 'api/searchHistory';
const { data: applicationData } = useQueryAPI({ const { data: applicationData } = useQueryAPI({
endpoint: 'api/enabledApplications', endpoint: 'api/enabledApplications',
}); });
@@ -94,8 +97,14 @@ export default function Search({
const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({ const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({
endpoint: searchEndpoint, endpoint: searchEndpoint,
}); });
const applications: Application[] = applicationData || []; const applications: Application[] = applicationData || [];
const filteredApplications = applications?.filter((app: Application) => const providers: Provider[] = providerData || [];
const guacamoleHost: string = guacamoleHostData?.[0]?.guacamoleHost;
const guacamoleClients: GuacamoleClient[] = guacamoleClientData || [];
const searchHistory: SearchHistoryItem[] = historyData || [];
const filteredApplications: Application[] = applications?.filter((app: Application) =>
app.name.toLowerCase().includes((searchValue as string).substring(1).toLowerCase()), app.name.toLowerCase().includes((searchValue as string).substring(1).toLowerCase()),
); );
const bookmarks: Bookmark[] = const bookmarks: Bookmark[] =
@@ -114,26 +123,23 @@ export default function Search({
bookmark.enabled === 1 && bookmark.enabled === 1 &&
bookmark.name.toLowerCase().includes((searchValue as string).toLowerCase()), bookmark.name.toLowerCase().includes((searchValue as string).toLowerCase()),
) || []; ) || [];
const providers = providerData || [];
const guacamoleHost = guacamoleHostData?.[0]?.guacamoleHost; const filteredGuacamoleClients: GuacamoleClient[] =
const guacamoleClients = guacamoleClientData || [];
const filteredGuacamoleClients =
guacamoleClients.filter((client: GuacamoleClient) => guacamoleClients.filter((client: GuacamoleClient) =>
client.clientName client.clientName
.toLowerCase() .toLowerCase()
.includes((searchValue as string).substring(1).toLowerCase()), .includes((searchValue as string).substring(1).toLowerCase()),
) || []; ) || [];
const searchHistory = historyData || [];
const [filteredHistory, setFilteredHistory] = 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 [placeholderValue, setPlaceholderValue] = useState<string>(''); const [placeholderValue, setPlaceholderValue] = useState<string>('');
const [listIndex, setListIndex] = useState(-1);
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
const [historyMultiplier, setHistoryMultiplier] = useState(1); const [historyMultiplier, setHistoryMultiplier] = useState(1);
const Icon = allIcons[providerIcon];
const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure();
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
useDisclosure(false);
const handleResetSearch = () => { const handleResetSearch = () => {
setSearchValue(''); setSearchValue('');
@@ -143,21 +149,6 @@ export default function Search({
setProviderURL(defaultProviderURL); setProviderURL(defaultProviderURL);
}; };
const handleSearchHistoryItem = ({ option }: { option: SearchHistoryItem }) => {
const url =
providerData.find((provider: Provider) => provider.prefix === option.prefix)?.url ??
defaultProviderURL;
open(`${url}${option.searchTerm}`, '_self');
};
const handleAppLaunchItem = ({ option }: { option: any }) => {
const validUrl =
option.url.startsWith('http://') || option.url.startsWith('https://')
? option.url
: `http://${option.url}`;
open(validUrl, '_self');
};
const handleGuacamoleClientLaunchItem = ({ option }: { option: any }) => { const handleGuacamoleClientLaunchItem = ({ option }: { option: any }) => {
const validUrl = `${guacamoleHost}/#/client/${option.clientIdentifier}`; const validUrl = `${guacamoleHost}/#/client/${option.clientIdentifier}`;
open(validUrl, '_self'); open(validUrl, '_self');
@@ -324,7 +315,7 @@ export default function Search({
setProviderPrefix('/g'); setProviderPrefix('/g');
setProviderIcon('FaGoogle'); setProviderIcon('FaGoogle');
} else if (item.target.value.startsWith('/')) { } else if (item.target.value.startsWith('/')) {
const provider = providers?.find( const provider: Provider | undefined = providers?.find(
(p: Provider) => p.prefix === item.target.value.split(' ')[0], (p: Provider) => p.prefix === item.target.value.split(' ')[0],
); );
setProviderLabel(provider?.name ?? 'Invalid Search'); setProviderLabel(provider?.name ?? 'Invalid Search');
@@ -434,112 +425,6 @@ export default function Search({
return historyOptions; return historyOptions;
}; };
const renderItem: any = (item: any) => {
const Icon = item.option.prefix
? allIcons[
item.option.prefix === '/g'
? 'FaGoogle'
: (providers.find((p: Provider) => p.prefix === item.option.prefix)?.icon ??
'')
]
: item.option.icon
? allIcons[item.option.icon]
: item.option.type && item.option.type === 'SSH'
? allIcons.FaTerminal
: item.option.type === 'RDP'
? allIcons.FaComputer
: allIcons.FaDesktop;
return (searchValue as string).startsWith('!') ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
justify='center'
w={'100%'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleAppLaunchItem(item);
}}
>
<Text pos={'absolute'} left={'1rem'} c={'var(--mantine-color-accent-0)'}>
Launch
</Text>
{Icon && (
<Icon
style={{
color: 'var(--mantine-color-primary-0)',
height: '2rem',
width: '2rem',
}}
/>
)}
<Text pos={'absolute'} right={'1rem'} c={'var(--mantine-color-accent-0)'}>
{item.option.name}
</Text>
</Group>
) : (searchValue as string).startsWith('>') && personal.enableGuacamole === 1 ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
justify='center'
w={'100%'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleGuacamoleClientLaunchItem(item);
}}
>
<Text pos={'absolute'} left={'1rem'} c={'var(--mantine-color-accent-0)'}>
Connect to
</Text>
<Icon
style={{
color: 'var(--mantine-color-primary-0)',
height: '2rem',
width: '2rem',
}}
/>
<Text pos={'absolute'} right={'1rem'} c={'var(--mantine-color-accent-0)'}>
{item.option.name || item.option.clientName || 'Invalid connection'}
</Text>
</Group>
) : item.option.value === 'load' ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
w={'100%'}
h={'2rem'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setHistoryMultiplier((prev: number) => prev + 1);
}}
>
<Text
w={'100%'}
c={'var(--mantine-color-accent-0)'}
style={{ textAlign: 'center' }}
>
{item.option.label}
</Text>
</Group>
) : (
<SearchSelectItem
item={item}
handleSearchHistoryItem={handleSearchHistoryItem}
searchEndpoint={searchEndpoint}
setSearchValue={setSearchValue}
Icon={Icon}
/>
);
};
useEffect(() => { useEffect(() => {
if (historyData && historyDataIsFetched) { if (historyData && historyDataIsFetched) {
if (filteredHistory.length === 0) { if (filteredHistory.length === 0) {
@@ -580,6 +465,7 @@ export default function Search({
setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle'); setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle');
} }
}, [defaultProviderData, defaultProviderDataIsFetched]); }, [defaultProviderData, defaultProviderDataIsFetched]);
return ( return (
<> <>
{dropdownOpened && <Overlay blur={5} onClick={closeDropdown} fixed zIndex={200} />} {dropdownOpened && <Overlay blur={5} onClick={closeDropdown} fixed zIndex={200} />}
@@ -647,7 +533,19 @@ export default function Search({
bg: 'transparent', bg: 'transparent',
}} }}
scrollAreaProps={{ viewportRef: ref }} scrollAreaProps={{ viewportRef: ref }}
renderOption={(item: any) => renderItem(item)} renderOption={(item: any) => (
<RenderItem
item={item}
allIcons={allIcons}
providers={providers}
searchValue={searchValue}
setSearchValue={setSearchValue}
personal={personal}
handleGuacamoleClientLaunchItem={handleGuacamoleClientLaunchItem}
setHistoryMultiplier={setHistoryMultiplier}
searchEndpoint={searchEndpoint}
/>
)}
onFocus={() => { onFocus={() => {
if (searchValue !== '') { if (searchValue !== '') {
openDropdown(); openDropdown();
@@ -703,17 +601,22 @@ export default function Search({
</Text> </Text>
<Group align='start' justify='space-between'> <Group align='start' justify='space-between'>
<Stack w={'49%'} gap={8}> <Stack w={'49%'} gap={8}>
<RecommendedBookmarks {bookmarks.length > 0 && (
bookmarks={bookmarks} <RecommendedBookmarks
searchValue={searchValue} bookmarks={bookmarks}
dropdownOpened={dropdownOpened} searchValue={searchValue}
/> dropdownOpened={dropdownOpened}
<RecommendedClients />
filteredGuacamoleClients={filteredGuacamoleClients} )}
searchValue={searchValue} {personal.enableGuacamole === 1 &&
dropdownOpened={dropdownOpened} filteredGuacamoleClients.length > 0 && (
handleGuacamoleClientLaunchItem={handleGuacamoleClientLaunchItem} <RecommendedClients
/> filteredGuacamoleClients={filteredGuacamoleClients}
searchValue={searchValue}
dropdownOpened={dropdownOpened}
handleGuacamoleClientLaunchItem={handleGuacamoleClientLaunchItem}
/>
)}
</Stack> </Stack>
<RecommendedApplications <RecommendedApplications
searchValue={searchValue} searchValue={searchValue}

View File

@@ -0,0 +1,144 @@
import { Group, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import type { PersonalData, Provider, SearchHistoryItem } from 'types';
import SearchSelectItem from './SearchSelectItem';
export default function RenderItem({
item,
allIcons,
providers,
searchValue,
setSearchValue,
personal,
handleGuacamoleClientLaunchItem,
setHistoryMultiplier,
searchEndpoint,
}: {
item: any;
allIcons: any;
providers: Provider[];
searchValue: string | number;
setSearchValue: React.Dispatch<React.SetStateAction<string | number>>;
personal: PersonalData;
handleGuacamoleClientLaunchItem: ({ option }: { option: any }) => void;
setHistoryMultiplier: React.Dispatch<React.SetStateAction<number>>;
searchEndpoint: string;
}) {
const { defaultProviderURL } = useCommonHooks();
const handleSearchHistoryItem = ({ option }: { option: SearchHistoryItem }) => {
const url =
providers.find((provider: Provider) => provider.prefix === option.prefix)?.url ??
defaultProviderURL;
open(`${url}${option.searchTerm}`, '_self');
};
const handleAppLaunchItem = ({ option }: { option: any }) => {
const validUrl =
option.url.startsWith('http://') || option.url.startsWith('https://')
? option.url
: `http://${option.url}`;
open(validUrl, '_self');
};
const Icon = item.option.prefix
? allIcons[
item.option.prefix === '/g'
? 'FaGoogle'
: (providers.find((p: Provider) => p.prefix === item.option.prefix)?.icon ?? '')
]
: item.option.icon
? allIcons[item.option.icon]
: item.option.type && item.option.type === 'SSH'
? allIcons.FaTerminal
: item.option.type === 'RDP'
? allIcons.FaComputer
: allIcons.FaDesktop;
return (searchValue as string).startsWith('!') ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
justify='center'
w={'100%'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleAppLaunchItem(item);
}}
>
<Text pos={'absolute'} left={'1rem'} c={'var(--mantine-color-accent-0)'}>
Launch
</Text>
{Icon && (
<Icon
style={{
color: 'var(--mantine-color-primary-0)',
height: '2rem',
width: '2rem',
}}
/>
)}
<Text pos={'absolute'} right={'1rem'} c={'var(--mantine-color-accent-0)'}>
{item.option.name}
</Text>
</Group>
) : (searchValue as string).startsWith('>') && personal.enableGuacamole === 1 ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
justify='center'
w={'100%'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleGuacamoleClientLaunchItem(item);
}}
>
<Text pos={'absolute'} left={'1rem'} c={'var(--mantine-color-accent-0)'}>
Connect to
</Text>
<Icon
style={{
color: 'var(--mantine-color-primary-0)',
height: '2rem',
width: '2rem',
}}
/>
<Text pos={'absolute'} right={'1rem'} c={'var(--mantine-color-accent-0)'}>
{item.option.name || item.option.clientName || 'Invalid connection'}
</Text>
</Group>
) : item.option.value === 'load' ? (
<Group
pos={'relative'}
m={0}
p={0}
gap={0}
w={'100%'}
h={'2rem'}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setHistoryMultiplier((prev: number) => prev + 1);
}}
>
<Text
w={'100%'}
c={'var(--mantine-color-accent-0)'}
style={{ textAlign: 'center' }}
>
{item.option.label}
</Text>
</Group>
) : (
<SearchSelectItem
item={item}
handleSearchHistoryItem={handleSearchHistoryItem}
searchEndpoint={searchEndpoint}
setSearchValue={setSearchValue}
Icon={Icon}
/>
);
}

View File

@@ -19,7 +19,7 @@ function SettingsButton() {
const { data: wolEnabledData, isFetched: wolEnabledDataIsFetched } = useQueryAPI({ const { data: wolEnabledData, isFetched: wolEnabledDataIsFetched } = useQueryAPI({
endpoint: 'api/wolEnabled', endpoint: 'api/wolEnabled',
}); });
const wolClients = wolClientData || []; const wolClients: WolClient[] = wolClientData || [];
const [enabled, setEnabled] = useState<number>(0); const [enabled, setEnabled] = useState<number>(0);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]); const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]);

View File

@@ -36,7 +36,7 @@ const BookmarkSettings = () => {
const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({ const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({
endpoint: bookmarksEndpoint, endpoint: bookmarksEndpoint,
}); });
const bookmarks = bookmarkData || []; const bookmarks: Bookmark[] = bookmarkData || [];
const categoriesEndpoint = 'api/bookmarkCategories'; const categoriesEndpoint = 'api/bookmarkCategories';
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({ const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: categoriesEndpoint, endpoint: categoriesEndpoint,
@@ -50,7 +50,7 @@ const BookmarkSettings = () => {
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500); const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const foundBookmarkGroups = [ const foundBookmarkGroups: BookmarkGroup[] = [
...new Set( ...new Set(
bookmarks bookmarks
.filter( .filter(
@@ -68,7 +68,7 @@ const BookmarkSettings = () => {
), ),
), ),
]; ];
const foundGroups = groupData.filter((a: BookmarkGroup) => const foundGroups: BookmarkGroup[] = groupData.filter((a: BookmarkGroup) =>
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()), a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
); );
const handleClose = () => { const handleClose = () => {

View File

@@ -46,7 +46,7 @@ const GuacamoleClients = () => {
useQueryAPI({ useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
}); });
const guacamoleClients = const guacamoleClients: GuacamoleClient[] =
guacamoleClientsData.filter( guacamoleClientsData.filter(
(a: GuacamoleClient) => (a: GuacamoleClient) =>
a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||

View File

@@ -33,7 +33,7 @@ const MessageSettings = () => {
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const messages = const messages: Message[] =
messageData?.filter((m: Message) => messageData?.filter((m: Message) =>
m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()), m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
) || []; ) || [];

View File

@@ -36,12 +36,14 @@ import SearchProviderSettingsItem from './SearchProviderSettingsItem';
const SearchProviderSettings = () => { const SearchProviderSettings = () => {
const allIcons: any = { ...fa6, ...vsc, ...si }; const allIcons: any = { ...fa6, ...vsc, ...si };
const defaultProvider = { const defaultProvider: Provider = {
id: undefined, id: undefined,
name: '', name: '',
url: '', url: '',
prefix: '', prefix: '',
default: 0, default: 0,
enabled: 1,
icon: null,
}; };
const form = useForm({ const form = useForm({
initialValues: defaultProvider, initialValues: defaultProvider,
@@ -57,7 +59,7 @@ const SearchProviderSettings = () => {
}); });
const [searchValue, setSearchValue] = useState<string | number>(''); const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const providers = const providers: Provider[] =
providerData providerData
.filter( .filter(
(a: Provider) => (a: Provider) =>