diff --git a/package-lock.json b/package-lock.json
index 29b0121..3927809 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
- "react-router-dom": "^7.7.0",
+ "react-router-dom": "^7.7.1",
"recharts": "^3.1.0"
},
"devDependencies": {
@@ -3683,9 +3683,9 @@
}
},
"node_modules/react-router": {
- "version": "7.7.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz",
- "integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==",
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.1.tgz",
+ "integrity": "sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -3705,12 +3705,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.7.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.0.tgz",
- "integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==",
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.1.tgz",
+ "integrity": "sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==",
"license": "MIT",
"dependencies": {
- "react-router": "7.7.0"
+ "react-router": "7.7.1"
},
"engines": {
"node": ">=20.0.0"
diff --git a/package.json b/package.json
index 64a791a..791baa6 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
- "react-router-dom": "^7.7.0",
+ "react-router-dom": "^7.7.1",
"recharts": "^3.1.0"
},
"devDependencies": {
diff --git a/src/pages/settings/components/Theming/Themes/ThemeCard.tsx b/src/pages/settings/components/Theming/Themes/ThemeCard.tsx
index 4f11d10..71976ad 100644
--- a/src/pages/settings/components/Theming/Themes/ThemeCard.tsx
+++ b/src/pages/settings/components/Theming/Themes/ThemeCard.tsx
@@ -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 (
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(false);
- const handlers = useLongPress(() => {
- setPressed(true);
- });
- const { isMobile, theme, getObjectDiff } = useCommonHooks();
- const viewportRef = useRef(null);
- const [query, setQuery] = useState('');
- const [hovered, setHovered] = useState(-1);
- const [colorId, setColorId] = useState('');
- const [colorValue, setColorValue] = useState('#000000');
- const [defaultTheme, setDefaultTheme] = useState({ ...selectedTheme });
- const [designerTheme, setDesignerTheme] = useState({
- ...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 (
-
- setDesignerTheme({
- ...designerTheme,
- backgroundImage: item,
- })
- }
- >
-
-
- {item.replace(/\.[^/.]+$/, '')}
-
- {item !== 'None' && (
-
- {re.exec(item.toUpperCase())?.[1]}
-
- )}
-
-
- );
- });
-
- const handleSwatchClick = () => {
- closeBackgroundPicker();
- openPicker();
- setDesignerTheme(selectedTheme);
- };
-
- useEffect(() => {
- setDesignerTheme(selectedTheme);
- if (defaultTheme.id !== selectedTheme.id) {
- setDefaultTheme(selectedTheme);
- }
- }, [selectedTheme]);
-
- return (
- {
- handleClose();
- setDuplicate(false);
- }}
- fullScreen={isMobile}
- centered
- keepMounted={false}
- >
-
-
-
- {!isNew && (
-
- Name:
-
- 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)',
- },
- }}
- />
-
- )}
-
-
-
- {isNew ? (
-
- {!duplicate ? (
- <>
-
- Please choose an option:
-
-
-
- {}}
- allThemes={[]}
- themePlaylists={[]}
- />
-
-
-
-
- {}}
- allThemes={[]}
- themePlaylists={[]}
- />
-
-
-
-
-
- >
- ) : (
-
-
- {allThemes?.map((miniTheme: Theme) => {
- return (
- {
- !pressed && setSelectedTheme(miniTheme);
- setPressed(false);
- }}
- >
- {}}
- />
-
- );
- })}
-
-
-
-
-
-
-
-
-
- )}
-
- ) : (
- <>
-
-
-
-
- {
- setColorId('primaryColor');
- setColorValue(selectedTheme.primaryColor);
- handleSwatchClick();
- }}
- color={selectedTheme.primaryColor}
- bd={`1px solid ${selectedTheme.primaryColor}`}
- pos={'absolute'}
- top={'6.25rem'}
- left={'1.5rem'}
- />
-
-
- {
- setColorId('accentColor');
- setColorValue(selectedTheme.accentColor);
- handleSwatchClick();
- }}
- color={selectedTheme.accentColor}
- bd={`1px solid ${selectedTheme.primaryColor}`}
- pos={'absolute'}
- bottom={'6rem'}
- left={'11rem'}
- />
-
-
- {
- setColorId('backgroundColor');
- setColorValue(selectedTheme.backgroundColor);
- handleSwatchClick();
- }}
- color={selectedTheme.backgroundColor}
- bd={`1px solid ${selectedTheme.primaryColor}`}
- pos={'absolute'}
- bottom={'10rem'}
- right={'3.5rem'}
- />
-
-
- {
- setColorId('borderColor');
- setColorValue(selectedTheme.borderColor);
- handleSwatchClick();
- }}
- color={selectedTheme.borderColor}
- bd={`1px solid ${selectedTheme.primaryColor}`}
- pos={'absolute'}
- top={'4.5rem'}
- right={'15%'}
- />
-
-
- {
- setColorId('searchColor');
- setColorValue(selectedTheme.searchColor);
- handleSwatchClick();
- }}
- color={selectedTheme.searchColor}
- bd={`1px solid ${selectedTheme.primaryColor}`}
- pos={'absolute'}
- top={'3.25rem'}
- left={'6.5rem'}
- />
-
-
- {
- closePicker();
- openBackgroundPicker();
- setDesignerTheme(selectedTheme);
- }}
- pos={'absolute'}
- right={0}
- >
-
-
-
-
-
- {pickerOpen && (
-
-
- Change{' '}
- {
- BackgroundProperties[
- colorId as keyof typeof BackgroundProperties
- ]
- }
-
- {colorId === 'backgroundColor' && (
- {
- setColorValue(e);
- setDesignerTheme({
- ...designerTheme,
- backgroundColor: e,
- });
- }}
- format={'hex'}
- w={'90%'}
- mb={'1.5rem'}
- />
- )}
- {colorId !== 'backgroundColor' && (
- {
- setColorValue(e);
- setDesignerTheme({
- ...designerTheme,
- [colorId]: e,
- });
- }}
- format={'hexa'}
- w={'90%'}
- />
- )}
- setColorValue(e.target.value)}
- />
-
-
-
-
-
- )}
- {backgroundPicker && (
-
-
- {
- 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;
- });
- }
- }}
- />
-
-
-
- {items}
-
-
-
-
-
-
-
- )}
- {!pickerOpen && !backgroundPicker && (
-
-
- {
- setSelectedTheme({
- ...selectedTheme,
- textShadowsEnabled: e.target.checked ? 1 : 0,
- });
- }}
- styles={{
- root: { width: '50%' },
- body: {
- width: '100%',
- display: 'flex',
- justifyContent: 'space-between',
- },
- }}
- />
- {
- setSelectedTheme({
- ...selectedTheme,
- appShadowsEnabled: e.target.checked ? 1 : 0,
- });
- }}
- styles={{
- root: { width: '50%' },
- body: {
- width: '100%',
- display: 'flex',
- justifyContent: 'space-between',
- },
- }}
- />
-
- {JSON.stringify(defaultTheme) !== JSON.stringify(selectedTheme) && (
- <>
- Changes:
-
-
- {getObjectDiff(defaultTheme, designerTheme) !== null &&
- Object.keys(
- getObjectDiff(defaultTheme, designerTheme),
- ).map((key: any) => {
- return (
-
-
- {
- BackgroundProperties[
- key as keyof typeof BackgroundProperties
- ]
- }
-
- {key === 'name' && (
-
- {defaultTheme[key as keyof Theme]}
- →
- {designerTheme[key as keyof Theme]}
-
- )}
- {key === 'backgroundImage' && (
-
-
- →
-
-
- )}
- {key.includes('Color') && (
-
-
- →
-
-
- )}
- {key.includes('Shadow') && (
-
- {defaultTheme[key as keyof Theme] === 0 ? (
- Off
- ) : (
- On
- )}
- →
- {designerTheme[key as keyof Theme] === 0 ? (
- Off
- ) : (
- On
- )}
-
- )}
-
- );
- })}
-
-
- >
- )}
-
-
- )}
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-}
diff --git a/src/pages/settings/components/Theming/Themes/ThemeModal/NewThemeStarter.tsx b/src/pages/settings/components/Theming/Themes/ThemeModal/NewThemeStarter.tsx
new file mode 100644
index 0000000..c8b3c16
--- /dev/null
+++ b/src/pages/settings/components/Theming/Themes/ThemeModal/NewThemeStarter.tsx
@@ -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(false);
+ const handlers = useLongPress(() => {
+ setPressed(true);
+ });
+ const randomTheme = {
+ ...allThemes[Math.floor(Math.random() * allThemes.length)],
+ randomEnabled: 1,
+ };
+ return (
+
+ {!duplicate ? (
+ <>
+
+ Please choose an option:
+
+
+
+ {}}
+ />
+
+
+
+
+ {}}
+ />
+
+
+
+
+
+ >
+ ) : (
+
+
+ {allThemes?.map((miniTheme: Theme) => {
+ return (
+ {
+ !pressed && setSelectedTheme(miniTheme);
+ setPressed(false);
+ }}
+ >
+ {}}
+ />
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/pages/settings/components/Theming/Themes/ThemeModal/ThemeEditor.tsx b/src/pages/settings/components/Theming/Themes/ThemeModal/ThemeEditor.tsx
new file mode 100644
index 0000000..98736a0
--- /dev/null
+++ b/src/pages/settings/components/Theming/Themes/ThemeModal/ThemeEditor.tsx
@@ -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(null);
+ const [colorId, setColorId] = useState('');
+ 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 (
+
+ setDesignerTheme({
+ ...designerTheme,
+ backgroundImage: item,
+ })
+ }
+ >
+
+
+ {item.replace(/\.[^/.]+$/, '')}
+
+ {item !== 'None' && (
+
+ {re.exec(item.toUpperCase())?.[1]}
+
+ )}
+
+
+ );
+ });
+
+ 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 (
+ <>
+
+
+
+
+ {
+ setColorId('primaryColor');
+ setColorValue(selectedTheme.primaryColor);
+ handleSwatchClick();
+ }}
+ color={selectedTheme.primaryColor}
+ bd={`1px solid ${selectedTheme.primaryColor}`}
+ pos={'absolute'}
+ top={'6.25rem'}
+ left={'1.5rem'}
+ />
+
+
+ {
+ setColorId('accentColor');
+ setColorValue(selectedTheme.accentColor);
+ handleSwatchClick();
+ }}
+ color={selectedTheme.accentColor}
+ bd={`1px solid ${selectedTheme.primaryColor}`}
+ pos={'absolute'}
+ bottom={'6rem'}
+ left={'11rem'}
+ />
+
+
+ {
+ setColorId('backgroundColor');
+ setColorValue(selectedTheme.backgroundColor);
+ handleSwatchClick();
+ }}
+ color={selectedTheme.backgroundColor}
+ bd={`1px solid ${selectedTheme.primaryColor}`}
+ pos={'absolute'}
+ bottom={'10rem'}
+ right={'3.5rem'}
+ />
+
+
+ {
+ setColorId('borderColor');
+ setColorValue(selectedTheme.borderColor);
+ handleSwatchClick();
+ }}
+ color={selectedTheme.borderColor}
+ bd={`1px solid ${selectedTheme.primaryColor}`}
+ pos={'absolute'}
+ top={'4.5rem'}
+ right={'15%'}
+ />
+
+
+ {
+ setColorId('searchColor');
+ setColorValue(selectedTheme.searchColor);
+ handleSwatchClick();
+ }}
+ color={selectedTheme.searchColor}
+ bd={`1px solid ${selectedTheme.primaryColor}`}
+ pos={'absolute'}
+ top={'3.25rem'}
+ left={'6.5rem'}
+ />
+
+
+ {
+ closePicker();
+ openBackgroundPicker();
+ setDesignerTheme(selectedTheme);
+ }}
+ pos={'absolute'}
+ right={0}
+ >
+
+
+
+
+
+ {pickerOpen && (
+
+
+ Change{' '}
+ {BackgroundProperties[colorId as keyof typeof BackgroundProperties]}
+
+ {colorId === 'backgroundColor' && (
+ {
+ setColorValue(e);
+ setDesignerTheme({
+ ...designerTheme,
+ backgroundColor: e,
+ });
+ }}
+ format={'hex'}
+ w={'90%'}
+ mb={'1.5rem'}
+ />
+ )}
+ {colorId !== 'backgroundColor' && (
+ {
+ setColorValue(e);
+ setDesignerTheme({
+ ...designerTheme,
+ [colorId]: e,
+ });
+ }}
+ format={'hexa'}
+ w={'90%'}
+ />
+ )}
+ setColorValue(e.target.value)}
+ />
+
+
+
+
+
+
+ )}
+ {backgroundPicker && (
+
+
+ {
+ 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;
+ });
+ }
+ }}
+ />
+
+
+
+ {items}
+
+
+
+
+
+
+
+ )}
+ {!pickerOpen && !backgroundPicker && (
+
+
+ {
+ setSelectedTheme({
+ ...selectedTheme,
+ textShadowsEnabled: e.target.checked ? 1 : 0,
+ });
+ }}
+ styles={{
+ root: { width: '50%' },
+ body: {
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'space-between',
+ },
+ }}
+ />
+ {
+ setSelectedTheme({
+ ...selectedTheme,
+ appShadowsEnabled: e.target.checked ? 1 : 0,
+ });
+ }}
+ styles={{
+ root: { width: '50%' },
+ body: {
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'space-between',
+ },
+ }}
+ />
+
+ {JSON.stringify(defaultTheme) !== JSON.stringify(selectedTheme) && (
+ <>
+
+ Changes:
+
+
+
+ {getObjectDiff(defaultTheme, designerTheme) !== null &&
+ Object.keys(getObjectDiff(defaultTheme, designerTheme)).map(
+ (key: any) => {
+ return (
+
+
+ {
+ BackgroundProperties[
+ key as keyof typeof BackgroundProperties
+ ]
+ }
+
+ {key === 'name' && (
+
+ {defaultTheme[key as keyof Theme]}
+ →
+ {designerTheme[key as keyof Theme]}
+
+ )}
+ {key === 'backgroundImage' && (
+
+
+ →
+
+
+ )}
+ {key.includes('Color') && (
+
+
+ →
+
+
+ )}
+ {key.includes('Shadow') && (
+
+ {defaultTheme[key as keyof Theme] === 0 ? (
+ Off
+ ) : (
+ On
+ )}
+ →
+ {designerTheme[key as keyof Theme] === 0 ? (
+ Off
+ ) : (
+ On
+ )}
+
+ )}
+
+ );
+ },
+ )}
+
+
+ >
+ )}
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/settings/components/Theming/Themes/ThemeModal/ThemeModal.tsx b/src/pages/settings/components/Theming/Themes/ThemeModal/ThemeModal.tsx
new file mode 100644
index 0000000..472a3fd
--- /dev/null
+++ b/src/pages/settings/components/Theming/Themes/ThemeModal/ThemeModal.tsx
@@ -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({ ...selectedTheme });
+ const [designerTheme, setDesignerTheme] = useState({
+ ...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 (
+ {
+ handleClose();
+ setDuplicate(false);
+ }}
+ fullScreen={isMobile}
+ centered
+ keepMounted={true}
+ >
+
+
+
+ {!isNew && (
+
+ Name:
+
+ 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)',
+ },
+ }}
+ />
+
+ )}
+
+
+
+ {isNew ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/pages/settings/components/Theming/Themes/Themes.tsx b/src/pages/settings/components/Theming/Themes/Themes.tsx
index 4ae62cd..1bd5cfb 100644
--- a/src/pages/settings/components/Theming/Themes/Themes.tsx
+++ b/src/pages/settings/components/Theming/Themes/Themes.tsx
@@ -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([]);
- 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() {
@@ -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:
-
+
+