Theme modal updates
This commit is contained in:
@@ -12,14 +12,16 @@ import { MiniAppItem, MiniGroupItem, ThemeManagementPill } from '.';
|
||||
|
||||
export function ThemeCard({
|
||||
cardTheme,
|
||||
handleOpen,
|
||||
allThemes,
|
||||
themePlaylists,
|
||||
setSelectedTheme,
|
||||
openThemeModal,
|
||||
}: {
|
||||
cardTheme: Theme;
|
||||
handleOpen: (cardTheme: Theme) => void;
|
||||
allThemes: Theme[];
|
||||
themePlaylists: ThemePlaylist[];
|
||||
setSelectedTheme: (theme: Theme) => void;
|
||||
openThemeModal: () => void;
|
||||
}) {
|
||||
const { hovered, ref: hoverRef } = useHover();
|
||||
const { ref, width } = useElementSize();
|
||||
@@ -29,6 +31,10 @@ export function ThemeCard({
|
||||
const [hoverPreview, setHoverPreview] = useDebouncedState(false, 1000);
|
||||
const [transition, setTransition] = useDebouncedState(false, 200);
|
||||
|
||||
const handleOpen = (theme: Theme) => {
|
||||
setSelectedTheme(theme);
|
||||
openThemeModal();
|
||||
};
|
||||
return (
|
||||
<Group
|
||||
gap={0}
|
||||
|
||||
@@ -1,928 +0,0 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
ColorPicker,
|
||||
ColorSwatch,
|
||||
Divider,
|
||||
Group,
|
||||
Image,
|
||||
Modal,
|
||||
Overlay,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
UnstyledButton,
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure, useLongPress } from '@mantine/hooks';
|
||||
import { useCommonHooks } from 'hooks';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FaArrowRight } from 'react-icons/fa';
|
||||
import { FiImage, FiSave, FiX } from 'react-icons/fi';
|
||||
import { modalClasses, scrollAreaClasses, textClasses, textInputClasses } from 'styles';
|
||||
import { BackgroundProperties, type Theme } from 'types';
|
||||
import { ThemeCard } from './ThemeCard';
|
||||
import { ThemeMiniCard } from './ThemeMiniCard';
|
||||
import { ThemeModalContent } from './ThemeModalContent';
|
||||
import { ThemePreviewMiddleware } from './ThemePreviewMiddleware';
|
||||
|
||||
export function ThemeModal({
|
||||
opened,
|
||||
handleClose,
|
||||
selectedTheme,
|
||||
setSelectedTheme,
|
||||
openPicker,
|
||||
backgroundList,
|
||||
pickerOpen,
|
||||
closePicker,
|
||||
handleSaveTheme,
|
||||
isNew,
|
||||
setIsNew,
|
||||
allThemes,
|
||||
}: {
|
||||
opened: boolean;
|
||||
handleClose: () => void;
|
||||
selectedTheme: Theme;
|
||||
setSelectedTheme: (selectedTheme: Theme) => void;
|
||||
openPicker: () => void;
|
||||
backgroundList: string[];
|
||||
pickerOpen: boolean;
|
||||
closePicker: () => void;
|
||||
handleSaveTheme: () => void;
|
||||
isNew: boolean;
|
||||
setIsNew: (isNew: boolean) => void;
|
||||
allThemes: Theme[];
|
||||
}) {
|
||||
const [pressed, setPressed] = useState<any>(false);
|
||||
const handlers = useLongPress(() => {
|
||||
setPressed(true);
|
||||
});
|
||||
const { isMobile, theme, getObjectDiff } = useCommonHooks();
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const [query, setQuery] = useState('');
|
||||
const [hovered, setHovered] = useState(-1);
|
||||
const [colorId, setColorId] = useState<string>('');
|
||||
const [colorValue, setColorValue] = useState('#000000');
|
||||
const [defaultTheme, setDefaultTheme] = useState<Theme>({ ...selectedTheme });
|
||||
const [designerTheme, setDesignerTheme] = useState<Theme>({
|
||||
...selectedTheme,
|
||||
});
|
||||
const filtered = backgroundList.filter((item: string) =>
|
||||
item.toLowerCase().includes(query.toLowerCase()),
|
||||
);
|
||||
const [duplicate, setDuplicate] = useState(false);
|
||||
|
||||
const [backgroundPicker, { open: openBackgroundPicker, close: closeBackgroundPicker }] =
|
||||
useDisclosure(false);
|
||||
|
||||
const re = /(?:\.([^.]+))?$/;
|
||||
const items = filtered.map((item: string, index: number) => {
|
||||
return (
|
||||
<UnstyledButton
|
||||
data-list-item
|
||||
key={item}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '75px',
|
||||
background:
|
||||
item !== 'none' && item !== 'None'
|
||||
? index === hovered
|
||||
? `linear-gradient(${selectedTheme.backgroundColor}88, ${selectedTheme.backgroundColor}88), no-repeat center url(../../api/backgroundImages/thumbs/${item})`
|
||||
: `no-repeat center url(../../api/backgroundImages/thumbs/${item})`
|
||||
: 'repeating-linear-gradient(45deg, #33333366, #33333366 10px, #88888866 10px, #88888866 20px)',
|
||||
backgroundSize: item !== 'none' ? 'cover' : undefined,
|
||||
backgroundColor: item === 'none' ? '#00000088' : undefined,
|
||||
paddingLeft: '.5rem',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onClick={() =>
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundImage: item,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Group h={'100%'} w={'100%'} justify='space-between'>
|
||||
<Text
|
||||
style={{
|
||||
width: 'min-content',
|
||||
backdropFilter: 'blur(5px) brightness(.25)',
|
||||
padding: '.5rem',
|
||||
borderBottomRightRadius: 4,
|
||||
marginTop: '-2.15rem',
|
||||
marginLeft: '-.5rem',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{item.replace(/\.[^/.]+$/, '')}
|
||||
</Text>
|
||||
{item !== 'None' && (
|
||||
<Text
|
||||
style={{
|
||||
width: 'min-content',
|
||||
backdropFilter: 'blur(5px) brightness(.25)',
|
||||
padding: '.5rem',
|
||||
marginTop: '2rem',
|
||||
marginBottom: -1,
|
||||
borderTopLeftRadius: 4,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{re.exec(item.toUpperCase())?.[1]}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
);
|
||||
});
|
||||
|
||||
const handleSwatchClick = () => {
|
||||
closeBackgroundPicker();
|
||||
openPicker();
|
||||
setDesignerTheme(selectedTheme);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDesignerTheme(selectedTheme);
|
||||
if (defaultTheme.id !== selectedTheme.id) {
|
||||
setDefaultTheme(selectedTheme);
|
||||
}
|
||||
}, [selectedTheme]);
|
||||
|
||||
return (
|
||||
<Modal.Root
|
||||
size={'75%'}
|
||||
className={modalClasses.editThemeModal}
|
||||
classNames={modalClasses}
|
||||
opened={opened}
|
||||
onClose={() => {
|
||||
handleClose();
|
||||
setDuplicate(false);
|
||||
}}
|
||||
fullScreen={isMobile}
|
||||
centered
|
||||
keepMounted={false}
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header style={{ justifyContent: 'center' }}>
|
||||
{!isNew && (
|
||||
<Group pos={'absolute'}>
|
||||
<Text c={'var(--mantine-color-background-0)'}>Name:</Text>
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
classNames={textInputClasses}
|
||||
value={selectedTheme.name}
|
||||
onChange={(e) =>
|
||||
setSelectedTheme({ ...selectedTheme, name: e.target.value })
|
||||
}
|
||||
styles={{
|
||||
label: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
input: {
|
||||
color: selectedTheme.primaryColor,
|
||||
backgroundColor: selectedTheme.backgroundColor,
|
||||
border: 'none',
|
||||
borderBottom: '1px solid var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{isNew ? (
|
||||
<Stack align='center' p={'2rem'}>
|
||||
{!duplicate ? (
|
||||
<>
|
||||
<Text className={textClasses.settingsSubTitle} classNames={textClasses}>
|
||||
Please choose an option:
|
||||
</Text>
|
||||
<Group>
|
||||
<Stack
|
||||
justify='center'
|
||||
align='center'
|
||||
bg={'var(--mantine-color-primary-0)'}
|
||||
px={'1rem'}
|
||||
py={'3rem'}
|
||||
style={{ borderRadius: '8px' }}
|
||||
>
|
||||
<ThemeCard
|
||||
cardTheme={{
|
||||
id: undefined,
|
||||
name: 'Default',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: '#EEEEEE',
|
||||
searchColor: '#00000033',
|
||||
primaryColor: '#222222',
|
||||
accentColor: '#5c5c5c',
|
||||
borderColor: '#5c5c5c',
|
||||
randomEnabled: 1,
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
}}
|
||||
handleOpen={() => {}}
|
||||
allThemes={[]}
|
||||
themePlaylists={[]}
|
||||
/>
|
||||
<Button
|
||||
pos={'absolute'}
|
||||
w={'275px'}
|
||||
h={'115px'}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: '3px 3px var(--mantine-color-border-0)',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
styles={{
|
||||
label: {
|
||||
backgroundColor: 'var(--mantine-color-background-0)',
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
height: 'min-content',
|
||||
padding: '.5rem',
|
||||
borderRadius: 4,
|
||||
position: 'absolute',
|
||||
bottom: '-16px',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}}
|
||||
onClick={() => setIsNew(false)}
|
||||
>
|
||||
New Theme from Scratch
|
||||
</Button>
|
||||
</Stack>
|
||||
<Divider
|
||||
color={'var(--mantine-color-primary-0)'}
|
||||
my={'sm'}
|
||||
orientation='vertical'
|
||||
m={'2rem'}
|
||||
/>
|
||||
<Stack
|
||||
justify='center'
|
||||
align='center'
|
||||
pos={'relative'}
|
||||
bg={'var(--mantine-color-primary-0)'}
|
||||
px={'1rem'}
|
||||
py={'3rem'}
|
||||
style={{ borderRadius: '8px' }}
|
||||
>
|
||||
<ThemeCard
|
||||
cardTheme={{
|
||||
...allThemes[Math.floor(Math.random() * allThemes.length)],
|
||||
randomEnabled: 1,
|
||||
}}
|
||||
handleOpen={() => {}}
|
||||
allThemes={[]}
|
||||
themePlaylists={[]}
|
||||
/>
|
||||
<Overlay
|
||||
bg={'transparent'}
|
||||
zIndex={0}
|
||||
pos={'absolute'}
|
||||
w={'275px'}
|
||||
h={'115px'}
|
||||
top={'3rem'}
|
||||
left={'1rem'}
|
||||
style={{
|
||||
backdropFilter: 'grayscale(80%) brightness(80%)',
|
||||
mask: 'linear-gradient(105deg, rgba(0, 0, 0, 1) 48%, rgba(0, 0, 0, 0) 52%)',
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
}}
|
||||
/>
|
||||
<FaArrowRight
|
||||
size={45}
|
||||
style={{
|
||||
color: 'var(--mantine-color-background-0)',
|
||||
position: 'absolute',
|
||||
stroke: 'var(--mantine-color-accent-0)',
|
||||
strokeWidth: 25,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
pos={'absolute'}
|
||||
w={'275px'}
|
||||
h={'115px'}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: '3px 3px var(--mantine-color-border-0)',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
styles={{
|
||||
label: {
|
||||
backgroundColor: 'var(--mantine-color-background-0)',
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
height: 'min-content',
|
||||
padding: '.5rem',
|
||||
borderRadius: 4,
|
||||
position: 'absolute',
|
||||
bottom: '-16px',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}}
|
||||
onClick={() => setDuplicate(true)}
|
||||
>
|
||||
Duplicate existing Theme
|
||||
</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
</>
|
||||
) : (
|
||||
<Stack align='center'>
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='center'
|
||||
style={{
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-accent-0), var(--mantine-color-background-0))',
|
||||
padding: '1rem',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{allThemes?.map((miniTheme: Theme) => {
|
||||
return (
|
||||
<Box
|
||||
{...handlers}
|
||||
key={miniTheme.id}
|
||||
onClick={() => {
|
||||
!pressed && setSelectedTheme(miniTheme);
|
||||
setPressed(false);
|
||||
}}
|
||||
>
|
||||
<ThemePreviewMiddleware
|
||||
newThemeList={[miniTheme.id as number]}
|
||||
miniTheme={miniTheme}
|
||||
themeList={[]}
|
||||
selectedPlaylist={undefined}
|
||||
setSelectedPlaylist={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
<Group>
|
||||
<Group
|
||||
style={{
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-primary-0), var(--mantine-color-background-0))',
|
||||
padding: '1rem',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<ThemeMiniCard miniTheme={selectedTheme} />
|
||||
<Button
|
||||
disabled={selectedTheme.name === 'default'}
|
||||
onClick={() => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
name: `${selectedTheme.name} copy`,
|
||||
});
|
||||
setIsNew(false);
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</Group>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
setDuplicate(false);
|
||||
setSelectedTheme({
|
||||
id: undefined,
|
||||
name: 'Default',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: '#EEEEEE',
|
||||
searchColor: '#00000033',
|
||||
primaryColor: '#222222',
|
||||
accentColor: '#5c5c5c',
|
||||
borderColor: '#5c5c5c',
|
||||
randomEnabled: 1,
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
) : (
|
||||
<>
|
||||
<Group p={'2rem'} w={'100%'} align='flex-start' justify='space-between'>
|
||||
<Stack display={'flex'} pos={'relative'} w={isMobile ? '100%' : '70%'}>
|
||||
<ThemeModalContent selectedTheme={designerTheme} />
|
||||
<Tooltip
|
||||
label={'Change Primary Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('primaryColor');
|
||||
setColorValue(selectedTheme.primaryColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.primaryColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
top={'6.25rem'}
|
||||
left={'1.5rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Accent Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('accentColor');
|
||||
setColorValue(selectedTheme.accentColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.accentColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
bottom={'6rem'}
|
||||
left={'11rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Background Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('backgroundColor');
|
||||
setColorValue(selectedTheme.backgroundColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.backgroundColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
bottom={'10rem'}
|
||||
right={'3.5rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Border Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('borderColor');
|
||||
setColorValue(selectedTheme.borderColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.borderColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
top={'4.5rem'}
|
||||
right={'15%'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Search Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('searchColor');
|
||||
setColorValue(selectedTheme.searchColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.searchColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
top={'3.25rem'}
|
||||
left={'6.5rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Background'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
closePicker();
|
||||
openBackgroundPicker();
|
||||
setDesignerTheme(selectedTheme);
|
||||
}}
|
||||
pos={'absolute'}
|
||||
right={0}
|
||||
>
|
||||
<FiImage size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Stack
|
||||
w={isMobile ? '100%' : '28%'}
|
||||
h={'436px'}
|
||||
p={'1rem'}
|
||||
style={{
|
||||
borderRadius: '1rem',
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-primary-0) 20%, transparent)',
|
||||
}}
|
||||
>
|
||||
{pickerOpen && (
|
||||
<Stack h={'100%'} align='center' justify='space-between'>
|
||||
<Text
|
||||
className={textClasses.sectionTitle}
|
||||
classNames={textClasses}
|
||||
style={{
|
||||
textShadow: theme.textShadowsEnabled
|
||||
? '1px 1px 1px color-mix(in srgb, var(--mantine-color-accent-0), transparent 50%)'
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
Change{' '}
|
||||
{
|
||||
BackgroundProperties[
|
||||
colorId as keyof typeof BackgroundProperties
|
||||
]
|
||||
}
|
||||
</Text>
|
||||
{colorId === 'backgroundColor' && (
|
||||
<ColorPicker
|
||||
value={colorValue}
|
||||
onChange={(e) => {
|
||||
setColorValue(e);
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundColor: e,
|
||||
});
|
||||
}}
|
||||
format={'hex'}
|
||||
w={'90%'}
|
||||
mb={'1.5rem'}
|
||||
/>
|
||||
)}
|
||||
{colorId !== 'backgroundColor' && (
|
||||
<ColorPicker
|
||||
value={colorValue}
|
||||
onChange={(e) => {
|
||||
setColorValue(e);
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
[colorId]: e,
|
||||
});
|
||||
}}
|
||||
format={'hexa'}
|
||||
w={'90%'}
|
||||
/>
|
||||
)}
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
classNames={textInputClasses}
|
||||
w={'90%'}
|
||||
value={colorValue}
|
||||
onChange={(e) => setColorValue(e.target.value)}
|
||||
/>
|
||||
<Group mt={'auto'} justify='center'>
|
||||
<Button
|
||||
color={'green'}
|
||||
onClick={() => {
|
||||
switch (colorId) {
|
||||
case 'primaryColor':
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
primaryColor: colorValue,
|
||||
});
|
||||
break;
|
||||
case 'accentColor':
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
accentColor: colorValue,
|
||||
});
|
||||
break;
|
||||
case 'backgroundColor':
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
backgroundColor: colorValue,
|
||||
});
|
||||
break;
|
||||
case 'borderColor':
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
borderColor: colorValue,
|
||||
});
|
||||
break;
|
||||
case 'searchColor':
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
searchColor: colorValue,
|
||||
});
|
||||
break;
|
||||
}
|
||||
closePicker();
|
||||
}}
|
||||
>
|
||||
<FiSave />
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closePicker();
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
[colorId]: selectedTheme[colorId as keyof Theme],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiX />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
{backgroundPicker && (
|
||||
<Stack h={'100%'} align='center' justify='center'>
|
||||
<Group gap={0}>
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
classNames={textInputClasses}
|
||||
placeholder='Search'
|
||||
value={query.replace(/\.[^/.]+$/, '')}
|
||||
rightSection={
|
||||
<FiX
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
margin: '.25rem',
|
||||
}}
|
||||
onClick={() => {
|
||||
setQuery('');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
onChange={(event) => {
|
||||
setQuery(event.currentTarget.value);
|
||||
setHovered(-1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
setHovered((current) => {
|
||||
const nextIndex =
|
||||
current + 1 >= filtered.length ? current : current + 1;
|
||||
viewportRef.current
|
||||
?.querySelectorAll('[data-list-item]')
|
||||
?.[nextIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
return nextIndex;
|
||||
});
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
setHovered((current) => {
|
||||
const nextIndex = current - 1 < 0 ? current : current - 1;
|
||||
viewportRef.current
|
||||
?.querySelectorAll('[data-list-item]')
|
||||
?.[nextIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
return nextIndex;
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<ScrollArea.Autosize
|
||||
viewportRef={viewportRef}
|
||||
className={scrollAreaClasses.default}
|
||||
classNames={scrollAreaClasses}
|
||||
style={{
|
||||
height: 325,
|
||||
width: '90%',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Stack gap={0} align='center' display={'block'}>
|
||||
{items}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
<Group justify='center'>
|
||||
<Button
|
||||
color={'green'}
|
||||
onClick={() => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
backgroundImage: designerTheme.backgroundImage,
|
||||
});
|
||||
closeBackgroundPicker();
|
||||
}}
|
||||
>
|
||||
<FiSave />
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeBackgroundPicker();
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundImage: selectedTheme.backgroundImage,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiX />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
{!pickerOpen && !backgroundPicker && (
|
||||
<Stack h={'100%'} justify='space-between' align='center' gap={4}>
|
||||
<Stack w={'100%'} align='center' gap={4}>
|
||||
<Checkbox
|
||||
label='Text Shadows'
|
||||
checked={selectedTheme.textShadowsEnabled === 1}
|
||||
onChange={(e) => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
textShadowsEnabled: e.target.checked ? 1 : 0,
|
||||
});
|
||||
}}
|
||||
styles={{
|
||||
root: { width: '50%' },
|
||||
body: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
label='App Shadows'
|
||||
checked={selectedTheme.appShadowsEnabled === 1}
|
||||
onChange={(e) => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
appShadowsEnabled: e.target.checked ? 1 : 0,
|
||||
});
|
||||
}}
|
||||
styles={{
|
||||
root: { width: '50%' },
|
||||
body: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
{JSON.stringify(defaultTheme) !== JSON.stringify(selectedTheme) && (
|
||||
<>
|
||||
<Text fz={'1.5rem'}>Changes:</Text>
|
||||
<ScrollArea.Autosize
|
||||
w={'100%'}
|
||||
className={scrollAreaClasses.default}
|
||||
classNames={scrollAreaClasses}
|
||||
style={{
|
||||
backgroundColor: 'var(--mantine-color-accent-0)',
|
||||
borderRadius: 8,
|
||||
padding: '1rem',
|
||||
}}
|
||||
>
|
||||
<Stack w={'100%'} gap={0}>
|
||||
{getObjectDiff(defaultTheme, designerTheme) !== null &&
|
||||
Object.keys(
|
||||
getObjectDiff(defaultTheme, designerTheme),
|
||||
).map((key: any) => {
|
||||
return (
|
||||
<Stack
|
||||
w={'100%'}
|
||||
align='center'
|
||||
justify='center'
|
||||
gap={0}
|
||||
key={key}
|
||||
>
|
||||
<Text key={key}>
|
||||
{
|
||||
BackgroundProperties[
|
||||
key as keyof typeof BackgroundProperties
|
||||
]
|
||||
}
|
||||
</Text>
|
||||
{key === 'name' && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
<Text>{defaultTheme[key as keyof Theme]}</Text>
|
||||
<Text>→</Text>
|
||||
<Text>{designerTheme[key as keyof Theme]}</Text>
|
||||
</Group>
|
||||
)}
|
||||
{key === 'backgroundImage' && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
<Image
|
||||
w={'40%'}
|
||||
src={
|
||||
(
|
||||
defaultTheme[key as keyof Theme] as string
|
||||
).toLocaleLowerCase() === 'none'
|
||||
? 'https://placehold.co/540x360?text=None'
|
||||
: `../../api/backgroundImages/thumbs/${defaultTheme[key as keyof Theme] as string}`
|
||||
}
|
||||
/>
|
||||
<Text>→</Text>
|
||||
<Image
|
||||
w={'40%'}
|
||||
src={
|
||||
(
|
||||
designerTheme[
|
||||
key as keyof Theme
|
||||
] as string
|
||||
).toLocaleLowerCase() === 'none'
|
||||
? 'https://placehold.co/540x360?text=None'
|
||||
: `../../api/backgroundImages/thumbs/${designerTheme[key as keyof Theme] as string}`
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{key.includes('Color') && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
<ColorSwatch
|
||||
color={
|
||||
defaultTheme[key as keyof Theme] as string
|
||||
}
|
||||
/>
|
||||
<Text>→</Text>
|
||||
<ColorSwatch
|
||||
color={
|
||||
designerTheme[key as keyof Theme] as string
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{key.includes('Shadow') && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
{defaultTheme[key as keyof Theme] === 0 ? (
|
||||
<Text>Off</Text>
|
||||
) : (
|
||||
<Text>On</Text>
|
||||
)}
|
||||
<Text>→</Text>
|
||||
{designerTheme[key as keyof Theme] === 0 ? (
|
||||
<Text>Off</Text>
|
||||
) : (
|
||||
<Text>On</Text>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSelectedTheme({ ...defaultTheme });
|
||||
}}
|
||||
mih={'36px'}
|
||||
>
|
||||
Reset Changes
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group>
|
||||
<Button
|
||||
disabled={
|
||||
JSON.stringify(defaultTheme) === JSON.stringify(selectedTheme)
|
||||
}
|
||||
color={'green'}
|
||||
onClick={handleSaveTheme}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeBackgroundPicker();
|
||||
handleClose();
|
||||
setDuplicate(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { Box, Button, Divider, Group, Overlay, Stack, Text } from '@mantine/core';
|
||||
import { useLongPress } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { FaArrowRight } from 'react-icons/fa';
|
||||
import { textClasses } from 'styles';
|
||||
import type { Theme } from 'types';
|
||||
import { ThemeCard } from '../ThemeCard';
|
||||
import { ThemeMiniCard } from '../ThemeMiniCard';
|
||||
import { ThemePreviewMiddleware } from '../ThemePreviewMiddleware';
|
||||
|
||||
export function NewThemeStarter({
|
||||
allThemes,
|
||||
setIsNew,
|
||||
duplicate,
|
||||
setDuplicate,
|
||||
selectedTheme,
|
||||
setSelectedTheme,
|
||||
}: {
|
||||
allThemes: Theme[];
|
||||
setIsNew: (isNew: boolean) => void;
|
||||
duplicate: boolean;
|
||||
setDuplicate: (duplicate: boolean) => void;
|
||||
selectedTheme: Theme;
|
||||
setSelectedTheme: (selectedTheme: Theme) => void;
|
||||
}) {
|
||||
const [pressed, setPressed] = useState<any>(false);
|
||||
const handlers = useLongPress(() => {
|
||||
setPressed(true);
|
||||
});
|
||||
const randomTheme = {
|
||||
...allThemes[Math.floor(Math.random() * allThemes.length)],
|
||||
randomEnabled: 1,
|
||||
};
|
||||
return (
|
||||
<Stack align='center' p={'2rem'}>
|
||||
{!duplicate ? (
|
||||
<>
|
||||
<Text className={textClasses.settingsSubTitle} classNames={textClasses}>
|
||||
Please choose an option:
|
||||
</Text>
|
||||
<Group>
|
||||
<Stack
|
||||
justify='center'
|
||||
align='center'
|
||||
bg={'var(--mantine-color-primary-0)'}
|
||||
px={'1rem'}
|
||||
py={'3rem'}
|
||||
style={{ borderRadius: '8px' }}
|
||||
>
|
||||
<ThemeCard
|
||||
cardTheme={{
|
||||
id: undefined,
|
||||
name: 'Default',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: '#EEEEEE',
|
||||
searchColor: '#00000033',
|
||||
primaryColor: '#222222',
|
||||
accentColor: '#5c5c5c',
|
||||
borderColor: '#5c5c5c',
|
||||
randomEnabled: 1,
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
}}
|
||||
allThemes={[]}
|
||||
themePlaylists={[]}
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
openThemeModal={() => {}}
|
||||
/>
|
||||
<Button
|
||||
pos={'absolute'}
|
||||
w={'275px'}
|
||||
h={'115px'}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: '3px 3px var(--mantine-color-border-0)',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
styles={{
|
||||
label: {
|
||||
backgroundColor: 'var(--mantine-color-background-0)',
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
height: 'min-content',
|
||||
padding: '.5rem',
|
||||
borderRadius: 4,
|
||||
position: 'absolute',
|
||||
bottom: '-16px',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}}
|
||||
onClick={() => setIsNew(false)}
|
||||
>
|
||||
New Theme from Scratch
|
||||
</Button>
|
||||
</Stack>
|
||||
<Divider
|
||||
color={'var(--mantine-color-primary-0)'}
|
||||
my={'sm'}
|
||||
orientation='vertical'
|
||||
m={'2rem'}
|
||||
/>
|
||||
<Stack
|
||||
justify='center'
|
||||
align='center'
|
||||
pos={'relative'}
|
||||
bg={'var(--mantine-color-primary-0)'}
|
||||
px={'1rem'}
|
||||
py={'3rem'}
|
||||
style={{ borderRadius: '8px' }}
|
||||
>
|
||||
<ThemeCard
|
||||
cardTheme={randomTheme}
|
||||
allThemes={[]}
|
||||
themePlaylists={[]}
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
openThemeModal={() => {}}
|
||||
/>
|
||||
<Overlay
|
||||
bg={'transparent'}
|
||||
zIndex={0}
|
||||
pos={'absolute'}
|
||||
w={'275px'}
|
||||
h={'115px'}
|
||||
top={'3rem'}
|
||||
left={'1rem'}
|
||||
style={{
|
||||
backdropFilter: 'grayscale(80%) brightness(80%)',
|
||||
mask: 'linear-gradient(105deg, rgba(0, 0, 0, 1) 48%, rgba(0, 0, 0, 0) 52%)',
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
}}
|
||||
/>
|
||||
<FaArrowRight
|
||||
size={45}
|
||||
style={{
|
||||
color: 'var(--mantine-color-background-0)',
|
||||
position: 'absolute',
|
||||
stroke: 'var(--mantine-color-accent-0)',
|
||||
strokeWidth: 25,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
pos={'absolute'}
|
||||
w={'275px'}
|
||||
h={'115px'}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: '3px 3px var(--mantine-color-border-0)',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
styles={{
|
||||
label: {
|
||||
backgroundColor: 'var(--mantine-color-background-0)',
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
height: 'min-content',
|
||||
padding: '.5rem',
|
||||
borderRadius: 4,
|
||||
position: 'absolute',
|
||||
bottom: '-16px',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setDuplicate(true);
|
||||
setSelectedTheme(randomTheme);
|
||||
}}
|
||||
>
|
||||
Duplicate existing Theme
|
||||
</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
</>
|
||||
) : (
|
||||
<Stack align='center'>
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='center'
|
||||
style={{
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-accent-0), var(--mantine-color-background-0))',
|
||||
padding: '1rem',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{allThemes?.map((miniTheme: Theme) => {
|
||||
return (
|
||||
<Box
|
||||
{...handlers}
|
||||
key={miniTheme.id}
|
||||
onClick={() => {
|
||||
!pressed && setSelectedTheme(miniTheme);
|
||||
setPressed(false);
|
||||
}}
|
||||
>
|
||||
<ThemePreviewMiddleware
|
||||
newThemeList={[miniTheme.id as number]}
|
||||
miniTheme={miniTheme}
|
||||
themeList={[]}
|
||||
selectedPlaylist={undefined}
|
||||
setSelectedPlaylist={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
<Group>
|
||||
<Group
|
||||
style={{
|
||||
backgroundColor:
|
||||
'color-mix(in srgb, var(--mantine-color-primary-0), var(--mantine-color-background-0))',
|
||||
padding: '1rem',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<ThemeMiniCard miniTheme={selectedTheme} />
|
||||
<Button
|
||||
disabled={selectedTheme.name === 'default'}
|
||||
onClick={() => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
name: `${selectedTheme.name} copy`,
|
||||
});
|
||||
setIsNew(false);
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</Group>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
setDuplicate(false);
|
||||
setSelectedTheme({
|
||||
id: undefined,
|
||||
name: 'Default',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: '#EEEEEE',
|
||||
searchColor: '#00000033',
|
||||
primaryColor: '#222222',
|
||||
accentColor: '#5c5c5c',
|
||||
borderColor: '#5c5c5c',
|
||||
randomEnabled: 1,
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Checkbox,
|
||||
ColorPicker,
|
||||
ColorSwatch,
|
||||
Group,
|
||||
Image,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
UnstyledButton,
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks } from 'hooks';
|
||||
import { useRef, useState } from 'react';
|
||||
import { FaShuffle } from 'react-icons/fa6';
|
||||
import { FiImage, FiSave, FiShuffle, FiX } from 'react-icons/fi';
|
||||
import { scrollAreaClasses, textClasses, textInputClasses } from 'styles';
|
||||
import { BackgroundProperties, type Theme } from 'types';
|
||||
import { ThemeModalContent } from '../ThemeModalContent';
|
||||
|
||||
export function ThemeEditor({
|
||||
backgroundList,
|
||||
defaultTheme,
|
||||
designerTheme,
|
||||
setDesignerTheme,
|
||||
selectedTheme,
|
||||
setSelectedTheme,
|
||||
pickerOpen,
|
||||
openPicker,
|
||||
closePicker,
|
||||
handleClose,
|
||||
handleSaveTheme,
|
||||
setDuplicate,
|
||||
}: {
|
||||
backgroundList: string[];
|
||||
defaultTheme: Theme;
|
||||
designerTheme: Theme;
|
||||
setDesignerTheme: (theme: Theme) => void;
|
||||
selectedTheme: Theme;
|
||||
setSelectedTheme: (theme: Theme) => void;
|
||||
pickerOpen: boolean;
|
||||
openPicker: () => void;
|
||||
closePicker: () => void;
|
||||
handleClose: () => void;
|
||||
handleSaveTheme: () => void;
|
||||
setDuplicate: (duplicate: boolean) => void;
|
||||
}) {
|
||||
const [query, setQuery] = useState('');
|
||||
const [hovered, setHovered] = useState(-1);
|
||||
const { isMobile, theme, getObjectDiff } = useCommonHooks();
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const [colorId, setColorId] = useState<string>('');
|
||||
const [colorValue, setColorValue] = useState('#000000');
|
||||
|
||||
const re = /(?:\.([^.]+))?$/;
|
||||
const [backgroundPicker, { open: openBackgroundPicker, close: closeBackgroundPicker }] =
|
||||
useDisclosure(false);
|
||||
const filtered = backgroundList.filter((item: string) =>
|
||||
item.toLowerCase().includes(query.toLowerCase()),
|
||||
);
|
||||
const items = filtered.map((item: string, index: number) => {
|
||||
return (
|
||||
<UnstyledButton
|
||||
data-list-item
|
||||
key={item}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '75px',
|
||||
background:
|
||||
item !== 'none' && item !== 'None'
|
||||
? index === hovered
|
||||
? `linear-gradient(${selectedTheme.backgroundColor}88, ${selectedTheme.backgroundColor}88), no-repeat center url(../../api/backgroundImages/thumbs/${item})`
|
||||
: `no-repeat center url(../../api/backgroundImages/thumbs/${item})`
|
||||
: 'repeating-linear-gradient(45deg, #33333366, #33333366 10px, #88888866 10px, #88888866 20px)',
|
||||
backgroundSize: item !== 'none' ? 'cover' : undefined,
|
||||
backgroundColor: item === 'none' ? '#00000088' : undefined,
|
||||
paddingLeft: '.5rem',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onClick={() =>
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundImage: item,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Group h={'100%'} w={'100%'} justify='space-between'>
|
||||
<Text
|
||||
style={{
|
||||
width: 'min-content',
|
||||
backdropFilter: 'blur(5px) brightness(.25)',
|
||||
padding: '.5rem',
|
||||
borderBottomRightRadius: 4,
|
||||
marginTop: '-2.15rem',
|
||||
marginLeft: '-.5rem',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{item.replace(/\.[^/.]+$/, '')}
|
||||
</Text>
|
||||
{item !== 'None' && (
|
||||
<Text
|
||||
style={{
|
||||
width: 'min-content',
|
||||
backdropFilter: 'blur(5px) brightness(.25)',
|
||||
padding: '.5rem',
|
||||
marginTop: '2rem',
|
||||
marginBottom: -1,
|
||||
borderTopLeftRadius: 4,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{re.exec(item.toUpperCase())?.[1]}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
);
|
||||
});
|
||||
|
||||
const handleSwatchClick = () => {
|
||||
closeBackgroundPicker();
|
||||
openPicker();
|
||||
setDesignerTheme(selectedTheme);
|
||||
};
|
||||
|
||||
const randomColor = (withAlpha?: boolean) => {
|
||||
const letters = '0123456789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < (withAlpha ? 8 : 6); i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Group p={'2rem'} w={'100%'} align='flex-start' justify='space-between'>
|
||||
<Stack display={'flex'} pos={'relative'} w={isMobile ? '100%' : '70%'}>
|
||||
<ThemeModalContent selectedTheme={designerTheme} />
|
||||
<Tooltip
|
||||
label={'Change Primary Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('primaryColor');
|
||||
setColorValue(selectedTheme.primaryColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.primaryColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
top={'6.25rem'}
|
||||
left={'1.5rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Accent Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('accentColor');
|
||||
setColorValue(selectedTheme.accentColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.accentColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
bottom={'6rem'}
|
||||
left={'11rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Background Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('backgroundColor');
|
||||
setColorValue(selectedTheme.backgroundColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.backgroundColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
bottom={'10rem'}
|
||||
right={'3.5rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Border Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('borderColor');
|
||||
setColorValue(selectedTheme.borderColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.borderColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
top={'4.5rem'}
|
||||
right={'15%'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Search Color'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ColorSwatch
|
||||
onClick={() => {
|
||||
setColorId('searchColor');
|
||||
setColorValue(selectedTheme.searchColor);
|
||||
handleSwatchClick();
|
||||
}}
|
||||
color={selectedTheme.searchColor}
|
||||
bd={`1px solid ${selectedTheme.primaryColor}`}
|
||||
pos={'absolute'}
|
||||
top={'3.25rem'}
|
||||
left={'6.5rem'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Change Background'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
closePicker();
|
||||
openBackgroundPicker();
|
||||
setDesignerTheme(selectedTheme);
|
||||
}}
|
||||
pos={'absolute'}
|
||||
right={0}
|
||||
>
|
||||
<FiImage size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Stack
|
||||
w={isMobile ? '100%' : '28%'}
|
||||
h={'436px'}
|
||||
p={'1rem'}
|
||||
style={{
|
||||
borderRadius: '1rem',
|
||||
backgroundColor: 'var(--mantine-color-accent-0)',
|
||||
}}
|
||||
>
|
||||
{pickerOpen && (
|
||||
<Stack h={'100%'} align='center' justify='space-between'>
|
||||
<Text
|
||||
className={textClasses.sectionTitle}
|
||||
classNames={textClasses}
|
||||
style={{
|
||||
textShadow: theme.textShadowsEnabled
|
||||
? '1px 1px 1px color-mix(in srgb, var(--mantine-color-accent-0), transparent 50%)'
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
Change{' '}
|
||||
{BackgroundProperties[colorId as keyof typeof BackgroundProperties]}
|
||||
</Text>
|
||||
{colorId === 'backgroundColor' && (
|
||||
<ColorPicker
|
||||
value={colorValue}
|
||||
onChange={(e) => {
|
||||
setColorValue(e);
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundColor: e,
|
||||
});
|
||||
}}
|
||||
format={'hex'}
|
||||
w={'90%'}
|
||||
mb={'1.5rem'}
|
||||
/>
|
||||
)}
|
||||
{colorId !== 'backgroundColor' && (
|
||||
<ColorPicker
|
||||
value={colorValue}
|
||||
onChange={(e) => {
|
||||
setColorValue(e);
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
[colorId]: e,
|
||||
});
|
||||
}}
|
||||
format={'hexa'}
|
||||
w={'90%'}
|
||||
/>
|
||||
)}
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
classNames={textInputClasses}
|
||||
w={'90%'}
|
||||
value={colorValue}
|
||||
onChange={(e) => setColorValue(e.target.value)}
|
||||
/>
|
||||
<Group mt={'auto'} justify='center'>
|
||||
<Button
|
||||
c={'white'}
|
||||
onClick={() => {
|
||||
const rc = randomColor();
|
||||
setColorValue(rc);
|
||||
setDesignerTheme({
|
||||
...selectedTheme,
|
||||
[colorId]: rc,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiShuffle />
|
||||
</Button>
|
||||
<Button
|
||||
color={'green'}
|
||||
onClick={() => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
[colorId]: colorValue,
|
||||
});
|
||||
|
||||
closePicker();
|
||||
}}
|
||||
>
|
||||
<FiSave />
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closePicker();
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
[colorId]: selectedTheme[colorId as keyof Theme],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiX />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
{backgroundPicker && (
|
||||
<Stack h={'100%'} align='center' justify='center'>
|
||||
<Group gap={0}>
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
classNames={textInputClasses}
|
||||
placeholder='Search'
|
||||
value={query.replace(/\.[^/.]+$/, '')}
|
||||
rightSection={
|
||||
<FiX
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
margin: '.25rem',
|
||||
}}
|
||||
onClick={() => {
|
||||
setQuery('');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
onChange={(event) => {
|
||||
setQuery(event.currentTarget.value);
|
||||
setHovered(-1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
setHovered((current) => {
|
||||
const nextIndex =
|
||||
current + 1 >= filtered.length ? current : current + 1;
|
||||
viewportRef.current
|
||||
?.querySelectorAll('[data-list-item]')
|
||||
?.[nextIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
return nextIndex;
|
||||
});
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
setHovered((current) => {
|
||||
const nextIndex = current - 1 < 0 ? current : current - 1;
|
||||
viewportRef.current
|
||||
?.querySelectorAll('[data-list-item]')
|
||||
?.[nextIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
return nextIndex;
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<ScrollArea.Autosize
|
||||
viewportRef={viewportRef}
|
||||
className={scrollAreaClasses.default}
|
||||
classNames={scrollAreaClasses}
|
||||
style={{
|
||||
height: 325,
|
||||
width: '90%',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Stack gap={0} align='center' display={'block'}>
|
||||
{items}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
<Group justify='center'>
|
||||
<Button
|
||||
color={'green'}
|
||||
onClick={() => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
backgroundImage: designerTheme.backgroundImage,
|
||||
});
|
||||
closeBackgroundPicker();
|
||||
}}
|
||||
>
|
||||
<FiSave />
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closeBackgroundPicker();
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundImage: selectedTheme.backgroundImage,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiX />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
{!pickerOpen && !backgroundPicker && (
|
||||
<Stack h={'100%'} justify='space-between' align='center' gap={4}>
|
||||
<Stack w={'100%'} align='center' gap={4}>
|
||||
<Checkbox
|
||||
label='Text Shadows'
|
||||
checked={selectedTheme.textShadowsEnabled === 1}
|
||||
c={'var(--mantine-color-background-0)'}
|
||||
onChange={(e) => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
textShadowsEnabled: e.target.checked ? 1 : 0,
|
||||
});
|
||||
}}
|
||||
styles={{
|
||||
root: { width: '50%' },
|
||||
body: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
label='App Shadows'
|
||||
checked={selectedTheme.appShadowsEnabled === 1}
|
||||
c={'var(--mantine-color-background-0)'}
|
||||
onChange={(e) => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
appShadowsEnabled: e.target.checked ? 1 : 0,
|
||||
});
|
||||
}}
|
||||
styles={{
|
||||
root: { width: '50%' },
|
||||
body: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
{JSON.stringify(defaultTheme) !== JSON.stringify(selectedTheme) && (
|
||||
<>
|
||||
<Text c={'var(--mantine-color-background-0)'} fz={'1.5rem'}>
|
||||
Changes:
|
||||
</Text>
|
||||
<ScrollArea.Autosize
|
||||
w={'100%'}
|
||||
className={scrollAreaClasses.default}
|
||||
classNames={scrollAreaClasses}
|
||||
style={{
|
||||
backgroundColor: 'var(--mantine-color-background-0)',
|
||||
borderRadius: 8,
|
||||
padding: '1rem',
|
||||
}}
|
||||
>
|
||||
<Stack w={'100%'} gap={0}>
|
||||
{getObjectDiff(defaultTheme, designerTheme) !== null &&
|
||||
Object.keys(getObjectDiff(defaultTheme, designerTheme)).map(
|
||||
(key: any) => {
|
||||
return (
|
||||
<Stack
|
||||
w={'100%'}
|
||||
align='center'
|
||||
justify='center'
|
||||
gap={0}
|
||||
key={key}
|
||||
>
|
||||
<Text key={key}>
|
||||
{
|
||||
BackgroundProperties[
|
||||
key as keyof typeof BackgroundProperties
|
||||
]
|
||||
}
|
||||
</Text>
|
||||
{key === 'name' && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
<Text>{defaultTheme[key as keyof Theme]}</Text>
|
||||
<Text>→</Text>
|
||||
<Text>{designerTheme[key as keyof Theme]}</Text>
|
||||
</Group>
|
||||
)}
|
||||
{key === 'backgroundImage' && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
<Image
|
||||
w={'40%'}
|
||||
src={
|
||||
(
|
||||
defaultTheme[key as keyof Theme] as string
|
||||
).toLocaleLowerCase() === 'none'
|
||||
? 'https://placehold.co/540x360?text=None'
|
||||
: `../../api/backgroundImages/thumbs/${defaultTheme[key as keyof Theme] as string}`
|
||||
}
|
||||
/>
|
||||
<Text>→</Text>
|
||||
<Image
|
||||
w={'40%'}
|
||||
src={
|
||||
(
|
||||
designerTheme[key as keyof Theme] as string
|
||||
).toLocaleLowerCase() === 'none'
|
||||
? 'https://placehold.co/540x360?text=None'
|
||||
: `../../api/backgroundImages/thumbs/${designerTheme[key as keyof Theme] as string}`
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{key.includes('Color') && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
<ColorSwatch
|
||||
color={defaultTheme[key as keyof Theme] as string}
|
||||
/>
|
||||
<Text>→</Text>
|
||||
<ColorSwatch
|
||||
color={designerTheme[key as keyof Theme] as string}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{key.includes('Shadow') && (
|
||||
<Group w={'100%'} justify='center'>
|
||||
{defaultTheme[key as keyof Theme] === 0 ? (
|
||||
<Text>Off</Text>
|
||||
) : (
|
||||
<Text>On</Text>
|
||||
)}
|
||||
<Text>→</Text>
|
||||
{designerTheme[key as keyof Theme] === 0 ? (
|
||||
<Text>Off</Text>
|
||||
) : (
|
||||
<Text>On</Text>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</>
|
||||
)}
|
||||
<Group mih={'36px'}>
|
||||
<Tooltip
|
||||
label={'Randomize Colors!'}
|
||||
c={'var(--mantine-color-primary-0)'}
|
||||
bg={'var(--mantine-color-background-0)'}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSelectedTheme({
|
||||
...selectedTheme,
|
||||
backgroundColor: randomColor(),
|
||||
searchColor: randomColor(),
|
||||
primaryColor: randomColor(),
|
||||
accentColor: randomColor(),
|
||||
borderColor: randomColor(),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaShuffle />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSelectedTheme({ ...defaultTheme });
|
||||
}}
|
||||
>
|
||||
Reset Changes
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group>
|
||||
<Button
|
||||
disabled={JSON.stringify(defaultTheme) === JSON.stringify(selectedTheme)}
|
||||
color={'green'}
|
||||
onClick={handleSaveTheme}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
closeBackgroundPicker();
|
||||
setDuplicate(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { Group, Modal, Text, TextInput } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { modalClasses, textInputClasses } from 'styles';
|
||||
import type { Theme } from 'types';
|
||||
import { NewThemeStarter } from './NewThemeStarter';
|
||||
import { ThemeEditor } from './ThemeEditor';
|
||||
|
||||
export function ThemeModal({
|
||||
opened,
|
||||
selectedTheme,
|
||||
setSelectedTheme,
|
||||
backgroundList,
|
||||
isNew,
|
||||
setIsNew,
|
||||
allThemes,
|
||||
closeThemeModal,
|
||||
}: {
|
||||
opened: boolean;
|
||||
selectedTheme: Theme;
|
||||
setSelectedTheme: (selectedTheme: Theme) => void;
|
||||
backgroundList: string[];
|
||||
isNew: boolean;
|
||||
setIsNew: (isNew: boolean) => void;
|
||||
allThemes: Theme[];
|
||||
closeThemeModal: () => void;
|
||||
}) {
|
||||
const themesEndpoint = 'api/themes';
|
||||
const { isMobile } = useCommonHooks();
|
||||
const [defaultTheme, setDefaultTheme] = useState<Theme>({ ...selectedTheme });
|
||||
const [designerTheme, setDesignerTheme] = useState<Theme>({
|
||||
...selectedTheme,
|
||||
});
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
|
||||
const [pickerOpen, { open: openPicker, close: closePicker }] = useDisclosure(false);
|
||||
const [duplicate, setDuplicate] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
closeThemeModal();
|
||||
closePicker();
|
||||
setIsNew(false);
|
||||
setSelectedTheme({
|
||||
id: undefined,
|
||||
name: 'default',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: '#EEEEEE',
|
||||
searchColor: '#00000033',
|
||||
primaryColor: '#222222',
|
||||
accentColor: '#5c5c5c',
|
||||
borderColor: '#5c5c5c',
|
||||
randomEnabled: 1,
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveTheme = () => {
|
||||
mutateAsync({
|
||||
method: selectedTheme?.id === undefined ? 'POST' : 'PATCH',
|
||||
endpoint: themesEndpoint,
|
||||
body: selectedTheme,
|
||||
});
|
||||
handleClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDesignerTheme(selectedTheme);
|
||||
if (defaultTheme.id !== selectedTheme.id) {
|
||||
setDefaultTheme(selectedTheme);
|
||||
}
|
||||
}, [selectedTheme]);
|
||||
|
||||
return (
|
||||
<Modal.Root
|
||||
size={'75%'}
|
||||
className={modalClasses.editThemeModal}
|
||||
classNames={modalClasses}
|
||||
opened={opened}
|
||||
onClose={() => {
|
||||
handleClose();
|
||||
setDuplicate(false);
|
||||
}}
|
||||
fullScreen={isMobile}
|
||||
centered
|
||||
keepMounted={true}
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content>
|
||||
<Modal.Header style={{ justifyContent: 'center' }}>
|
||||
{!isNew && (
|
||||
<Group pos={'absolute'}>
|
||||
<Text c={'var(--mantine-color-background-0)'}>Name:</Text>
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
classNames={textInputClasses}
|
||||
value={selectedTheme.name}
|
||||
onChange={(e) =>
|
||||
setSelectedTheme({ ...selectedTheme, name: e.target.value })
|
||||
}
|
||||
styles={{
|
||||
label: {
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
input: {
|
||||
color: selectedTheme.primaryColor,
|
||||
backgroundColor: selectedTheme.backgroundColor,
|
||||
border: 'none',
|
||||
borderBottom: '1px solid var(--mantine-color-primary-0)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{isNew ? (
|
||||
<NewThemeStarter
|
||||
allThemes={allThemes}
|
||||
setIsNew={setIsNew}
|
||||
duplicate={duplicate}
|
||||
setDuplicate={setDuplicate}
|
||||
selectedTheme={selectedTheme}
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
/>
|
||||
) : (
|
||||
<ThemeEditor
|
||||
designerTheme={designerTheme}
|
||||
setDesignerTheme={setDesignerTheme}
|
||||
selectedTheme={selectedTheme}
|
||||
closePicker={closePicker}
|
||||
handleSaveTheme={handleSaveTheme}
|
||||
backgroundList={backgroundList}
|
||||
defaultTheme={defaultTheme}
|
||||
openPicker={openPicker}
|
||||
pickerOpen={pickerOpen}
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
handleClose={handleClose}
|
||||
setDuplicate={setDuplicate}
|
||||
/>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
import { Box, Button, Group, Overlay, Stack, Switch, Text, Tooltip } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Group,
|
||||
Overlay,
|
||||
Stack,
|
||||
Switch,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { NoItemsFound, SettingsHeader } from 'components';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useCommonHooks, useQueryAPI } from 'hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaDice } from 'react-icons/fa';
|
||||
import { FiChevronRight, FiEdit, FiMousePointer, FiStopCircle } from 'react-icons/fi';
|
||||
@@ -10,10 +20,9 @@ import { switchClasses, textClasses } from 'styles';
|
||||
import type { Theme, ThemePlaylist } from 'types';
|
||||
import { BackgroundModal } from '../Backgrounds';
|
||||
import { ThemeCard } from './ThemeCard';
|
||||
import { ThemeModal } from './ThemeModal';
|
||||
import { ThemeModal } from './ThemeModal/ThemeModal';
|
||||
|
||||
export function Themes() {
|
||||
const themesEndpoint = 'api/themes';
|
||||
const backgroundsEndpoint = 'api/backgrounds';
|
||||
const { data: allThemeData, isFetched: isAllThemeDataFetched } = useQueryAPI({
|
||||
endpoint: 'api/themes',
|
||||
@@ -26,7 +35,6 @@ export function Themes() {
|
||||
endpoint: 'api/themePlaylists',
|
||||
});
|
||||
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const [themeModalOpened, { open: openThemeModal, close: closeThemeModal }] =
|
||||
useDisclosure(false);
|
||||
const [
|
||||
@@ -48,7 +56,6 @@ export function Themes() {
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
});
|
||||
const [pickerOpen, { open: openPicker, close: closePicker }] = useDisclosure(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -63,44 +70,11 @@ export function Themes() {
|
||||
setCurrentThemePlaylist,
|
||||
} = useCommonHooks();
|
||||
|
||||
const handleOpen = (theme: Theme) => {
|
||||
setSelectedTheme(theme);
|
||||
openThemeModal();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
closeThemeModal();
|
||||
closePicker();
|
||||
setIsNew(false);
|
||||
setSelectedTheme({
|
||||
id: undefined,
|
||||
name: 'default',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: '#EEEEEE',
|
||||
searchColor: '#00000033',
|
||||
primaryColor: '#222222',
|
||||
accentColor: '#5c5c5c',
|
||||
borderColor: '#5c5c5c',
|
||||
randomEnabled: 1,
|
||||
textShadowsEnabled: 0,
|
||||
appShadowsEnabled: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNewTheme = () => {
|
||||
setIsNew(true);
|
||||
openThemeModal();
|
||||
};
|
||||
|
||||
const handleSaveTheme = () => {
|
||||
mutateAsync({
|
||||
method: selectedTheme?.id === undefined ? 'POST' : 'PATCH',
|
||||
endpoint: themesEndpoint,
|
||||
body: selectedTheme,
|
||||
});
|
||||
handleClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (backgroundData && isBackgroundFetched) {
|
||||
setBackgroundList(['None', ...backgroundData]);
|
||||
@@ -146,17 +120,13 @@ export function Themes() {
|
||||
<Stack>
|
||||
<ThemeModal
|
||||
opened={themeModalOpened}
|
||||
handleClose={handleClose}
|
||||
selectedTheme={selectedTheme}
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
openPicker={openPicker}
|
||||
backgroundList={backgroundList}
|
||||
pickerOpen={pickerOpen}
|
||||
closePicker={closePicker}
|
||||
handleSaveTheme={handleSaveTheme}
|
||||
isNew={isNew}
|
||||
setIsNew={setIsNew}
|
||||
allThemes={allThemes}
|
||||
closeThemeModal={closeThemeModal}
|
||||
/>
|
||||
<BackgroundModal
|
||||
backgroundModalOpened={backgroundModalOpened}
|
||||
@@ -239,8 +209,9 @@ export function Themes() {
|
||||
<ThemeCard
|
||||
allThemes={allThemeData}
|
||||
cardTheme={cardTheme}
|
||||
handleOpen={handleOpen}
|
||||
themePlaylists={themePlaylists}
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
openThemeModal={openThemeModal}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
@@ -259,8 +230,8 @@ export function Themes() {
|
||||
style={{
|
||||
backdropFilter: 'blur(5px) brightness(.75)',
|
||||
zIndex: 199,
|
||||
paddingTop: isMobile ? '.5rem' : '1.5rem',
|
||||
paddingBottom: '1.5rem',
|
||||
paddingTop: '.5rem',
|
||||
paddingBottom: '.5rem',
|
||||
paddingLeft: '1rem',
|
||||
paddingRight: '1rem',
|
||||
width: '100%',
|
||||
@@ -291,7 +262,16 @@ export function Themes() {
|
||||
>
|
||||
Current Settings:
|
||||
</Text>
|
||||
<Group>
|
||||
<Group
|
||||
style={{
|
||||
backgroundColor: 'var(--mantine-color-search-0)',
|
||||
borderRadius: '8px',
|
||||
paddingTop: '.5rem',
|
||||
paddingBottom: '.5rem',
|
||||
paddingLeft: '1rem',
|
||||
paddingRight: '1rem',
|
||||
}}
|
||||
>
|
||||
<Group>
|
||||
<Text
|
||||
style={{
|
||||
@@ -311,6 +291,11 @@ export function Themes() {
|
||||
{currentThemePlaylist === 'All' ? 'All' : currentThemePlaylist}
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider
|
||||
orientation='vertical'
|
||||
size={'xs'}
|
||||
color={'var(--mantine-color-primary-0)'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (currentThemePlaylist === 'All') {
|
||||
|
||||
@@ -4,7 +4,7 @@ export { ThemeCard } from './ThemeCard';
|
||||
export { ThemeExampleApp } from './ThemeExampleApp';
|
||||
export { ThemeManagementPill } from './ThemeManagementPill';
|
||||
export { ThemeMiniCard } from './ThemeMiniCard';
|
||||
export { ThemeModal } from './ThemeModal';
|
||||
export { ThemeModal } from './ThemeModal/ThemeModal';
|
||||
export { ThemeModalColorSwatches } from './ThemeModalColorSwatches';
|
||||
export { ThemeModalContent } from './ThemeModalContent';
|
||||
export { ThemePreviewMiddleware } from './ThemePreviewMiddleware';
|
||||
|
||||
Reference in New Issue
Block a user