Added SettingsHeader

This commit is contained in:
Sean Edmunds
2025-04-03 10:55:19 -05:00
parent 329a9bbc98
commit f5e193853f
11 changed files with 304 additions and 648 deletions

View File

@@ -41,6 +41,22 @@ export default function WeatherIcon({ textDescription }: { textDescription: stri
alt='RainIcon' alt='RainIcon'
/> />
); );
case 'Chance Showers':
return (
<Image
style={{ width: '75px', height: '75px' }}
src={RainIcon}
alt='RainIcon'
/>
);
case 'Slight Chance Showers':
return (
<Image
style={{ width: '75px', height: '75px' }}
src={RainIcon}
alt='RainIcon'
/>
);
case 'Clear': case 'Clear':
return ( return (
<Image <Image

View File

@@ -1,6 +1,5 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { import {
ActionIcon,
Box, Box,
Button, Button,
Card, Card,
@@ -8,27 +7,18 @@ import {
Grid, Grid,
GridCol, GridCol,
Group, Group,
Highlight,
Modal, Modal,
Popover, Popover,
Stack, Stack,
Switch,
Text, Text,
TextInput, TextInput,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiHelpCircle, FiPlus } from 'react-icons/fi'; import { cardClasses, modalClasses, textInputClasses } from 'styles';
import {
actionIconClasses,
buttonClasses,
cardClasses,
modalClasses,
textClasses,
textInputClasses,
} from 'styles';
import type { Application } from 'types'; import type { Application } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
import AppSettingsItem from './AppSettingsItem'; import AppSettingsItem from './AppSettingsItem';
import DebouncedApplication from './DebouncedApplication'; import DebouncedApplication from './DebouncedApplication';
@@ -236,33 +226,13 @@ const ApplicationSettings = () => {
</Modal.Body> </Modal.Body>
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Group align='center' justify='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={setDragging}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={openModal}
Applications title={'Applications'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'Applications are the apps that will be displayed on the homepage.',
<Popover.Target> highlightList: [
<ActionIcon
className={actionIconClasses.settingsTooltip}
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{'Applications are the apps that will be displayed on the homepage.'}
</Text>
<Highlight
highlight={[
'Applications', 'Applications',
'apps', 'apps',
'application', 'application',
@@ -271,64 +241,13 @@ const ApplicationSettings = () => {
'url', 'url',
'icon', 'icon',
'filter', 'filter',
]} ],
className={textClasses.settingsTooltip} description:
classNames={textClasses} 'Applications contain a name, base url, url, icon and filter. Currently the icon must be the mdi icon name. URLs can contain either http(s):// or not. Base URLs should start with /, otherwise they will cause errors.',
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{
'Applications contain a name, base url, url, icon and filter. Currently the icon must be the mdi icon name. URLs can contain either http(s):// or not. Base URLs should start with /, otherwise they will cause errors.'
}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={openModal}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
<Switch
label={<Text c={theme.primaryColor}>Reorder</Text>}
labelPosition='left'
pos={'absolute'}
right={isMobile ? '2rem' : '1rem'}
top={isMobile ? '10rem' : undefined}
onChange={(e) => {
setDragging(e.target.checked);
}}
color={theme.primaryColor}
styles={{
label: {
color: theme.primaryColor,
top: isMobile ? -22 : 0,
left: isMobile ? -8 : 0,
position: isMobile ? 'absolute' : undefined,
},
}}
/>
</Group>
{dragging ? ( {dragging ? (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Stack w={'100%'} align='center'> <Stack w={'100%'} align='center'>

View File

@@ -1,32 +1,21 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { import {
ActionIcon,
Box, Box,
Button, Button,
Card, Card,
Divider, Divider,
Group, Group,
Highlight,
Modal, Modal,
Popover,
Stack, Stack,
Switch,
Text, Text,
TextInput, TextInput,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiHelpCircle, FiPlus } from 'react-icons/fi'; import { cardClasses, modalClasses, textInputClasses } from 'styles';
import {
actionIconClasses,
buttonClasses,
cardClasses,
modalClasses,
textClasses,
textInputClasses,
} from 'styles';
import type { Bookmark, BookmarkGroup } from 'types'; import type { Bookmark, BookmarkGroup } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem'; import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
const BookmarkSettings = () => { const BookmarkSettings = () => {
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({ const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
@@ -190,33 +179,13 @@ const BookmarkSettings = () => {
</Modal.Body> </Modal.Body>
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Group align='center' justify='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={setDragging}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={openModal}
Bookmarks title={'Bookmarks'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'Bookmarks are the links that will be displayed on the homepage.',
<Popover.Target> highlightList: [
<ActionIcon
className={actionIconClasses.settingsTooltip}
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{'Bookmarks are the links that will be displayed on the homepage.'}
</Text>
<Highlight
highlight={[
'Bookmark', 'Bookmark',
'Bookmarks', 'Bookmarks',
'Group', 'Group',
@@ -225,64 +194,13 @@ const BookmarkSettings = () => {
'Links', 'Links',
'Category', 'Category',
'Categories', 'Categories',
]} ],
className={textClasses.settingsTooltip} description:
classNames={textClasses} 'Bookmarks can only belong to a single category, and will be displayed under that category on the homepage.',
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{
'Bookmarks can only belong to a single category, and will be displayed under that category on the homepage.'
}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={openModal}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
<Switch
label={<Text c={theme.primaryColor}>Reorder</Text>}
labelPosition='left'
pos={'absolute'}
right={isMobile ? '2rem' : '1rem'}
top={isMobile ? '10rem' : undefined}
onChange={(e) => {
setDragging(e.target.checked);
}}
color={theme.primaryColor}
styles={{
label: {
color: theme.primaryColor,
top: isMobile ? -22 : 0,
left: isMobile ? -8 : 0,
position: isMobile ? 'absolute' : undefined,
},
}}
/>
</Group>
{dragging ? ( {dragging ? (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Stack w={'100%'} align='center'> <Stack w={'100%'} align='center'>

View File

@@ -1,38 +1,27 @@
import { import {
ActionIcon,
Button, Button,
Card, Card,
Divider, Divider,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Highlight,
Modal, Modal,
Popover,
Stack, Stack,
Text,
TextInput, TextInput,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import DebouncedMessage from 'pages/settings/components/Messages/DebouncedMessage'; import DebouncedMessage from 'pages/settings/components/Messages/DebouncedMessage';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiHelpCircle, FiPlus } from 'react-icons/fi'; import { cardClasses, modalClasses, textInputClasses } from 'styles';
import {
actionIconClasses,
buttonClasses,
cardClasses,
modalClasses,
textClasses,
textInputClasses,
} from 'styles';
import type { Message } from 'types'; import type { Message } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
const MessageSettings = () => { const MessageSettings = () => {
const endpoint = 'api/messages'; const endpoint = 'api/messages';
const { data: messageData, isFetched: isMessagesFetched } = useQueryAPI({ const { data: messageData, isFetched: isMessagesFetched } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
}); });
const { theme, isMobile, modalStyle } = useCommonHooks(); const { isMobile, modalStyle } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
const [newMessage, setNewMessage] = useState(''); const [newMessage, setNewMessage] = useState('');
@@ -129,73 +118,19 @@ const MessageSettings = () => {
</Modal.Body> </Modal.Body>
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Group align='center' justify='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={undefined}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={openModal}
Messages title={'Messages'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'These custom messages will be displayed in the header on the homepage.',
<Popover.Target> highlightList: ['$firstName', '$lastName', '$^firstName', '$^lastName'],
<ActionIcon description:
className={actionIconClasses.settingsTooltip} 'Built-in variables include $firstName and $lastName. To force the text to upper case, use a carat (^), ie, $^firstName. Messages may contain any unicode text.',
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{
'These custom messages will be displayed in the header on the homepage.'
}
</Text>
<Highlight
highlight={['$firstName', '$lastName', '$^firstName', '$^lastName']}
className={textClasses.settingsTooltip}
classNames={textClasses}
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{
'Built-in variables include $firstName and $lastName. To force the text to upper case, use a carat (^), ie, $^firstName. Messages may contain any unicode text.'
}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={openModal}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
</Group>
<Stack w={'100%'} align='center'> <Stack w={'100%'} align='center'>
<Grid w={'75%'}> <Grid w={'75%'}>
{messages?.map((message: Message) => ( {messages?.map((message: Message) => (

View File

@@ -1,5 +1,4 @@
import { import {
ActionIcon,
Box, Box,
Button, Button,
Card, Card,
@@ -7,26 +6,17 @@ import {
Grid, Grid,
GridCol, GridCol,
Group, Group,
Highlight,
Modal, Modal,
Popover, Popover,
Stack, Stack,
Text,
TextInput, TextInput,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiHelpCircle, FiPlus } from 'react-icons/fi'; import { cardClasses, modalClasses, textInputClasses } from 'styles';
import {
actionIconClasses,
buttonClasses,
cardClasses,
modalClasses,
textClasses,
textInputClasses,
} from 'styles';
import type { Provider } from 'types'; import type { Provider } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedSearchProvider from './DebouncedSearchProvider'; import DebouncedSearchProvider from './DebouncedSearchProvider';
import SearchProviderSettingsItem from './SearchProviderSettingsItem'; import SearchProviderSettingsItem from './SearchProviderSettingsItem';
@@ -36,7 +26,7 @@ const SearchProviderSettings = () => {
endpoint: endpoint, endpoint: endpoint,
}); });
const [providerData, setProviderData] = useState<Provider[]>([]); const [providerData, setProviderData] = useState<Provider[]>([]);
const { theme, isMobile, modalStyle } = useCommonHooks(); const { isMobile, modalStyle } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [newProvider, setNewProvider] = useState<Provider>({ const [newProvider, setNewProvider] = useState<Provider>({
id: undefined, id: undefined,
@@ -160,71 +150,19 @@ const SearchProviderSettings = () => {
</Modal.Body> </Modal.Body>
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Group align='center' justify='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={undefined}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={openModal}
Search Providers title={'Search Providers'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'Search providers and shortcuts can be configured here.',
<Popover.Target> highlightList: ['Providers', 'search'],
<ActionIcon description:
className={actionIconClasses.settingsTooltip} 'Providers can be used in the search bar. By starting a search with the prefix, it will be redirected to the url of the provider to complete the search.',
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{'Search providers and shortcuts can be configured here.'}
</Text>
<Highlight
highlight={['Providers', 'search']}
className={textClasses.settingsTooltip}
classNames={textClasses}
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{
'Providers can be used in the search bar. By starting a search with the prefix, it will be redirected to the url of the provider to complete the search.'
}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={openModal}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
</Group>
<Stack w={'100%'} align='center'> <Stack w={'100%'} align='center'>
<Grid columns={18} w={'75%'}> <Grid columns={18} w={'75%'}>
{providerData?.map((provider: Provider) => ( {providerData?.map((provider: Provider) => (

View File

@@ -0,0 +1,113 @@
import {
ActionIcon,
Button,
Group,
Highlight,
Popover,
Stack,
Switch,
Text,
TextInput,
} from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { FiHelpCircle, FiPlus } from 'react-icons/fi';
import { actionIconClasses, buttonClasses, textClasses, textInputClasses } from 'styles';
function SettingsHeader({
setDragging,
openModal,
title,
tooltip,
searchValue,
setSearchValue,
}: any) {
const { isMobile, theme } = useCommonHooks();
return (
<Group align='center' justify='center' mt={'1rem'}>
<Group w={isMobile ? undefined : '100%'}>
<Text className={textClasses.settingsSubTitle} classNames={textClasses}>
{title}
</Text>
<Popover withArrow withOverlay withinPortal={false}>
<Popover.Target>
<ActionIcon
className={actionIconClasses.settingsTooltip}
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{tooltip.title}
</Text>
<Highlight
highlight={tooltip.highlightList}
className={textClasses.settingsTooltip}
classNames={textClasses}
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{tooltip.description}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={openModal}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}}
/>
{setDragging && (
<Switch
label={<Text c={theme.primaryColor}>Reorder</Text>}
labelPosition='left'
pos={'absolute'}
right={isMobile ? '2rem' : '1rem'}
top={isMobile ? '10rem' : undefined}
onChange={(e) => {
setDragging(e.target.checked);
}}
color={theme.primaryColor}
styles={{
label: {
color: theme.primaryColor,
top: isMobile ? -22 : 0,
left: isMobile ? -8 : 0,
position: isMobile ? 'absolute' : undefined,
},
}}
/>
)}
</Group>
);
}
export default SettingsHeader;

View File

@@ -39,6 +39,19 @@ function ThemeModal({
const filtered = backgroundList.filter((item: any) => const filtered = backgroundList.filter((item: any) =>
item.toLowerCase().includes(query.toLowerCase()), item.toLowerCase().includes(query.toLowerCase()),
); );
if (selectedTheme.name === undefined) {
setSelectedTheme({
id: undefined,
name: 'default',
backgroundImage: 'none',
backgroundColor: '#EEEEEE',
searchColor: '#00000033',
primaryColor: '#222222',
accentColor: '#5c5c5c',
borderColor: '#5c5c5c',
});
}
const re = /(?:\.([^.]+))?$/; const re = /(?:\.([^.]+))?$/;
const items = filtered.map((item: any, index: any) => ( const items = filtered.map((item: any, index: any) => (
<UnstyledButton <UnstyledButton

View File

@@ -30,6 +30,14 @@ function ThemePlaylistModal({
setThemeList(JSON.parse(selectedPlaylist?.themeList)); setThemeList(JSON.parse(selectedPlaylist?.themeList));
} }
}, [selectedPlaylist]); }, [selectedPlaylist]);
if (selectedPlaylist.name === undefined) {
setSelectedPlaylist({
name: 'New Playlist',
themeList: '[]',
});
}
return ( return (
<Modal.Root <Modal.Root
size={'50%'} size={'50%'}
@@ -40,7 +48,6 @@ function ThemePlaylistModal({
onClose={() => { onClose={() => {
handleClose(); handleClose();
setThemeList('[]'); setThemeList('[]');
setSelectedPlaylist({});
}} }}
style={{ style={{
'--color-text-pri': theme.primaryColor, '--color-text-pri': theme.primaryColor,

View File

@@ -1,20 +1,11 @@
import { import { Box, Group, Stack, Text } from '@mantine/core';
ActionIcon,
Box,
Button,
Group,
Highlight,
Popover,
Stack,
Text,
TextInput,
} from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useState } from 'react'; import { useState } from 'react';
import { FiChevronRight, FiHelpCircle, FiPlus } from 'react-icons/fi'; import { FiChevronRight } from 'react-icons/fi';
import { actionIconClasses, buttonClasses, textClasses, textInputClasses } from 'styles'; import { textClasses } from 'styles';
import type { ThemePlaylist } from 'types'; import type { ThemePlaylist } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
import ThemePlaylistCard from './ThemePlaylistCard'; import ThemePlaylistCard from './ThemePlaylistCard';
import ThemePlaylistModal from './ThemePlaylistModal'; import ThemePlaylistModal from './ThemePlaylistModal';
@@ -28,8 +19,7 @@ function ThemePlaylists() {
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
const [selectedPlaylist, setSelectedPlaylist] = useState<any>({}); const [selectedPlaylist, setSelectedPlaylist] = useState<any>({});
const { currentThemePlaylist, setCurrentThemePlaylist, theme, isMobile } = const { currentThemePlaylist, setCurrentThemePlaylist, theme } = useCommonHooks();
useCommonHooks();
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
@@ -39,6 +29,7 @@ function ThemePlaylists() {
}; };
const handleClose = () => { const handleClose = () => {
setSelectedPlaylist({});
close(); close();
}; };
@@ -52,7 +43,7 @@ function ThemePlaylists() {
}; };
return ( return (
<> <Stack>
<ThemePlaylistModal <ThemePlaylistModal
opened={opened} opened={opened}
handleClose={handleClose} handleClose={handleClose}
@@ -63,77 +54,19 @@ function ThemePlaylists() {
createPlaylist={mutateAsync} createPlaylist={mutateAsync}
/> />
<Stack h={'100%'} w={'100%'}> <Stack h={'100%'} w={'100%'}>
<Group w={'100%'} justify='center' align='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={undefined}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={open}
Theme Playlists title={'Theme Playlists'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'Theme playlists can be created here and applied to the homepage.',
<Popover.Target> highlightList: ['Clients', 'homepage'],
<ActionIcon description:
className={actionIconClasses.settingsTooltip} 'Playlists are lists of themes. They can be enabled either here or in the settings button on the homepage. When a playlist is enabled, it will shuffle the included themes on the homepage. If a theme is part of a playlist, it will appear while the playlist is active, regardless of random/shuffle.',
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{'Theme playlists can be created here and applied to the homepage.'}
</Text>
<Highlight
highlight={['Theme', 'Themes', 'Playlist', 'Playlists']}
className={textClasses.settingsTooltip}
classNames={textClasses}
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{
'Playlists are lists of themes. They can be enabled either here or in the settings button on the homepage. When a playlist is enabled, it will shuffle the included themes on the homepage. If a theme is part of a playlist, it will appear while the playlist is active, regardless of random/shuffle.'
}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={() => {
setSelectedPlaylist({
name: 'New Playlist',
themeList: '[]',
});
open();
}}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
</Group>
<Group justify='center' align='center' pos={'relative'}> <Group justify='center' align='center' pos={'relative'}>
<Text p={0} className={textClasses.settingsSubTitle} classNames={textClasses}> <Text p={0} className={textClasses.settingsSubTitle} classNames={textClasses}>
Defaults Defaults
@@ -222,7 +155,7 @@ function ThemePlaylists() {
})} })}
</Group> </Group>
</Stack> </Stack>
</> </Stack>
); );
} }

View File

@@ -1,25 +1,13 @@
import { import { Box, Group, Overlay, Stack, Switch, Text } from '@mantine/core';
ActionIcon,
Box,
Button,
Group,
Highlight,
Overlay,
Popover,
Stack,
Switch,
Text,
TextInput,
} from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiChevronRight, FiHelpCircle, FiPlus } from 'react-icons/fi'; import { FiChevronRight } from 'react-icons/fi';
import '@mantine/core/styles/ModalBase.css'; import '@mantine/core/styles/ModalBase.css';
import '@mantine/core/styles/Modal.css'; import '@mantine/core/styles/Modal.css';
import { FaDice } from 'react-icons/fa'; import { FaDice } from 'react-icons/fa';
import { actionIconClasses, buttonClasses, textClasses, textInputClasses } from 'styles';
import type { Theme } from 'types'; import type { Theme } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
import ThemeCard from './ThemeCard'; import ThemeCard from './ThemeCard';
import ThemeModal from './ThemeModal'; import ThemeModal from './ThemeModal';
function ThemesSettings() { function ThemesSettings() {
@@ -49,6 +37,7 @@ function ThemesSettings() {
const handleClose = () => { const handleClose = () => {
close(); close();
closePicker(); closePicker();
setSelectedTheme({});
}; };
const handleSaveTheme = () => { const handleSaveTheme = () => {
@@ -66,7 +55,7 @@ function ThemesSettings() {
} }
}, [backgroundData, isBackgroundFetched]); }, [backgroundData, isBackgroundFetched]);
return ( return (
<> <Stack>
<ThemeModal <ThemeModal
opened={opened} opened={opened}
handleClose={handleClose} handleClose={handleClose}
@@ -84,81 +73,18 @@ function ThemesSettings() {
handleSaveTheme={handleSaveTheme} handleSaveTheme={handleSaveTheme}
/> />
<Stack h={'100%'} w={'100%'}> <Stack h={'100%'} w={'100%'}>
<Group w={'100%'} justify='center' align='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={undefined}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={open}
Themes title={'Themes'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'Themes can be created here and applied to the homepage.',
<Popover.Target> highlightList: ['Clients', 'homepage'],
<ActionIcon description:
className={actionIconClasses.settingsTooltip} 'Themes are combinations of 5 colors and a background image, and can be added to a playlist. If a theme has been disabled, it will only be disabled with random/shuffle is enabled. If a theme is part of a playlist, it will appear while the playlist is active, regardless of random/shuffle.',
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
width: '500px',
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{'Themes can be created here and applied to the homepage.'}
</Text>
<Highlight
highlight={['Theme', 'Themes', 'Playlist', 'Playlists']}
className={textClasses.settingsTooltip}
classNames={textClasses}
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{
'Themes are combinations of 5 colors and a background image, and can be added to a playlist. If a theme has been disabled, it will only be disabled with random/shuffle is enabled. If a theme is part of a playlist, it will appear while the playlist is active, regardless of random/shuffle.'
}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={() => {
setSelectedTheme({
id: undefined,
name: 'default',
backgroundImage: 'none',
backgroundColor: '#EEEEEE',
searchColor: '#00000033',
primaryColor: '#222222',
accentColor: '#5c5c5c',
borderColor: '#5c5c5c',
});
open();
}}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
<Switch <Switch
label={ label={
@@ -171,7 +97,7 @@ function ThemesSettings() {
labelPosition='left' labelPosition='left'
pos={'absolute'} pos={'absolute'}
right={isMobile ? '2rem' : '1rem'} right={isMobile ? '2rem' : '1rem'}
top={isMobile ? '9.5rem' : undefined} top={isMobile ? '9.5rem' : '7rem'}
checked={currentTheme === 'random'} checked={currentTheme === 'random'}
onChange={(e) => { onChange={(e) => {
setCurrentTheme(e.target.checked ? 'random' : theme.name); setCurrentTheme(e.target.checked ? 'random' : theme.name);
@@ -179,7 +105,6 @@ function ThemesSettings() {
}} }}
color={theme.primaryColor} color={theme.primaryColor}
/> />
</Group>
<Group justify='center' align='center' pos={'relative'} mt={'1rem'}> <Group justify='center' align='center' pos={'relative'} mt={'1rem'}>
<Overlay <Overlay
display={currentTheme === 'random' ? 'flex' : 'none'} display={currentTheme === 'random' ? 'flex' : 'none'}
@@ -233,7 +158,7 @@ function ThemesSettings() {
))} ))}
</Group> </Group>
</Stack> </Stack>
</> </Stack>
); );
} }
export default ThemesSettings; export default ThemesSettings;

View File

@@ -1,31 +1,21 @@
import { import {
ActionIcon,
Button, Button,
Card, Card,
Divider, Divider,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Highlight,
Modal, Modal,
Popover, Popover,
Stack, Stack,
Text,
TextInput, TextInput,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'; import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiHelpCircle, FiPlus } from 'react-icons/fi'; import { cardClasses, modalClasses, textInputClasses } from 'styles';
import {
actionIconClasses,
buttonClasses,
cardClasses,
modalClasses,
textClasses,
textInputClasses,
} from 'styles';
import type { WolClient } from 'types'; import type { WolClient } from 'types';
import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedWolClient from './DebouncedWolClient'; import DebouncedWolClient from './DebouncedWolClient';
import WolClientSettingsItem from './WolClientSettingsItem'; import WolClientSettingsItem from './WolClientSettingsItem';
@@ -36,7 +26,7 @@ const WolClientSettings = () => {
}); });
const [wolClients, setWolClients] = useState<WolClient[]>([]); const [wolClients, setWolClients] = useState<WolClient[]>([]);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const { theme, isMobile, modalStyle } = useCommonHooks(); const { isMobile, modalStyle } = useCommonHooks();
const [newWolClient, setNewWolClient] = useState<WolClient>({ const [newWolClient, setNewWolClient] = useState<WolClient>({
id: undefined, id: undefined,
hostname: '', hostname: '',
@@ -141,69 +131,18 @@ const WolClientSettings = () => {
</Modal.Body> </Modal.Body>
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Group align='center' justify='center' mt={'1rem'}> <SettingsHeader
<Group w={isMobile ? undefined : '100%'}> setDragging={undefined}
<Text className={textClasses.settingsSubTitle} classNames={textClasses}> openModal={openModal}
WoL Clients title={'Search Providers'}
</Text> tooltip={{
<Popover withArrow withOverlay withinPortal={false}> title: 'Wake on Lan clients can be configured here.',
<Popover.Target> highlightList: ['Clients', 'homepage'],
<ActionIcon description: 'Clients can be powered on from the settings button the homepage.',
className={actionIconClasses.settingsTooltip}
classNames={actionIconClasses}
>
<FiHelpCircle />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown
style={{
color: theme.primaryColor,
backgroundColor: theme.backgroundColor,
}}
>
<Stack>
<Text className={textClasses.settingsTooltip} classNames={textClasses}>
{'Wake on Lan clients can be configured here.'}
</Text>
<Highlight
ta={'center'}
highlight={['Clients', 'homepage']}
className={textClasses.settingsTooltip}
classNames={textClasses}
highlightStyles={{
backgroundImage:
'linear-gradient(45deg, var(--color-text-acc), var(--color-text-pri))',
fontWeight: 700,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{'Clients can be powered on from the settings button the homepage.'}
</Highlight>
</Stack>
</Popover.Dropdown>
</Popover>
<Button
className={buttonClasses.tableButton}
classNames={buttonClasses}
onClick={openModal}
>
<FiPlus />
</Button>
</Group>
<TextInput
w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.settingsItem}
classNames={textInputClasses}
autoFocus={!isMobile}
placeholder='Search...'
value={searchValue}
onInput={(event: any) => {
setSearchValue(event.target.value);
}} }}
searchValue={searchValue}
setSearchValue={setSearchValue}
/> />
</Group>
<Stack w={'100%'} align='center'> <Stack w={'100%'} align='center'>
<Grid columns={18} w={'75%'}> <Grid columns={18} w={'75%'}>
{wolClients?.map((client: WolClient) => ( {wolClients?.map((client: WolClient) => (