import { Card, Group, type OptionsFilter, Overlay, Select, Stack, Text, } from '@mantine/core'; import { useDisclosure, useElementSize } from '@mantine/hooks'; import { HelpModal, WeatherModal } from 'components'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useEffect, useState } from 'react'; import * as fa6 from 'react-icons/fa6'; import * as si from 'react-icons/si'; import * as vsc from 'react-icons/vsc'; import { selectClasses } from 'styles'; import type { Application, Bookmark, CurrentWeather, ExtendedForecast, GuacamoleClient, PersonalData, Provider, SearchHistoryItem, } from 'types'; import RecommendedApplications from './components/RecommendedApplications'; import RecommendedBookmarks from './components/RecommendedBookmarks'; import RecommendedClients from './components/RecommendedClients'; import SearchSelectItem from './components/SearchSelectItem'; export default function Search({ current, extendedForecast, weatherModalOpened, closeWeatherModal, openWeatherModal, position, personal, }: { current: CurrentWeather; extendedForecast: ExtendedForecast[]; weatherModalOpened: boolean; closeWeatherModal: () => void; openWeatherModal: () => void; position: string; personal: PersonalData; }) { 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, providerLabel, setProviderLabel, providerURL, setProviderURL, providerPrefix, setProviderPrefix, providerIcon, setProviderIcon, searchValue, setSearchValue, defaultProviderURL, setDefaultProviderURL, defaultProviderLabel, setDefaultProviderLabel, defaultProviderPrefix, setDefaultProviderPrefix, defaultProviderIcon, setDefaultProviderIcon, } = useCommonHooks(); const [placeholderValue, setPlaceholderValue] = useState(''); const [value, setValue] = useState(0); const [historyMultiplier, setHistoryMultiplier] = useState(1); const Icon = allIcons[providerIcon]; const handleResetSearch = () => { setSearchValue(''); setListIndex(-1); setHistoryMultiplier(1); setProviderLabel(defaultProviderLabel); 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 validUrl = `${guacamoleHost}/#/client/${option.clientIdentifier}`; open(validUrl, '_self'); }; const handleKeyPress = (item: any) => { let option = ''; let subtext = ''; if (item.target.value.startsWith('/')) { option = item.target.value.substr(1, item.target.value.indexOf(' ') - 1) ?? item.target.value.substr(1); subtext = item.target.value.substr(2 + option.length); } else { subtext = item.target.value; } if (item.key === 'Enter') { if (providerLabel === 'Show help') { setSearchValue(''); closeDropdown(); openProviderModal(); } else if (item.target.value.startsWith('/')) { mutateAsync({ method: 'POST', endpoint: searchEndpoint, body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext }, }).then(() => { open(`${providerURL}${subtext}`, '_self'); }); } 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), )?.url; const validUrl = url?.startsWith('http://') || url?.startsWith('https://') ? url : `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), ); open(`${guacamoleHost}/#/client/${client?.clientIdentifier}`, '_self'); } else if (urlPattern.test(item.target.value)) { if (/^(https?:\/\/).*/i.test(item.target.value)) globalThis.location = item.target.value; else open(`https://${item.target.value}`, '_self'); } else if (item.target.value.length > 0) { mutateAsync({ method: 'POST', endpoint: searchEndpoint, body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext }, }).then(() => { open(`${providerURL}${subtext}`, '_self'); }); } else { open(`${providerURL}${subtext}`, '_self'); } } }; const handleKeyUp = (item: any) => { if (item.code === 'Escape') { setListIndex(-1); handleResetSearch(); setPlaceholderValue(''); if (dropdownOpened) { closeDropdown(); } } if (item.code === 'ArrowDown' && !dropdownOpened) { setListIndex(0); openDropdown(); setPlaceholderValue( searchHistory.length > 0 ? `${searchHistory[listIndex].prefix} ${searchHistory[listIndex].searchTerm}` : '', ); } if (!providerLabel.startsWith('Invalid')) { handleKeyPress(item); } if (dropdownOpened) { if (searchValue === '') { if (item.code === 'ArrowUp') { if (listIndex === 0) { setListIndex(9); } else { setListIndex(listIndex - 1); } } else if (item.code === 'ArrowDown') { if (listIndex === 9) { setListIndex(0); } else { setListIndex(listIndex + 1); } } else if (item.code === 'ArrowRight' || item.code === 'Tab') { setSearchValue( searchHistory.length > 0 ? `${searchHistory[listIndex].prefix} ${searchHistory[listIndex].searchTerm}` : '', ); setValue(searchHistory[listIndex].id ?? 0); } } else if (searchValue !== '') { if (item.code === 'ArrowUp') { if (listIndex === 0) { setListIndex(filteredHistory.length - 1); } else if (listIndex >= filteredHistory.length) { setListIndex(filteredHistory.length - 1); } else { setListIndex(listIndex - 1); } } else if (item.code === 'ArrowDown') { if (listIndex >= filteredHistory.length - 1) { setListIndex(0); } else if (value === 0) { setListIndex(0); setValue(-1); } else { setListIndex(listIndex + 1); } } else if (item.code === 'ArrowRight' || item.code === 'Tab') { setSearchValue( `${filteredHistory[listIndex].prefix} ${filteredHistory[listIndex].searchTerm}`, ); setValue(filteredHistory[listIndex].id ?? 0); } } } }; const onInput = (item: any) => { openDropdown(); setSearchValue(item.currentTarget.value); if ( item.target.value === '/?' || item.target.value === '!help' || item.target.value === '/help' || item.target.value === '!h' || item.target.value === '/h' ) { setProviderLabel('Show help'); } else if (item.target.value.startsWith('/g')) { setProviderLabel('Google Search'); setProviderURL('https://www.google.com/search?q='); setProviderPrefix('/g'); setProviderIcon('FaGoogle'); } else if (item.target.value.startsWith('/')) { const provider = providers?.find( (p: Provider) => p.prefix === item.target.value.split(' ')[0], ); setProviderLabel(provider?.name ?? 'Invalid Search'); setProviderURL(provider?.url ?? ''); setProviderPrefix(provider?.prefix ?? ''); setProviderIcon(provider?.icon ?? ''); } else if (item.target.value.startsWith('!')) { if (item.target.value === '!w' || item.target.value === '!weather') { setProviderLabel('Show weather'); } else if ( applications?.find( (app: Application) => app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1), ) ) { setProviderLabel(`Launch ${item.target.value.substr(1)}`); } else { setProviderLabel('Invalid function'); } } else if (item.target.value.startsWith('>') && personal.enableGuacamole === 1) { if ( guacamoleClients?.find( (client: GuacamoleClient) => client.clientName.toLowerCase() === item.target.value.toLowerCase().substr(1), ) ) { setProviderLabel(`Connect to ${item.target.value.substr(1)}`); } else { setProviderLabel('Invalid connection'); } } else { setProviderLabel(defaultProviderLabel); setProviderURL(defaultProviderURL); setProviderPrefix(defaultProviderPrefix); setProviderIcon(defaultProviderIcon); } }; 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, }; }); } 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, }; }); } const historyOptions = options .filter((option: any) => { return splittedSearch.every((searchWord) => option.value .toLowerCase() .trim() .split(' ') .some((word: string) => word.includes(searchWord)), ); }) .slice(0, 10 * historyMultiplier); if (historyOptions.length >= 10 * historyMultiplier) { return [ ...historyOptions, { label: 'Load more history...', value: 'load', id: 0, dateTimeStamp: '', prefix: '', provider: '', searchTerm: '', }, ]; } 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('!') ? ( { e.preventDefault(); e.stopPropagation(); handleAppLaunchItem(item); }} > Launch {Icon && ( )} {item.option.name} ) : (searchValue as string).startsWith('>') && personal.enableGuacamole === 1 ? ( { e.preventDefault(); e.stopPropagation(); handleGuacamoleClientLaunchItem(item); }} > Connect to {item.option.name || item.option.clientName || 'Invalid connection'} ) : item.option.value === 'load' ? ( { e.preventDefault(); e.stopPropagation(); setHistoryMultiplier((prev: number) => prev + 1); }} > {item.option.label} ) : ( ); }; 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)); } } }, [historyData, historyDataIsFetched]); useEffect(() => { if (dropdownOpened && listIndex >= 0) { setPlaceholderValue( searchHistory.length > 0 ? `${searchHistory[listIndex].prefix} ${searchHistory[listIndex].searchTerm}` : '', ); } }, [listIndex, dropdownOpened]); useEffect(() => { const splittedSearch = (searchValue as string).toLowerCase().trim().split(' '); const filtered = searchHistory.filter((item: SearchHistoryItem) => { const option = `${item.prefix} ${item.searchTerm}`; const words = option.toLowerCase().trim().split(' '); return splittedSearch.every((searchWord) => words.some((word: string) => word.includes(searchWord)), ); }); setFilteredHistory(filtered); }, [searchValue]); useEffect(() => { if (defaultProviderData && defaultProviderDataIsFetched) { setDefaultProviderURL( defaultProviderData[0]?.url ?? 'https://www.google.com/search?q=', ); setDefaultProviderPrefix(defaultProviderData[0]?.prefix ?? '/g'); setDefaultProviderLabel(defaultProviderData[0]?.name ?? 'Google Search'); setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle'); } }, [defaultProviderData, defaultProviderDataIsFetched]); return ( <> {dropdownOpened && }