diff --git a/package-lock.json b/package-lock.json
index 7d646a3..fc14285 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,10 +9,10 @@
"version": "1.0.0",
"dependencies": {
"@hello-pangea/dnd": "^18.0.1",
- "@mantine/core": "^8.1.3",
- "@mantine/form": "^8.1.3",
- "@mantine/hooks": "^8.1.3",
- "@mantine/notifications": "^8.1.3",
+ "@mantine/core": "^8.2.1",
+ "@mantine/form": "^8.2.1",
+ "@mantine/hooks": "^8.2.1",
+ "@mantine/notifications": "^8.2.1",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.83.0",
"@tanstack/react-query-persist-client": "^5.83.0",
@@ -25,7 +25,7 @@
"recharts": "^3.1.0"
},
"devDependencies": {
- "@types/node": "^24.0.14",
+ "@types/node": "^24.0.15",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.7.0",
@@ -930,9 +930,9 @@
}
},
"node_modules/@mantine/core": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.1.3.tgz",
- "integrity": "sha512-2WOPC8GSN3MApet0MccSn6LaXRhcP6SVtZnbuHoqJ/atrfK7kLE66ILr4OXov7JAj1ASJ4Xk0bOXmu5fBExAvQ==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.2.1.tgz",
+ "integrity": "sha512-KxvydotyFRdrRbqULUX2G35/GddPFju9XQUv/vdDWu1ytIWZViTguc+WSj1aBd0DtfRrSaofU5ezZISEXVrPBA==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.26.28",
@@ -943,15 +943,15 @@
"type-fest": "^4.27.0"
},
"peerDependencies": {
- "@mantine/hooks": "8.1.3",
+ "@mantine/hooks": "8.2.1",
"react": "^18.x || ^19.x",
"react-dom": "^18.x || ^19.x"
}
},
"node_modules/@mantine/form": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.1.3.tgz",
- "integrity": "sha512-OoSVv2cyjKRZ+C4Rw63VsnO3qjKGZHJkd6DSJTVRQHXfDr10hxmC5yXgxGKsxGQ+xFd4ZCdtzPUU2BoWbHfZAA==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.2.1.tgz",
+ "integrity": "sha512-XXGdO3IgTNcDl6lP7FIysEaH0rP6CfOGulxQA3ot+hHA8/JxmHWoWeoKOBE8I6QW7UYqaBxARxgBHLMcp8qASw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -962,34 +962,34 @@
}
},
"node_modules/@mantine/hooks": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.1.3.tgz",
- "integrity": "sha512-yL4SbyYjrkmtIhscswajNz9RL0iO2+V8CMtOi0KISch2rPNvTAJNumFuZaXgj4UHeDc0JQYSmcZ+EW8NGm7xcQ==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.2.1.tgz",
+ "integrity": "sha512-gnRDk5FXCD9fa0AjlAj9otCsZL9QJzVrpYZk9KjOEoP5XR1TEE2F9/rGbajh1UVjPnD3jUlNLRJMH0YHTlA65A==",
"license": "MIT",
"peerDependencies": {
"react": "^18.x || ^19.x"
}
},
"node_modules/@mantine/notifications": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.1.3.tgz",
- "integrity": "sha512-Xy6f/l1yLTo77hz8X80sOuY+HW80e1rn8ucygx9TAexK5+XtyriOv26TQ3EJ6Ej5jlchtZRFEUJ4tJGRWjGCNg==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.2.1.tgz",
+ "integrity": "sha512-9s4oN0ruX7AqUghZIXEwx9/t+fHATe/kvn5O5Zum+MFoOH46p8k4ljNb6BBDKTtT3clRHx7KHTR7OrLWlDEjrw==",
"license": "MIT",
"dependencies": {
- "@mantine/store": "8.1.3",
+ "@mantine/store": "8.2.1",
"react-transition-group": "4.4.5"
},
"peerDependencies": {
- "@mantine/core": "8.1.3",
- "@mantine/hooks": "8.1.3",
+ "@mantine/core": "8.2.1",
+ "@mantine/hooks": "8.2.1",
"react": "^18.x || ^19.x",
"react-dom": "^18.x || ^19.x"
}
},
"node_modules/@mantine/store": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.1.3.tgz",
- "integrity": "sha512-rO72LfSJqSNCwufqJxTWiHMyOR6sR3mqAcnBcw/f5aTvyOYoHZzlm4q4+TL8/2vYGRVsr9YM2Ez6HQ1vk/RR8g==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.2.1.tgz",
+ "integrity": "sha512-4DAVJcI5Sa8Zez/23DV4eipYbqRfw7r+UkuWczPFyhAU+BZdfIql1gFipojAlR0FpWbB8aL0F7wFKNKfkZ0YCQ==",
"license": "MIT",
"peerDependencies": {
"react": "^18.x || ^19.x"
@@ -1846,9 +1846,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.0.14",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
- "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
+ "version": "24.0.15",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz",
+ "integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 5dd2347..d407c09 100644
--- a/package.json
+++ b/package.json
@@ -11,10 +11,10 @@
},
"dependencies": {
"@hello-pangea/dnd": "^18.0.1",
- "@mantine/core": "^8.1.3",
- "@mantine/form": "^8.1.3",
- "@mantine/hooks": "^8.1.3",
- "@mantine/notifications": "^8.1.3",
+ "@mantine/core": "^8.2.1",
+ "@mantine/form": "^8.2.1",
+ "@mantine/hooks": "^8.2.1",
+ "@mantine/notifications": "^8.2.1",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.83.0",
"@tanstack/react-query-persist-client": "^5.83.0",
@@ -27,7 +27,7 @@
"recharts": "^3.1.0"
},
"devDependencies": {
- "@types/node": "^24.0.14",
+ "@types/node": "^24.0.15",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.7.0",
diff --git a/src/hooks/useCommonHooks.ts b/src/hooks/useCommonHooks.ts
index 471171f..66dc536 100644
--- a/src/hooks/useCommonHooks.ts
+++ b/src/hooks/useCommonHooks.ts
@@ -125,6 +125,48 @@ export function useCommonHooks() {
return k;
}
+ function getObjectDiff(original: any, current: any) {
+ const changes: any = {};
+
+ // Check current object's properties
+ for (const [key, value] of Object.entries(current)) {
+ if (!(key in original)) {
+ changes[key] = {
+ oldValue: undefined,
+ newValue: value,
+ };
+ continue;
+ }
+
+ const originalValue = original[key];
+ const currentValue = value;
+
+ // Handle different types of comparisons
+ if (
+ originalValue !== currentValue &&
+ String(originalValue) !== String(currentValue) &&
+ JSON.stringify(originalValue) !== JSON.stringify(currentValue)
+ ) {
+ changes[key] = {
+ oldValue: originalValue,
+ newValue: currentValue,
+ };
+ }
+ }
+
+ // Check for removed properties
+ for (const key of Object.keys(original)) {
+ if (!(key in current)) {
+ changes[key] = {
+ oldValue: original[key],
+ newValue: undefined,
+ };
+ }
+ }
+
+ return Object.keys(changes).length === 0 ? null : changes;
+ }
+
return {
isMobile,
theme,
@@ -132,6 +174,7 @@ export function useCommonHooks() {
urlPattern,
fixDigits,
reorder,
+ getObjectDiff,
handleThemeChange,
handleDelete,
themeName,
diff --git a/src/pages/settings/components/Theming/Backgrounds/BackgroundLightbox.tsx b/src/pages/settings/components/Theming/Backgrounds/BackgroundLightbox.tsx
new file mode 100644
index 0000000..2c061f2
--- /dev/null
+++ b/src/pages/settings/components/Theming/Backgrounds/BackgroundLightbox.tsx
@@ -0,0 +1,38 @@
+import { Group, Image, Modal, Text } from '@mantine/core';
+
+export function BackgroundLightbox({
+ opened,
+ close,
+ background,
+}: {
+ opened: boolean;
+ close: () => void;
+ background: string;
+}) {
+ return (
+
+
+ {background}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/settings/components/Theming/Backgrounds/BackgroundModal.tsx b/src/pages/settings/components/Theming/Backgrounds/BackgroundModal.tsx
index 798027f..a844b0c 100644
--- a/src/pages/settings/components/Theming/Backgrounds/BackgroundModal.tsx
+++ b/src/pages/settings/components/Theming/Backgrounds/BackgroundModal.tsx
@@ -12,7 +12,7 @@ import {
Text,
TextInput,
} from '@mantine/core';
-import { useDebouncedValue, useElementSize } from '@mantine/hooks';
+import { useDebouncedValue, useDisclosure, useElementSize } from '@mantine/hooks';
import { useCommonHooks } from 'hooks';
import { useFileUploadAPI } from 'hooks/useAPI';
import { useEffect, useState } from 'react';
@@ -24,6 +24,7 @@ import {
scrollAreaClasses,
textInputClasses,
} from 'styles';
+import { BackgroundLightbox } from './BackgroundLightbox';
import { DeleteBackground } from './DeleteBackground';
import { EditBackground } from './EditBackground';
@@ -38,7 +39,10 @@ export function BackgroundModal({
backgroundList: string[];
inUseBackgrounds: string[];
}) {
+ const [opened, { open, close }] = useDisclosure(false);
+ const [lightboxName, setLightboxName] = useState('');
const { ref, width } = useElementSize();
+ const [hovered, setHovered] = useState('');
const [background, setBackground] = useState(null);
const [fileName, setFileName] = useState('');
const [newFileName, setNewFileName] = useState('');
@@ -64,326 +68,359 @@ export function BackgroundModal({
}, [debouncedSearch]);
return (
- {
- setBackground(null);
- setFileName('');
- setNewFileName('');
- handleBackgroundModalClose();
- setConfirm(false);
- setEditing(false);
- }}
- fullScreen={isMobile}
- centered
- keepMounted
- >
-
-
-
-
-
- Manage Backgrounds
-
-
-
-
-
-
- {!editing && !confirm && !adding && searchValue !== undefined && (
-
- {
- setIsLoading(true);
- setSearchValue(event.target.value);
- }}
- style={{
- zIndex: 100,
- paddingTop: 0,
- }}
- styles={
- theme.textShadowsEnabled
- ? {
- label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
- }
- : undefined
- }
- rightSection={
- isLoading ? (
-
-
-
- ) : (
- searchValue && (
- setSearchValue('')}
- >
-
-
- )
- )
- }
- />
-
-
- )}
- {confirm && (
-
- )}
- {editing && (
-
- )}
- {!editing && !confirm && !adding && (
-
-
- {backgroundList
- .filter((background) => background !== 'None')
- .filter(
- (background) =>
- !debouncedSearch ||
- background
- .toLocaleLowerCase()
- .includes(debouncedSearch.toLocaleLowerCase()),
- )
- .map((background) => (
-
-
- {background}
-
-
- {
- setFileName(background);
- setEditing(true);
- }}
- >
-
-
- {
- setFileName(background);
- setConfirm(true);
- }}
- >
-
-
-
- ))}
-
-
- )}
- {adding && (
-
-
-
-
- setNewFileName(e.target.value)}
- pr={'xl'}
- pl={0}
- styles={{
- input: {
- color: 'var(--mantine-color-primary-0)',
- borderColor: 'var(--mantine-color-primary-0)',
- },
- }}
- />
-
-
+ {opened && (
+
+ )}
+ {
+ setBackground(null);
+ setFileName('');
+ setNewFileName('');
+ handleBackgroundModalClose();
+ setConfirm(false);
+ setEditing(false);
+ }}
+ fullScreen={isMobile}
+ centered
+ keepMounted={false}
+ >
+
+
+
+
+
+ Manage Backgrounds
+
+
+
+
+
+
+ {!editing && !confirm && !adding && searchValue !== undefined && (
+
+ {
+ setIsLoading(true);
+ setSearchValue(event.target.value);
+ }}
style={{
- aspectRatio: '16/9',
- width: '35%',
- backgroundSize: 'cover',
- backgroundPosition: 'center',
- backgroundRepeat: 'no-repeat',
- backgroundColor: 'var(--mantine-color-search-0)',
- padding: '2rem',
- margin: '1rem',
- marginRight: 0,
+ zIndex: 100,
+ paddingTop: 0,
+ }}
+ styles={
+ theme.textShadowsEnabled
+ ? {
+ label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
+ }
+ : undefined
+ }
+ rightSection={
+ isLoading ? (
+
+
+
+ ) : (
+ searchValue && (
+ setSearchValue('')}
+ >
+
+
+ )
+ )
+ }
+ />
+
+
+
-
-
-
-
-
+ }}
+ >
+ Cancel
+
+
+
+ )}
+
+
+
+
+ >
);
}
diff --git a/src/pages/settings/components/Theming/Backgrounds/DeleteBackground.tsx b/src/pages/settings/components/Theming/Backgrounds/DeleteBackground.tsx
index 34ce7a0..a0ff898 100644
--- a/src/pages/settings/components/Theming/Backgrounds/DeleteBackground.tsx
+++ b/src/pages/settings/components/Theming/Backgrounds/DeleteBackground.tsx
@@ -46,7 +46,7 @@ export function DeleteBackground({
style={{
aspectRatio: '16/9',
height: '100%',
- backgroundImage: `url(../../api/backgroundImages/${fileName})`,
+ backgroundImage: `url(../../api/backgroundImages/thumbs/${fileName})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
diff --git a/src/pages/settings/components/Theming/Backgrounds/EditBackground.tsx b/src/pages/settings/components/Theming/Backgrounds/EditBackground.tsx
index 8291c18..95c780c 100644
--- a/src/pages/settings/components/Theming/Backgrounds/EditBackground.tsx
+++ b/src/pages/settings/components/Theming/Backgrounds/EditBackground.tsx
@@ -1,4 +1,4 @@
-import { Button, Card, Group, Stack, Text, TextInput } from '@mantine/core';
+import { Button, Group, Image, Stack, Text, TextInput } from '@mantine/core';
import { useCommonHooks, useMutationAPI } from 'hooks';
import { useState } from 'react';
import { textInputClasses } from 'styles';
@@ -27,7 +27,6 @@ export function EditBackground({
align='center'
style={{
width: '100%',
- height: '30rem',
backgroundColor: 'var(--mantine-color-search-0)',
padding: '1rem',
borderRadius: '.5rem',
@@ -42,7 +41,7 @@ export function EditBackground({
Updating the name will update the associated themes.
)}
-
+
-
-
+
+
void;
handleSaveTheme: () => void;
}) {
- const { isMobile, theme } = useCommonHooks();
+ const { isMobile, theme, getObjectDiff } = useCommonHooks();
const viewportRef = useRef(null);
const [query, setQuery] = useState('');
const [hovered, setHovered] = useState(-1);
@@ -71,8 +72,8 @@ export function ThemeModal({
background:
item !== 'none' && item !== 'None'
? index === hovered
- ? `linear-gradient(${selectedTheme.backgroundColor}88, ${selectedTheme.backgroundColor}88), no-repeat center url(../../api/backgroundImages/${item})`
- : `no-repeat center url(../../api/backgroundImages/${item})`
+ ? `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,
@@ -491,27 +492,131 @@ export function ThemeModal({
)}
{!pickerOpen && !backgroundPicker && (
-
- {
- setSelectedTheme({
- ...selectedTheme,
- textShadowsEnabled: e.target.checked ? 1 : 0,
- });
- }}
- />
- {
- setSelectedTheme({
- ...selectedTheme,
- appShadowsEnabled: e.target.checked ? 1 : 0,
- });
- }}
- />
+
+
+ {
+ 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 (
+
+ {key}
+ {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
+ )}
+
+ )}
+
+ );
+ },
+ )}
+
+
+ >
+ )}
{
setSelectedTheme({ ...defaultTheme });
diff --git a/src/pages/settings/components/Theming/Themes/ThemeModalContent.tsx b/src/pages/settings/components/Theming/Themes/ThemeModalContent.tsx
index e0ef9ea..c2af660 100644
--- a/src/pages/settings/components/Theming/Themes/ThemeModalContent.tsx
+++ b/src/pages/settings/components/Theming/Themes/ThemeModalContent.tsx
@@ -10,7 +10,7 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
style={{
backgroundImage:
selectedTheme?.backgroundImage.toLocaleLowerCase() !== 'none'
- ? `linear-gradient(${selectedTheme?.backgroundColor}33, ${selectedTheme?.backgroundColor}33), url(../../api/backgroundImages/${selectedTheme?.backgroundImage})`
+ ? `linear-gradient(${selectedTheme?.backgroundColor}33, ${selectedTheme?.backgroundColor}33), url(../../api/backgroundImages/thumbs/${selectedTheme?.backgroundImage})`
: undefined,
backgroundSize:
selectedTheme?.backgroundImage.toLocaleLowerCase() !== 'none'
diff --git a/src/pages/settings/components/Theming/Themes/ThemeSelect.tsx b/src/pages/settings/components/Theming/Themes/ThemeSelect.tsx
index f65badb..25bc00f 100644
--- a/src/pages/settings/components/Theming/Themes/ThemeSelect.tsx
+++ b/src/pages/settings/components/Theming/Themes/ThemeSelect.tsx
@@ -61,7 +61,7 @@ export function ThemeSelect({
color: theme?.primaryColor,
backgroundImage:
theme?.backgroundImage.toLocaleLowerCase() !== 'none'
- ? `linear-gradient(${theme?.backgroundColor}33, ${theme?.backgroundColor}33), url(../../api/backgroundImages/${theme?.backgroundImage})`
+ ? `linear-gradient(${theme?.backgroundColor}33, ${theme?.backgroundColor}33), url(../../api/backgroundImages/thumbs/${theme?.backgroundImage})`
: undefined,
backgroundSize:
theme?.backgroundImage.toLocaleLowerCase() !== 'none' ? 'cover' : undefined,
@@ -88,7 +88,7 @@ export function ThemeSelect({
style={{
backgroundImage:
theme?.backgroundImage.toLocaleLowerCase() !== 'none'
- ? `linear-gradient(${theme?.backgroundColor}33, ${theme?.backgroundColor}33), url(../../api/backgroundImages/${theme?.backgroundImage})`
+ ? `linear-gradient(${theme?.backgroundColor}33, ${theme?.backgroundColor}33), url(../../api/backgroundImages/thumbs/${theme?.backgroundImage})`
: undefined,
backgroundSize:
theme?.backgroundImage.toLocaleLowerCase() !== 'none'