Files
TriggerFrontend/src/pages/settings/components/Theming/ThemePlaylists.tsx
Sean Edmunds 3d8a5ad7c8 Style updates
2025-07-11 18:12:25 -05:00

308 lines
9.4 KiB
TypeScript

import { Box, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { FiChevronRight } from 'react-icons/fi';
import { textClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types';
import NoItemsFound from '../Shared/NoItemsFound';
import SettingsHeader from '../Shared/SettingsHeader';
import ThemePlaylistCard from './ThemePlaylistCard';
import ThemePlaylistModal from './ThemePlaylistModal';
function ThemePlaylists() {
const themesEndpoint = 'api/themes';
const { data: allThemeData } = useQueryAPI({
endpoint: themesEndpoint,
});
const themePlaylistsEndpoint = 'api/themePlaylists';
const { data: themePlaylistData, isFetched: isThemePlaylistDataFetched } = useQueryAPI({
endpoint: themePlaylistsEndpoint,
});
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
const defaultPlaylist = {
id: undefined,
default: 0,
name: 'New Playlist',
themeList: '[]',
};
const { mutateAsync } = useMutationAPI();
const [opened, { open, close }] = useDisclosure(false);
const [selectedPlaylist, setSelectedPlaylist] =
useState<ThemePlaylist>(defaultPlaylist);
const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isLoading, setIsLoading] = useState(false);
const handleOpen = (playlist: ThemePlaylist) => {
setSelectedPlaylist(playlist);
open();
};
const handleClose = () => {
setSelectedPlaylist(defaultPlaylist);
close();
};
const handleSavePlaylist = () => {
mutateAsync({
method: selectedPlaylist?.id === undefined ? 'POST' : 'PATCH',
endpoint: themePlaylistsEndpoint,
body: selectedPlaylist ?? {},
});
close();
};
useEffect(() => {
if (themePlaylistData && isThemePlaylistDataFetched) {
setThemePlaylists(
themePlaylistData.filter(
(a: ThemePlaylist) =>
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
a.name.includes('Time of Day'),
),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [themePlaylistData, isThemePlaylistDataFetched, debouncedSearch]);
return (
<Stack>
<ThemePlaylistModal
opened={opened}
handleClose={handleClose}
selectedPlaylist={selectedPlaylist}
defaultPlaylist={defaultPlaylist}
setSelectedPlaylist={setSelectedPlaylist}
handleSavePlaylist={handleSavePlaylist}
allThemes={allThemeData}
createPlaylist={mutateAsync}
/>
<SettingsHeader
setDragging={undefined}
openModal={open}
title={'Theme Playlists'}
tooltip={{
title: 'Theme playlists can be created here and applied to the homepage.',
highlightList: ['theme', 'themes', 'homepage', 'playlist'],
description:
'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.',
}}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchPlaceholder={'Search Theme Playlists...'}
isLoading={isLoading}
setIsLoading={setIsLoading}
/>
<Stack mt={isMobile ? '10rem' : '6rem'}>
{allThemeData && (
<>
<Group justify='center' align='center' pos={'relative'}>
<Text
p={0}
className={textClasses.settingsSubTitle}
classNames={textClasses}
>
Defaults
</Text>
</Group>
<Group justify='center' align='center' pos={'relative'}>
<Group pos={'relative'}>
{'All' === currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
left: -14,
zIndex: 10,
fontSize: '2rem',
fontWeight: 'bold',
borderRadius: '50%',
backgroundColor: 'var(--mantine-color-border-0)',
color: 'var(--mantine-color-background-0)',
}}
/>
)}
<Box>
<ThemePlaylistCard
cardPlaylist={{
id: undefined,
name: 'All',
default: 1,
themeList: `[${allThemeData?.map((a: Theme) => a.id) ?? ''}]`,
}}
handleOpen={handleOpen}
allThemes={allThemeData}
/>
</Box>
</Group>
<Group pos={'relative'}>
{'Backgrounds' === currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
left: -14,
zIndex: 10,
fontSize: '2rem',
fontWeight: 'bold',
borderRadius: '50%',
backgroundColor: 'var(--mantine-color-border-0)',
color:
theme.borderColor === theme.primaryColor
? 'var(--mantine-color-accent-0)'
: 'var(--mantine-color-background-0)',
}}
/>
)}
<Box>
<ThemePlaylistCard
cardPlaylist={{
id: undefined,
name: 'Backgrounds',
default: 1,
themeList: `[${allThemeData?.filter((a: any) => !a.backgroundImage.toLowerCase().includes('none')).map((a: Theme) => a.id) ?? ''}]`,
}}
handleOpen={handleOpen}
allThemes={allThemeData}
/>
</Box>
</Group>
<Group pos={'relative'}>
{'No Backgrounds' === currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
left: -14,
zIndex: 10,
fontSize: '2rem',
fontWeight: 'bold',
borderRadius: '50%',
backgroundColor: 'var(--mantine-color-border-0)',
color:
theme.borderColor === theme.primaryColor
? 'var(--mantine-color-accent-0)'
: 'var(--mantine-color-background-0)',
}}
/>
)}
<Box>
<ThemePlaylistCard
cardPlaylist={{
id: undefined,
name: 'No Backgrounds',
default: 1,
themeList: `[${allThemeData?.filter((a: any) => a.backgroundImage.toLowerCase().includes('none')).map((a: Theme) => a.id) ?? ''}]`,
}}
handleOpen={handleOpen}
allThemes={allThemeData}
/>
</Box>
</Group>
<Group pos={'relative'}>
{'Time of Day' === currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
left: -14,
zIndex: 10,
fontSize: '2rem',
fontWeight: 'bold',
borderRadius: '50%',
backgroundColor: 'var(--mantine-color-border-0)',
color:
theme.borderColor === theme.primaryColor
? 'var(--mantine-color-accent-0)'
: 'var(--mantine-color-background-0)',
}}
/>
)}
<Box>
<ThemePlaylistCard
cardPlaylist={{
id:
themePlaylists?.filter(
(a: ThemePlaylist) => a.name === 'Time of Day',
)[0]?.id ?? undefined,
name: 'Time of Day',
default: 1,
themeList:
themePlaylists?.filter(
(a: ThemePlaylist) => a.name === 'Time of Day',
)[0]?.themeList ?? '[]',
}}
handleOpen={handleOpen}
allThemes={allThemeData}
/>
</Box>
</Group>
</Group>
<Group justify='center' align='center' pos={'relative'}>
<Text
p={0}
className={textClasses.settingsSubTitle}
classNames={textClasses}
>
User Made
</Text>
</Group>
<Group
justify='center'
align='center'
pos={'relative'}
mb={isMobile ? '6rem' : undefined}
>
{themePlaylists.length > 1 ? (
<SimpleGrid cols={isMobile ? 1 : 4}>
{themePlaylists
.filter((playlist: ThemePlaylist) => playlist.default === 0)
.sort((a: ThemePlaylist, b: ThemePlaylist) =>
a.name > b.name ? 1 : -1,
)
.map((cardPlaylist: ThemePlaylist) => {
return (
<Group key={cardPlaylist.name} pos={'relative'}>
{cardPlaylist.name === currentThemePlaylist && (
<FiChevronRight
style={{
position: 'absolute',
left: -14,
zIndex: 10,
fontSize: '2rem',
fontWeight: 'bold',
borderRadius: '50%',
backgroundColor: 'var(--mantine-color-border-0)',
color:
theme.borderColor === theme.primaryColor
? 'var(--mantine-color-accent-0)'
: 'var(--mantine-color-primary-0)',
}}
/>
)}
<Box>
<ThemePlaylistCard
cardPlaylist={cardPlaylist}
handleOpen={handleOpen}
allThemes={allThemeData}
/>
</Box>
</Group>
);
})}
</SimpleGrid>
) : (
<Group w={'100%'} style={{ display: 'flex', justifyContent: 'center' }}>
<NoItemsFound />
</Group>
)}
</Group>
</>
)}
</Stack>
</Stack>
);
}
export default ThemePlaylists;