Theme modal updates
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -21,7 +21,7 @@
|
|||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.7.0",
|
"react-router-dom": "^7.7.1",
|
||||||
"recharts": "^3.1.0"
|
"recharts": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -3683,9 +3683,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.7.0",
|
"version": "7.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.1.tgz",
|
||||||
"integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==",
|
"integrity": "sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": "^1.0.1",
|
"cookie": "^1.0.1",
|
||||||
@@ -3705,12 +3705,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "7.7.0",
|
"version": "7.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.1.tgz",
|
||||||
"integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==",
|
"integrity": "sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-router": "7.7.0"
|
"react-router": "7.7.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.7.0",
|
"react-router-dom": "^7.7.1",
|
||||||
"recharts": "^3.1.0"
|
"recharts": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ import { MiniAppItem, MiniGroupItem, ThemeManagementPill } from '.';
|
|||||||
|
|
||||||
export function ThemeCard({
|
export function ThemeCard({
|
||||||
cardTheme,
|
cardTheme,
|
||||||
handleOpen,
|
|
||||||
allThemes,
|
allThemes,
|
||||||
themePlaylists,
|
themePlaylists,
|
||||||
|
setSelectedTheme,
|
||||||
|
openThemeModal,
|
||||||
}: {
|
}: {
|
||||||
cardTheme: Theme;
|
cardTheme: Theme;
|
||||||
handleOpen: (cardTheme: Theme) => void;
|
|
||||||
allThemes: Theme[];
|
allThemes: Theme[];
|
||||||
themePlaylists: ThemePlaylist[];
|
themePlaylists: ThemePlaylist[];
|
||||||
|
setSelectedTheme: (theme: Theme) => void;
|
||||||
|
openThemeModal: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { hovered, ref: hoverRef } = useHover();
|
const { hovered, ref: hoverRef } = useHover();
|
||||||
const { ref, width } = useElementSize();
|
const { ref, width } = useElementSize();
|
||||||
@@ -29,6 +31,10 @@ export function ThemeCard({
|
|||||||
const [hoverPreview, setHoverPreview] = useDebouncedState(false, 1000);
|
const [hoverPreview, setHoverPreview] = useDebouncedState(false, 1000);
|
||||||
const [transition, setTransition] = useDebouncedState(false, 200);
|
const [transition, setTransition] = useDebouncedState(false, 200);
|
||||||
|
|
||||||
|
const handleOpen = (theme: Theme) => {
|
||||||
|
setSelectedTheme(theme);
|
||||||
|
openThemeModal();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
gap={0}
|
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 { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||||
import { NoItemsFound, SettingsHeader } from 'components';
|
import { NoItemsFound, SettingsHeader } from 'components';
|
||||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
import { useCommonHooks, useQueryAPI } from 'hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FaDice } from 'react-icons/fa';
|
import { FaDice } from 'react-icons/fa';
|
||||||
import { FiChevronRight, FiEdit, FiMousePointer, FiStopCircle } from 'react-icons/fi';
|
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 type { Theme, ThemePlaylist } from 'types';
|
||||||
import { BackgroundModal } from '../Backgrounds';
|
import { BackgroundModal } from '../Backgrounds';
|
||||||
import { ThemeCard } from './ThemeCard';
|
import { ThemeCard } from './ThemeCard';
|
||||||
import { ThemeModal } from './ThemeModal';
|
import { ThemeModal } from './ThemeModal/ThemeModal';
|
||||||
|
|
||||||
export function Themes() {
|
export function Themes() {
|
||||||
const themesEndpoint = 'api/themes';
|
|
||||||
const backgroundsEndpoint = 'api/backgrounds';
|
const backgroundsEndpoint = 'api/backgrounds';
|
||||||
const { data: allThemeData, isFetched: isAllThemeDataFetched } = useQueryAPI({
|
const { data: allThemeData, isFetched: isAllThemeDataFetched } = useQueryAPI({
|
||||||
endpoint: 'api/themes',
|
endpoint: 'api/themes',
|
||||||
@@ -26,7 +35,6 @@ export function Themes() {
|
|||||||
endpoint: 'api/themePlaylists',
|
endpoint: 'api/themePlaylists',
|
||||||
});
|
});
|
||||||
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
|
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
|
||||||
const { mutateAsync } = useMutationAPI();
|
|
||||||
const [themeModalOpened, { open: openThemeModal, close: closeThemeModal }] =
|
const [themeModalOpened, { open: openThemeModal, close: closeThemeModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
const [
|
const [
|
||||||
@@ -48,7 +56,6 @@ export function Themes() {
|
|||||||
textShadowsEnabled: 0,
|
textShadowsEnabled: 0,
|
||||||
appShadowsEnabled: 0,
|
appShadowsEnabled: 0,
|
||||||
});
|
});
|
||||||
const [pickerOpen, { open: openPicker, close: closePicker }] = useDisclosure(false);
|
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -63,44 +70,11 @@ export function Themes() {
|
|||||||
setCurrentThemePlaylist,
|
setCurrentThemePlaylist,
|
||||||
} = useCommonHooks();
|
} = 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 = () => {
|
const handleNewTheme = () => {
|
||||||
setIsNew(true);
|
setIsNew(true);
|
||||||
openThemeModal();
|
openThemeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveTheme = () => {
|
|
||||||
mutateAsync({
|
|
||||||
method: selectedTheme?.id === undefined ? 'POST' : 'PATCH',
|
|
||||||
endpoint: themesEndpoint,
|
|
||||||
body: selectedTheme,
|
|
||||||
});
|
|
||||||
handleClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (backgroundData && isBackgroundFetched) {
|
if (backgroundData && isBackgroundFetched) {
|
||||||
setBackgroundList(['None', ...backgroundData]);
|
setBackgroundList(['None', ...backgroundData]);
|
||||||
@@ -146,17 +120,13 @@ export function Themes() {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<ThemeModal
|
<ThemeModal
|
||||||
opened={themeModalOpened}
|
opened={themeModalOpened}
|
||||||
handleClose={handleClose}
|
|
||||||
selectedTheme={selectedTheme}
|
selectedTheme={selectedTheme}
|
||||||
setSelectedTheme={setSelectedTheme}
|
setSelectedTheme={setSelectedTheme}
|
||||||
openPicker={openPicker}
|
|
||||||
backgroundList={backgroundList}
|
backgroundList={backgroundList}
|
||||||
pickerOpen={pickerOpen}
|
|
||||||
closePicker={closePicker}
|
|
||||||
handleSaveTheme={handleSaveTheme}
|
|
||||||
isNew={isNew}
|
isNew={isNew}
|
||||||
setIsNew={setIsNew}
|
setIsNew={setIsNew}
|
||||||
allThemes={allThemes}
|
allThemes={allThemes}
|
||||||
|
closeThemeModal={closeThemeModal}
|
||||||
/>
|
/>
|
||||||
<BackgroundModal
|
<BackgroundModal
|
||||||
backgroundModalOpened={backgroundModalOpened}
|
backgroundModalOpened={backgroundModalOpened}
|
||||||
@@ -239,8 +209,9 @@ export function Themes() {
|
|||||||
<ThemeCard
|
<ThemeCard
|
||||||
allThemes={allThemeData}
|
allThemes={allThemeData}
|
||||||
cardTheme={cardTheme}
|
cardTheme={cardTheme}
|
||||||
handleOpen={handleOpen}
|
|
||||||
themePlaylists={themePlaylists}
|
themePlaylists={themePlaylists}
|
||||||
|
setSelectedTheme={setSelectedTheme}
|
||||||
|
openThemeModal={openThemeModal}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -259,8 +230,8 @@ export function Themes() {
|
|||||||
style={{
|
style={{
|
||||||
backdropFilter: 'blur(5px) brightness(.75)',
|
backdropFilter: 'blur(5px) brightness(.75)',
|
||||||
zIndex: 199,
|
zIndex: 199,
|
||||||
paddingTop: isMobile ? '.5rem' : '1.5rem',
|
paddingTop: '.5rem',
|
||||||
paddingBottom: '1.5rem',
|
paddingBottom: '.5rem',
|
||||||
paddingLeft: '1rem',
|
paddingLeft: '1rem',
|
||||||
paddingRight: '1rem',
|
paddingRight: '1rem',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -291,7 +262,16 @@ export function Themes() {
|
|||||||
>
|
>
|
||||||
Current Settings:
|
Current Settings:
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--mantine-color-search-0)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
paddingTop: '.5rem',
|
||||||
|
paddingBottom: '.5rem',
|
||||||
|
paddingLeft: '1rem',
|
||||||
|
paddingRight: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Group>
|
<Group>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
@@ -311,6 +291,11 @@ export function Themes() {
|
|||||||
{currentThemePlaylist === 'All' ? 'All' : currentThemePlaylist}
|
{currentThemePlaylist === 'All' ? 'All' : currentThemePlaylist}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
<Divider
|
||||||
|
orientation='vertical'
|
||||||
|
size={'xs'}
|
||||||
|
color={'var(--mantine-color-primary-0)'}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentThemePlaylist === 'All') {
|
if (currentThemePlaylist === 'All') {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export { ThemeCard } from './ThemeCard';
|
|||||||
export { ThemeExampleApp } from './ThemeExampleApp';
|
export { ThemeExampleApp } from './ThemeExampleApp';
|
||||||
export { ThemeManagementPill } from './ThemeManagementPill';
|
export { ThemeManagementPill } from './ThemeManagementPill';
|
||||||
export { ThemeMiniCard } from './ThemeMiniCard';
|
export { ThemeMiniCard } from './ThemeMiniCard';
|
||||||
export { ThemeModal } from './ThemeModal';
|
export { ThemeModal } from './ThemeModal/ThemeModal';
|
||||||
export { ThemeModalColorSwatches } from './ThemeModalColorSwatches';
|
export { ThemeModalColorSwatches } from './ThemeModalColorSwatches';
|
||||||
export { ThemeModalContent } from './ThemeModalContent';
|
export { ThemeModalContent } from './ThemeModalContent';
|
||||||
export { ThemePreviewMiddleware } from './ThemePreviewMiddleware';
|
export { ThemePreviewMiddleware } from './ThemePreviewMiddleware';
|
||||||
|
|||||||
Reference in New Issue
Block a user