Style updates and default fixes
This commit is contained in:
473
src/pages/homepage/components/SettingsButton/SettingsButton.tsx
Normal file
473
src/pages/homepage/components/SettingsButton/SettingsButton.tsx
Normal file
@@ -0,0 +1,473 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Group,
|
||||
Menu,
|
||||
MultiSelect,
|
||||
Overlay,
|
||||
Select,
|
||||
Slider,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaDice, FaSync } from 'react-icons/fa';
|
||||
import { FiMenu, FiPower, FiSettings, FiShuffle } from 'react-icons/fi';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
actionIconClasses,
|
||||
buttonClasses,
|
||||
menuClasses,
|
||||
multiSelectClasses,
|
||||
selectClasses,
|
||||
sliderClasses,
|
||||
stackClasses,
|
||||
} from 'styles';
|
||||
import type { AppCategory, Theme, ThemePlaylist, WolClient } from 'types';
|
||||
|
||||
function SettingsButton() {
|
||||
const { data, isFetched } = useQueryAPI({ endpoint: 'api/appCategories' });
|
||||
const { data: wolClients, isFetched: wolClientsIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/wolClients',
|
||||
});
|
||||
const [wolClientsData, setWolClientsData] = useState<WolClient[]>([]);
|
||||
const { data: allThemes, isFetched: isAllThemesFetched } = useQueryAPI({
|
||||
endpoint: 'api/themes',
|
||||
});
|
||||
const { data: themePlaylists } = useQueryAPI({
|
||||
endpoint: 'api/themePlaylists',
|
||||
});
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [allThemeData, setAllThemeData] = useState<Theme[]>([]);
|
||||
const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]);
|
||||
const [selectedClient, setSelectedClient] = useState<WolClient>(wolClients?.[0]);
|
||||
const [overlayOpened, setOverlayOpened] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const notify = (notification: any) => toast(notification, { position: 'bottom-right' });
|
||||
const {
|
||||
currentTheme,
|
||||
setCurrentTheme,
|
||||
setThemeName,
|
||||
handleThemeChange,
|
||||
theme,
|
||||
appFilter,
|
||||
setAppFilter,
|
||||
appSort,
|
||||
setAppSort,
|
||||
appDensity,
|
||||
setAppDensity,
|
||||
bookmarkSort,
|
||||
setBookmarkSort,
|
||||
bookmarkFilter,
|
||||
setBookmarkFilter,
|
||||
bookmarkDensity,
|
||||
setBookmarkDensity,
|
||||
isMobile,
|
||||
currentThemePlaylist,
|
||||
setCurrentThemePlaylist,
|
||||
} = useCommonHooks();
|
||||
const handleWakeRequest = () => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
endpoint: 'api/wakeClient',
|
||||
body: selectedClient || wolClients?.[0],
|
||||
});
|
||||
};
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleRefresh = () => {
|
||||
queryClient.refetchQueries();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const list: [any] = data.map((category: AppCategory) => {
|
||||
return { value: category.filterName, label: category.filterName };
|
||||
});
|
||||
list.unshift({ value: 'All', label: 'All' });
|
||||
setCategories(list);
|
||||
}
|
||||
}, [data, isFetched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wolClients && wolClientsIsFetched) {
|
||||
setWolClientsData(wolClients);
|
||||
}
|
||||
}, [wolClients, wolClientsIsFetched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allThemes && isAllThemesFetched) {
|
||||
setAllThemeData(allThemes);
|
||||
}
|
||||
}, [allThemes, isAllThemesFetched]);
|
||||
return (
|
||||
<>
|
||||
{overlayOpened && <Overlay pos={'fixed'} />}
|
||||
<Menu
|
||||
classNames={menuClasses}
|
||||
width={isMobile ? '98%' : 'auto'}
|
||||
withinPortal={false}
|
||||
withArrow
|
||||
shadow='md'
|
||||
position='top'
|
||||
onOpen={() => setOverlayOpened(true)}
|
||||
onClose={() => setOverlayOpened(false)}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Button className={buttonClasses.settingsButton} classNames={buttonClasses}>
|
||||
<FiMenu />
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown
|
||||
style={{
|
||||
backgroundColor:
|
||||
theme.backgroundImage.toLowerCase() !== 'none' &&
|
||||
theme.backgroundImage !== undefined
|
||||
? 'transparent'
|
||||
: theme.backgroundColor,
|
||||
}}
|
||||
>
|
||||
<Group p={'1rem'} gap={12}>
|
||||
<Stack display={'flex'} align='center'>
|
||||
<Stack
|
||||
gap={0}
|
||||
align='center'
|
||||
className={stackClasses.menuStack}
|
||||
classNames={stackClasses}
|
||||
style={{ aspectRatio: '1/1' }}
|
||||
>
|
||||
<Menu.Label>Wake on LAN</Menu.Label>
|
||||
<Button
|
||||
classNames={buttonClasses}
|
||||
className={buttonClasses.wakeButton}
|
||||
disabled={!wolClientsData?.length || selectedClient === null}
|
||||
onClick={() => {
|
||||
handleWakeRequest();
|
||||
notify('Wake command sent');
|
||||
}}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<FiPower size={32} />
|
||||
</Stack>
|
||||
</Button>
|
||||
<Select
|
||||
labelProps={{ display: 'none' }}
|
||||
placeholder='Pick a host...'
|
||||
defaultValue={wolClientsData?.[0]?.hostname}
|
||||
data={
|
||||
wolClientsData
|
||||
?.map((wolClient: WolClient) => {
|
||||
return {
|
||||
value: wolClient.hostname,
|
||||
label: `${wolClient.hostname} (${wolClient.macAddress})`,
|
||||
hostname: wolClient.hostname,
|
||||
macAddress: wolClient.macAddress,
|
||||
};
|
||||
})
|
||||
.toSorted((a: any, b: any) => a.label.localeCompare(b.label)) || []
|
||||
}
|
||||
w='92%'
|
||||
classNames={{
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
onChange={(_value, option: any) => setSelectedClient(option)}
|
||||
onLoad={(option: any) => setSelectedClient(option)}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
gap={0}
|
||||
align='center'
|
||||
className={stackClasses.menuStack}
|
||||
classNames={stackClasses}
|
||||
style={{ aspectRatio: '1/1' }}
|
||||
>
|
||||
<Menu.Label pb={'.25rem'}>Themes</Menu.Label>
|
||||
<Group mt={'-1.5rem'} mb={'-.25rem'} w={'95%'} justify='space-between'>
|
||||
<Tooltip label='Shuffle Themes' color={theme.primaryColor} withArrow>
|
||||
<ActionIcon
|
||||
className={actionIconClasses.menuActions}
|
||||
classNames={actionIconClasses}
|
||||
style={{
|
||||
backgroundColor:
|
||||
currentTheme === 'random'
|
||||
? `${theme?.searchColor === theme.primaryColor ? theme.searchColor?.substring(0, 7) : theme.primaryColor?.substring(0, 7)}88`
|
||||
: 'transparent',
|
||||
color:
|
||||
currentTheme === 'random'
|
||||
? theme.accentColor
|
||||
: theme.primaryColor,
|
||||
}}
|
||||
onClick={() => {
|
||||
setCurrentTheme(
|
||||
currentTheme === 'random' ? theme.name : 'random',
|
||||
);
|
||||
setCurrentThemePlaylist('All');
|
||||
}}
|
||||
>
|
||||
<FiShuffle />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label='Random Theme' color={theme.primaryColor} withArrow>
|
||||
<ActionIcon
|
||||
className={actionIconClasses.menuActions}
|
||||
classNames={actionIconClasses}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.primaryColor,
|
||||
}}
|
||||
onClick={() => {
|
||||
setCurrentThemePlaylist('All');
|
||||
handleThemeChange(allThemeData, true);
|
||||
}}
|
||||
>
|
||||
<FaDice />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Text fw={'bold'} fz={18}>
|
||||
Currently:{' '}
|
||||
</Text>
|
||||
{theme.name}
|
||||
<Text fw={'bold'} fz={18}>
|
||||
Playlist:{' '}
|
||||
</Text>
|
||||
<Select
|
||||
labelProps={{ display: 'none' }}
|
||||
placeholder='Pick a playlist...'
|
||||
value={currentThemePlaylist}
|
||||
data={
|
||||
themePlaylists
|
||||
?.map((playlist: ThemePlaylist) => {
|
||||
return {
|
||||
value: playlist.name,
|
||||
label: playlist.name,
|
||||
};
|
||||
})
|
||||
.toSorted((a: any, b: any) => a.label.localeCompare(b.label)) ?? []
|
||||
}
|
||||
w='92%'
|
||||
classNames={{
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
onChange={(_value, option: any) => {
|
||||
setCurrentThemePlaylist(option.value);
|
||||
if (option.value !== 'Time of Day') {
|
||||
setThemeName('random');
|
||||
} else {
|
||||
const themeList = JSON.parse(
|
||||
themePlaylists.find(
|
||||
(t: ThemePlaylist) => t.name === 'Time of Day',
|
||||
).themeList,
|
||||
);
|
||||
setThemeName(
|
||||
allThemes.find(
|
||||
(t: Theme) =>
|
||||
t.id ===
|
||||
Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
||||
).name,
|
||||
);
|
||||
}
|
||||
}}
|
||||
onLoad={(option: any) => setCurrentThemePlaylist(option)}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack display={'flex'} align='center'>
|
||||
<Stack
|
||||
gap={0}
|
||||
align='center'
|
||||
justify='space-between'
|
||||
className={stackClasses.menuStack}
|
||||
classNames={stackClasses}
|
||||
pos={'relative'}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
borderRadius: 8,
|
||||
position: 'absolute',
|
||||
left: '-12px',
|
||||
top: '-8px',
|
||||
backgroundColor: theme.primaryColor,
|
||||
color: theme.backgroundColor,
|
||||
}}
|
||||
>
|
||||
Apps
|
||||
</Text>
|
||||
<Text fz={14} mt={16}>
|
||||
Density
|
||||
</Text>
|
||||
<Stack m={0} p={0} gap={0} w={'90%'}>
|
||||
<Slider
|
||||
className={sliderClasses.default}
|
||||
classNames={sliderClasses}
|
||||
restrictToMarks
|
||||
value={appDensity}
|
||||
onChange={(value) => setAppDensity(value)}
|
||||
min={1}
|
||||
max={8}
|
||||
/>
|
||||
</Stack>
|
||||
<Group justify='center' mb={'.25rem'}>
|
||||
<MultiSelect
|
||||
label='Filter'
|
||||
data={
|
||||
categories.toSorted((a: any, b: any) =>
|
||||
a.label.localeCompare(b.label),
|
||||
) ?? []
|
||||
}
|
||||
placeholder={
|
||||
appFilter.length > 0
|
||||
? appFilter.length === 1
|
||||
? appFilter[0]
|
||||
: `${appFilter.length} apps selected`
|
||||
: 'No apps selected'
|
||||
}
|
||||
defaultValue={appFilter}
|
||||
onChange={(data: any) => {
|
||||
setAppFilter(data);
|
||||
}}
|
||||
classNames={multiSelectClasses}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
/>
|
||||
<Select
|
||||
label={'Sorting'}
|
||||
placeholder='Sort Applications...'
|
||||
value={appSort}
|
||||
data={['Custom', 'A>Z', 'Z>A', 'Newest', 'Oldest']}
|
||||
classNames={{
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
onChange={(_value, option: any) => {
|
||||
setAppSort(option.value);
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Stack
|
||||
gap={0}
|
||||
align='center'
|
||||
justify='space-between'
|
||||
className={stackClasses.menuStack}
|
||||
classNames={stackClasses}
|
||||
pos={'relative'}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
borderRadius: 8,
|
||||
position: 'absolute',
|
||||
left: '-12px',
|
||||
top: '-8px',
|
||||
backgroundColor: theme.primaryColor,
|
||||
color: theme.backgroundColor,
|
||||
}}
|
||||
>
|
||||
Bookmarks
|
||||
</Text>
|
||||
<Text fz={14} mt={16}>
|
||||
Density
|
||||
</Text>
|
||||
<Stack m={0} p={0} gap={0} w={'90%'}>
|
||||
<Slider
|
||||
className={sliderClasses.default}
|
||||
classNames={sliderClasses}
|
||||
restrictToMarks
|
||||
value={bookmarkDensity}
|
||||
onChange={(value) => setBookmarkDensity(value)}
|
||||
min={1}
|
||||
max={8}
|
||||
/>
|
||||
</Stack>
|
||||
<Group justify='center' mb={'.25rem'}>
|
||||
<MultiSelect
|
||||
label='Filter'
|
||||
data={
|
||||
categories.toSorted((a: any, b: any) =>
|
||||
a.label.localeCompare(b.label),
|
||||
) ?? []
|
||||
}
|
||||
placeholder={
|
||||
bookmarkFilter.length > 0
|
||||
? bookmarkFilter.length === 1
|
||||
? bookmarkFilter[0]
|
||||
: `${bookmarkFilter.length} bookmarks selected`
|
||||
: 'No bookmarks selected'
|
||||
}
|
||||
defaultValue={bookmarkFilter}
|
||||
onChange={(data: any) => {
|
||||
setBookmarkFilter(data);
|
||||
}}
|
||||
classNames={multiSelectClasses}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
/>
|
||||
<Select
|
||||
label={'Sorting'}
|
||||
placeholder='Sort Bookmarks...'
|
||||
value={bookmarkSort}
|
||||
data={['Custom', 'A>Z', 'Z>A', 'Newest', 'Oldest']}
|
||||
classNames={{
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
onChange={(_value, option: any) => {
|
||||
setBookmarkSort(option.value);
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group mt={'-.75rem'} w={'100%'} align='center' justify='center'>
|
||||
<Button
|
||||
leftSection={<FiSettings />}
|
||||
onClick={() => navigate(`/${import.meta.env.VITE_BASE_URL}/settings`)}
|
||||
className={buttonClasses.settingsMenuButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<FaSync />}
|
||||
onClick={handleRefresh}
|
||||
className={buttonClasses.settingsMenuButton}
|
||||
classNames={buttonClasses}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</Group>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsButton;
|
||||
Reference in New Issue
Block a user