Bugfixes and updates
This commit is contained in:
@@ -1,32 +1,13 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
type ComboboxItem,
|
||||
Group,
|
||||
Menu,
|
||||
MultiSelect,
|
||||
Overlay,
|
||||
Select,
|
||||
Slider,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { Button, Group, Menu, Stack } from '@mantine/core';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaDice } from 'react-icons/fa';
|
||||
import { FiMenu, FiPower, FiSettings, FiShuffle } from 'react-icons/fi';
|
||||
import { FiMenu, FiSettings } from 'react-icons/fi';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
actionIconClasses,
|
||||
buttonClasses,
|
||||
menuClasses,
|
||||
multiSelectClasses,
|
||||
selectClasses,
|
||||
sliderClasses,
|
||||
stackClasses,
|
||||
} from 'styles';
|
||||
import type { AppCategory, Theme, ThemePlaylist, WolClient } from 'types';
|
||||
import { buttonClasses, menuClasses } from 'styles';
|
||||
import type { AppCategory, WolClient } from 'types';
|
||||
import SettingsCard from './SettingsCard';
|
||||
import ThemingCard from './ThemingCard';
|
||||
import WakeOnLan from './WakeOnLanCard';
|
||||
|
||||
function SettingsButton() {
|
||||
const { data: appCategoryData, isFetched: appCategoryDataIsFetched } = useQueryAPI({
|
||||
@@ -36,24 +17,14 @@ function SettingsButton() {
|
||||
endpoint: 'api/wolClients',
|
||||
});
|
||||
const [wolClients, setWolClients] = useState<WolClient[]>([]);
|
||||
const { data: allThemeData, isFetched: allThemeDataIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/themes',
|
||||
});
|
||||
const { data: themePlaylistData } = useQueryAPI({
|
||||
endpoint: 'api/themePlaylists',
|
||||
});
|
||||
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [allThemes, setAllThemes] = useState<Theme[]>([]);
|
||||
const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]);
|
||||
const [selectedClient] = useState<WolClient>(wolClientData?.[0]);
|
||||
const [overlayOpened, setOverlayOpened] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
currentTheme,
|
||||
setCurrentTheme,
|
||||
setThemeName,
|
||||
handleThemeChange,
|
||||
theme,
|
||||
isMobile,
|
||||
appFilter,
|
||||
setAppFilter,
|
||||
appSort,
|
||||
@@ -66,10 +37,8 @@ function SettingsButton() {
|
||||
setBookmarkFilter,
|
||||
bookmarkDensity,
|
||||
setBookmarkDensity,
|
||||
isMobile,
|
||||
currentThemePlaylist,
|
||||
setCurrentThemePlaylist,
|
||||
} = useCommonHooks();
|
||||
|
||||
const handleWakeRequest = () => {
|
||||
mutateAsync({
|
||||
method: 'POST',
|
||||
@@ -94,459 +63,84 @@ function SettingsButton() {
|
||||
}
|
||||
}, [wolClientData, wolClientDataIsFetched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allThemeData && allThemeDataIsFetched) {
|
||||
setAllThemes(allThemeData);
|
||||
}
|
||||
}, [allThemeData, allThemeDataIsFetched]);
|
||||
return (
|
||||
<>
|
||||
{overlayOpened && (
|
||||
<Overlay
|
||||
pos={'fixed'}
|
||||
blur={3}
|
||||
onTouchStart={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Menu
|
||||
classNames={menuClasses}
|
||||
width={isMobile ? '98%' : 'auto'}
|
||||
withArrow
|
||||
shadow='md'
|
||||
position='top'
|
||||
onOpen={() => setOverlayOpened(true)}
|
||||
onClose={() => setOverlayOpened(false)}
|
||||
<Menu
|
||||
classNames={menuClasses}
|
||||
width={isMobile ? '98%' : 'auto'}
|
||||
withArrow
|
||||
shadow='md'
|
||||
position='top'
|
||||
withOverlay
|
||||
overlayProps={{
|
||||
blur: 5,
|
||||
}}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Button className={buttonClasses.settingsButton} classNames={buttonClasses}>
|
||||
<FiMenu />
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown
|
||||
style={{
|
||||
backgroundColor:
|
||||
theme.backgroundImage.toLowerCase() !== 'none' &&
|
||||
theme.backgroundImage !== undefined
|
||||
? 'transparent'
|
||||
: 'var(--mantine-color-background-0)',
|
||||
}}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Button className={buttonClasses.settingsButton} classNames={buttonClasses}>
|
||||
<FiMenu />
|
||||
<Group p={'1rem'} gap={12}>
|
||||
<Stack
|
||||
justify='center'
|
||||
align='center'
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: isMobile ? 'row' : 'column',
|
||||
width: isMobile ? '100%' : undefined,
|
||||
}}
|
||||
>
|
||||
<WakeOnLan
|
||||
wolClients={wolClients}
|
||||
selectedClient={selectedClient}
|
||||
handleWakeRequest={handleWakeRequest}
|
||||
/>
|
||||
<ThemingCard />
|
||||
</Stack>
|
||||
<Stack display={'flex'} align='center'>
|
||||
<SettingsCard
|
||||
categories={categories}
|
||||
sectionTitle={'Applications'}
|
||||
filter={appFilter}
|
||||
setFilter={setAppFilter}
|
||||
sort={appSort}
|
||||
setSort={setAppSort}
|
||||
density={appDensity}
|
||||
setDensity={setAppDensity}
|
||||
/>
|
||||
<SettingsCard
|
||||
categories={categories}
|
||||
sectionTitle={'Bookmarks'}
|
||||
filter={bookmarkFilter}
|
||||
setFilter={setBookmarkFilter}
|
||||
sort={bookmarkSort}
|
||||
setSort={setBookmarkSort}
|
||||
density={bookmarkDensity}
|
||||
setDensity={setBookmarkDensity}
|
||||
/>
|
||||
</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>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown
|
||||
style={{
|
||||
backgroundColor:
|
||||
theme.backgroundImage.toLowerCase() !== 'none' &&
|
||||
theme.backgroundImage !== undefined
|
||||
? 'transparent'
|
||||
: 'var(--mantine-color-background-0)',
|
||||
}}
|
||||
>
|
||||
<Group p={'1rem'} gap={12}>
|
||||
<Stack
|
||||
justify='center'
|
||||
align='center'
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: isMobile ? 'row' : 'column',
|
||||
width: isMobile ? '100%' : undefined,
|
||||
}}
|
||||
>
|
||||
<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={!wolClients?.length || selectedClient === null}
|
||||
onClick={() => {
|
||||
handleWakeRequest();
|
||||
}}
|
||||
>
|
||||
<Stack gap={0} align='center'>
|
||||
<FiPower size={32} />
|
||||
</Stack>
|
||||
</Button>
|
||||
<Select
|
||||
labelProps={{ display: 'none' }}
|
||||
placeholder='Pick a host...'
|
||||
defaultValue={wolClients?.[0]?.hostname}
|
||||
data={
|
||||
wolClients
|
||||
?.map((wolClient: WolClient) => {
|
||||
return {
|
||||
value: wolClient.hostname,
|
||||
label: `${wolClient.hostname} (${wolClient.macAddress})`,
|
||||
hostname: wolClient.hostname,
|
||||
macAddress: wolClient.macAddress,
|
||||
};
|
||||
})
|
||||
.toSorted((a: ComboboxItem, b: ComboboxItem) =>
|
||||
a.label.localeCompare(b.label),
|
||||
) ?? []
|
||||
}
|
||||
w='92%'
|
||||
classNames={{
|
||||
root: selectClasses.menuSelectRoot,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{
|
||||
withinPortal: false,
|
||||
position: 'top',
|
||||
width: 'fit-content',
|
||||
}}
|
||||
/>
|
||||
</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'
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
withArrow
|
||||
>
|
||||
<ActionIcon
|
||||
className={actionIconClasses.menuActions}
|
||||
classNames={actionIconClasses}
|
||||
disabled={currentThemePlaylist === 'Time of Day'}
|
||||
style={{
|
||||
backgroundColor:
|
||||
currentTheme === 'random'
|
||||
? `${theme?.searchColor === theme?.primaryColor ? theme.searchColor?.substring(0, 7) : theme.primaryColor?.substring(0, 7)}88`
|
||||
: 'transparent',
|
||||
color:
|
||||
currentTheme === 'random'
|
||||
? 'var(--mantine-color-accent-0)'
|
||||
: 'var(--mantine-color-primary-0)',
|
||||
}}
|
||||
onClick={() => {
|
||||
setCurrentTheme(
|
||||
currentTheme === 'random' ? theme.name : 'random',
|
||||
);
|
||||
setCurrentThemePlaylist('All');
|
||||
}}
|
||||
>
|
||||
<FiShuffle />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label='Random Theme'
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
withArrow
|
||||
>
|
||||
<ActionIcon
|
||||
className={actionIconClasses.menuActions}
|
||||
classNames={actionIconClasses}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
}}
|
||||
disabled={currentThemePlaylist === 'Time of Day'}
|
||||
onClick={() => {
|
||||
const themeList = JSON.parse(
|
||||
themePlaylistData?.find(
|
||||
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
|
||||
)?.themeList ?? '[]',
|
||||
);
|
||||
if (themeList.length > 0) {
|
||||
const filteredThemes = allThemes.filter((t: Theme) =>
|
||||
themeList.includes(t.id),
|
||||
);
|
||||
handleThemeChange(filteredThemes, false);
|
||||
} else if (currentThemePlaylist === 'All') {
|
||||
handleThemeChange(allThemes, false);
|
||||
} else if (currentThemePlaylist === 'Backgrounds') {
|
||||
handleThemeChange(
|
||||
allThemes.filter(
|
||||
(a: any) =>
|
||||
!a.backgroundImage.toLowerCase().includes('none'),
|
||||
),
|
||||
false,
|
||||
);
|
||||
} else if (currentThemePlaylist === 'No Backgrounds') {
|
||||
handleThemeChange(
|
||||
allThemes.filter((a: any) =>
|
||||
a.backgroundImage.toLowerCase().includes('none'),
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FaDice />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Text fw={'bold'} fz={'1.125rem'}>
|
||||
Currently:{' '}
|
||||
</Text>
|
||||
{theme.name}
|
||||
<Text fw={'bold'} fz={'1.125rem'}>
|
||||
Playlist:{' '}
|
||||
</Text>
|
||||
<Select
|
||||
labelProps={{ display: 'none' }}
|
||||
placeholder='Pick a playlist...'
|
||||
value={currentThemePlaylist}
|
||||
data={[
|
||||
{ value: 'All', label: 'All' },
|
||||
{
|
||||
value: 'Backgrounds',
|
||||
label: 'Backgrounds',
|
||||
disabled:
|
||||
allThemes.filter(
|
||||
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
||||
).length === 0,
|
||||
},
|
||||
{
|
||||
value: 'No Backgrounds',
|
||||
label: 'No Backgrounds',
|
||||
disabled:
|
||||
allThemes.filter((a: any) =>
|
||||
a.backgroundImage.toLowerCase().includes('none'),
|
||||
).length === 0,
|
||||
},
|
||||
...(themePlaylistData
|
||||
?.map((playlist: ThemePlaylist) => {
|
||||
return {
|
||||
value: playlist.name,
|
||||
label: playlist.name,
|
||||
disabled: JSON.parse(playlist.themeList).length === 0,
|
||||
};
|
||||
})
|
||||
.toSorted((a: ComboboxItem, b: ComboboxItem) =>
|
||||
a.label.localeCompare(b.label),
|
||||
) ?? []),
|
||||
]}
|
||||
w='92%'
|
||||
classNames={{
|
||||
root: selectClasses.menuSelectRoot,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{
|
||||
withinPortal: false,
|
||||
position: 'top',
|
||||
}}
|
||||
onChange={(_value, option: ComboboxItem) => {
|
||||
setCurrentThemePlaylist(option.value);
|
||||
if (option.value !== 'Time of Day') {
|
||||
setThemeName('random');
|
||||
} else {
|
||||
const themeList = JSON.parse(
|
||||
themePlaylistData.find(
|
||||
(t: ThemePlaylist) => t.name === 'Time of Day',
|
||||
).themeList,
|
||||
);
|
||||
setThemeName(
|
||||
allThemeData.find(
|
||||
(t: Theme) =>
|
||||
t.id ===
|
||||
Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
||||
).name,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</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: '.75rem',
|
||||
paddingLeft: '.5rem',
|
||||
paddingRight: '.5rem',
|
||||
paddingTop: '.125rem',
|
||||
paddingBottom: '.125rem',
|
||||
borderRadius: 8,
|
||||
position: 'absolute',
|
||||
left: -12,
|
||||
top: -8,
|
||||
backgroundColor: 'var(--mantine-color-primary-0)',
|
||||
color: 'var(--mantine-color-background-0)',
|
||||
}}
|
||||
>
|
||||
Apps
|
||||
</Text>
|
||||
<Text fz={'.875rem'} mt={'1rem'}>
|
||||
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: ComboboxItem, b: ComboboxItem) =>
|
||||
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: string[]) => {
|
||||
setAppFilter(data);
|
||||
}}
|
||||
classNames={multiSelectClasses}
|
||||
comboboxProps={{
|
||||
withinPortal: false,
|
||||
position: 'top',
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
label={'Sorting'}
|
||||
placeholder='Sort Applications...'
|
||||
value={appSort}
|
||||
data={['Custom', 'A>Z', 'Z>A', 'Newest', 'Oldest']}
|
||||
classNames={{
|
||||
root: selectClasses.menuSelectRoot,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
comboboxProps={{
|
||||
withinPortal: false,
|
||||
position: 'top',
|
||||
}}
|
||||
onChange={(_value, option: ComboboxItem) => {
|
||||
setAppSort(option.value);
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Stack
|
||||
gap={0}
|
||||
align='center'
|
||||
justify='space-between'
|
||||
className={stackClasses.menuStack}
|
||||
classNames={stackClasses}
|
||||
pos={'relative'}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: '.75rem',
|
||||
paddingLeft: '.5rem',
|
||||
paddingRight: '.5rem',
|
||||
paddingTop: '.125rem',
|
||||
paddingBottom: '.125rem',
|
||||
borderRadius: 8,
|
||||
position: 'absolute',
|
||||
left: -12,
|
||||
top: -8,
|
||||
backgroundColor: 'var(--mantine-color-primary-0)',
|
||||
color: 'var(--mantine-color-background-0)',
|
||||
}}
|
||||
>
|
||||
Bookmarks
|
||||
</Text>
|
||||
<Text fz={'.875rem'} mt={'1rem'}>
|
||||
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: ComboboxItem, b: ComboboxItem) =>
|
||||
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: string[]) => {
|
||||
setBookmarkFilter(data);
|
||||
}}
|
||||
classNames={multiSelectClasses}
|
||||
comboboxProps={{
|
||||
withinPortal: false,
|
||||
position: 'top',
|
||||
}}
|
||||
/>
|
||||
<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,
|
||||
root: selectClasses.menuSelectRoot,
|
||||
}}
|
||||
comboboxProps={{
|
||||
withinPortal: false,
|
||||
position: 'top',
|
||||
}}
|
||||
onChange={(_value, option: ComboboxItem) => {
|
||||
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>
|
||||
</Group>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</>
|
||||
</Group>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user