This commit is contained in:
@@ -5,7 +5,7 @@ import type { SearchHistoryItem } from 'types';
|
||||
|
||||
const columnHelper = createColumnHelper<SearchHistoryItem>();
|
||||
export const SearchHistoryColumns = (
|
||||
setSelectedItem: React.Dispatch<React.SetStateAction<any>>,
|
||||
setSelectedItem: React.Dispatch<React.SetStateAction<SearchHistoryItem | undefined>>,
|
||||
deleteItemOpen: () => void,
|
||||
filtersEnabled: boolean,
|
||||
setFiltersEnabled: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
|
||||
@@ -6,14 +6,14 @@ import HelpSection from './HelpSection';
|
||||
|
||||
export function HelpModal({
|
||||
handleResetSearch,
|
||||
providerModalOpened,
|
||||
modalOpened,
|
||||
closeProviderModal,
|
||||
applicationData,
|
||||
providerData,
|
||||
clientData,
|
||||
}: {
|
||||
handleResetSearch: () => void;
|
||||
providerModalOpened: boolean;
|
||||
modalOpened: boolean;
|
||||
closeProviderModal: () => void;
|
||||
applicationData: Application[];
|
||||
providerData: Provider[];
|
||||
@@ -25,7 +25,7 @@ export function HelpModal({
|
||||
size={'60%'}
|
||||
className={modalClasses.helpModal}
|
||||
classNames={modalClasses}
|
||||
opened={providerModalOpened}
|
||||
opened={modalOpened}
|
||||
fullScreen={isMobile}
|
||||
centered
|
||||
onClose={() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { AddItemModal } from './AddItemModal';
|
||||
export { AppItem } from './Applications/AppItem';
|
||||
export { SearchHistoryColumns } from './Columns/SearchHistoryColumns';
|
||||
export { DataTable } from './DataTable/DataTable';
|
||||
export { DebouncedInput } from './DebouncedInput';
|
||||
export { DeleteItemModal } from './DeleteItemModal';
|
||||
|
||||
@@ -10,12 +10,14 @@ import type {
|
||||
Message,
|
||||
PersonalData,
|
||||
} from 'types';
|
||||
import Applications from './components/Applications/Applications';
|
||||
import Bookmarks from './components/Bookmarks/Bookmarks';
|
||||
import Header from './components/Header/Header';
|
||||
import LargeHeader from './components/Header/LargeHeader';
|
||||
import Search from './components/Search/Search';
|
||||
import SettingsButton from './components/SettingsButton/SettingsButton';
|
||||
import {
|
||||
Applications,
|
||||
Bookmarks,
|
||||
Header,
|
||||
LargeHeader,
|
||||
Search,
|
||||
SettingsButton,
|
||||
} from './components';
|
||||
|
||||
export function Homepage() {
|
||||
const {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCommonHooks, useQueryAPI } from 'hooks';
|
||||
import { textClasses } from 'styles';
|
||||
import type { Application } from 'types';
|
||||
|
||||
function Applications({ contentType }: { contentType: string[] }) {
|
||||
export function Applications({ contentType }: { contentType: string[] }) {
|
||||
const { data: applicationData } = useQueryAPI({
|
||||
endpoint: 'api/enabledApplications',
|
||||
});
|
||||
@@ -76,5 +76,3 @@ function Applications({ contentType }: { contentType: string[] }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Applications;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { textClasses } from 'styles';
|
||||
import type { Bookmark, BookmarkGroup } from 'types';
|
||||
import BookmarkGroups from './BookmarkGroups';
|
||||
|
||||
function Bookmarks({ contentType }: { contentType: string[] }) {
|
||||
export function Bookmarks({ contentType }: { contentType: string[] }) {
|
||||
const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/bookmarks',
|
||||
});
|
||||
@@ -95,5 +95,3 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Bookmarks;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import { textClasses } from 'styles';
|
||||
import type { CurrentWeather } from 'types';
|
||||
|
||||
function Header({
|
||||
export function Header({
|
||||
greet,
|
||||
date,
|
||||
current,
|
||||
@@ -140,5 +140,3 @@ function Header({
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
|
||||
@@ -3,7 +3,13 @@ import { useCommonHooks } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { textClasses } from 'styles';
|
||||
|
||||
function LargeHeader({ greet, date }: { greet: string | undefined; date: string }) {
|
||||
export function LargeHeader({
|
||||
greet,
|
||||
date,
|
||||
}: {
|
||||
greet: string | undefined;
|
||||
date: string;
|
||||
}) {
|
||||
const [time, setTime] = useState(new Date());
|
||||
const {
|
||||
isMobile,
|
||||
@@ -79,5 +85,3 @@ function LargeHeader({ greet, date }: { greet: string | undefined; date: string
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LargeHeader;
|
||||
|
||||
@@ -30,7 +30,7 @@ import RecommendedBookmarks from './components/RecommendedBookmarks';
|
||||
import RecommendedClients from './components/RecommendedClients';
|
||||
import RenderItem from './components/RenderItem';
|
||||
|
||||
export default function Search({
|
||||
export function Search({
|
||||
current,
|
||||
extendedForecast,
|
||||
weatherModalOpened,
|
||||
@@ -634,21 +634,25 @@ export default function Search({
|
||||
</Card>
|
||||
)}
|
||||
</Stack>
|
||||
<HelpModal
|
||||
handleResetSearch={handleResetSearch}
|
||||
applicationData={applications}
|
||||
providerData={providers}
|
||||
clientData={guacamoleClients}
|
||||
providerModalOpened={providerModalOpened}
|
||||
closeProviderModal={closeProviderModal}
|
||||
/>
|
||||
<WeatherModal
|
||||
current={current}
|
||||
extendedForecast={extendedForecast}
|
||||
weatherModalOpened={weatherModalOpened}
|
||||
handleResetSearch={handleResetSearch}
|
||||
closeWeatherModal={closeWeatherModal}
|
||||
/>
|
||||
{providerModalOpened && (
|
||||
<HelpModal
|
||||
handleResetSearch={handleResetSearch}
|
||||
applicationData={applications}
|
||||
providerData={providers}
|
||||
clientData={guacamoleClients}
|
||||
modalOpened={providerModalOpened}
|
||||
closeProviderModal={closeProviderModal}
|
||||
/>
|
||||
)}
|
||||
{weatherModalOpened && (
|
||||
<WeatherModal
|
||||
current={current}
|
||||
extendedForecast={extendedForecast}
|
||||
weatherModalOpened={weatherModalOpened}
|
||||
handleResetSearch={handleResetSearch}
|
||||
closeWeatherModal={closeWeatherModal}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import SettingsCard from './SettingsCard';
|
||||
import ThemingCard from './ThemingCard';
|
||||
import WakeOnLan from './WakeOnLanCard';
|
||||
|
||||
function SettingsButton({ openProviderModal }: { openProviderModal: () => void }) {
|
||||
export function SettingsButton({ openProviderModal }: { openProviderModal: () => void }) {
|
||||
const { data: appCategoryData } = useQueryAPI({
|
||||
endpoint: 'api/appCategories',
|
||||
});
|
||||
@@ -171,5 +171,3 @@ function SettingsButton({ openProviderModal }: { openProviderModal: () => void }
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsButton;
|
||||
|
||||
6
src/pages/homepage/components/index.ts
Normal file
6
src/pages/homepage/components/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { Applications } from './Applications/Applications';
|
||||
export { Bookmarks } from './Bookmarks/Bookmarks';
|
||||
export { Header } from './Header/Header';
|
||||
export { LargeHeader } from './Header/LargeHeader';
|
||||
export { Search } from './Search/Search';
|
||||
export { SettingsButton } from './SettingsButton/SettingsButton';
|
||||
@@ -13,16 +13,18 @@ import {
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { actionIconClasses, scrollAreaClasses, tabClasses, textClasses } from 'styles';
|
||||
import type { PersonalData } from 'types';
|
||||
import Applications from './components/Applications/Applications';
|
||||
import Bookmarks from './components/Bookmarks/Bookmarks';
|
||||
import GuacamoleClients from './components/GuacamoleClients/GuacamoleClients';
|
||||
import Messages from './components/Messages/Messages';
|
||||
import Personalization from './components/Personalization/Personalization';
|
||||
import SearchHistory from './components/Search/SearchHistory';
|
||||
import SearchProviders from './components/Search/SearchProviders';
|
||||
import { ThemePlaylists } from './components/Theming/ThemePlaylists';
|
||||
import { Themes } from './components/Theming/Themes';
|
||||
import WolClients from './components/WoL/WolClients';
|
||||
import {
|
||||
Applications,
|
||||
Bookmarks,
|
||||
GuacamoleClients,
|
||||
Messages,
|
||||
Personalization,
|
||||
SearchHistory,
|
||||
SearchProviders,
|
||||
ThemePlaylists,
|
||||
Themes,
|
||||
WolClients,
|
||||
} from './components';
|
||||
|
||||
export function Settings() {
|
||||
const endpoint = 'api/personalData';
|
||||
|
||||
@@ -26,7 +26,7 @@ import { buttonClasses, popoverClasses, selectClasses, textInputClasses } from '
|
||||
import type { Application } from 'types';
|
||||
import DebouncedApplication from './DebouncedApplication';
|
||||
|
||||
const ApplicationSettings = () => {
|
||||
export function Applications() {
|
||||
const allIcons: any = { ...fa6, ...vsc, ...si };
|
||||
const defaultApplication: Application = {
|
||||
id: null,
|
||||
@@ -141,165 +141,167 @@ const ApplicationSettings = () => {
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new application'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: appsEndpoint,
|
||||
body: {
|
||||
...values,
|
||||
index: applications.length,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='eg. Nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('baseUrl')}
|
||||
{...form.getInputProps('baseUrl')}
|
||||
label={'Base URL'}
|
||||
placeholder='eg. /nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={isMobile ? '94%' : '92%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'URL'}
|
||||
placeholder='eg. https://domain.example.com/nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'100%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center' pos={'relative'}>
|
||||
<Select
|
||||
key={form.key('icon')}
|
||||
{...form.getInputProps('icon')}
|
||||
label={'Icon'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'45%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Search for an icon...'
|
||||
data={Object.keys(allIcons).map((icon) => {
|
||||
return { value: icon, label: icon };
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const Icon = allIcons[item.option.value];
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
<Icon />
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('filter')}
|
||||
{...form.getInputProps('filter')}
|
||||
label={'Filter'}
|
||||
placeholder='eg. Storage'
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={defaultApplication.filterName}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new application'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: appsEndpoint,
|
||||
body: {
|
||||
...values,
|
||||
index: applications.length,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='eg. Nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('baseUrl')}
|
||||
{...form.getInputProps('baseUrl')}
|
||||
label={'Base URL'}
|
||||
placeholder='eg. /nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={isMobile ? '94%' : '92%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'URL'}
|
||||
placeholder='eg. https://domain.example.com/nextcloud'
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'100%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center' pos={'relative'}>
|
||||
<Select
|
||||
key={form.key('icon')}
|
||||
{...form.getInputProps('icon')}
|
||||
label={'Icon'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'45%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Search for an icon...'
|
||||
data={Object.keys(allIcons).map((icon) => {
|
||||
return { value: icon, label: icon };
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const Icon = allIcons[item.option.value];
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
<Icon />
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('filter')}
|
||||
{...form.getInputProps('filter')}
|
||||
label={'Filter'}
|
||||
placeholder='eg. Storage'
|
||||
className={textInputClasses.settingsItem}
|
||||
pt={'1rem'}
|
||||
w={'45%'}
|
||||
classNames={textInputClasses}
|
||||
defaultValue={defaultApplication.filterName}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Stack
|
||||
mt={isMobile ? '10rem' : '6rem'}
|
||||
@@ -410,5 +412,4 @@ const ApplicationSettings = () => {
|
||||
</DragDropContext>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default ApplicationSettings;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function DebouncedApplication({
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
const [app, setApp] = useState<Application>(application);
|
||||
const [update, setUpdate] = useState<any>();
|
||||
const [update, setUpdate] = useState<Application>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
@@ -53,22 +53,24 @@ function DebouncedApplication({
|
||||
className={cardClasses.settingsItem}
|
||||
classNames={cardClasses}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(app, 'api/applications')}
|
||||
title='Delete Application'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this application?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${app.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(app, 'api/applications')}
|
||||
title='Delete Application'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this application?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${app.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
@@ -192,7 +194,7 @@ function DebouncedApplication({
|
||||
onChange={(value: string | null) => {
|
||||
setUpdate({
|
||||
...app,
|
||||
icon: value,
|
||||
icon: value as Application['icon'],
|
||||
});
|
||||
}}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { buttonClasses, textInputClasses } from 'styles';
|
||||
import type { Bookmark, BookmarkGroup } from 'types';
|
||||
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
|
||||
|
||||
const BookmarkSettings = () => {
|
||||
export function Bookmarks() {
|
||||
const defaultGroup = {
|
||||
id: null,
|
||||
categoryName: '',
|
||||
@@ -68,7 +68,7 @@ const BookmarkSettings = () => {
|
||||
),
|
||||
),
|
||||
];
|
||||
const foundGroups: BookmarkGroup[] = groupData.filter((a: BookmarkGroup) =>
|
||||
const foundGroups: BookmarkGroup[] = groupData?.filter((a: BookmarkGroup) =>
|
||||
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
|
||||
);
|
||||
const handleClose = () => {
|
||||
@@ -121,79 +121,81 @@ const BookmarkSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Bookmark Group'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: categoriesEndpoint,
|
||||
body: {
|
||||
...values,
|
||||
categoryIndex: groups.length,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('categoryName')}
|
||||
{...form.getInputProps('categoryName')}
|
||||
label={'Category Name'}
|
||||
placeholder='eg. Shopping'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('filterName')}
|
||||
{...form.getInputProps('filterName')}
|
||||
label={'Filter Name'}
|
||||
placeholder='eg. Personal'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Bookmark Group'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: categoriesEndpoint,
|
||||
body: {
|
||||
...values,
|
||||
categoryIndex: groups.length,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('categoryName')}
|
||||
{...form.getInputProps('categoryName')}
|
||||
label={'Category Name'}
|
||||
placeholder='eg. Shopping'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('filterName')}
|
||||
{...form.getInputProps('filterName')}
|
||||
label={'Filter Name'}
|
||||
placeholder='eg. Personal'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingsHeader
|
||||
setDragging={groups.length > 0 ? setDragging : undefined}
|
||||
openModal={openModal}
|
||||
@@ -337,5 +339,4 @@ const BookmarkSettings = () => {
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default BookmarkSettings;
|
||||
}
|
||||
|
||||
@@ -25,20 +25,20 @@ function DebouncedBookmarkItem({
|
||||
(a: BookmarkGroup, b: BookmarkGroup) =>
|
||||
a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(),
|
||||
) || [];
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [update, setUpdate] = useState<Bookmark>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
closeModal();
|
||||
};
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
if (debounced && Object.keys(debounced).length) {
|
||||
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced });
|
||||
}
|
||||
}, [debounced]);
|
||||
|
||||
useEffect(() => {
|
||||
if (update !== bookmark && Object.keys(update).length) {
|
||||
if (update && update !== bookmark && Object.keys(update).length) {
|
||||
setBookmark(update);
|
||||
}
|
||||
}, [update]);
|
||||
@@ -49,22 +49,24 @@ function DebouncedBookmarkItem({
|
||||
className={cardClasses.settingsItem}
|
||||
classNames={cardClasses}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(bookmark, 'api/bookmarks')}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${bookmark.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
title='Delete Bookmark'
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(bookmark, 'api/bookmarks')}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${bookmark.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
title='Delete Bookmark'
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
@@ -142,7 +144,7 @@ function DebouncedBookmarkItem({
|
||||
onChange={(value: string | null) => {
|
||||
setUpdate({
|
||||
...bookmark,
|
||||
category: value,
|
||||
category: value as BookmarkGroup['categoryName'],
|
||||
});
|
||||
}}
|
||||
searchable
|
||||
|
||||
@@ -48,7 +48,7 @@ function DebouncedGroupSettingsItem({
|
||||
const [popoverOpened, setPopoverOpened] = useState(false);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [group, setGroup] = useState<BookmarkGroup>(bookmarkGroup);
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [update, setUpdate] = useState<BookmarkGroup>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const [newBookmark, setNewBookmark] = useState<Bookmark>({
|
||||
id: undefined,
|
||||
@@ -89,18 +89,18 @@ function DebouncedGroupSettingsItem({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
if (debounced && Object.keys(debounced).length) {
|
||||
mutateAsync({
|
||||
method: 'PATCH',
|
||||
endpoint: 'api/bookmarkCategories',
|
||||
body: debounced,
|
||||
});
|
||||
setUpdate({});
|
||||
setUpdate(undefined);
|
||||
}
|
||||
}, [debounced]);
|
||||
|
||||
useEffect(() => {
|
||||
if (update !== group && Object.keys(update).length) {
|
||||
if (update && update !== group && Object.keys(update).length) {
|
||||
setGroup(update);
|
||||
}
|
||||
}, [update]);
|
||||
@@ -113,28 +113,30 @@ function DebouncedGroupSettingsItem({
|
||||
|
||||
return (
|
||||
<Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
handleDelete={() => {
|
||||
bookmarks.length > 0 && handleDelete(bookmarks, 'api/bookmarks');
|
||||
handleDelete(group, 'api/bookmarkCategories');
|
||||
}}
|
||||
title='Delete message'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'175px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark group?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${group.categoryName}"`}</Text>
|
||||
<Text style={{ fontWeight: 'bold', textTransform: 'uppercase' }}>
|
||||
This will also delete all bookmarks in this group!
|
||||
</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
handleDelete={() => {
|
||||
bookmarks.length > 0 && handleDelete(bookmarks, 'api/bookmarks');
|
||||
handleDelete(group, 'api/bookmarkCategories');
|
||||
}}
|
||||
title='Delete message'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'175px'} align='center'>
|
||||
<Text>Are you sure you want to delete this bookmark group?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${group.categoryName}"`}</Text>
|
||||
<Text style={{ fontWeight: 'bold', textTransform: 'uppercase' }}>
|
||||
This will also delete all bookmarks in this group!
|
||||
</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Card.Section
|
||||
p={'1rem'}
|
||||
pt={0}
|
||||
|
||||
@@ -38,7 +38,7 @@ function DebouncedGuacamoleClient({
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const types: string[] = ['SSH', 'RDP', 'VNC'];
|
||||
const [client, setClient] = useState<GuacamoleClient>(guacamoleClient);
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [update, setUpdate] = useState<GuacamoleClient>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
@@ -46,13 +46,13 @@ function DebouncedGuacamoleClient({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
if (debounced && Object.keys(debounced).length) {
|
||||
mutateAsync({ method: 'PATCH', endpoint: 'api/guacamoleClients', body: debounced });
|
||||
}
|
||||
}, [debounced]);
|
||||
|
||||
useEffect(() => {
|
||||
if (update !== client && Object.keys(update).length) {
|
||||
if (update && update !== client && Object.keys(update).length) {
|
||||
setClient(update);
|
||||
}
|
||||
}, [update]);
|
||||
@@ -62,22 +62,24 @@ function DebouncedGuacamoleClient({
|
||||
className={cardClasses.settingsItem}
|
||||
classNames={cardClasses}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(client, 'api/guacamoleClients')}
|
||||
title={'Delete Guacamole Client'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this WoL Client?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${client.clientName}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(client, 'api/guacamoleClients')}
|
||||
title={'Delete Guacamole Client'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this WoL Client?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${client.clientName}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
@@ -177,7 +179,7 @@ function DebouncedGuacamoleClient({
|
||||
onChange={(value: string | null) => {
|
||||
setUpdate({
|
||||
...guacamoleClient,
|
||||
type: value,
|
||||
type: value as GuacamoleClient['type'],
|
||||
});
|
||||
}}
|
||||
searchable
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Card, Stack, Text } from '@mantine/core';
|
||||
import { FaDesktop, FaTerminal } from 'react-icons/fa';
|
||||
import { FaComputer } from 'react-icons/fa6';
|
||||
import { FaComputer, FaDesktop, FaTerminal } from 'react-icons/fa6';
|
||||
import { cardClasses } from 'styles';
|
||||
import type { GuacamoleClient } from 'types';
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@ import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaTerminal } from 'react-icons/fa';
|
||||
import { FaComputer, FaDesktop } from 'react-icons/fa6';
|
||||
import { FaComputer, FaDesktop, FaTerminal } from 'react-icons/fa6';
|
||||
import { buttonClasses, popoverClasses, selectClasses, textInputClasses } from 'styles';
|
||||
import type { GuacamoleClient } from 'types';
|
||||
import DebouncedGuacamoleClient from './DebouncedGuacamoleClient';
|
||||
import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem';
|
||||
|
||||
const GuacamoleClients = () => {
|
||||
export function GuacamoleClients() {
|
||||
const defaultGuacamoleClient: GuacamoleClient = {
|
||||
id: undefined,
|
||||
clientName: '',
|
||||
@@ -73,127 +72,129 @@ const GuacamoleClients = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Guacamole Client'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: { ...values, enabled: 1 },
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('clientName')}
|
||||
{...form.getInputProps('clientName')}
|
||||
label={'Client Name'}
|
||||
placeholder='eg. Workstation'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('clientIdentifier')}
|
||||
{...form.getInputProps('clientIdentifier')}
|
||||
label={'clientIdentifier'}
|
||||
placeholder='eg. MzIAYwBteXNxbA'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center' pos={'relative'}>
|
||||
<Select
|
||||
key={form.key('type')}
|
||||
{...form.getInputProps('type')}
|
||||
label={'Type'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'45%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Client Type'
|
||||
data={types.map((type) => {
|
||||
return {
|
||||
label: type,
|
||||
value: type,
|
||||
};
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
{item.option.value === 'SSH' && <FaTerminal />}
|
||||
{item.option.value === 'RDP' && <FaComputer />}
|
||||
{item.option.value === 'VNC' && <FaDesktop />}
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
withAsterisk
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Guacamole Client'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: { ...values, enabled: 1 },
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('clientName')}
|
||||
{...form.getInputProps('clientName')}
|
||||
label={'Client Name'}
|
||||
placeholder='eg. Workstation'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('clientIdentifier')}
|
||||
{...form.getInputProps('clientIdentifier')}
|
||||
label={'clientIdentifier'}
|
||||
placeholder='eg. MzIAYwBteXNxbA'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={'100%'} justify='center' pos={'relative'}>
|
||||
<Select
|
||||
key={form.key('type')}
|
||||
{...form.getInputProps('type')}
|
||||
label={'Type'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'45%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Client Type'
|
||||
data={types.map((type) => {
|
||||
return {
|
||||
label: type,
|
||||
value: type,
|
||||
};
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
{item.option.value === 'SSH' && <FaTerminal />}
|
||||
{item.option.value === 'RDP' && <FaComputer />}
|
||||
{item.option.value === 'VNC' && <FaDesktop />}
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
withAsterisk
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
@@ -267,5 +268,4 @@ const GuacamoleClients = () => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default GuacamoleClients;
|
||||
}
|
||||
|
||||
@@ -23,22 +23,24 @@ function DebouncedMessage({
|
||||
w={'100%'}
|
||||
style={{ alignItems: 'center' }}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
handleDelete={() => handleDelete(message, 'api/messages')}
|
||||
title='Delete message'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this message?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${message.message}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
handleDelete={() => handleDelete(message, 'api/messages')}
|
||||
title='Delete message'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this message?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${message.message}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -16,7 +16,7 @@ import { buttonClasses, textInputClasses } from 'styles';
|
||||
import type { Message } from 'types';
|
||||
import DebouncedMessage from './DebouncedMessage';
|
||||
|
||||
const MessageSettings = () => {
|
||||
export function Messages() {
|
||||
const endpoint = 'api/messages';
|
||||
const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
@@ -83,60 +83,62 @@ const MessageSettings = () => {
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new message'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: { message: values.message },
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<TextInput
|
||||
autoFocus={!isMobile}
|
||||
key={form.key('message')}
|
||||
{...form.getInputProps('message')}
|
||||
label={'Message'}
|
||||
placeholder='eg. Hello, World!'
|
||||
w={'45%'}
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new message'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: { message: values.message },
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<TextInput
|
||||
autoFocus={!isMobile}
|
||||
key={form.key('message')}
|
||||
{...form.getInputProps('message')}
|
||||
label={'Message'}
|
||||
placeholder='eg. Hello, World!'
|
||||
w={'45%'}
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Stack
|
||||
mt={isMobile ? '10rem' : '6rem'}
|
||||
mb={isMobile ? '6rem' : undefined}
|
||||
@@ -173,5 +175,4 @@ const MessageSettings = () => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default MessageSettings;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import FontPicker from 'react-fontpicker-ts';
|
||||
import { cardClasses, switchClasses, textClasses, textInputClasses } from 'styles';
|
||||
import type { PersonalData } from 'types';
|
||||
|
||||
const PersonalizationSettings = () => {
|
||||
export function Personalization() {
|
||||
const endpoint = 'api/personalData';
|
||||
const { data: personalData, isFetching: personalDataIsFetching } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
@@ -27,14 +27,14 @@ const PersonalizationSettings = () => {
|
||||
personal?.enableGuacamole === 1,
|
||||
);
|
||||
const [defaultFont, setDefaultFont] = useState<string>(personal?.defaultFont);
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [update, setUpdate] = useState<PersonalData>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const { isMobile, theme } = useCommonHooks();
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
if (debounced && Object.keys(debounced).length) {
|
||||
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
|
||||
setUpdate({});
|
||||
setUpdate(undefined);
|
||||
}
|
||||
}, [debounced]);
|
||||
|
||||
@@ -322,5 +322,4 @@ const PersonalizationSettings = () => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default PersonalizationSettings;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function DebouncedSearchProvider({
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
const [provider, setProvider] = useState<Provider>(searchProvider);
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [update, setUpdate] = useState<Provider>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -39,13 +39,13 @@ function DebouncedSearchProvider({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
if (debounced && Object.keys(debounced).length) {
|
||||
mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced });
|
||||
}
|
||||
}, [debounced]);
|
||||
|
||||
useEffect(() => {
|
||||
if (update !== provider && Object.keys(update).length) {
|
||||
if (update && update !== provider && Object.keys(update).length) {
|
||||
setProvider(update);
|
||||
}
|
||||
}, [update]);
|
||||
@@ -56,22 +56,24 @@ function DebouncedSearchProvider({
|
||||
className={cardClasses.settingsItem}
|
||||
classNames={cardClasses}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(provider, 'api/providers')}
|
||||
title={'Delete Search Provider'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Search Provider?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${provider.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(provider, 'api/providers')}
|
||||
title={'Delete Search Provider'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Search Provider?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${provider.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Group, Stack, Text } from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { DataTable, DeleteItemModal, SettingsHeader } from 'components';
|
||||
import { SearchHistoryColumns } from 'components/Columns/SearchHistoryColumns';
|
||||
import {
|
||||
DataTable,
|
||||
DeleteItemModal,
|
||||
SearchHistoryColumns,
|
||||
SettingsHeader,
|
||||
} from 'components';
|
||||
import { useCommonHooks, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { textClasses } from 'styles';
|
||||
import type { SearchHistoryItem } from 'types';
|
||||
|
||||
const SearchHistory = () => {
|
||||
export function SearchHistory() {
|
||||
const searchEndpoint = 'api/searchHistory';
|
||||
const { data: historyData, isFetching: historyDataIsFetching } = useQueryAPI({
|
||||
endpoint: searchEndpoint,
|
||||
@@ -41,28 +45,30 @@ const SearchHistory = () => {
|
||||
|
||||
return (
|
||||
<Stack w={'100%'}>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(searchHistoryItem, 'api/searchHistory')}
|
||||
title={'Delete Search History Item'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Search History Item?</Text>
|
||||
<Group>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`${searchHistoryItem?.provider}`}</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${searchHistoryItem?.searchTerm}"`}</Text>
|
||||
</Group>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(searchHistoryItem, 'api/searchHistory')}
|
||||
title={'Delete Search History Item'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Search History Item?</Text>
|
||||
<Group>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`${searchHistoryItem?.provider}`}</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${searchHistoryItem?.searchTerm}"`}</Text>
|
||||
</Group>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={undefined}
|
||||
@@ -107,6 +113,4 @@ const SearchHistory = () => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchHistory;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import type { Provider } from 'types';
|
||||
import DebouncedSearchProvider from './DebouncedSearchProvider';
|
||||
import SearchProviderSettingsItem from './SearchProviderSettingsItem';
|
||||
|
||||
const SearchProviderSettings = () => {
|
||||
export function SearchProviders() {
|
||||
const allIcons: any = { ...fa6, ...vsc, ...si };
|
||||
const defaultProvider: Provider = {
|
||||
id: undefined,
|
||||
@@ -88,30 +88,120 @@ const SearchProviderSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Search Provider'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: values,
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Stack w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center' pos={'relative'}>
|
||||
{modalOpened && (
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new Search Provider'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: values,
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Stack w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center' pos={'relative'}>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='eg. Google'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: {
|
||||
boxShadow: '2px 2px var(--mantine-color-border-0)',
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
w={'30%'}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('prefix')}
|
||||
{...form.getInputProps('prefix')}
|
||||
label={'Prefix'}
|
||||
placeholder='eg. /g'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: {
|
||||
boxShadow: '2px 2px var(--mantine-color-border-0)',
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
w={'30%'}
|
||||
/>
|
||||
<Select
|
||||
key={form.key('icon')}
|
||||
{...form.getInputProps('icon')}
|
||||
label={'Icon'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'30%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Search for an icon...'
|
||||
data={Object.keys(allIcons).map((icon) => {
|
||||
return { value: icon, label: icon };
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: {
|
||||
boxShadow: '2px 2px var(--mantine-color-border-0)',
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderOption={(
|
||||
item: ComboboxLikeRenderOptionInput<ComboboxItem>,
|
||||
) => {
|
||||
const Icon = allIcons[item.option.value];
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
<Icon />
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
label={'Name'}
|
||||
placeholder='eg. Google'
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'Url'}
|
||||
placeholder='eg. https://www.google.com/search?q='
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'94%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
@@ -121,141 +211,61 @@ const SearchProviderSettings = () => {
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
w={'30%'}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('prefix')}
|
||||
{...form.getInputProps('prefix')}
|
||||
label={'Prefix'}
|
||||
placeholder='eg. /g'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
w={'30%'}
|
||||
/>
|
||||
<Select
|
||||
key={form.key('icon')}
|
||||
{...form.getInputProps('icon')}
|
||||
label={'Icon'}
|
||||
className={selectClasses.settingsItem}
|
||||
classNames={{
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
dropdown: selectClasses.dropdown,
|
||||
}}
|
||||
w={'30%'}
|
||||
mt={'.75rem'}
|
||||
limit={100}
|
||||
placeholder='Search for an icon...'
|
||||
data={Object.keys(allIcons).map((icon) => {
|
||||
return { value: icon, label: icon };
|
||||
})}
|
||||
comboboxProps={{
|
||||
zIndex: 3000,
|
||||
}}
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const Icon = allIcons[item.option.value];
|
||||
return (
|
||||
<Group>
|
||||
{item.option.value}
|
||||
<Icon />
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
maxDropdownHeight={175}
|
||||
onDropdownOpen={() => setIsOpen(true)}
|
||||
onDropdownClose={() => setIsOpen(false)}
|
||||
/>
|
||||
</Stack>
|
||||
<Group
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '1rem',
|
||||
top: '.5rem',
|
||||
width: 'fit-content',
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-background-0), transparent 50%)',
|
||||
padding: '.5rem',
|
||||
borderRadius: '10px',
|
||||
color: 'var(--mantine-color-search-0)',
|
||||
backdropFilter: 'brightness(0.5)',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
label={'Default'}
|
||||
refProp='rootRef'
|
||||
withArrow
|
||||
>
|
||||
<Switch
|
||||
key={form.key('default')}
|
||||
{...form.getInputProps('default')}
|
||||
className={switchClasses.enable}
|
||||
classNames={switchClasses}
|
||||
defaultChecked={false}
|
||||
thumbIcon={<FiPower />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<TextInput
|
||||
key={form.key('url')}
|
||||
{...form.getInputProps('url')}
|
||||
label={'Url'}
|
||||
placeholder='eg. https://www.google.com/search?q='
|
||||
className={textInputClasses.settingsItem}
|
||||
w={'94%'}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Group
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '1rem',
|
||||
top: '.5rem',
|
||||
width: 'fit-content',
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-background-0), transparent 50%)',
|
||||
padding: '.5rem',
|
||||
borderRadius: '10px',
|
||||
color: 'var(--mantine-color-search-0)',
|
||||
backdropFilter: 'brightness(0.5)',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
label={'Default'}
|
||||
refProp='rootRef'
|
||||
withArrow
|
||||
>
|
||||
<Switch
|
||||
key={form.key('default')}
|
||||
{...form.getInputProps('default')}
|
||||
className={switchClasses.enable}
|
||||
classNames={switchClasses}
|
||||
defaultChecked={false}
|
||||
thumbIcon={<FiPower />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
@@ -333,5 +343,4 @@ const SearchProviderSettings = () => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default SearchProviderSettings;
|
||||
}
|
||||
|
||||
@@ -67,16 +67,18 @@ export function ThemePlaylists() {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<ThemePlaylistModal
|
||||
opened={opened}
|
||||
handleClose={handleClose}
|
||||
selectedPlaylist={selectedPlaylist}
|
||||
defaultPlaylist={defaultPlaylist}
|
||||
setSelectedPlaylist={setSelectedPlaylist}
|
||||
handleSavePlaylist={handleSavePlaylist}
|
||||
allThemes={allThemeData}
|
||||
createPlaylist={mutateAsync}
|
||||
/>
|
||||
{opened && (
|
||||
<ThemePlaylistModal
|
||||
opened={opened}
|
||||
handleClose={handleClose}
|
||||
selectedPlaylist={selectedPlaylist}
|
||||
defaultPlaylist={defaultPlaylist}
|
||||
setSelectedPlaylist={setSelectedPlaylist}
|
||||
handleSavePlaylist={handleSavePlaylist}
|
||||
allThemes={allThemeData}
|
||||
createPlaylist={mutateAsync}
|
||||
/>
|
||||
)}
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={open}
|
||||
|
||||
@@ -42,22 +42,24 @@ export function ThemePlaylistCard({
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
handleDelete={() => handleDelete(cardPlaylist, 'api/themePlaylists')}
|
||||
title='Delete Theme Playlist'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Theme Playlist?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${cardPlaylist.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={closeModal}
|
||||
handleDelete={() => handleDelete(cardPlaylist, 'api/themePlaylists')}
|
||||
title='Delete Theme Playlist'
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this Theme Playlist?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${cardPlaylist.name}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -28,7 +28,6 @@ export function ThemePlaylistModal({
|
||||
}) {
|
||||
const { isMobile } = useCommonHooks();
|
||||
const [themeList, setThemeList] = useState<any>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPlaylist?.themeList) {
|
||||
setThemeList(JSON.parse(selectedPlaylist?.themeList));
|
||||
@@ -49,7 +48,7 @@ export function ThemePlaylistModal({
|
||||
centered
|
||||
onClose={() => {
|
||||
handleClose();
|
||||
setThemeList('[]');
|
||||
setThemeList([]);
|
||||
}}
|
||||
keepMounted={false}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ActionIcon, Group, isLightColor, Stack, Text } from '@mantine/core';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
import { useState } from 'react';
|
||||
import { FaTrashAlt } from 'react-icons/fa';
|
||||
import { FaPencil } from 'react-icons/fa6';
|
||||
import { FaPencil, FaTrash } from 'react-icons/fa6';
|
||||
import { FiEdit, FiPower, FiShuffle } from 'react-icons/fi';
|
||||
import { actionIconClasses } from 'styles';
|
||||
import type { Theme, ThemePlaylist } from 'types';
|
||||
@@ -238,7 +237,7 @@ export function ThemeManagementPill({
|
||||
onClick={openModal}
|
||||
disabled={cardTheme.name === theme.name}
|
||||
>
|
||||
<FaTrashAlt
|
||||
<FaTrash
|
||||
style={{ padding: '.25rem', filter: 'brightness(1.25)' }}
|
||||
color='Brown'
|
||||
/>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function NewThemeStarter({
|
||||
setSelectedTheme: (selectedTheme: Theme) => void;
|
||||
setDefaultTheme: (selectedTheme: Theme) => void;
|
||||
}) {
|
||||
const [pressed, setPressed] = useState<any>(false);
|
||||
const [pressed, setPressed] = useState<boolean>(false);
|
||||
const handlers = useLongPress(() => {
|
||||
setPressed(true);
|
||||
});
|
||||
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { useCommonHooks } from 'hooks';
|
||||
import { FaClipboard } from 'react-icons/fa';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import { FaClipboard, FaX } from 'react-icons/fa6';
|
||||
import { scrollAreaClasses, toolTipClasses } from 'styles';
|
||||
import { type Theme, ThemeProperties } from 'types';
|
||||
|
||||
@@ -104,7 +103,7 @@ export default function ThemeChanges({
|
||||
pos={'absolute'}
|
||||
right={0}
|
||||
>
|
||||
<FiX />
|
||||
<FaX />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Box, Stack, Text } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import { FiCheck } from 'react-icons/fi';
|
||||
import { boxClasses, textClasses } from 'styles';
|
||||
import type { Theme } from 'types';
|
||||
|
||||
export function ThemeExampleApp({ selectedTheme }: { selectedTheme: Theme }) {
|
||||
const [textDecoration, setTextDecoration] = useState<string>('none');
|
||||
|
||||
return (
|
||||
<Box
|
||||
component='a'
|
||||
onMouseOver={() => setTextDecoration('underline')}
|
||||
onMouseOut={() => setTextDecoration('none')}
|
||||
className={boxClasses.appItemBox}
|
||||
h={'64px'}
|
||||
>
|
||||
<Stack
|
||||
style={{
|
||||
marginRight: '1rem',
|
||||
marginLeft: '1rem',
|
||||
}}
|
||||
justify='center'
|
||||
>
|
||||
<FiCheck style={{ color: selectedTheme?.primaryColor }} />
|
||||
</Stack>
|
||||
<Stack
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
c={selectedTheme?.primaryColor}
|
||||
className={textClasses.appName}
|
||||
classNames={textClasses}
|
||||
>
|
||||
{'Example App'}
|
||||
</Text>
|
||||
<Text
|
||||
c={selectedTheme?.accentColor}
|
||||
className={textClasses.appUrl}
|
||||
classNames={textClasses}
|
||||
style={{
|
||||
textDecoration: textDecoration,
|
||||
}}
|
||||
>
|
||||
{'https://www.example.com'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -11,9 +11,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { useCommonHooks } from 'hooks';
|
||||
import FontPicker from 'react-fontpicker-ts';
|
||||
import { FaFont, FaGoogle } from 'react-icons/fa';
|
||||
import { FaShuffle } from 'react-icons/fa6';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import { FaFont, FaGoogle, FaShuffle, FaX } from 'react-icons/fa6';
|
||||
import { textClasses, toolTipClasses } from 'styles';
|
||||
import type { Theme } from 'types';
|
||||
import ThemeChanges from './ThemeChanges';
|
||||
@@ -192,7 +190,7 @@ export default function ThemeOptions({
|
||||
borderRadius: '0px',
|
||||
}}
|
||||
>
|
||||
<FiX />
|
||||
<FaX />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
@@ -17,7 +17,7 @@ export function ThemePreviewMiddleware({
|
||||
selectedPlaylist: any;
|
||||
setSelectedPlaylist: (selectedPlaylist: any) => void;
|
||||
}) {
|
||||
const [pressed, setPressed] = useState<any>(false);
|
||||
const [pressed, setPressed] = useState<boolean>(false);
|
||||
const handlers = useLongPress(() => {
|
||||
setPressed(true);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { DeleteItemModal } from 'components';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FiTrash } from 'react-icons/fi';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
import { cardClasses, textClasses, textInputClasses, toolTipClasses } from 'styles';
|
||||
import type { WolClient } from 'types';
|
||||
|
||||
@@ -19,7 +19,7 @@ function DebouncedWolClient({
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
const [client, setClient] = useState<WolClient>(wolClient);
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [update, setUpdate] = useState<WolClient>();
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
@@ -27,13 +27,13 @@ function DebouncedWolClient({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
if (debounced && Object.keys(debounced).length) {
|
||||
mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced });
|
||||
}
|
||||
}, [debounced]);
|
||||
|
||||
useEffect(() => {
|
||||
if (update !== client && Object.keys(update).length) {
|
||||
if (update && update !== client && Object.keys(update).length) {
|
||||
setClient(update);
|
||||
}
|
||||
}, [update]);
|
||||
@@ -44,22 +44,24 @@ function DebouncedWolClient({
|
||||
className={cardClasses.settingsItem}
|
||||
classNames={cardClasses}
|
||||
>
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(client, 'api/WolClients')}
|
||||
title={'Delete WoL Client'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this WoL Client?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${client.hostname}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<DeleteItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
handleDelete={() => handleDelete(client, 'api/WolClients')}
|
||||
title={'Delete WoL Client'}
|
||||
body={
|
||||
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
|
||||
<Text>Are you sure you want to delete this WoL Client?</Text>
|
||||
<Text
|
||||
className={textClasses.deleteItem}
|
||||
classNames={textClasses}
|
||||
>{`"${client.hostname}"`}</Text>
|
||||
<Text c='red'>This action cannot be undone!</Text>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
@@ -73,7 +75,7 @@ function DebouncedWolClient({
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
<FiTrash color='black' />
|
||||
<FaTrash color='black' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Group w={'100%'} justify='center'>
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { WolClient } from 'types';
|
||||
import DebouncedWolClient from './DebouncedWolClient';
|
||||
import WolClientSettingsItem from './WolClientSettingsItem';
|
||||
|
||||
const WolClientSettings = () => {
|
||||
export function WolClients() {
|
||||
const defaultWolClient: WolClient = {
|
||||
id: undefined,
|
||||
hostname: '',
|
||||
@@ -65,76 +65,78 @@ const WolClientSettings = () => {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new WoL Client'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: values,
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('hostname')}
|
||||
{...form.getInputProps('hostname')}
|
||||
label={'Hostname'}
|
||||
placeholder='eg. Workstation'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('macAddress')}
|
||||
{...form.getInputProps('macAddress')}
|
||||
label={'MAC Address'}
|
||||
placeholder='eg. 00:00:00:00:00:00'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
{modalOpened && (
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
handleClose={handleClose}
|
||||
title={'Add a new WoL Client'}
|
||||
body={
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: endpoint,
|
||||
body: values,
|
||||
});
|
||||
handleClose();
|
||||
})}
|
||||
>
|
||||
<Stack gap={0} w={'100%'} align='center'>
|
||||
<Group w={'100%'} justify='center'>
|
||||
<TextInput
|
||||
key={form.key('hostname')}
|
||||
{...form.getInputProps('hostname')}
|
||||
label={'Hostname'}
|
||||
placeholder='eg. Workstation'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
key={form.key('macAddress')}
|
||||
{...form.getInputProps('macAddress')}
|
||||
label={'MAC Address'}
|
||||
placeholder='eg. 00:00:00:00:00:00'
|
||||
className={textInputClasses.settingsItem}
|
||||
classNames={textInputClasses}
|
||||
withAsterisk
|
||||
styles={
|
||||
theme.textShadowsEnabled
|
||||
? {
|
||||
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Group mt={'1rem'}>
|
||||
<Button
|
||||
type='submit'
|
||||
className={buttonClasses.saveButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
className={buttonClasses.cancelButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
@@ -204,5 +206,4 @@ const WolClientSettings = () => {
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default WolClientSettings;
|
||||
}
|
||||
|
||||
10
src/pages/settings/components/index.ts
Normal file
10
src/pages/settings/components/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export { Applications } from './Applications/Applications';
|
||||
export { Bookmarks } from './Bookmarks/Bookmarks';
|
||||
export { GuacamoleClients } from './GuacamoleClients/GuacamoleClients';
|
||||
export { Messages } from './Messages/Messages';
|
||||
export { Personalization } from './Personalization/Personalization';
|
||||
export { SearchHistory } from './Search/SearchHistory';
|
||||
export { SearchProviders } from './Search/SearchProviders';
|
||||
export { ThemePlaylists } from './Theming/ThemePlaylists';
|
||||
export { Themes } from './Theming/Themes';
|
||||
export { WolClients } from './WoL/WolClients';
|
||||
Reference in New Issue
Block a user