Files
TriggerFrontend/src/pages/settings/components/Theming/ThemeCard.tsx
2025-07-10 14:37:44 -05:00

361 lines
9.4 KiB
TypeScript

import { ActionIcon, Box, Button, Group, Overlay, Stack, Text } from '@mantine/core';
import { useDisclosure, useElementSize } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI } from 'hooks';
import { useState } from 'react';
import { FiEdit, FiShuffle, FiTrash, FiX } from 'react-icons/fi';
import { actionIconClasses, boxClasses } from 'styles';
import type { Theme, ThemePlaylist } from 'types';
import DeleteItemModal from '../Shared/DeleteItemModal';
function ThemeCard({
cardTheme,
handleOpen,
allThemes,
themePlaylists,
}: {
cardTheme: Theme;
handleOpen: (cardTheme: Theme) => void;
allThemes: Theme[];
themePlaylists: ThemePlaylist[];
}) {
const { ref, width } = useElementSize();
const [hover, setHover] = useState(false);
const [shiftEdit, setShiftEdit] = useState<number>(0);
const [shiftShuffle, setShiftShuffle] = useState<number>(0);
const { mutateAsync } = useMutationAPI();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const {
handleThemeChange,
currentTheme,
setCurrentTheme,
theme,
currentThemePlaylist,
setCurrentThemePlaylist,
handleDelete,
} = useCommonHooks();
return (
<Group
gap={0}
align='center'
justify='center'
display={'grid'}
pos={'relative'}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<DeleteItemModal
modalOpened={modalOpened}
handleClose={closeModal}
handleDelete={() => handleDelete(cardTheme, 'api/themes')}
title={'Delete Theme'}
body={
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text>Are you sure you want to delete this theme?</Text>
<Text
style={{
fontWeight: 'bold',
fontSize: '1.25rem',
padding: '.5rem',
margin: '.5rem',
borderRadius: '10px',
backgroundColor: 'var(--mantine-color-accent-0)',
color: 'var(--mantine-color-background-0)',
}}
>{`"${cardTheme.name}"`}</Text>
<Text c='red'>This action cannot be undone!</Text>
</Stack>
}
/>
<Text
style={{
position: 'absolute',
top: -10,
left: 6,
paddingRight: '.25rem',
paddingLeft: '.25rem',
backgroundColor: cardTheme.backgroundColor,
borderRadius: '4px',
color:
cardTheme.borderColor === cardTheme.primaryColor
? cardTheme.accentColor
: cardTheme.primaryColor,
zIndex: 3,
boxShadow: `2px -2px ${cardTheme.borderColor}`,
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
maxWidth: width,
}}
>
{cardTheme.name}
</Text>
<Button
ref={ref}
w={'275px'}
h={'115px'}
disabled={currentTheme === 'random'}
style={{
backgroundImage:
cardTheme?.backgroundImage.toLocaleLowerCase() !== 'none'
? `linear-gradient(${cardTheme?.backgroundColor}33, ${cardTheme?.backgroundColor}33), url(../../api/backgroundImages/${cardTheme?.backgroundImage})`
: undefined,
backgroundSize:
cardTheme?.backgroundImage.toLocaleLowerCase() !== 'none'
? 'cover'
: undefined,
backgroundColor:
cardTheme?.backgroundImage.toLocaleLowerCase() === 'none'
? cardTheme?.backgroundColor
: undefined,
backgroundPosition: 'center',
border: `solid ${cardTheme.borderColor}`,
filter: cardTheme.randomEnabled === 0 ? 'saturate(0)' : undefined,
}}
onClick={() => {
setCurrentTheme(cardTheme.name);
setCurrentThemePlaylist('All');
}}
>
{hover && currentTheme !== 'random' && <Overlay opacity={0.5} zIndex={1} />}
<Group w={'100%'} gap={0} display={'flex'} align='center' justify='center'>
<Stack align='center'>
<Box
style={{
height: '10px',
width: '100px',
backgroundColor: cardTheme.searchColor,
borderRadius: '4px',
borderBottom: `solid 1px ${cardTheme.borderColor}`,
}}
/>
<Group justify='space-between'>
<Box
style={{
height: '.5rem',
width: '100px',
backgroundColor: cardTheme.accentColor,
borderRadius: '4px',
}}
/>
<Box
style={{
height: '.5rem',
width: '50px',
backgroundColor: cardTheme.primaryColor,
borderRadius: '4px',
}}
/>
</Group>
<Group w={'100%'}>
<Box
style={{
height: '.25rem',
width: '40px',
backgroundColor: cardTheme.primaryColor,
borderRadius: '4px',
}}
/>
</Group>
<Group w={'100%'}>
<Box
className={boxClasses.appItemExampleText}
style={{
color: cardTheme.primaryColor,
}}
>
lorem ipsum
</Box>
<Box
className={boxClasses.appItemExampleText}
style={{
color: cardTheme.primaryColor,
}}
>
lorem ipsum
</Box>
<Box
className={boxClasses.appItemExampleText}
style={{
color: cardTheme.primaryColor,
}}
>
lorem ipsum
</Box>
</Group>
</Stack>
</Group>
</Button>
{currentTheme === 'random' && cardTheme.name === theme.name ? (
<Group>
<Group
onMouseEnter={() => {
setShiftEdit(2);
}}
onMouseLeave={() => {
setShiftEdit(0);
}}
onClick={() => {
handleOpen(cardTheme);
}}
gap={0}
style={{
zIndex: 300,
position: 'absolute',
left: 0,
top: 27 - shiftEdit,
backgroundColor:
shiftEdit === 0 ? 'white' : 'var(--mantine-color-background-0)',
borderRadius: '50%',
padding: '.5rem',
margin: '.25rem',
border: 'solid 2px',
borderColor: shiftEdit === 0 ? 'black' : 'var(--mantine-color-primary-0)',
transition: 'all 0.2s',
}}
>
<FiEdit
style={{
fontSize: '2rem',
fontWeight: 'bold',
color: shiftEdit === 0 ? 'black' : 'var(--mantine-color-primary-0)',
cursor: 'pointer',
}}
/>
</Group>
<Group
onMouseEnter={() => {
setShiftShuffle(2);
}}
onMouseLeave={() => {
setShiftShuffle(0);
}}
onClick={() => {
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,
);
} else {
const themeList = JSON.parse(
themePlaylists?.find(
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
)?.themeList ?? '[]',
);
const filteredThemes = allThemes.filter((t: Theme) =>
themeList.includes(t.id),
);
handleThemeChange(filteredThemes, false);
}
}}
gap={0}
style={{
zIndex: 300,
position: 'absolute',
right: 0,
top: 27 - shiftShuffle,
backgroundColor:
shiftShuffle === 0 ? 'white' : 'var(--mantine-color-background-0)',
borderRadius: '50%',
padding: '.5rem',
margin: '.25rem',
border: 'solid 2px',
borderColor:
shiftShuffle === 0 ? 'black' : 'var(--mantine-color-primary-0)',
transition: 'all 0.2s',
}}
>
<FiShuffle
style={{
fontSize: '2rem',
fontWeight: 'bold',
color: shiftShuffle === 0 ? 'black' : 'var(--mantine-color-primary-0)',
cursor: 'pointer',
}}
/>
</Group>
</Group>
) : (
<Stack
gap={0}
style={{
zIndex: 2,
padding: '.25rem',
paddingRight: 0,
position: 'absolute',
right: 3,
backgroundColor: '#ffffff88',
borderTopLeftRadius: '10px',
borderBottomLeftRadius: '10px',
}}
align='center'
justify='center'
display={
currentTheme === 'random' && cardTheme.name === theme.name ? 'none' : 'flex'
}
>
<ActionIcon
style={{
background: cardTheme.randomEnabled
? 'rgba(255,255,255,1)'
: 'rgba(255,255,255,0.5)',
margin: '.125rem',
}}
styles={{
root: {
height: '10px',
width: '10px',
},
icon: {
height: '20px',
width: '20px',
fontSize: '1.5rem',
margin: 0,
padding: 0,
},
}}
onClick={() => {
mutateAsync({
method: 'PATCH',
endpoint: 'api/themes',
body: { ...cardTheme, randomEnabled: !cardTheme.randomEnabled },
});
}}
>
{cardTheme.randomEnabled ? <FiShuffle color='green' /> : <FiX color='red' />}
</ActionIcon>
<ActionIcon
className={actionIconClasses.themeEditButton}
classNames={actionIconClasses}
onClick={() => handleOpen(cardTheme)}
mb={'.125rem'}
>
<FiEdit color='green' />
</ActionIcon>
<ActionIcon
className={actionIconClasses.themeEditButton}
classNames={actionIconClasses}
onClick={openModal}
disabled={cardTheme.name === theme.name}
>
<FiTrash color='red' />
</ActionIcon>
</Stack>
)}
</Group>
);
}
export default ThemeCard;