Compare commits
20 Commits
de4a963a08
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 148bef7dfd | |||
| e6c5f02a50 | |||
| 2f8d3ad58e | |||
| 2cf34f86fa | |||
| ecf061da1f | |||
| 69dae97a79 | |||
| ffd241dad8 | |||
| 142a45ef54 | |||
| 4bcd1e4858 | |||
| 1f86881533 | |||
| a78ed0dad0 | |||
| 1e614790f6 | |||
| e4b07a3907 | |||
| 01dd6b1aa2 | |||
| 78e6bd3374 | |||
| bd9e77c382 | |||
| ea56aa8402 | |||
| 204c9fcd02 | |||
| bf761df56d | |||
| 68fa451d19 |
@@ -116,11 +116,8 @@ sudo pm2 start server.cjs
|
|||||||
### TODO
|
### TODO
|
||||||
|
|
||||||
- Mobile improvements
|
- Mobile improvements
|
||||||
- New Carosel on weather description
|
- New Carousel on weather description
|
||||||
- Updates to weather modal
|
|
||||||
- Add mantine-led for monitoring status (https://gfazioli.github.io/mantine-led)
|
- Add mantine-led for monitoring status (https://gfazioli.github.io/mantine-led)
|
||||||
- Update Theme playlists
|
|
||||||
- Include mantine-reflection (https://gfazioli.github.io/mantine-reflection#example-complex-components)
|
|
||||||
- Explore https://gfazioli.github.io/mantine-scene/
|
- Explore https://gfazioli.github.io/mantine-scene/
|
||||||
- Explore https://gfazioli.github.io/mantine-window/
|
- Explore https://gfazioli.github.io/mantine-window/
|
||||||
- Upgrade spinners/loaders (https://gfazioli.github.io/mantine-spinner#example-random)
|
- Upgrade spinners/loaders (https://gfazioli.github.io/mantine-spinner#example-random)
|
||||||
532
package-lock.json
generated
532
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@@ -10,43 +10,43 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gfazioli/mantine-border-animate": "^3.0.5",
|
"@gfazioli/mantine-border-animate": "^3.0.7",
|
||||||
"@gfazioli/mantine-depth-select": "^2.0.15",
|
"@gfazioli/mantine-depth-select": "^2.0.17",
|
||||||
"@gfazioli/mantine-parallax": "^4.0.13",
|
"@gfazioli/mantine-parallax": "^4.0.15",
|
||||||
"@gfazioli/mantine-reflection": "^3.0.13",
|
"@gfazioli/mantine-reflection": "^3.0.15",
|
||||||
"@gfazioli/mantine-text-animate": "^4.0.15",
|
"@gfazioli/mantine-text-animate": "^4.0.17",
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@mantine/core": "^9.7.1",
|
"@mantine/core": "^9.7.3",
|
||||||
"@mantine/dates": "^9.7.1",
|
"@mantine/dates": "^9.7.3",
|
||||||
"@mantine/form": "^9.7.1",
|
"@mantine/form": "^9.7.3",
|
||||||
"@mantine/hooks": "^9.7.1",
|
"@mantine/hooks": "^9.7.3",
|
||||||
"@mantine/notifications": "^9.7.1",
|
"@mantine/notifications": "^9.7.3",
|
||||||
"@tanstack/react-query": "^5.104.1",
|
"@tanstack/react-query": "^5.104.5",
|
||||||
"@tanstack/react-query-devtools": "^5.104.1",
|
"@tanstack/react-query-devtools": "^5.104.5",
|
||||||
"@tanstack/react-query-persist-client": "^5.104.1",
|
"@tanstack/react-query-persist-client": "^5.104.5",
|
||||||
"@tanstack/react-table": "^9.2.8",
|
"@tanstack/react-table": "^9.3.1",
|
||||||
"@tanstack/react-virtual": "^3.14.14",
|
"@tanstack/react-virtual": "^3.14.22",
|
||||||
"idb-keyval": "^6.3.0",
|
"idb-keyval": "^6.3.0",
|
||||||
"jotai": "^3.0.1",
|
"jotai": "^3.0.1",
|
||||||
"openmeteo": "^1.2.3",
|
"openmeteo": "^1.2.3",
|
||||||
"react": "^19.3.0",
|
"react": "^19.3.1",
|
||||||
"react-dom": "^19.3.0",
|
"react-dom": "^19.3.1",
|
||||||
"react-fontpicker-ts": "^1.3.0",
|
"react-fontpicker-ts": "^1.3.0",
|
||||||
"react-icons": "^5.7.0",
|
"react-icons": "^5.7.0",
|
||||||
"react-router-dom": "^7.18.4",
|
"react-router-dom": "^7.18.4",
|
||||||
"recharts": "^3.10.1"
|
"recharts": "^3.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^26.6.4",
|
"@types/node": "^26.6.5",
|
||||||
"@types/react": "^19.3.0",
|
"@types/react": "^19.3.0",
|
||||||
"@types/react-dom": "^19.3.0",
|
"@types/react-dom": "^19.3.0",
|
||||||
"@vitejs/plugin-react": "^6.1.2",
|
"@vitejs/plugin-react": "^6.1.2",
|
||||||
"@vitest/coverage-v8": "^5.0.3",
|
"@vitest/coverage-v8": "^5.0.5",
|
||||||
"@vitest/ui": "^5.0.3",
|
"@vitest/ui": "^5.0.5",
|
||||||
"typescript": "^7.0.2",
|
"typescript": "^7.0.2",
|
||||||
"vite": "^8.3.4",
|
"vite": "^8.3.5",
|
||||||
"vite-plugin-dts": "~5.1.2",
|
"vite-plugin-dts": "~5.1.2",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
"vite-tsconfig-paths": "^6.1.1",
|
||||||
"vitest": "^5.0.3"
|
"vitest": "^5.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/App.tsx
60
src/App.tsx
@@ -37,17 +37,11 @@ function App() {
|
|||||||
const { data: themePlaylists, isFetched: isThemePlaylistsFetched } = useQueryAPI({
|
const { data: themePlaylists, isFetched: isThemePlaylistsFetched } = useQueryAPI({
|
||||||
endpoint: 'api/themePlaylists',
|
endpoint: 'api/themePlaylists',
|
||||||
});
|
});
|
||||||
const { data: personalData, isFetched: isPersonalDataFetched } = useQueryAPI({
|
const { data: personalData } = useQueryAPI({
|
||||||
endpoint: 'api/personalData',
|
endpoint: 'api/personalData',
|
||||||
});
|
});
|
||||||
const {
|
const { settings, updateSetting, theme, setTheme, handleThemeChange } =
|
||||||
theme,
|
useCommonHooks();
|
||||||
setTheme,
|
|
||||||
themeName,
|
|
||||||
setThemeName,
|
|
||||||
handleThemeChange,
|
|
||||||
currentThemePlaylist,
|
|
||||||
} = useCommonHooks();
|
|
||||||
const [loaded, setLoaded] = useState<boolean>(false);
|
const [loaded, setLoaded] = useState<boolean>(false);
|
||||||
const overrides = createTheme({
|
const overrides = createTheme({
|
||||||
primaryColor: 'primary',
|
primaryColor: 'primary',
|
||||||
@@ -68,22 +62,23 @@ function App() {
|
|||||||
'shift + t',
|
'shift + t',
|
||||||
() => {
|
() => {
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
themePlaylists?.find((t: ThemePlaylist) => t?.name === currentThemePlaylist)
|
themePlaylists?.find(
|
||||||
?.themeList ?? '[]',
|
(t: ThemePlaylist) => t?.name === settings.currentThemePlaylist,
|
||||||
|
)?.themeList ?? '[]',
|
||||||
);
|
);
|
||||||
if (themeList.length > 0 && currentThemePlaylist !== 'Time of Day') {
|
if (themeList.length > 0 && settings.currentThemePlaylist !== 'Time of Day') {
|
||||||
const filteredThemes = allThemes.filter((t: Theme) => themeList.includes(t.id));
|
const filteredThemes = allThemes.filter((t: Theme) => themeList.includes(t.id));
|
||||||
handleThemeChange(filteredThemes, false);
|
handleThemeChange(filteredThemes, false);
|
||||||
} else if (currentThemePlaylist === 'All') {
|
} else if (settings.currentThemePlaylist === 'All') {
|
||||||
handleThemeChange(allThemes, true);
|
handleThemeChange(allThemes, true);
|
||||||
} else if (currentThemePlaylist === 'Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter(
|
allThemes.filter(
|
||||||
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else if (currentThemePlaylist === 'No Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'No Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter((a: any) =>
|
allThemes.filter((a: any) =>
|
||||||
a.backgroundImage.toLowerCase().includes('none'),
|
a.backgroundImage.toLowerCase().includes('none'),
|
||||||
@@ -119,22 +114,26 @@ function App() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
allThemes &&
|
allThemes &&
|
||||||
themeName &&
|
settings.theme &&
|
||||||
personalData &&
|
personalData &&
|
||||||
Object.keys(personalData).length > 0 &&
|
Object.keys(personalData).length > 0 &&
|
||||||
isThemePlaylistsFetched
|
isThemePlaylistsFetched
|
||||||
) {
|
) {
|
||||||
if (currentThemePlaylist === 'All') {
|
Object.keys(personalData[0]).forEach((key: any) => {
|
||||||
if (themeName === 'random') {
|
updateSetting(key, personalData[0][key]);
|
||||||
|
});
|
||||||
|
if (settings.currentThemePlaylist === 'All') {
|
||||||
|
if (settings.theme === 'random') {
|
||||||
handleThemeChange(allThemes, false);
|
handleThemeChange(allThemes, false);
|
||||||
} else {
|
} else {
|
||||||
const temp = allThemes.find((t: Theme) => t.name === themeName);
|
const temp = allThemes.find((t: Theme) => t.name === settings.theme);
|
||||||
setTheme(temp);
|
setTheme(temp);
|
||||||
}
|
}
|
||||||
} else if (currentThemePlaylist === 'Time of Day') {
|
} else if (settings.currentThemePlaylist === 'Time of Day') {
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
themePlaylists?.find((t: ThemePlaylist) => t.name === currentThemePlaylist)
|
themePlaylists?.find(
|
||||||
.themeList,
|
(t: ThemePlaylist) => t.name === settings.currentThemePlaylist,
|
||||||
|
).themeList,
|
||||||
);
|
);
|
||||||
setTheme(
|
setTheme(
|
||||||
allThemes.find(
|
allThemes.find(
|
||||||
@@ -142,34 +141,35 @@ function App() {
|
|||||||
t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (currentThemePlaylist === 'Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter((a: any) => !a.backgroundImage.toLowerCase().includes('none')),
|
allThemes.filter((a: any) => !a.backgroundImage.toLowerCase().includes('none')),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else if (currentThemePlaylist === 'No Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'No Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter((a: any) => a.backgroundImage.toLowerCase().includes('none')),
|
allThemes.filter((a: any) => a.backgroundImage.toLowerCase().includes('none')),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
themePlaylists?.find((t: ThemePlaylist) => t.name === currentThemePlaylist)
|
themePlaylists?.find(
|
||||||
.themeList,
|
(t: ThemePlaylist) => t.name === settings.currentThemePlaylist,
|
||||||
|
).themeList,
|
||||||
);
|
);
|
||||||
const filteredThemes = allThemes.filter((t: Theme) => themeList.includes(t.id));
|
const filteredThemes = allThemes.filter((t: Theme) => themeList.includes(t.id));
|
||||||
setThemeName('random');
|
updateSetting('theme', 'random');
|
||||||
handleThemeChange(filteredThemes, false);
|
handleThemeChange(filteredThemes, false);
|
||||||
}
|
}
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isAllThemesFetched,
|
isAllThemesFetched,
|
||||||
themeName,
|
settings.theme,
|
||||||
allThemes,
|
allThemes,
|
||||||
currentThemePlaylist,
|
settings.currentThemePlaylist,
|
||||||
isThemePlaylistsFetched,
|
isThemePlaylistsFetched,
|
||||||
isPersonalDataFetched,
|
personalData,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function AppItem({
|
|||||||
h={height}
|
h={height}
|
||||||
w={width}
|
w={width}
|
||||||
duration={3}
|
duration={3}
|
||||||
beamMode={'conic'}
|
beamMode={'wedge'}
|
||||||
borderWidth={scale * 3}
|
borderWidth={scale * 3}
|
||||||
borderOpacity={0.75}
|
borderOpacity={0.75}
|
||||||
size={(scale * 48) / (width / height < 2 ? 2 : 1)}
|
size={(scale * 48) / (width / height < 2 ? 2 : 1)}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Group,
|
Group,
|
||||||
Highlight,
|
Highlight,
|
||||||
|
Menu,
|
||||||
Popover,
|
Popover,
|
||||||
Stack,
|
Stack,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -10,7 +11,8 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useCommonHooks } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FiHelpCircle, FiLoader, FiPlus, FiX } from 'react-icons/fi';
|
import { FaSort } from 'react-icons/fa';
|
||||||
|
import { FiHelpCircle, FiPlus, FiX } from 'react-icons/fi';
|
||||||
import {
|
import {
|
||||||
actionIconClasses,
|
actionIconClasses,
|
||||||
buttonClasses,
|
buttonClasses,
|
||||||
@@ -31,6 +33,9 @@ export function SettingsHeader({
|
|||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
isLoading,
|
isLoading,
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
|
sortBy,
|
||||||
|
setSortBy,
|
||||||
|
customSearchEnabled,
|
||||||
}: {
|
}: {
|
||||||
setDragging: undefined | ((dragging: boolean) => void);
|
setDragging: undefined | ((dragging: boolean) => void);
|
||||||
openModal: undefined | (() => void);
|
openModal: undefined | (() => void);
|
||||||
@@ -45,6 +50,9 @@ export function SettingsHeader({
|
|||||||
searchPlaceholder: string | undefined;
|
searchPlaceholder: string | undefined;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
setIsLoading: (isLoading: boolean) => void;
|
setIsLoading: (isLoading: boolean) => void;
|
||||||
|
sortBy: string;
|
||||||
|
setSortBy: undefined | ((sortBy: string) => void);
|
||||||
|
customSearchEnabled: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { isMobile, theme } = useCommonHooks();
|
const { isMobile, theme } = useCommonHooks();
|
||||||
const [disabled, setDisabled] = useState(false);
|
const [disabled, setDisabled] = useState(false);
|
||||||
@@ -123,10 +131,15 @@ export function SettingsHeader({
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
{searchValue !== undefined && (
|
{searchValue !== undefined && (
|
||||||
<DebouncedInput
|
<Group
|
||||||
disabled={disabled}
|
gap={4}
|
||||||
w={isMobile ? undefined : '35%'}
|
w={isMobile ? undefined : '35%'}
|
||||||
pos={isMobile ? undefined : 'absolute'}
|
pos={isMobile ? undefined : 'absolute'}
|
||||||
|
>
|
||||||
|
<DebouncedInput
|
||||||
|
disabled={disabled}
|
||||||
|
w={'90%'}
|
||||||
|
// pos={isMobile ? undefined : 'absolute'}
|
||||||
className={textInputClasses.default}
|
className={textInputClasses.default}
|
||||||
classNames={textInputClasses}
|
classNames={textInputClasses}
|
||||||
autoFocus={!isMobile}
|
autoFocus={!isMobile}
|
||||||
@@ -151,9 +164,7 @@ export function SettingsHeader({
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
className={actionIconClasses.loadingSpinner}
|
className={actionIconClasses.loadingSpinner}
|
||||||
classNames={actionIconClasses}
|
classNames={actionIconClasses}
|
||||||
>
|
/>
|
||||||
<FiLoader />
|
|
||||||
</ActionIcon>
|
|
||||||
) : (
|
) : (
|
||||||
searchValue && (
|
searchValue && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
@@ -167,8 +178,100 @@ export function SettingsHeader({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{setSortBy !== undefined && !disabled && (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Target>
|
||||||
|
<ActionIcon>
|
||||||
|
<FaSort />
|
||||||
|
</ActionIcon>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Label>Sort By</Menu.Label>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setSortBy('A>Z');
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
sortBy === 'A>Z'
|
||||||
|
? 'var(--mantine-color-background-0)'
|
||||||
|
: 'var(--mantine-color-primary-0)',
|
||||||
|
backgroundColor:
|
||||||
|
sortBy === 'A>Z' ? 'var(--mantine-color-primary-0)' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'A>Z'}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setSortBy('Z>A');
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
sortBy === 'Z>A'
|
||||||
|
? 'var(--mantine-color-background-0)'
|
||||||
|
: 'var(--mantine-color-primary-0)',
|
||||||
|
backgroundColor:
|
||||||
|
sortBy === 'Z>A' ? 'var(--mantine-color-primary-0)' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'Z>A'}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setSortBy('Newest');
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
sortBy === 'Newest'
|
||||||
|
? 'var(--mantine-color-background-0)'
|
||||||
|
: 'var(--mantine-color-primary-0)',
|
||||||
|
backgroundColor:
|
||||||
|
sortBy === 'Newest' ? 'var(--mantine-color-primary-0)' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'Newest'}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setSortBy('Oldest');
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
sortBy === 'Oldest'
|
||||||
|
? 'var(--mantine-color-background-0)'
|
||||||
|
: 'var(--mantine-color-primary-0)',
|
||||||
|
backgroundColor:
|
||||||
|
sortBy === 'Oldest' ? 'var(--mantine-color-primary-0)' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'Oldest'}
|
||||||
|
</Menu.Item>
|
||||||
|
{customSearchEnabled && (
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setSortBy('Custom');
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
sortBy === 'Custom'
|
||||||
|
? 'var(--mantine-color-background-0)'
|
||||||
|
: 'var(--mantine-color-primary-0)',
|
||||||
|
backgroundColor:
|
||||||
|
sortBy === 'Custom'
|
||||||
|
? 'var(--mantine-color-primary-0)'
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'Custom'}
|
||||||
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
{setDragging && (
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{setDragging && sortBy === 'Custom' && (
|
||||||
<Switch
|
<Switch
|
||||||
className={switchClasses.default}
|
className={switchClasses.default}
|
||||||
classNames={switchClasses}
|
classNames={switchClasses}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export default function CurrentWeather({
|
|||||||
<>
|
<>
|
||||||
{current && (
|
{current && (
|
||||||
<Card
|
<Card
|
||||||
|
w={'100%'}
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--mantine-color-primary-0)',
|
color: 'var(--mantine-color-primary-0)',
|
||||||
backgroundColor: 'var(--mantine-color-search-0)',
|
backgroundColor: 'var(--mantine-color-search-0)',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ActionIcon, Box, Card, Group, Stack, Text } from '@mantine/core';
|
import { ActionIcon, Box, Card, Group, Stack, Text } from '@mantine/core';
|
||||||
import { useCommonHooks } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { FaLocationArrow, FaSun, FaWind } from 'react-icons/fa';
|
import { FaArrowLeft, FaLocationArrow, FaSun, FaWind } from 'react-icons/fa';
|
||||||
import { FaDroplet, FaX } from 'react-icons/fa6';
|
import { FaDroplet } from 'react-icons/fa6';
|
||||||
import { WiRain, WiSunrise, WiSunset } from 'react-icons/wi';
|
import { WiRain, WiSunrise, WiSunset } from 'react-icons/wi';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import { WeatherCodes } from 'types';
|
import { WeatherCodes } from 'types';
|
||||||
@@ -32,29 +32,12 @@ export default function ExtendedForecastDetails({
|
|||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
boxShadow:
|
|
||||||
'15px 15px 2px color-mix(in srgb, var(--mantine-color-border-0), transparent 50%)',
|
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
p={0}
|
p={0}
|
||||||
>
|
>
|
||||||
<ActionIcon
|
|
||||||
pos={'absolute'}
|
|
||||||
top={-12}
|
|
||||||
right={-12}
|
|
||||||
style={{
|
|
||||||
borderRadius: '100%',
|
|
||||||
boxShadow: '2px 2px var(--mantine-color-search-0)',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedDay(undefined);
|
|
||||||
setSelectedHourly(undefined);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaX />
|
|
||||||
</ActionIcon>
|
|
||||||
<Group
|
<Group
|
||||||
w={'100%'}
|
w={'100%'}
|
||||||
justify='center'
|
justify='center'
|
||||||
@@ -66,11 +49,25 @@ export default function ExtendedForecastDetails({
|
|||||||
borderTopRightRadius: '8px',
|
borderTopRightRadius: '8px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ActionIcon
|
||||||
|
pos={'absolute'}
|
||||||
|
left={16}
|
||||||
|
style={{
|
||||||
|
borderRadius: '100%',
|
||||||
|
boxShadow: '2px 2px var(--mantine-color-search-0)',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedDay(undefined);
|
||||||
|
setSelectedHourly(undefined);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaArrowLeft />
|
||||||
|
</ActionIcon>
|
||||||
<Text fw={600} fz={'1.5rem'}>
|
<Text fw={600} fz={'1.5rem'}>
|
||||||
{selectedDay.time.toLocaleDateString('en-US', { weekday: 'long' })}
|
{selectedDay?.time.toLocaleDateString('en-US', { weekday: 'long' })}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fw={600} fz={'1.5rem'}>
|
<Text fw={600} fz={'1.5rem'}>
|
||||||
{selectedDay.time.toLocaleDateString()}
|
{selectedDay?.time.toLocaleDateString()}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Stack align='center' p={16}>
|
<Stack align='center' p={16}>
|
||||||
@@ -95,8 +92,8 @@ export default function ExtendedForecastDetails({
|
|||||||
leftSide={<Text>Feels like</Text>}
|
leftSide={<Text>Feels like</Text>}
|
||||||
rightSide={
|
rightSide={
|
||||||
<Text>
|
<Text>
|
||||||
{selectedDay.apparentTemperatureMax?.toFixed(0) ??
|
{selectedDay?.apparentTemperatureMax?.toFixed(0) ??
|
||||||
selectedDay.temperatureMax?.toFixed(0)}
|
selectedDay?.temperatureMax?.toFixed(0)}
|
||||||
°<sup>F</sup>
|
°<sup>F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
@@ -105,12 +102,12 @@ export default function ExtendedForecastDetails({
|
|||||||
</Box>
|
</Box>
|
||||||
<Group pos={'relative'}>
|
<Group pos={'relative'}>
|
||||||
<Text fz={isMobile ? '5rem' : '7rem'} fw={400} style={{ lineHeight: 1 }}>
|
<Text fz={isMobile ? '5rem' : '7rem'} fw={400} style={{ lineHeight: 1 }}>
|
||||||
{selectedDay.temperatureMax?.toFixed(0)}
|
{selectedDay?.temperatureMax?.toFixed(0)}
|
||||||
<sup style={{ fontSize: '3rem' }}>°F</sup>
|
<sup style={{ fontSize: '3rem' }}>°F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text style={{ lineHeight: 1 }} pos={'absolute'} bottom={20} right={0}>
|
<Text style={{ lineHeight: 1 }} pos={'absolute'} bottom={20} right={0}>
|
||||||
/ {selectedDay.temperatureMin?.toFixed(0)}°<sup>F</sup>
|
/ {selectedDay?.temperatureMin?.toFixed(0)}°<sup>F</sup>
|
||||||
</Text>
|
</Text>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@@ -131,7 +128,7 @@ export default function ExtendedForecastDetails({
|
|||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
justify={
|
justify={
|
||||||
WeatherCodes[Number(selectedDay.weatherCode)].length > 25
|
WeatherCodes[Number(selectedDay?.weatherCode)]?.length > 25
|
||||||
? 'flex-start'
|
? 'flex-start'
|
||||||
: 'center'
|
: 'center'
|
||||||
}
|
}
|
||||||
@@ -146,21 +143,21 @@ export default function ExtendedForecastDetails({
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
className={
|
className={
|
||||||
WeatherCodes[Number(selectedDay.weatherCode)].length > 25
|
WeatherCodes[Number(selectedDay?.weatherCode)]?.length > 25
|
||||||
? textClasses.autoScrollText
|
? textClasses.autoScrollText
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
classNames={textClasses}
|
classNames={textClasses}
|
||||||
style={{ lineHeight: 1 }}
|
style={{ lineHeight: 1 }}
|
||||||
>
|
>
|
||||||
{selectedDay.temperature_2m?.toFixed(0) === '69'
|
{selectedDay?.temperature_2m?.toFixed(0) === '69'
|
||||||
? 'Nice'
|
? 'Nice'
|
||||||
: WeatherCodes[Number(selectedDay.weatherCode)]}
|
: WeatherCodes[Number(selectedDay?.weatherCode)]}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<WeatherIcon size={'xl'} code={selectedDay.weatherCode} />
|
<WeatherIcon size={'xl'} code={selectedDay?.weatherCode} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
@@ -174,7 +171,7 @@ export default function ExtendedForecastDetails({
|
|||||||
<Text>Wind</Text>
|
<Text>Wind</Text>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{selectedDay.windSpeed?.toFixed(0)}mph</Text>}
|
rightSide={<Text>{selectedDay?.windSpeed?.toFixed(0)}mph</Text>}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -184,7 +181,7 @@ export default function ExtendedForecastDetails({
|
|||||||
<Text>Gusts</Text>
|
<Text>Gusts</Text>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{selectedDay.windGusts?.toFixed(0)}mph</Text>}
|
rightSide={<Text>{selectedDay?.windGusts?.toFixed(0)}mph</Text>}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -203,11 +200,11 @@ export default function ExtendedForecastDetails({
|
|||||||
<FaLocationArrow
|
<FaLocationArrow
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--mantine-color-accent-0)',
|
color: 'var(--mantine-color-accent-0)',
|
||||||
transform: `rotate(${-48 + (selectedDay.windDirection || 0)}deg)`,
|
transform: `rotate(${-48 + (selectedDay?.windDirection || 0)}deg)`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Text fz={12}>{selectedDay.windDirection?.toFixed(0)}°</Text>
|
<Text fz={12}>{selectedDay?.windDirection?.toFixed(0)}°</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
<Group justify='space-between'>
|
<Group justify='space-between'>
|
||||||
@@ -229,7 +226,7 @@ export default function ExtendedForecastDetails({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Text fz={12}>UV {selectedDay.uvIndexMax?.toFixed(0)}</Text>
|
<Text fz={12}>UV {selectedDay?.uvIndexMax?.toFixed(0)}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -246,7 +243,7 @@ export default function ExtendedForecastDetails({
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{selectedDay.sunrise}</Text>}
|
rightSide={<Text>{selectedDay?.sunrise}</Text>}
|
||||||
highlight='left'
|
highlight='left'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -263,7 +260,7 @@ export default function ExtendedForecastDetails({
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{selectedDay.sunset}</Text>}
|
rightSide={<Text>{selectedDay?.sunset}</Text>}
|
||||||
highlight='left'
|
highlight='left'
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -297,7 +294,7 @@ export default function ExtendedForecastDetails({
|
|||||||
<Text>Precipitation %</Text>
|
<Text>Precipitation %</Text>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{selectedDay.precipitationProbability}%</Text>}
|
rightSide={<Text>{selectedDay?.precipitationProbability}%</Text>}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
@@ -307,7 +304,7 @@ export default function ExtendedForecastDetails({
|
|||||||
<Text>Precipitation In.</Text>
|
<Text>Precipitation In.</Text>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{selectedDay.precipitationSum.toFixed(2)} in.</Text>}
|
rightSide={<Text>{selectedDay?.precipitationSum.toFixed(2)} in.</Text>}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function WeatherGraphs({
|
|||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
<ResponsiveContainer width={'100%'} height={150}>
|
<ResponsiveContainer width={'100%'} height={150}>
|
||||||
<ComposedChart data={hourlyForecast}>
|
<ComposedChart data={hourlyForecast} responsive>
|
||||||
<XAxis
|
<XAxis
|
||||||
type='category'
|
type='category'
|
||||||
stroke={'var(--mantine-color-primary-0)'}
|
stroke={'var(--mantine-color-primary-0)'}
|
||||||
@@ -129,7 +129,7 @@ export default function WeatherGraphs({
|
|||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<ResponsiveContainer width={'100%'} height={150}>
|
<ResponsiveContainer width={'100%'} height={150}>
|
||||||
<AreaChart data={hourlyForecast}>
|
<AreaChart data={hourlyForecast} responsive>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id={colorPrecipitation} x1='0' y1='0' x2='0' y2='1'>
|
<linearGradient id={colorPrecipitation} x1='0' y1='0' x2='0' y2='1'>
|
||||||
<stop
|
<stop
|
||||||
@@ -160,7 +160,7 @@ export default function WeatherGraphs({
|
|||||||
type='category'
|
type='category'
|
||||||
stroke={'var(--mantine-color-primary-0)'}
|
stroke={'var(--mantine-color-primary-0)'}
|
||||||
dataKey={(data) => {
|
dataKey={(data) => {
|
||||||
return new Date(data.startTime).toLocaleTimeString('en-US');
|
return new Date(data.startTime).toLocaleString('en-US');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} />
|
<YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Group, Modal, Overlay, Stack, Text } from '@mantine/core';
|
import { Box, Group, Modal, Text } from '@mantine/core';
|
||||||
import { useCommonHooks } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { modalClasses } from 'styles';
|
import { modalClasses } from 'styles';
|
||||||
@@ -19,7 +19,7 @@ export function WeatherModal({
|
|||||||
handleResetSearch: () => void;
|
handleResetSearch: () => void;
|
||||||
closeWeatherModal: () => void;
|
closeWeatherModal: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { isMobile, currentUser } = useCommonHooks();
|
const { isMobile, settings } = useCommonHooks();
|
||||||
const [selectedDay, setSelectedDay] = useState<any>(undefined);
|
const [selectedDay, setSelectedDay] = useState<any>(undefined);
|
||||||
const [selectedHourly, setSelectedHourly] = useState<any>(undefined);
|
const [selectedHourly, setSelectedHourly] = useState<any>(undefined);
|
||||||
|
|
||||||
@@ -79,32 +79,34 @@ export function WeatherModal({
|
|||||||
Weather Forecast for{' '}
|
Weather Forecast for{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={'1.125rem'} fw={600}>
|
<Text fz={'1.125rem'} fw={600}>
|
||||||
{currentUser.physicalAddress}
|
{settings.physicalAddress}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
<Modal.CloseButton />
|
<Modal.CloseButton />
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body m={0}>
|
<Modal.Body style={{ overflowX: 'hidden', whiteSpace: 'nowrap' }}>
|
||||||
<Stack w={'100%'} h={'100%'} pos={'relative'}>
|
<Group w={'200%'} gap={0}>
|
||||||
{selectedDay && (
|
<Box
|
||||||
<Overlay
|
w={'50%'}
|
||||||
h={'100%'}
|
|
||||||
opacity={1}
|
|
||||||
blur={10}
|
|
||||||
onClick={() => setSelectedDay(undefined)}
|
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
transition: 'all 0.3s ease-in-out',
|
||||||
justifyContent: 'center',
|
transform: selectedDay ? 'translateX(-110%)' : 'translateX(0%)',
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: 'color-mix(in srgb, black, transparent 50%)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack
|
<CurrentWeather current={current} />
|
||||||
w={'65%'}
|
<WeatherGraphs hourly={current?.hourly} />
|
||||||
align={'center'}
|
<ExtendedForecastCard
|
||||||
justify={'center'}
|
daily={current?.daily}
|
||||||
style={{ cursor: 'pointer' }}
|
setSelectedDay={setSelectedDay}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
w={'50%'}
|
||||||
|
style={{
|
||||||
|
transition: 'all 0.3s ease-in-out',
|
||||||
|
transform: selectedDay ? 'translateX(-100%)' : 'translateX(10%)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ExtendedForecastDetails
|
<ExtendedForecastDetails
|
||||||
selectedDay={selectedDay}
|
selectedDay={selectedDay}
|
||||||
@@ -112,16 +114,8 @@ export function WeatherModal({
|
|||||||
selectedHourly={selectedHourly}
|
selectedHourly={selectedHourly}
|
||||||
setSelectedHourly={setSelectedHourly}
|
setSelectedHourly={setSelectedHourly}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</Overlay>
|
</Group>
|
||||||
)}
|
|
||||||
<CurrentWeather current={current} />
|
|
||||||
<WeatherGraphs hourly={current?.hourly} />
|
|
||||||
<ExtendedForecastCard
|
|
||||||
daily={current?.daily}
|
|
||||||
setSelectedDay={setSelectedDay}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
</Modal.Root>
|
</Modal.Root>
|
||||||
|
|||||||
@@ -306,23 +306,53 @@ export function useMutationAPI() {
|
|||||||
body: object | undefined | null;
|
body: object | undefined | null;
|
||||||
}) => callEndpoint(method, endpoint, body),
|
}) => callEndpoint(method, endpoint, body),
|
||||||
onSuccess: (_, { endpoint }) => {
|
onSuccess: (_, { endpoint }) => {
|
||||||
queryClient.refetchQueries({
|
queryClient
|
||||||
|
.refetchQueries({
|
||||||
queryKey: ['GET', endpoint],
|
queryKey: ['GET', endpoint],
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
endpoint === 'api/bookmarkOrder' &&
|
endpoint === 'api/bookmarkOrder' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/bookmarks'] });
|
queryClient
|
||||||
|
.refetchQueries({ queryKey: ['GET', 'api/bookmarks'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
endpoint === 'api/bookmarkCategoryOrder' &&
|
endpoint === 'api/bookmarkCategoryOrder' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/bookmarkCategories'] });
|
queryClient
|
||||||
|
.refetchQueries({ queryKey: ['GET', 'api/bookmarkCategories'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
endpoint === 'api/applicationOrder' &&
|
endpoint === 'api/applicationOrder' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/applications'] });
|
queryClient
|
||||||
|
.refetchQueries({ queryKey: ['GET', 'api/applications'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
endpoint === 'api/applications' &&
|
endpoint === 'api/applications' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/enabledApplications'] });
|
queryClient
|
||||||
|
.refetchQueries({ queryKey: ['GET', 'api/enabledApplications'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
endpoint === 'api/bookmarks' &&
|
endpoint === 'api/bookmarks' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/themes'] });
|
queryClient.refetchQueries({ queryKey: ['GET', 'api/themes'] }).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
endpoint === 'api/providers' &&
|
endpoint === 'api/providers' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/providers/default'] });
|
queryClient
|
||||||
|
.refetchQueries({ queryKey: ['GET', 'api/providers/default'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
endpoint === 'api/personalData' &&
|
endpoint === 'api/personalData' &&
|
||||||
queryClient.refetchQueries({ queryKey: ['GET', 'api/personalData'] });
|
queryClient
|
||||||
|
.refetchQueries({ queryKey: ['GET', 'api/personalData'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -426,9 +456,21 @@ export function useFileUploadAPI() {
|
|||||||
}) => callEndpoint(method, endpoint, body),
|
}) => callEndpoint(method, endpoint, body),
|
||||||
onMutate: () => {},
|
onMutate: () => {},
|
||||||
onSuccess: (_variables) => {
|
onSuccess: (_variables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['GET', 'api/backgrounds'] });
|
queryClient
|
||||||
queryClient.invalidateQueries({ queryKey: ['GET', 'api/themes'] });
|
.invalidateQueries({ queryKey: ['GET', 'api/backgrounds'] })
|
||||||
queryClient.invalidateQueries({ queryKey: ['GET', 'api/themePlaylists'] });
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
queryClient
|
||||||
|
.invalidateQueries({ queryKey: ['GET', 'api/themes'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
queryClient
|
||||||
|
.invalidateQueries({ queryKey: ['GET', 'api/themePlaylists'] })
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,98 +2,25 @@ import { useLocalStorage } from '@mantine/hooks';
|
|||||||
import { appTheme } from 'contexts';
|
import { appTheme } from 'contexts';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { Location, Theme, User } from 'types';
|
import type { Settings, Theme } from 'types';
|
||||||
import { useMutationAPI } from './useAPI';
|
import { useMutationAPI } from './useAPI';
|
||||||
|
|
||||||
export function useCommonHooks() {
|
const initialSettings: Settings = {
|
||||||
const { mutateAsync } = useMutationAPI();
|
theme: 'random',
|
||||||
const isMobile = globalThis.matchMedia('(max-width: 600px)').matches;
|
appFilter: ['All'],
|
||||||
const [theme, setTheme] = useAtom<Theme>(appTheme);
|
appSort: 'A>Z',
|
||||||
const [themeName, setThemeName] = useLocalStorage<string>({
|
appDensity: 6,
|
||||||
key: 'theme',
|
appScale: 1,
|
||||||
defaultValue: 'random',
|
bookmarkSort: 'A>Z',
|
||||||
});
|
bookmarkFilter: ['All'],
|
||||||
const [appFilter, setAppFilter] = useLocalStorage<string[]>({
|
bookmarkDensity: 6,
|
||||||
key: 'appFilter',
|
bookmarkScale: 1,
|
||||||
defaultValue: ['All'],
|
currentThemePlaylist: 'All',
|
||||||
});
|
defaultProviderLabel: 'Google Search',
|
||||||
const [appSort, setAppSort] = useLocalStorage<string>({
|
defaultProviderURL: 'https://www.google.com/search?q=',
|
||||||
key: 'appSort',
|
defaultProviderPrefix: '/g',
|
||||||
defaultValue: 'A>Z',
|
defaultProviderIcon: 'FaGoogle',
|
||||||
});
|
timeFormat: '12h',
|
||||||
const [appDensity, setAppDensity] = useLocalStorage<number>({
|
|
||||||
key: 'appDensity',
|
|
||||||
defaultValue: 6,
|
|
||||||
});
|
|
||||||
const [appScale, setAppScale] = useLocalStorage<number>({
|
|
||||||
key: 'appScale',
|
|
||||||
defaultValue: 1,
|
|
||||||
});
|
|
||||||
const [bookmarkSort, setBookmarkSort] = useLocalStorage<string>({
|
|
||||||
key: 'bookmarkSort',
|
|
||||||
defaultValue: 'A>Z',
|
|
||||||
});
|
|
||||||
const [bookmarkFilter, setBookmarkFilter] = useLocalStorage<string[]>({
|
|
||||||
key: 'bookmarkFilter',
|
|
||||||
defaultValue: ['All'],
|
|
||||||
});
|
|
||||||
const [bookmarkDensity, setBookmarkDensity] = useLocalStorage<number>({
|
|
||||||
key: 'bookmarkDensity',
|
|
||||||
defaultValue: 6,
|
|
||||||
});
|
|
||||||
const [bookmarkScale, setBookmarkScale] = useLocalStorage<number>({
|
|
||||||
key: 'bookmarkScale',
|
|
||||||
defaultValue: 1,
|
|
||||||
});
|
|
||||||
const [currentTheme, setCurrentTheme] = useLocalStorage<string>({
|
|
||||||
key: 'theme',
|
|
||||||
defaultValue: 'random',
|
|
||||||
});
|
|
||||||
const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage<string>({
|
|
||||||
key: 'themePlaylist',
|
|
||||||
defaultValue: 'All',
|
|
||||||
});
|
|
||||||
const [defaultProviderLabel, setDefaultProviderLabel] = useLocalStorage<string>({
|
|
||||||
key: 'providerLabel',
|
|
||||||
defaultValue: 'Google Search',
|
|
||||||
});
|
|
||||||
const [defaultProviderURL, setDefaultProviderURL] = useLocalStorage<string>({
|
|
||||||
key: 'providerURL',
|
|
||||||
defaultValue: 'https://www.google.com/search?q=',
|
|
||||||
});
|
|
||||||
const [defaultProviderPrefix, setDefaultProviderPrefix] = useLocalStorage<string>({
|
|
||||||
key: 'providerPrefix',
|
|
||||||
defaultValue: '/g',
|
|
||||||
});
|
|
||||||
const [defaultProviderIcon, setDefaultProviderIcon] = useLocalStorage<string>({
|
|
||||||
key: 'providerIcon',
|
|
||||||
defaultValue: 'FaGoogle',
|
|
||||||
});
|
|
||||||
const [timeFormat, setTimeFormat] = useLocalStorage<string>({
|
|
||||||
key: 'timeFormat',
|
|
||||||
defaultValue: '12h',
|
|
||||||
});
|
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
|
||||||
const [providerLabel, setProviderLabel] = useState(defaultProviderLabel);
|
|
||||||
const [providerURL, setProviderURL] = useState(defaultProviderURL);
|
|
||||||
const [providerPrefix, setProviderPrefix] = useState(defaultProviderPrefix);
|
|
||||||
const [providerIcon, setProviderIcon] = useState(defaultProviderIcon);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setProviderLabel(defaultProviderLabel);
|
|
||||||
setProviderURL(defaultProviderURL);
|
|
||||||
setProviderPrefix(defaultProviderPrefix);
|
|
||||||
setProviderIcon(defaultProviderIcon);
|
|
||||||
}, [
|
|
||||||
defaultProviderLabel,
|
|
||||||
defaultProviderURL,
|
|
||||||
defaultProviderPrefix,
|
|
||||||
defaultProviderIcon,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [currentUser, setCurrentUser] = useLocalStorage<User>({
|
|
||||||
key: 'user',
|
|
||||||
defaultValue: {
|
|
||||||
firstName: 'First',
|
firstName: 'First',
|
||||||
lastName: 'Last',
|
lastName: 'Last',
|
||||||
nickname: 'Boss',
|
nickname: 'Boss',
|
||||||
@@ -103,25 +30,52 @@ export function useCommonHooks() {
|
|||||||
enableWol: 0,
|
enableWol: 0,
|
||||||
enableGuacamole: 0,
|
enableGuacamole: 0,
|
||||||
guacamoleHost: 'https://domain.example.com/guacamole',
|
guacamoleHost: 'https://domain.example.com/guacamole',
|
||||||
},
|
|
||||||
});
|
|
||||||
const [currentLocation, setCurrentLocation] = useLocalStorage<Location>({
|
|
||||||
key: 'location',
|
|
||||||
defaultValue: {
|
|
||||||
lat: 29.750950134386066,
|
lat: 29.750950134386066,
|
||||||
lon: -95.36220654860034,
|
lon: -95.36220654860034,
|
||||||
},
|
messageSettingsSort: 'Oldest',
|
||||||
|
appSettingsSort: 'Custom',
|
||||||
|
bookmarkSettingsSort: 'Custom',
|
||||||
|
themesSettingsSort: 'Oldest',
|
||||||
|
providerSettingsSort: 'Oldest',
|
||||||
|
wolClientsSettingsSort: 'A>Z',
|
||||||
|
guacamoleClientsSettingsSort: 'A>Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useCommonHooks() {
|
||||||
|
const { mutateAsync } = useMutationAPI();
|
||||||
|
const isMobile = globalThis.matchMedia('(max-width: 600px)').matches;
|
||||||
|
const [theme, setTheme] = useAtom<Theme>(appTheme);
|
||||||
|
|
||||||
|
const [settings, setSettings] = useLocalStorage<Settings>({
|
||||||
|
key: 'trigger_settings',
|
||||||
|
defaultValue: initialSettings,
|
||||||
});
|
});
|
||||||
|
|
||||||
const urlPattern = new RegExp(
|
// Helper to update specific keys without wiping the rest of the object
|
||||||
String.raw`^(https?:\/\/)?` +
|
const updateSetting = <K extends keyof Settings>(key: K, value: Settings[K]) => {
|
||||||
String.raw`((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|` +
|
setSettings((prev) => ({ ...prev, [key]: value }));
|
||||||
String.raw`((\d{1,3}\.){3}\d{1,3}))` +
|
};
|
||||||
String.raw`(\:\d+)?(\/[-a-z\d%_.~+]*)*` +
|
|
||||||
String.raw`(\?[;&a-z\d%_.~+=-]*)?` +
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
String.raw`(\#[-a-z\d_]*)?$`,
|
const [providerLabel, setProviderLabel] = useState(settings.defaultProviderLabel);
|
||||||
'i',
|
const [providerURL, setProviderURL] = useState(settings.defaultProviderURL);
|
||||||
);
|
const [providerPrefix, setProviderPrefix] = useState(settings.defaultProviderPrefix);
|
||||||
|
const [providerIcon, setProviderIcon] = useState(settings.defaultProviderIcon);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setProviderLabel(settings.defaultProviderLabel);
|
||||||
|
setProviderURL(settings.defaultProviderURL);
|
||||||
|
setProviderPrefix(settings.defaultProviderPrefix);
|
||||||
|
setProviderIcon(settings.defaultProviderIcon);
|
||||||
|
}, [
|
||||||
|
settings.defaultProviderLabel,
|
||||||
|
settings.defaultProviderURL,
|
||||||
|
settings.defaultProviderPrefix,
|
||||||
|
settings.defaultProviderIcon,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const urlPattern =
|
||||||
|
/^(https?:\/\/)?([\w.-]+)\.([a-z]{2,})(:\d+)?(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(#[-a-z\d_]*)?$/i;
|
||||||
|
|
||||||
const textShadowStyle =
|
const textShadowStyle =
|
||||||
'1px 1px 1px color-mix(in srgb, var(--mantine-color-accent-0), transparent 50%)';
|
'1px 1px 1px color-mix(in srgb, var(--mantine-color-accent-0), transparent 50%)';
|
||||||
@@ -139,22 +93,24 @@ export function useCommonHooks() {
|
|||||||
const enabled = themes
|
const enabled = themes
|
||||||
.filter((t: Theme) => t.randomEnabled === 1)
|
.filter((t: Theme) => t.randomEnabled === 1)
|
||||||
.filter((t: Theme) => t.name !== theme.name);
|
.filter((t: Theme) => t.name !== theme.name);
|
||||||
if (enabled.length === 0 && currentThemePlaylist === 'All') {
|
if (enabled.length === 0 && settings.currentThemePlaylist === 'All') {
|
||||||
setTheme(themes[0]);
|
setTheme(themes[0]);
|
||||||
save && setThemeName(themes[0].name);
|
if (save) updateSetting('theme', themes[0].name);
|
||||||
} else if (currentThemePlaylist === 'All') {
|
} else if (settings.currentThemePlaylist === 'All') {
|
||||||
const index = Math.floor(Math.random() * enabled.length);
|
const index = Math.floor(Math.random() * enabled.length);
|
||||||
setTheme(enabled[index]);
|
setTheme(enabled[index]);
|
||||||
save && setThemeName(enabled[index].name);
|
if (save) updateSetting('theme', enabled[index].name);
|
||||||
} else {
|
} else {
|
||||||
const index = Math.floor(Math.random() * themes.length);
|
const index = Math.floor(Math.random() * themes.length);
|
||||||
setTheme(themes[index]);
|
setTheme(themes[index]);
|
||||||
save && setThemeName(themes[index].name);
|
if (save) updateSetting('theme', themes[index].name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (item: any, endpoint: string) => {
|
const handleDelete = (item: any, endpoint: string) => {
|
||||||
mutateAsync({ method: 'DELETE', endpoint: endpoint, body: item });
|
mutateAsync({ method: 'DELETE', endpoint: endpoint, body: item }).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconFilter = (selectedTheme?: Theme) => {
|
const iconFilter = (selectedTheme?: Theme) => {
|
||||||
@@ -178,7 +134,6 @@ export function useCommonHooks() {
|
|||||||
const getObjectDiff = (original: any, current: any) => {
|
const getObjectDiff = (original: any, current: any) => {
|
||||||
const changes: any = {};
|
const changes: any = {};
|
||||||
|
|
||||||
// Check current object's properties
|
|
||||||
for (const [key, value] of Object.entries(current)) {
|
for (const [key, value] of Object.entries(current)) {
|
||||||
if (!(key in original)) {
|
if (!(key in original)) {
|
||||||
changes[key] = {
|
changes[key] = {
|
||||||
@@ -191,7 +146,6 @@ export function useCommonHooks() {
|
|||||||
const originalValue = original[key];
|
const originalValue = original[key];
|
||||||
const currentValue = value;
|
const currentValue = value;
|
||||||
|
|
||||||
// Handle different types of comparisons
|
|
||||||
if (
|
if (
|
||||||
originalValue !== currentValue &&
|
originalValue !== currentValue &&
|
||||||
String(originalValue) !== String(currentValue) &&
|
String(originalValue) !== String(currentValue) &&
|
||||||
@@ -204,7 +158,6 @@ export function useCommonHooks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for removed properties
|
|
||||||
for (const key of Object.keys(original)) {
|
for (const key of Object.keys(original)) {
|
||||||
if (!(key in current)) {
|
if (!(key in current)) {
|
||||||
changes[key] = {
|
changes[key] = {
|
||||||
@@ -218,6 +171,8 @@ export function useCommonHooks() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
settings,
|
||||||
|
updateSetting,
|
||||||
isMobile,
|
isMobile,
|
||||||
theme,
|
theme,
|
||||||
setTheme,
|
setTheme,
|
||||||
@@ -228,28 +183,6 @@ export function useCommonHooks() {
|
|||||||
getObjectDiff,
|
getObjectDiff,
|
||||||
handleThemeChange,
|
handleThemeChange,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
themeName,
|
|
||||||
setThemeName,
|
|
||||||
appFilter,
|
|
||||||
setAppFilter,
|
|
||||||
appSort,
|
|
||||||
setAppSort,
|
|
||||||
appDensity,
|
|
||||||
setAppDensity,
|
|
||||||
appScale,
|
|
||||||
setAppScale,
|
|
||||||
bookmarkSort,
|
|
||||||
setBookmarkSort,
|
|
||||||
bookmarkFilter,
|
|
||||||
setBookmarkFilter,
|
|
||||||
bookmarkDensity,
|
|
||||||
setBookmarkDensity,
|
|
||||||
bookmarkScale,
|
|
||||||
setBookmarkScale,
|
|
||||||
currentTheme,
|
|
||||||
setCurrentTheme,
|
|
||||||
currentThemePlaylist,
|
|
||||||
setCurrentThemePlaylist,
|
|
||||||
searchValue,
|
searchValue,
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
providerLabel,
|
providerLabel,
|
||||||
@@ -260,20 +193,6 @@ export function useCommonHooks() {
|
|||||||
setProviderPrefix,
|
setProviderPrefix,
|
||||||
providerIcon,
|
providerIcon,
|
||||||
setProviderIcon,
|
setProviderIcon,
|
||||||
defaultProviderLabel,
|
|
||||||
setDefaultProviderLabel,
|
|
||||||
defaultProviderURL,
|
|
||||||
setDefaultProviderURL,
|
|
||||||
defaultProviderPrefix,
|
|
||||||
setDefaultProviderPrefix,
|
|
||||||
defaultProviderIcon,
|
|
||||||
setDefaultProviderIcon,
|
|
||||||
timeFormat,
|
|
||||||
setTimeFormat,
|
|
||||||
currentUser,
|
|
||||||
setCurrentUser,
|
|
||||||
currentLocation,
|
|
||||||
setCurrentLocation,
|
|
||||||
textShadowStyle,
|
textShadowStyle,
|
||||||
largeTextShadowStyle,
|
largeTextShadowStyle,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Box, Grid, Stack } from '@mantine/core';
|
import { Box, Stack } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { useCommonHooks, useOpenMeteoAPI, useOSMAPI, useQueryAPI } from 'hooks';
|
import { useCommonHooks, useOpenMeteoAPI, useOSMAPI, useQueryAPI } from 'hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -15,14 +15,7 @@ import {
|
|||||||
} from './components';
|
} from './components';
|
||||||
|
|
||||||
export function Homepage() {
|
export function Homepage() {
|
||||||
const {
|
const { isMobile, settings, updateSetting } = useCommonHooks();
|
||||||
appFilter,
|
|
||||||
bookmarkFilter,
|
|
||||||
isMobile,
|
|
||||||
setCurrentUser,
|
|
||||||
currentLocation,
|
|
||||||
setCurrentLocation,
|
|
||||||
} = useCommonHooks();
|
|
||||||
const dateOptions: Intl.DateTimeFormatOptions = isMobile
|
const dateOptions: Intl.DateTimeFormatOptions = isMobile
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
@@ -34,7 +27,7 @@ export function Homepage() {
|
|||||||
|
|
||||||
const date = new Date().toLocaleDateString('en-US', dateOptions);
|
const date = new Date().toLocaleDateString('en-US', dateOptions);
|
||||||
|
|
||||||
const { data: personalData, isFetched: personalIsFetched } = useQueryAPI({
|
const { data: personalData } = useQueryAPI({
|
||||||
endpoint: 'api/personalData',
|
endpoint: 'api/personalData',
|
||||||
});
|
});
|
||||||
const { data: messageData } = useQueryAPI({
|
const { data: messageData } = useQueryAPI({
|
||||||
@@ -44,8 +37,8 @@ export function Homepage() {
|
|||||||
address: personalData?.[0]?.physicalAddress,
|
address: personalData?.[0]?.physicalAddress,
|
||||||
});
|
});
|
||||||
const { data: meteoWeather } = useOpenMeteoAPI({
|
const { data: meteoWeather } = useOpenMeteoAPI({
|
||||||
lat: weatherMetadata?.[0].lat ?? currentLocation.lat,
|
lat: weatherMetadata?.[0].lat ?? settings.lat,
|
||||||
lon: weatherMetadata?.[0].lon ?? currentLocation.lon,
|
lon: weatherMetadata?.[0].lon ?? settings.lon,
|
||||||
});
|
});
|
||||||
|
|
||||||
const personal: PersonalData = personalData?.[0];
|
const personal: PersonalData = personalData?.[0];
|
||||||
@@ -59,11 +52,6 @@ export function Homepage() {
|
|||||||
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
|
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (personalData && personalIsFetched) {
|
|
||||||
setCurrentUser(personalData[0]);
|
|
||||||
}
|
|
||||||
}, [personalData, personalIsFetched]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (personal) {
|
if (personal) {
|
||||||
setDisplayedGreeting(() => {
|
setDisplayedGreeting(() => {
|
||||||
@@ -87,21 +75,23 @@ export function Homepage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (meteoWeather) {
|
if (meteoWeather) {
|
||||||
setMeteoCurrent(meteoWeather);
|
setMeteoCurrent(meteoWeather);
|
||||||
setCurrentLocation({
|
updateSetting('lat', weatherMetadata?.[0].lat);
|
||||||
lat: weatherMetadata?.[0].lat,
|
updateSetting('lon', weatherMetadata?.[0].lon);
|
||||||
lon: weatherMetadata?.[0].lon,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [meteoWeather]);
|
}, [meteoWeather]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
gap={0}
|
gap={0}
|
||||||
h={appFilter.length > 0 || bookmarkFilter.length > 0 ? undefined : '100%'}
|
h={
|
||||||
|
settings.appFilter.length > 0 || settings.bookmarkFilter.length > 0
|
||||||
|
? undefined
|
||||||
|
: '100%'
|
||||||
|
}
|
||||||
className={stackClasses.containerStack}
|
className={stackClasses.containerStack}
|
||||||
justify='center'
|
justify='center'
|
||||||
>
|
>
|
||||||
{appFilter.length > 0 || bookmarkFilter.length > 0 ? (
|
{settings.appFilter.length > 0 || settings.bookmarkFilter.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<Search
|
<Search
|
||||||
current={meteoCurrent}
|
current={meteoCurrent}
|
||||||
@@ -120,16 +110,16 @@ export function Homepage() {
|
|||||||
current={meteoCurrent}
|
current={meteoCurrent}
|
||||||
openWeatherModal={openWeatherModal}
|
openWeatherModal={openWeatherModal}
|
||||||
/>
|
/>
|
||||||
<Applications contentType={appFilter} />
|
<Applications contentType={settings.appFilter} />
|
||||||
<Bookmarks contentType={bookmarkFilter} />
|
<Bookmarks contentType={settings.bookmarkFilter} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Box h={'100%'} w={'100%'}>
|
<Box h={'100%'} w={'100%'}>
|
||||||
<Stack align='center'>
|
<Stack align='center'>
|
||||||
<LargeHeader greet={displayedGreeting} date={date} />
|
<LargeHeader greet={displayedGreeting} date={date} />
|
||||||
<Grid grow columns={24} pos={'absolute'} bottom={'1rem'}>
|
<Box w={'860px'} pos={'absolute'} bottom={'1rem'}>
|
||||||
{meteoCurrent && <CurrentWeather current={meteoCurrent} />}
|
{meteoCurrent && <CurrentWeather current={meteoCurrent} />}
|
||||||
</Grid>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Search
|
<Search
|
||||||
current={meteoCurrent}
|
current={meteoCurrent}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ export function Applications({ contentType }: { contentType: string[] }) {
|
|||||||
const { data: applicationData } = useQueryAPI({
|
const { data: applicationData } = useQueryAPI({
|
||||||
endpoint: 'api/enabledApplications',
|
endpoint: 'api/enabledApplications',
|
||||||
});
|
});
|
||||||
const { appSort, appDensity, appScale, theme, textShadowStyle } = useCommonHooks();
|
const { settings, theme, textShadowStyle } = useCommonHooks();
|
||||||
const applications: Application[] = applicationData
|
const applications: Application[] = applicationData
|
||||||
?.sort((a: Application, b: Application) => {
|
?.sort((a: Application, b: Application) => {
|
||||||
switch (appSort) {
|
switch (settings.appSort) {
|
||||||
case 'A>Z':
|
case 'A>Z':
|
||||||
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
|
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
case 'Z>A':
|
case 'Z>A':
|
||||||
@@ -54,7 +54,7 @@ export function Applications({ contentType }: { contentType: string[] }) {
|
|||||||
style={{
|
style={{
|
||||||
gap: '.25rem',
|
gap: '.25rem',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: `repeat(${appDensity}, 1fr)`,
|
gridTemplateColumns: `repeat(${settings.appDensity}, 1fr)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{applications?.map((application: Application) => {
|
{applications?.map((application: Application) => {
|
||||||
@@ -65,7 +65,7 @@ export function Applications({ contentType }: { contentType: string[] }) {
|
|||||||
url={application.url}
|
url={application.url}
|
||||||
icon={application.icon}
|
icon={application.icon}
|
||||||
enabled={application.enabled}
|
enabled={application.enabled}
|
||||||
scale={appScale}
|
scale={settings.appScale}
|
||||||
linksActive
|
linksActive
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,15 +11,14 @@ export function Bookmarks({ contentType }: { contentType: string[] }) {
|
|||||||
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
|
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
|
||||||
endpoint: 'api/bookmarkCategories',
|
endpoint: 'api/bookmarkCategories',
|
||||||
});
|
});
|
||||||
const { bookmarkSort, bookmarkDensity, bookmarkScale, theme, textShadowStyle } =
|
const { settings, theme, textShadowStyle } = useCommonHooks();
|
||||||
useCommonHooks();
|
|
||||||
const groupList: BookmarkGroup[] = groupData?.map((category: BookmarkGroup) => {
|
const groupList: BookmarkGroup[] = groupData?.map((category: BookmarkGroup) => {
|
||||||
return category;
|
return category;
|
||||||
});
|
});
|
||||||
const bookmarks: Bookmark[] = bookmarkData || [];
|
const bookmarks: Bookmark[] = bookmarkData || [];
|
||||||
const groups: BookmarkGroup[] = groupList
|
const groups: BookmarkGroup[] = groupList
|
||||||
?.toSorted((a: BookmarkGroup, b: BookmarkGroup) => {
|
?.toSorted((a: BookmarkGroup, b: BookmarkGroup) => {
|
||||||
switch (bookmarkSort) {
|
switch (settings.bookmarkSort) {
|
||||||
case 'A>Z':
|
case 'A>Z':
|
||||||
return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase()
|
return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase()
|
||||||
? 1
|
? 1
|
||||||
@@ -71,7 +70,7 @@ export function Bookmarks({ contentType }: { contentType: string[] }) {
|
|||||||
style={{
|
style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridRowGap: '0px',
|
gridRowGap: '0px',
|
||||||
gridTemplateColumns: `repeat(${bookmarkDensity}, 1fr)`,
|
gridTemplateColumns: `repeat(${settings.bookmarkDensity}, 1fr)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{groups
|
{groups
|
||||||
@@ -88,7 +87,7 @@ export function Bookmarks({ contentType }: { contentType: string[] }) {
|
|||||||
(bookmark: Bookmark) =>
|
(bookmark: Bookmark) =>
|
||||||
bookmark.category === category.categoryName,
|
bookmark.category === category.categoryName,
|
||||||
)}
|
)}
|
||||||
bookmarkScale={bookmarkScale}
|
bookmarkScale={settings.bookmarkScale}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ export function Header({
|
|||||||
const [time, setTime] = useState(new Date());
|
const [time, setTime] = useState(new Date());
|
||||||
const {
|
const {
|
||||||
isMobile,
|
isMobile,
|
||||||
currentUser,
|
|
||||||
fixDigits,
|
fixDigits,
|
||||||
theme,
|
theme,
|
||||||
textShadowStyle,
|
textShadowStyle,
|
||||||
largeTextShadowStyle,
|
largeTextShadowStyle,
|
||||||
timeFormat,
|
settings,
|
||||||
setTimeFormat,
|
updateSetting,
|
||||||
} = useCommonHooks();
|
} = useCommonHooks();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -51,12 +50,12 @@ export function Header({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{greet
|
{greet
|
||||||
?.replace('$firstName', currentUser.firstName)
|
?.replace('$firstName', settings.firstName)
|
||||||
.replace('$lastName', currentUser.lastName)
|
.replace('$lastName', settings.lastName)
|
||||||
.replace('$nickname', currentUser.nickname)
|
.replace('$nickname', settings.nickname)
|
||||||
.replace('$^firstName', currentUser.firstName.toUpperCase())
|
.replace('$^firstName', settings.firstName.toUpperCase())
|
||||||
.replace('$^lastName', currentUser.lastName.toUpperCase())
|
.replace('$^lastName', settings.lastName.toUpperCase())
|
||||||
.replace('$^nickname', currentUser.nickname?.toUpperCase())}
|
.replace('$^nickname', settings.nickname?.toUpperCase())}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
@@ -123,10 +122,12 @@ export function Header({
|
|||||||
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
|
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
timeFormat === '12h' ? setTimeFormat('24h') : setTimeFormat('12h')
|
settings.timeFormat === '12h'
|
||||||
|
? updateSetting('timeFormat', '24h')
|
||||||
|
: updateSetting('timeFormat', '12h')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{timeFormat === '12h'
|
{settings.timeFormat === '12h'
|
||||||
? `${
|
? `${
|
||||||
time.getHours() === 0
|
time.getHours() === 0
|
||||||
? 12
|
? 12
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ export function LargeHeader({
|
|||||||
const [time, setTime] = useState(new Date());
|
const [time, setTime] = useState(new Date());
|
||||||
const {
|
const {
|
||||||
isMobile,
|
isMobile,
|
||||||
currentUser,
|
|
||||||
fixDigits,
|
fixDigits,
|
||||||
theme,
|
theme,
|
||||||
textShadowStyle,
|
textShadowStyle,
|
||||||
largeTextShadowStyle,
|
largeTextShadowStyle,
|
||||||
timeFormat,
|
settings,
|
||||||
setTimeFormat,
|
updateSetting,
|
||||||
} = useCommonHooks();
|
} = useCommonHooks();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -40,12 +39,12 @@ export function LargeHeader({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{greet
|
{greet
|
||||||
?.replace('$firstName', currentUser.firstName)
|
?.replace('$firstName', settings.firstName)
|
||||||
.replace('$lastName', currentUser.lastName)
|
.replace('$lastName', settings.lastName)
|
||||||
.replace('$nickname', currentUser.nickname)
|
.replace('$nickname', settings.nickname)
|
||||||
.replace('$^firstName', currentUser.firstName.toUpperCase())
|
.replace('$^firstName', settings.firstName.toUpperCase())
|
||||||
.replace('$^lastName', currentUser.lastName.toUpperCase())
|
.replace('$^lastName', settings.lastName.toUpperCase())
|
||||||
.replace('$^nickname', currentUser.nickname?.toUpperCase())}
|
.replace('$^nickname', settings.nickname?.toUpperCase())}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
fz={'2.25rem'}
|
fz={'2.25rem'}
|
||||||
@@ -69,10 +68,12 @@ export function LargeHeader({
|
|||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
timeFormat === '12h' ? setTimeFormat('24h') : setTimeFormat('12h')
|
settings.timeFormat === '12h'
|
||||||
|
? updateSetting('timeFormat', '24h')
|
||||||
|
: updateSetting('timeFormat', '12h')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{timeFormat === '12h'
|
{settings.timeFormat === '12h'
|
||||||
? `${
|
? `${
|
||||||
time.getHours() === 0
|
time.getHours() === 0
|
||||||
? 12
|
? 12
|
||||||
|
|||||||
@@ -63,14 +63,8 @@ export function Search({
|
|||||||
setProviderIcon,
|
setProviderIcon,
|
||||||
searchValue,
|
searchValue,
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
defaultProviderURL,
|
settings,
|
||||||
setDefaultProviderURL,
|
updateSetting,
|
||||||
defaultProviderLabel,
|
|
||||||
setDefaultProviderLabel,
|
|
||||||
defaultProviderPrefix,
|
|
||||||
setDefaultProviderPrefix,
|
|
||||||
defaultProviderIcon,
|
|
||||||
setDefaultProviderIcon,
|
|
||||||
} = useCommonHooks();
|
} = useCommonHooks();
|
||||||
const { ref, height } = useElementSize();
|
const { ref, height } = useElementSize();
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
@@ -146,8 +140,8 @@ export function Search({
|
|||||||
setSearchValue('');
|
setSearchValue('');
|
||||||
setListIndex(-1);
|
setListIndex(-1);
|
||||||
setHistoryMultiplier(1);
|
setHistoryMultiplier(1);
|
||||||
setProviderLabel(defaultProviderLabel);
|
setProviderLabel(settings.defaultProviderLabel);
|
||||||
setProviderURL(defaultProviderURL);
|
setProviderURL(settings.defaultProviderURL);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGuacamoleClientLaunchItem = ({ option }: { option: any }) => {
|
const handleGuacamoleClientLaunchItem = ({ option }: { option: any }) => {
|
||||||
@@ -176,8 +170,12 @@ export function Search({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: searchEndpoint,
|
endpoint: searchEndpoint,
|
||||||
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
|
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
open(`${providerURL}${subtext}`, '_self');
|
open(`${providerURL}${subtext}`, '_self');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
} else if (providerLabel === 'Show weather') {
|
} else if (providerLabel === 'Show weather') {
|
||||||
setSearchValue('');
|
setSearchValue('');
|
||||||
@@ -211,8 +209,12 @@ export function Search({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: searchEndpoint,
|
endpoint: searchEndpoint,
|
||||||
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
|
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
open(`${providerURL}${subtext}`, '_self');
|
open(`${providerURL}${subtext}`, '_self');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
open(`${providerURL}${subtext}`, '_self');
|
open(`${providerURL}${subtext}`, '_self');
|
||||||
@@ -372,10 +374,10 @@ export function Search({
|
|||||||
setProviderIcon('');
|
setProviderIcon('');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setProviderLabel(defaultProviderLabel);
|
setProviderLabel(settings.defaultProviderLabel);
|
||||||
setProviderURL(defaultProviderURL);
|
setProviderURL(settings.defaultProviderURL);
|
||||||
setProviderPrefix(defaultProviderPrefix);
|
setProviderPrefix(settings.defaultProviderPrefix);
|
||||||
setProviderIcon(defaultProviderIcon);
|
setProviderIcon(settings.defaultProviderIcon);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -460,12 +462,16 @@ export function Search({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultProviderData && defaultProviderDataIsFetched) {
|
if (defaultProviderData && defaultProviderDataIsFetched) {
|
||||||
setDefaultProviderURL(
|
updateSetting(
|
||||||
|
'defaultProviderURL',
|
||||||
defaultProviderData[0]?.url ?? 'https://www.google.com/search?q=',
|
defaultProviderData[0]?.url ?? 'https://www.google.com/search?q=',
|
||||||
);
|
);
|
||||||
setDefaultProviderPrefix(defaultProviderData[0]?.prefix ?? '/g');
|
updateSetting('defaultProviderPrefix', defaultProviderData[0]?.prefix ?? '/g');
|
||||||
setDefaultProviderLabel(defaultProviderData[0]?.name ?? 'Google Search');
|
updateSetting('defaultProviderIcon', defaultProviderData[0]?.icon ?? 'FaGoogle');
|
||||||
setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle');
|
updateSetting(
|
||||||
|
'defaultProviderLabel',
|
||||||
|
defaultProviderData[0]?.name ?? 'Google Search',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [defaultProviderData, defaultProviderDataIsFetched]);
|
}, [defaultProviderData, defaultProviderDataIsFetched]);
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ export default function RenderItem({
|
|||||||
setHistoryMultiplier: React.Dispatch<React.SetStateAction<number>>;
|
setHistoryMultiplier: React.Dispatch<React.SetStateAction<number>>;
|
||||||
searchEndpoint: string;
|
searchEndpoint: string;
|
||||||
}) {
|
}) {
|
||||||
const { defaultProviderURL } = useCommonHooks();
|
const { settings } = useCommonHooks();
|
||||||
const handleSearchHistoryItem = ({ option }: { option: SearchHistoryItem }) => {
|
const handleSearchHistoryItem = ({ option }: { option: SearchHistoryItem }) => {
|
||||||
const url =
|
const url =
|
||||||
providers.find((provider: Provider) => provider.prefix === option.prefix)?.url ??
|
providers.find((provider: Provider) => provider.prefix === option.prefix)?.url ??
|
||||||
defaultProviderURL;
|
settings.defaultProviderURL;
|
||||||
open(`${url}${option.searchTerm}`, '_self');
|
open(`${url}${option.searchTerm}`, '_self');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ function SearchSelectItem({
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
endpoint: searchEndpoint,
|
endpoint: searchEndpoint,
|
||||||
body: { id: item.option.id },
|
body: { id: item.option.id },
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -33,26 +33,7 @@ export function SettingsButton({ openProviderModal }: { openProviderModal: () =>
|
|||||||
const [selectedClient, setSelectedClient] = useState<WolClient>(wolClientData?.[0]);
|
const [selectedClient, setSelectedClient] = useState<WolClient>(wolClientData?.[0]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const {
|
const { theme, isMobile, settings } = useCommonHooks();
|
||||||
theme,
|
|
||||||
isMobile,
|
|
||||||
appFilter,
|
|
||||||
setAppFilter,
|
|
||||||
appSort,
|
|
||||||
setAppSort,
|
|
||||||
appDensity,
|
|
||||||
setAppDensity,
|
|
||||||
appScale,
|
|
||||||
setAppScale,
|
|
||||||
bookmarkSort,
|
|
||||||
setBookmarkSort,
|
|
||||||
bookmarkFilter,
|
|
||||||
setBookmarkFilter,
|
|
||||||
bookmarkDensity,
|
|
||||||
setBookmarkDensity,
|
|
||||||
bookmarkScale,
|
|
||||||
setBookmarkScale,
|
|
||||||
} = useCommonHooks();
|
|
||||||
|
|
||||||
const handleWakeRequest = () => {
|
const handleWakeRequest = () => {
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
@@ -123,26 +104,18 @@ export function SettingsButton({ openProviderModal }: { openProviderModal: () =>
|
|||||||
<SettingsCard
|
<SettingsCard
|
||||||
categories={categories}
|
categories={categories}
|
||||||
sectionTitle={'Applications'}
|
sectionTitle={'Applications'}
|
||||||
filter={appFilter}
|
filter={settings.appFilter}
|
||||||
setFilter={setAppFilter}
|
sort={settings.appSort}
|
||||||
sort={appSort}
|
density={settings.appDensity}
|
||||||
setSort={setAppSort}
|
scale={settings.appScale}
|
||||||
density={appDensity}
|
|
||||||
setDensity={setAppDensity}
|
|
||||||
scale={appScale}
|
|
||||||
setScale={setAppScale}
|
|
||||||
/>
|
/>
|
||||||
<SettingsCard
|
<SettingsCard
|
||||||
categories={categories}
|
categories={categories}
|
||||||
sectionTitle={'Bookmarks'}
|
sectionTitle={'Bookmarks'}
|
||||||
filter={bookmarkFilter}
|
filter={settings.bookmarkFilter}
|
||||||
setFilter={setBookmarkFilter}
|
sort={settings.bookmarkSort}
|
||||||
sort={bookmarkSort}
|
density={settings.bookmarkDensity}
|
||||||
setSort={setBookmarkSort}
|
scale={settings.bookmarkScale}
|
||||||
density={bookmarkDensity}
|
|
||||||
setDensity={setBookmarkDensity}
|
|
||||||
scale={bookmarkScale}
|
|
||||||
setScale={setBookmarkScale}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -14,26 +14,18 @@ function SettingsCard({
|
|||||||
categories,
|
categories,
|
||||||
sectionTitle,
|
sectionTitle,
|
||||||
filter,
|
filter,
|
||||||
setFilter,
|
|
||||||
sort,
|
sort,
|
||||||
setSort,
|
|
||||||
density,
|
density,
|
||||||
setDensity,
|
|
||||||
scale,
|
scale,
|
||||||
setScale,
|
|
||||||
}: {
|
}: {
|
||||||
categories: ComboboxItem[];
|
categories: ComboboxItem[];
|
||||||
sectionTitle: string;
|
sectionTitle: string;
|
||||||
filter: any;
|
filter: any;
|
||||||
setFilter: any;
|
|
||||||
sort: any;
|
sort: any;
|
||||||
setSort: any;
|
|
||||||
density: any;
|
density: any;
|
||||||
setDensity: any;
|
|
||||||
scale: any;
|
scale: any;
|
||||||
setScale: any;
|
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useCommonHooks();
|
const { theme, updateSetting } = useCommonHooks();
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
gap={0}
|
gap={0}
|
||||||
@@ -70,11 +62,23 @@ function SettingsCard({
|
|||||||
className={sliderClasses.default}
|
className={sliderClasses.default}
|
||||||
classNames={sliderClasses}
|
classNames={sliderClasses}
|
||||||
value={density}
|
value={density}
|
||||||
onChange={(value) => setDensity(value)}
|
onChange={(value) => {
|
||||||
|
if (sectionTitle === 'Applications') {
|
||||||
|
updateSetting('appDensity', value);
|
||||||
|
} else if (sectionTitle === 'Bookmarks') {
|
||||||
|
updateSetting('bookmarkDensity', value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
min={1}
|
min={1}
|
||||||
max={16}
|
max={16}
|
||||||
marks={[{ value: 6, label: 'Default' }]}
|
marks={[{ value: 6, label: 'Default' }]}
|
||||||
onDoubleClick={() => setDensity(6)}
|
onDoubleClick={() => {
|
||||||
|
if (sectionTitle === 'Applications') {
|
||||||
|
updateSetting('appDensity', 6);
|
||||||
|
} else if (sectionTitle === 'Bookmarks') {
|
||||||
|
updateSetting('bookmarkDensity', 6);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
{scale !== undefined && (
|
{scale !== undefined && (
|
||||||
@@ -86,12 +90,24 @@ function SettingsCard({
|
|||||||
className={sliderClasses.default}
|
className={sliderClasses.default}
|
||||||
classNames={sliderClasses}
|
classNames={sliderClasses}
|
||||||
value={scale}
|
value={scale}
|
||||||
onChange={(value) => setScale(value)}
|
onChange={(value) => {
|
||||||
|
if (sectionTitle === 'Applications') {
|
||||||
|
updateSetting('appScale', value);
|
||||||
|
} else if (sectionTitle === 'Bookmarks') {
|
||||||
|
updateSetting('bookmarkScale', value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
min={0.2}
|
min={0.2}
|
||||||
max={3}
|
max={3}
|
||||||
marks={[{ value: 1, label: 'Default' }]}
|
marks={[{ value: 1, label: 'Default' }]}
|
||||||
onDoubleClick={() => setScale(1)}
|
onDoubleClick={() => {
|
||||||
|
if (sectionTitle === 'Applications') {
|
||||||
|
updateSetting('appScale', 1);
|
||||||
|
} else if (sectionTitle === 'Bookmarks') {
|
||||||
|
updateSetting('bookmarkScale', 1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@@ -113,7 +129,11 @@ function SettingsCard({
|
|||||||
}
|
}
|
||||||
defaultValue={filter}
|
defaultValue={filter}
|
||||||
onChange={(data: string[]) => {
|
onChange={(data: string[]) => {
|
||||||
setFilter(data);
|
if (sectionTitle === 'Applications') {
|
||||||
|
updateSetting('appFilter', data);
|
||||||
|
} else if (sectionTitle === 'Bookmarks') {
|
||||||
|
updateSetting('bookmarkFilter', data);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
classNames={multiSelectClasses}
|
classNames={multiSelectClasses}
|
||||||
comboboxProps={{
|
comboboxProps={{
|
||||||
@@ -147,7 +167,11 @@ function SettingsCard({
|
|||||||
position: 'top',
|
position: 'top',
|
||||||
}}
|
}}
|
||||||
onChange={(_value, option: ComboboxItem) => {
|
onChange={(_value, option: ComboboxItem) => {
|
||||||
setSort(option.value);
|
if (sectionTitle === 'Applications') {
|
||||||
|
updateSetting('appSort', option.value);
|
||||||
|
} else if (sectionTitle === 'Bookmarks') {
|
||||||
|
updateSetting('bookmarkSort', option.value);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
labelProps={{ top: 8 }}
|
labelProps={{ top: 8 }}
|
||||||
styles={
|
styles={
|
||||||
|
|||||||
@@ -31,16 +31,8 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
});
|
});
|
||||||
const allThemes: Theme[] = allThemeData || [];
|
const allThemes: Theme[] = allThemeData || [];
|
||||||
|
|
||||||
const {
|
const { isMobile, handleThemeChange, theme, settings, updateSetting } =
|
||||||
isMobile,
|
useCommonHooks();
|
||||||
currentTheme,
|
|
||||||
setCurrentTheme,
|
|
||||||
setThemeName,
|
|
||||||
handleThemeChange,
|
|
||||||
theme,
|
|
||||||
currentThemePlaylist,
|
|
||||||
setCurrentThemePlaylist,
|
|
||||||
} = useCommonHooks();
|
|
||||||
|
|
||||||
const [current, setCurrent] = useState<string>(theme.name);
|
const [current, setCurrent] = useState<string>(theme.name);
|
||||||
|
|
||||||
@@ -128,11 +120,11 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
disabled={currentThemePlaylist === 'Time of Day' || isAnimating}
|
disabled={settings.currentThemePlaylist === 'Time of Day' || isAnimating}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
themePlaylistData?.find(
|
themePlaylistData?.find(
|
||||||
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
|
(t: ThemePlaylist) => t?.name === settings.currentThemePlaylist,
|
||||||
)?.themeList ?? '[]',
|
)?.themeList ?? '[]',
|
||||||
);
|
);
|
||||||
if (themeList.length > 0) {
|
if (themeList.length > 0) {
|
||||||
@@ -140,16 +132,16 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
themeList.includes(t.id),
|
themeList.includes(t.id),
|
||||||
);
|
);
|
||||||
handleThemeChange(filteredThemes, false);
|
handleThemeChange(filteredThemes, false);
|
||||||
} else if (currentThemePlaylist === 'All') {
|
} else if (settings.currentThemePlaylist === 'All') {
|
||||||
handleThemeChange(allThemes, true);
|
handleThemeChange(allThemes, true);
|
||||||
} else if (currentThemePlaylist === 'Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter(
|
allThemes.filter(
|
||||||
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else if (currentThemePlaylist === 'No Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'No Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter((a: any) =>
|
allThemes.filter((a: any) =>
|
||||||
a.backgroundImage.toLowerCase().includes('none'),
|
a.backgroundImage.toLowerCase().includes('none'),
|
||||||
@@ -169,7 +161,7 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
placeholder='Pick a playlist...'
|
placeholder='Pick a playlist...'
|
||||||
mb={'.25rem'}
|
mb={'.25rem'}
|
||||||
ml={'.25rem'}
|
ml={'.25rem'}
|
||||||
value={currentThemePlaylist}
|
value={settings.currentThemePlaylist}
|
||||||
data={[
|
data={[
|
||||||
{ value: 'All', label: 'All' },
|
{ value: 'All', label: 'All' },
|
||||||
{
|
{
|
||||||
@@ -226,20 +218,21 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onChange={(_value, option: ComboboxItem) => {
|
onChange={(_value, option: ComboboxItem) => {
|
||||||
setCurrentThemePlaylist(option.value);
|
updateSetting('currentThemePlaylist', option.value);
|
||||||
if (option.value === 'Time of Day') {
|
if (option.value === 'Time of Day') {
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
themePlaylistData.find((t: ThemePlaylist) => t.name === 'Time of Day')
|
themePlaylistData.find((t: ThemePlaylist) => t.name === 'Time of Day')
|
||||||
.themeList,
|
.themeList,
|
||||||
);
|
);
|
||||||
setThemeName(
|
updateSetting(
|
||||||
|
'theme',
|
||||||
allThemeData.find(
|
allThemeData.find(
|
||||||
(t: Theme) =>
|
(t: Theme) =>
|
||||||
t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
||||||
).name,
|
).name,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setThemeName('random');
|
updateSetting('theme', 'random');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -252,12 +245,12 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
className={actionIconClasses.menuActions}
|
className={actionIconClasses.menuActions}
|
||||||
classNames={actionIconClasses}
|
classNames={actionIconClasses}
|
||||||
disabled={currentThemePlaylist === 'Time of Day' || isAnimating}
|
disabled={settings.currentThemePlaylist === 'Time of Day' || isAnimating}
|
||||||
h={40}
|
h={40}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
color:
|
color:
|
||||||
currentTheme === 'random'
|
settings.theme === 'random'
|
||||||
? 'var(--mantine-color-accent-0)'
|
? 'var(--mantine-color-accent-0)'
|
||||||
: 'var(--mantine-color-primary-0)',
|
: 'var(--mantine-color-primary-0)',
|
||||||
borderTopRightRadius: 0,
|
borderTopRightRadius: 0,
|
||||||
@@ -271,7 +264,7 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
styles={{
|
styles={{
|
||||||
icon: {
|
icon: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
currentTheme === 'random'
|
settings.theme === 'random'
|
||||||
? `${theme?.searchColor === theme?.primaryColor ? theme.searchColor?.substring(0, 7) : theme.primaryColor?.substring(0, 7)}88`
|
? `${theme?.searchColor === theme?.primaryColor ? theme.searchColor?.substring(0, 7) : theme.primaryColor?.substring(0, 7)}88`
|
||||||
: 'transparent',
|
: 'transparent',
|
||||||
width: 'fit-content',
|
width: 'fit-content',
|
||||||
@@ -284,8 +277,8 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentTheme(currentTheme === 'random' ? theme.name : 'random');
|
updateSetting('theme', 'random');
|
||||||
setCurrentThemePlaylist('All');
|
updateSetting('currentThemePlaylist', 'All');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FiShuffle size={20} />
|
<FiShuffle size={20} />
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import DebouncedApplication from './DebouncedApplication';
|
|||||||
|
|
||||||
export function Applications() {
|
export function Applications() {
|
||||||
const allIcons: any = { ...fa6, ...vsc, ...si };
|
const allIcons: any = { ...fa6, ...vsc, ...si };
|
||||||
|
const { isMobile, reorder, theme, settings, updateSetting } = useCommonHooks();
|
||||||
const defaultApplication: Application = {
|
const defaultApplication: Application = {
|
||||||
id: null,
|
id: null,
|
||||||
name: '',
|
name: '',
|
||||||
@@ -38,6 +39,7 @@ export function Applications() {
|
|||||||
index: 0,
|
index: 0,
|
||||||
enabled: 1,
|
enabled: 1,
|
||||||
};
|
};
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
@@ -54,19 +56,40 @@ export function Applications() {
|
|||||||
const { data: applicationData, isFetching: applicationDataIsFetching } = useQueryAPI({
|
const { data: applicationData, isFetching: applicationDataIsFetching } = useQueryAPI({
|
||||||
endpoint: appsEndpoint,
|
endpoint: appsEndpoint,
|
||||||
});
|
});
|
||||||
const applications: Application[] =
|
const applications: Application[] = applicationData
|
||||||
applicationData
|
|
||||||
?.filter(
|
?.filter(
|
||||||
(a: Application) =>
|
(a: Application) =>
|
||||||
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.baseUrl.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
a.baseUrl.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
||||||
)
|
)
|
||||||
.sort((a: Application, b: Application) => (a.index > b.index ? 1 : -1)) || [];
|
.sort((a: Application, b: Application) => {
|
||||||
|
if (!dragging) {
|
||||||
|
switch (settings.appSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Custom':
|
||||||
|
return a.index > b.index ? 1 : -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.index > b.index ? 1 : -1;
|
||||||
|
});
|
||||||
const [applicationList, setApplicationList] = useState<Application[]>([]);
|
const [applicationList, setApplicationList] = useState<Application[]>([]);
|
||||||
const { isMobile, reorder, theme } = useCommonHooks();
|
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
const [dragging, setDragging] = useState(false);
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -108,13 +131,15 @@ export function Applications() {
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
endpoint: appsOrderEndpoint,
|
endpoint: appsOrderEndpoint,
|
||||||
body: applicationList,
|
body: applicationList,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [applicationList]);
|
}, [applicationList]);
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
setDragging={applications.length > 0 ? setDragging : undefined}
|
setDragging={applications?.length > 0 ? setDragging : undefined}
|
||||||
openModal={openModal}
|
openModal={openModal}
|
||||||
title={'Applications'}
|
title={'Applications'}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
@@ -140,6 +165,11 @@ export function Applications() {
|
|||||||
searchPlaceholder={'Search Applications...'}
|
searchPlaceholder={'Search Applications...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.appSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('appSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled
|
||||||
/>
|
/>
|
||||||
{modalOpened && (
|
{modalOpened && (
|
||||||
<AddItemModal
|
<AddItemModal
|
||||||
@@ -149,12 +179,12 @@ export function Applications() {
|
|||||||
body={
|
body={
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit((values) => {
|
onSubmit={form.onSubmit((values) => {
|
||||||
mutateAsync({
|
void mutateAsync({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: appsEndpoint,
|
endpoint: appsEndpoint,
|
||||||
body: {
|
body: {
|
||||||
...values,
|
...values,
|
||||||
index: applications.length,
|
index: applications?.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
@@ -318,7 +348,7 @@ export function Applications() {
|
|||||||
size: 'xl',
|
size: 'xl',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{applications.length > 0 ? (
|
{applications?.length > 0 ? (
|
||||||
<Droppable droppableId={'groupList'}>
|
<Droppable droppableId={'groupList'}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -38,7 +38,13 @@ function DebouncedApplication({
|
|||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounced && Object.keys(debounced).length > 0) {
|
if (debounced && Object.keys(debounced).length > 0) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: 'api/applications', body: debounced });
|
mutateAsync({
|
||||||
|
method: 'PATCH',
|
||||||
|
endpoint: 'api/applications',
|
||||||
|
body: debounced,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type { Bookmark, BookmarkGroup } from 'types';
|
|||||||
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
|
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
|
||||||
|
|
||||||
export function Bookmarks() {
|
export function Bookmarks() {
|
||||||
|
const { isMobile, reorder, theme, settings, updateSetting } = useCommonHooks();
|
||||||
const defaultGroup = {
|
const defaultGroup = {
|
||||||
id: null,
|
id: null,
|
||||||
categoryName: '',
|
categoryName: '',
|
||||||
@@ -32,6 +33,7 @@ export function Bookmarks() {
|
|||||||
filterName: isNotEmpty('Filter name is required'),
|
filterName: isNotEmpty('Filter name is required'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
const bookmarksEndpoint = 'api/bookmarks';
|
const bookmarksEndpoint = 'api/bookmarks';
|
||||||
const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({
|
const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({
|
||||||
endpoint: bookmarksEndpoint,
|
endpoint: bookmarksEndpoint,
|
||||||
@@ -41,10 +43,8 @@ export function Bookmarks() {
|
|||||||
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
|
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
|
||||||
endpoint: categoriesEndpoint,
|
endpoint: categoriesEndpoint,
|
||||||
});
|
});
|
||||||
const [dragging, setDragging] = useState(false);
|
|
||||||
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
|
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
|
||||||
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
|
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
|
||||||
const { isMobile, reorder, theme } = useCommonHooks();
|
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||||
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
|
||||||
@@ -71,6 +71,7 @@ export function Bookmarks() {
|
|||||||
const foundGroups: BookmarkGroup[] = groupData?.filter((a: BookmarkGroup) =>
|
const foundGroups: BookmarkGroup[] = groupData?.filter((a: BookmarkGroup) =>
|
||||||
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
|
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
closeModal();
|
closeModal();
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -99,15 +100,50 @@ export function Bookmarks() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (groupData && groupsIsFetched) {
|
if (groupData && groupsIsFetched) {
|
||||||
const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort(
|
const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort(
|
||||||
(a: BookmarkGroup, b: BookmarkGroup) =>
|
(a: BookmarkGroup, b: BookmarkGroup) => {
|
||||||
a.categoryIndex > b.categoryIndex ? 1 : -1,
|
if (!dragging) {
|
||||||
|
switch (settings.bookmarkSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.categoryName.toLocaleLowerCase() >
|
||||||
|
b.categoryName.toLocaleLowerCase()
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.categoryName.toLocaleLowerCase() <
|
||||||
|
b.categoryName.toLocaleLowerCase()
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Custom':
|
||||||
|
return a.categoryIndex > b.categoryIndex ? 1 : -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.categoryIndex > b.categoryIndex ? 1 : -1;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
setGroups(groupList);
|
setGroups(groupList);
|
||||||
}
|
}
|
||||||
if (debouncedGroupSearch === searchValue) {
|
if (debouncedGroupSearch === searchValue) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [groupData, groupsIsFetched, debouncedGroupSearch]);
|
}, [
|
||||||
|
groupData,
|
||||||
|
groupsIsFetched,
|
||||||
|
debouncedGroupSearch,
|
||||||
|
settings.bookmarkSettingsSort,
|
||||||
|
dragging,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (groupList.length > 0 && groupList !== groups) {
|
if (groupList.length > 0 && groupList !== groups) {
|
||||||
@@ -115,6 +151,8 @@ export function Bookmarks() {
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
endpoint: 'api/bookmarkCategoryOrder',
|
endpoint: 'api/bookmarkCategoryOrder',
|
||||||
body: groupList,
|
body: groupList,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [groupList]);
|
}, [groupList]);
|
||||||
@@ -136,6 +174,8 @@ export function Bookmarks() {
|
|||||||
...values,
|
...values,
|
||||||
categoryIndex: groups.length,
|
categoryIndex: groups.length,
|
||||||
},
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})}
|
})}
|
||||||
@@ -221,6 +261,11 @@ export function Bookmarks() {
|
|||||||
searchPlaceholder={'Search Bookmarks...'}
|
searchPlaceholder={'Search Bookmarks...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.bookmarkSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('bookmarkSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled
|
||||||
/>
|
/>
|
||||||
{dragging ? (
|
{dragging ? (
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ function DebouncedBookmarkItem({
|
|||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounced && Object.keys(debounced).length) {
|
if (debounced && Object.keys(debounced).length) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced });
|
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarks', body: debounced }).catch(
|
||||||
|
(error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ function DebouncedGroupSettingsItem({
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
endpoint: 'api/bookmarkCategories',
|
endpoint: 'api/bookmarkCategories',
|
||||||
body: debounced,
|
body: debounced,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
setUpdate(undefined);
|
setUpdate(undefined);
|
||||||
}
|
}
|
||||||
@@ -107,7 +109,13 @@ function DebouncedGroupSettingsItem({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bookmarkList && bookmarkList !== bookmarks) {
|
if (bookmarkList && bookmarkList !== bookmarks) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarkOrder', body: bookmarkList });
|
mutateAsync({
|
||||||
|
method: 'PATCH',
|
||||||
|
endpoint: 'api/bookmarkOrder',
|
||||||
|
body: bookmarkList,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [bookmarkList]);
|
}, [bookmarkList]);
|
||||||
|
|
||||||
@@ -351,6 +359,8 @@ function DebouncedGroupSettingsItem({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: 'api/bookmarks',
|
endpoint: 'api/bookmarks',
|
||||||
body: { ...values, index: bookmarks.length },
|
body: { ...values, index: bookmarks.length },
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
setPopoverOpened(false);
|
setPopoverOpened(false);
|
||||||
form.reset();
|
form.reset();
|
||||||
|
|||||||
@@ -47,7 +47,13 @@ function DebouncedGuacamoleClient({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounced && Object.keys(debounced).length) {
|
if (debounced && Object.keys(debounced).length) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: 'api/guacamoleClients', body: debounced });
|
mutateAsync({
|
||||||
|
method: 'PATCH',
|
||||||
|
endpoint: 'api/guacamoleClients',
|
||||||
|
body: debounced,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
|
|
||||||
@@ -92,6 +98,7 @@ function DebouncedGuacamoleClient({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
openModal();
|
openModal();
|
||||||
}}
|
}}
|
||||||
|
style={{ zIndex: 250 }}
|
||||||
>
|
>
|
||||||
<FiTrash color='black' />
|
<FiTrash color='black' />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import DebouncedGuacamoleClient from './DebouncedGuacamoleClient';
|
|||||||
import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem';
|
import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem';
|
||||||
|
|
||||||
export function GuacamoleClients() {
|
export function GuacamoleClients() {
|
||||||
|
const { isMobile, theme, settings, updateSetting } = useCommonHooks();
|
||||||
const defaultGuacamoleClient: GuacamoleClient = {
|
const defaultGuacamoleClient: GuacamoleClient = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
clientName: '',
|
clientName: '',
|
||||||
@@ -46,15 +47,41 @@ export function GuacamoleClients() {
|
|||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
});
|
});
|
||||||
const guacamoleClients: GuacamoleClient[] =
|
const guacamoleClients: GuacamoleClient[] =
|
||||||
guacamoleClientsData.filter(
|
guacamoleClientsData
|
||||||
|
?.filter(
|
||||||
(a: GuacamoleClient) =>
|
(a: GuacamoleClient) =>
|
||||||
a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.clientName
|
||||||
|
.toLowerCase()
|
||||||
|
.includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.clientIdentifier
|
a.clientIdentifier
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes((debouncedSearch as string).toLowerCase()),
|
.includes((debouncedSearch as string).toLowerCase()),
|
||||||
) || [];
|
)
|
||||||
|
?.sort((a: GuacamoleClient, b: GuacamoleClient) => {
|
||||||
|
switch (settings.guacamoleClientsSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.clientName.toLocaleLowerCase() > b.clientName.toLocaleLowerCase()
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.clientName.toLocaleLowerCase() < b.clientName.toLocaleLowerCase()
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}) || [];
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
const { isMobile, theme } = useCommonHooks();
|
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -84,6 +111,8 @@ export function GuacamoleClients() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
body: { ...values, enabled: 1 },
|
body: { ...values, enabled: 1 },
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})}
|
})}
|
||||||
@@ -210,6 +239,11 @@ export function GuacamoleClients() {
|
|||||||
searchPlaceholder={'Search Clients...'}
|
searchPlaceholder={'Search Clients...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.guacamoleClientsSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('guacamoleClientsSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
mt={isMobile ? '10rem' : '6rem'}
|
mt={isMobile ? '10rem' : '6rem'}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type { Message } from 'types';
|
|||||||
import DebouncedMessage from './DebouncedMessage';
|
import DebouncedMessage from './DebouncedMessage';
|
||||||
|
|
||||||
export function Messages() {
|
export function Messages() {
|
||||||
|
const { isMobile, theme, settings, updateSetting } = useCommonHooks();
|
||||||
const endpoint = 'api/messages';
|
const endpoint = 'api/messages';
|
||||||
const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({
|
const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
@@ -31,12 +32,32 @@ export function Messages() {
|
|||||||
});
|
});
|
||||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const { isMobile, theme } = useCommonHooks();
|
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
const messages: Message[] =
|
const messages: Message[] =
|
||||||
messageData?.filter((m: Message) =>
|
messageData
|
||||||
|
?.filter((m: Message) =>
|
||||||
m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
||||||
) || [];
|
)
|
||||||
|
.sort((a: Message, b: Message) => {
|
||||||
|
switch (settings.messageSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.message.toLocaleLowerCase() > b.message.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.message.toLocaleLowerCase() < b.message.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}) || [];
|
||||||
const [message, setMessage] = useState({});
|
const [message, setMessage] = useState({});
|
||||||
const [debounced] = useDebouncedValue(message, 1000);
|
const [debounced] = useDebouncedValue(message, 1000);
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
@@ -49,7 +70,11 @@ export function Messages() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(debounced).length) {
|
if (Object.keys(debounced).length) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
|
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }).catch(
|
||||||
|
(error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
|
|
||||||
@@ -82,6 +107,11 @@ export function Messages() {
|
|||||||
searchPlaceholder={'Search Messages...'}
|
searchPlaceholder={'Search Messages...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.messageSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('messageSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
{modalOpened && (
|
{modalOpened && (
|
||||||
<AddItemModal
|
<AddItemModal
|
||||||
@@ -95,6 +125,8 @@ export function Messages() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
body: { message: values.message },
|
body: { message: values.message },
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ export function Personalization() {
|
|||||||
const { isMobile, theme } = useCommonHooks();
|
const { isMobile, theme } = useCommonHooks();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounced && Object.keys(debounced).length) {
|
if (debounced && Object.keys(debounced).length) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
|
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }).catch(
|
||||||
|
(error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
setUpdate(undefined);
|
setUpdate(undefined);
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
@@ -87,6 +91,9 @@ export function Personalization() {
|
|||||||
searchPlaceholder={undefined}
|
searchPlaceholder={undefined}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
setIsLoading={() => {}}
|
setIsLoading={() => {}}
|
||||||
|
sortBy={''}
|
||||||
|
setSortBy={undefined}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack align='center' mt={'6rem'} pos={'relative'}>
|
<Stack align='center' mt={'6rem'} pos={'relative'}>
|
||||||
<LoadingOverlay
|
<LoadingOverlay
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ function DebouncedSearchProvider({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounced && Object.keys(debounced).length) {
|
if (debounced && Object.keys(debounced).length) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced });
|
mutateAsync({ method: 'PATCH', endpoint: 'api/providers', body: debounced }).catch(
|
||||||
|
(error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ export function SearchHistory() {
|
|||||||
searchPlaceholder={'Search your Search History...'}
|
searchPlaceholder={'Search your Search History...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={''}
|
||||||
|
setSortBy={undefined}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
mt={isMobile ? '10rem' : '6rem'}
|
mt={isMobile ? '10rem' : '6rem'}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import DebouncedSearchProvider from './DebouncedSearchProvider';
|
|||||||
import SearchProviderSettingsItem from './SearchProviderSettingsItem';
|
import SearchProviderSettingsItem from './SearchProviderSettingsItem';
|
||||||
|
|
||||||
export function SearchProviders() {
|
export function SearchProviders() {
|
||||||
|
const { isMobile, theme, iconFilter, settings, updateSetting } = useCommonHooks();
|
||||||
const allIcons: any = { ...fa6, ...vsc, ...si };
|
const allIcons: any = { ...fa6, ...vsc, ...si };
|
||||||
const defaultProvider: Provider = {
|
const defaultProvider: Provider = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -62,14 +63,32 @@ export function SearchProviders() {
|
|||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const providers: Provider[] =
|
const providers: Provider[] =
|
||||||
providerData
|
providerData
|
||||||
.filter(
|
?.filter(
|
||||||
(a: Provider) =>
|
(a: Provider) =>
|
||||||
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.prefix.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
a.prefix.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
||||||
)
|
)
|
||||||
.sort((a: Provider, b: Provider) => (a.prefix > b.prefix ? 1 : -1)) ?? [];
|
?.sort((a: Provider, b: Provider) => {
|
||||||
const { isMobile, theme, iconFilter } = useCommonHooks();
|
switch (settings.providerSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}) ?? [];
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@@ -100,6 +119,8 @@ export function SearchProviders() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
body: values,
|
body: values,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})}
|
})}
|
||||||
@@ -281,6 +302,11 @@ export function SearchProviders() {
|
|||||||
searchPlaceholder={'Search Providers...'}
|
searchPlaceholder={'Search Providers...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.providerSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('providerSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
mt={isMobile ? '10rem' : '6rem'}
|
mt={isMobile ? '10rem' : '6rem'}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ export function DeleteBackground({
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
endpoint: 'api/backgrounds',
|
endpoint: 'api/backgrounds',
|
||||||
body: { fileName },
|
body: { fileName },
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ export function EditBackground({
|
|||||||
fileName.length,
|
fileName.length,
|
||||||
)}`,
|
)}`,
|
||||||
},
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
@@ -81,6 +83,8 @@ export function EditBackground({
|
|||||||
fileName,
|
fileName,
|
||||||
newFileName: rename,
|
newFileName: rename,
|
||||||
},
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleEditBackgroundModalClose();
|
handleEditBackgroundModalClose();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Box, Group, LoadingOverlay, SimpleGrid, Stack, Text } from '@mantine/co
|
|||||||
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, useMutationAPI, useQueryAPI } from 'hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { FiChevronRight } from 'react-icons/fi';
|
import { FiChevronRight } from 'react-icons/fi';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import type { Theme, ThemePlaylist } from 'types';
|
import type { Theme, ThemePlaylist } from 'types';
|
||||||
@@ -21,13 +21,17 @@ export function ThemePlaylists() {
|
|||||||
});
|
});
|
||||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const themePlaylists: ThemePlaylist[] =
|
|
||||||
|
const themePlaylists: ThemePlaylist[] = useMemo(() => {
|
||||||
|
return (
|
||||||
themePlaylistData?.filter(
|
themePlaylistData?.filter(
|
||||||
(a: ThemePlaylist) =>
|
(a: ThemePlaylist) =>
|
||||||
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.name.includes('Time of Day'),
|
a.name.includes('Time of Day'),
|
||||||
) || [];
|
) || []
|
||||||
const defaultPlaylist = {
|
);
|
||||||
|
}, [themePlaylistData, debouncedSearch]);
|
||||||
|
const defaultPlaylist: ThemePlaylist = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
default: 0,
|
default: 0,
|
||||||
name: 'New Playlist',
|
name: 'New Playlist',
|
||||||
@@ -37,33 +41,99 @@ export function ThemePlaylists() {
|
|||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const [selectedPlaylist, setSelectedPlaylist] =
|
const [selectedPlaylist, setSelectedPlaylist] =
|
||||||
useState<ThemePlaylist>(defaultPlaylist);
|
useState<ThemePlaylist>(defaultPlaylist);
|
||||||
const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
|
const { settings, theme, isMobile } = useCommonHooks();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleOpen = (playlist: ThemePlaylist) => {
|
const handleOpen = useCallback(
|
||||||
|
(playlist: ThemePlaylist) => {
|
||||||
setSelectedPlaylist(playlist);
|
setSelectedPlaylist(playlist);
|
||||||
open();
|
open();
|
||||||
};
|
},
|
||||||
|
[open],
|
||||||
|
);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = useCallback(() => {
|
||||||
setSelectedPlaylist(defaultPlaylist);
|
setSelectedPlaylist(defaultPlaylist);
|
||||||
close();
|
close();
|
||||||
};
|
}, [close]);
|
||||||
|
|
||||||
const handleSavePlaylist = () => {
|
const handleSavePlaylist = useCallback(() => {
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
method: selectedPlaylist?.id === undefined ? 'POST' : 'PATCH',
|
method: selectedPlaylist?.id === undefined ? 'POST' : 'PATCH',
|
||||||
endpoint: themePlaylistsEndpoint,
|
endpoint: themePlaylistsEndpoint,
|
||||||
body: selectedPlaylist ?? {},
|
body: selectedPlaylist ?? {},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
close();
|
close();
|
||||||
};
|
}, [selectedPlaylist, mutateAsync, themePlaylistsEndpoint, close]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedSearch === searchValue) {
|
if (debouncedSearch === searchValue) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [debouncedSearch]);
|
}, [debouncedSearch, searchValue]);
|
||||||
|
|
||||||
|
const allThemesIds = useMemo(
|
||||||
|
() => allThemeData?.map((a: Theme) => a.id) ?? [],
|
||||||
|
[allThemeData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const backgroundsThemes = useMemo(
|
||||||
|
() =>
|
||||||
|
allThemeData?.filter(
|
||||||
|
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
||||||
|
) ?? [],
|
||||||
|
[allThemeData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const noBackgroundsThemes = useMemo(
|
||||||
|
() =>
|
||||||
|
allThemeData?.filter((a: any) =>
|
||||||
|
a.backgroundImage.toLowerCase().includes('none'),
|
||||||
|
) ?? [],
|
||||||
|
[allThemeData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const timeOfDayPlaylist = useMemo(() => {
|
||||||
|
const found = themePlaylists?.find((a: ThemePlaylist) => a.name === 'Time of Day');
|
||||||
|
return {
|
||||||
|
id: found?.id,
|
||||||
|
name: 'Time of Day',
|
||||||
|
default: 1,
|
||||||
|
themeList: found?.themeList ?? '[]',
|
||||||
|
};
|
||||||
|
}, [themePlaylists]);
|
||||||
|
|
||||||
|
const allStack = useMemo(
|
||||||
|
() => ({
|
||||||
|
id: undefined,
|
||||||
|
name: 'All',
|
||||||
|
default: 1,
|
||||||
|
themeList: `[${allThemesIds.join(',')}]`,
|
||||||
|
}),
|
||||||
|
[allThemesIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const backgroundsStack = useMemo(
|
||||||
|
() => ({
|
||||||
|
id: undefined,
|
||||||
|
name: 'Backgrounds',
|
||||||
|
default: 1,
|
||||||
|
themeList: `[${backgroundsThemes.map((a: Theme) => a.id).join(',')}]`,
|
||||||
|
}),
|
||||||
|
[backgroundsThemes],
|
||||||
|
);
|
||||||
|
|
||||||
|
const noBackgroundsStack = useMemo(
|
||||||
|
() => ({
|
||||||
|
id: undefined,
|
||||||
|
name: 'No Backgrounds',
|
||||||
|
default: 1,
|
||||||
|
themeList: `[${noBackgroundsThemes.map((a: Theme) => a.id).join(',')}]`,
|
||||||
|
}),
|
||||||
|
[noBackgroundsThemes],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -94,8 +164,11 @@ export function ThemePlaylists() {
|
|||||||
searchPlaceholder={'Search Theme Playlists...'}
|
searchPlaceholder={'Search Theme Playlists...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={''}
|
||||||
|
setSortBy={undefined}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack mt={isMobile ? '10rem' : '6rem'}>
|
<Stack mt={isMobile ? '10rem' : '6rem'} gap={'2rem'}>
|
||||||
<LoadingOverlay
|
<LoadingOverlay
|
||||||
visible={themePlaylistDataIsFetching}
|
visible={themePlaylistDataIsFetching}
|
||||||
zIndex={180}
|
zIndex={180}
|
||||||
@@ -108,7 +181,7 @@ export function ThemePlaylists() {
|
|||||||
/>
|
/>
|
||||||
{allThemeData && (
|
{allThemeData && (
|
||||||
<>
|
<>
|
||||||
<Group justify='center' align='center' pos={'relative'}>
|
<Stack w={'100%'} justify='center' align='center' pos={'relative'}>
|
||||||
<Text
|
<Text
|
||||||
p={0}
|
p={0}
|
||||||
className={textClasses.settingsSubTitle}
|
className={textClasses.settingsSubTitle}
|
||||||
@@ -116,10 +189,9 @@ export function ThemePlaylists() {
|
|||||||
>
|
>
|
||||||
Defaults
|
Defaults
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
<Group>
|
||||||
<Group justify='center' align='center' pos={'relative'}>
|
|
||||||
<Group pos={'relative'}>
|
<Group pos={'relative'}>
|
||||||
{'All' === currentThemePlaylist && (
|
{'All' === settings.currentThemePlaylist && (
|
||||||
<FiChevronRight
|
<FiChevronRight
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -133,21 +205,62 @@ export function ThemePlaylists() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Box>
|
|
||||||
<ThemePlaylistStack
|
<ThemePlaylistStack
|
||||||
cardPlaylist={{
|
cardPlaylist={allStack}
|
||||||
id: undefined,
|
|
||||||
name: 'All',
|
|
||||||
default: 1,
|
|
||||||
themeList: `[${allThemeData?.map((a: Theme) => a.id) ?? ''}]`,
|
|
||||||
}}
|
|
||||||
handleOpen={handleOpen}
|
handleOpen={handleOpen}
|
||||||
allThemes={allThemeData}
|
allThemes={allThemeData}
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
</Group>
|
</Group>
|
||||||
<Group pos={'relative'}>
|
<Group pos={'relative'}>
|
||||||
{'Backgrounds' === currentThemePlaylist && (
|
{'Backgrounds' === settings.currentThemePlaylist && (
|
||||||
|
<FiChevronRight
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: -14,
|
||||||
|
zIndex: 10,
|
||||||
|
fontSize: '2rem',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'var(--mantine-color-border-0)',
|
||||||
|
color:
|
||||||
|
theme.borderColor === theme.primaryColor
|
||||||
|
? 'var(--mantine-color-accent-0)'
|
||||||
|
: 'var(--mantine-color-background-0)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ThemePlaylistStack
|
||||||
|
cardPlaylist={backgroundsStack}
|
||||||
|
handleOpen={handleOpen}
|
||||||
|
allThemes={allThemeData}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group pos={'relative'}>
|
||||||
|
{'No Backgrounds' === settings.currentThemePlaylist && (
|
||||||
|
<FiChevronRight
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: -14,
|
||||||
|
zIndex: 10,
|
||||||
|
fontSize: '2rem',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'var(--mantine-color-border-0)',
|
||||||
|
color:
|
||||||
|
theme.borderColor === theme.primaryColor
|
||||||
|
? 'var(--mantine-color-accent-0)'
|
||||||
|
: 'var(--mantine-color-background-0)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ThemePlaylistStack
|
||||||
|
cardPlaylist={noBackgroundsStack}
|
||||||
|
handleOpen={handleOpen}
|
||||||
|
allThemes={allThemeData}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group pos={'relative'}>
|
||||||
|
{'Time of Day' === settings.currentThemePlaylist && (
|
||||||
<FiChevronRight
|
<FiChevronRight
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -166,86 +279,15 @@ export function ThemePlaylists() {
|
|||||||
)}
|
)}
|
||||||
<Box>
|
<Box>
|
||||||
<ThemePlaylistStack
|
<ThemePlaylistStack
|
||||||
cardPlaylist={{
|
cardPlaylist={timeOfDayPlaylist}
|
||||||
id: undefined,
|
|
||||||
name: 'Backgrounds',
|
|
||||||
default: 1,
|
|
||||||
themeList: `[${allThemeData?.filter((a: any) => !a.backgroundImage.toLowerCase().includes('none')).map((a: Theme) => a.id) ?? ''}]`,
|
|
||||||
}}
|
|
||||||
handleOpen={handleOpen}
|
|
||||||
allThemes={allThemeData}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Group>
|
|
||||||
<Group pos={'relative'}>
|
|
||||||
{'No Backgrounds' === currentThemePlaylist && (
|
|
||||||
<FiChevronRight
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: -14,
|
|
||||||
zIndex: 10,
|
|
||||||
fontSize: '2rem',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: 'var(--mantine-color-border-0)',
|
|
||||||
color:
|
|
||||||
theme.borderColor === theme.primaryColor
|
|
||||||
? 'var(--mantine-color-accent-0)'
|
|
||||||
: 'var(--mantine-color-background-0)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Box>
|
|
||||||
<ThemePlaylistStack
|
|
||||||
cardPlaylist={{
|
|
||||||
id: undefined,
|
|
||||||
name: 'No Backgrounds',
|
|
||||||
default: 1,
|
|
||||||
themeList: `[${allThemeData?.filter((a: any) => a.backgroundImage.toLowerCase().includes('none')).map((a: Theme) => a.id) ?? ''}]`,
|
|
||||||
}}
|
|
||||||
handleOpen={handleOpen}
|
|
||||||
allThemes={allThemeData}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Group>
|
|
||||||
<Group pos={'relative'}>
|
|
||||||
{'Time of Day' === currentThemePlaylist && (
|
|
||||||
<FiChevronRight
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: -14,
|
|
||||||
zIndex: 10,
|
|
||||||
fontSize: '2rem',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: 'var(--mantine-color-border-0)',
|
|
||||||
color:
|
|
||||||
theme.borderColor === theme.primaryColor
|
|
||||||
? 'var(--mantine-color-accent-0)'
|
|
||||||
: 'var(--mantine-color-background-0)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Box>
|
|
||||||
<ThemePlaylistStack
|
|
||||||
cardPlaylist={{
|
|
||||||
id:
|
|
||||||
themePlaylists?.find(
|
|
||||||
(a: ThemePlaylist) => a.name === 'Time of Day',
|
|
||||||
)?.id ?? undefined,
|
|
||||||
name: 'Time of Day',
|
|
||||||
default: 1,
|
|
||||||
themeList:
|
|
||||||
themePlaylists?.find(
|
|
||||||
(a: ThemePlaylist) => a.name === 'Time of Day',
|
|
||||||
)?.themeList ?? '[]',
|
|
||||||
}}
|
|
||||||
handleOpen={handleOpen}
|
handleOpen={handleOpen}
|
||||||
allThemes={allThemeData}
|
allThemes={allThemeData}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
<Stack w={'100%'} justify='center' align='center' pos={'relative'}>
|
||||||
<Group justify='center' align='center' pos={'relative'}>
|
<Group justify='center' align='center' pos={'relative'}>
|
||||||
<Text
|
<Text
|
||||||
p={0}
|
p={0}
|
||||||
@@ -271,7 +313,7 @@ export function ThemePlaylists() {
|
|||||||
.map((cardPlaylist: ThemePlaylist) => {
|
.map((cardPlaylist: ThemePlaylist) => {
|
||||||
return (
|
return (
|
||||||
<Group key={cardPlaylist.name} pos={'relative'}>
|
<Group key={cardPlaylist.name} pos={'relative'}>
|
||||||
{cardPlaylist.name === currentThemePlaylist && (
|
{cardPlaylist.name === settings.currentThemePlaylist && (
|
||||||
<FiChevronRight
|
<FiChevronRight
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -305,6 +347,7 @@ export function ThemePlaylists() {
|
|||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
</Stack>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function ThemePlaylistCard({
|
|||||||
allThemes: Theme[];
|
allThemes: Theme[];
|
||||||
}) {
|
}) {
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { setThemeName, setCurrentThemePlaylist, handleDelete } = useCommonHooks();
|
const { updateSetting, handleDelete } = useCommonHooks();
|
||||||
const defaultLists = ['All', 'Backgrounds', 'No Backgrounds'];
|
const defaultLists = ['All', 'Backgrounds', 'No Backgrounds'];
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
@@ -117,8 +117,8 @@ export function ThemePlaylistCard({
|
|||||||
border: 'solid var(--mantine-color-border-0)',
|
border: 'solid var(--mantine-color-border-0)',
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentThemePlaylist(cardPlaylist.name);
|
updateSetting('currentThemePlaylist', cardPlaylist.name);
|
||||||
setThemeName('random');
|
updateSetting('theme', 'random');
|
||||||
}}
|
}}
|
||||||
disabled={themeList.length === 0}
|
disabled={themeList.length === 0}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Parallax } from '@gfazioli/mantine-parallax';
|
import { Parallax } from '@gfazioli/mantine-parallax';
|
||||||
import { Reflection } from '@gfazioli/mantine-reflection';
|
import { Reflection } from '@gfazioli/mantine-reflection';
|
||||||
import { ActionIcon, Box, Group, Stack, Text } from '@mantine/core';
|
import { ActionIcon, Box, Group, Overlay, Popover, Stack, Text } from '@mantine/core';
|
||||||
import { useDebouncedCallback, useDisclosure } from '@mantine/hooks';
|
import { useDebouncedCallback, useDisclosure, useLongPress } from '@mantine/hooks';
|
||||||
import { DeleteItemModal } from 'components';
|
import { DeleteItemModal } from 'components';
|
||||||
import { useCommonHooks } from 'hooks';
|
import { useCommonHooks } from 'hooks';
|
||||||
import { useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { FiEdit, FiTrash } from 'react-icons/fi';
|
import { FiEdit, FiTrash } from 'react-icons/fi';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import type { Theme, ThemePlaylist } from 'types';
|
import type { Theme, ThemePlaylist } from 'types';
|
||||||
import { ThemeCard } from '../../Themes';
|
import { ThemeCardPreview } from '../../Themes/ThemeCard';
|
||||||
|
import { ThemeModalContent } from '../../Themes/ThemeModal/components/ThemeModalContent';
|
||||||
|
|
||||||
export function ThemePlaylistStack({
|
export function ThemePlaylistStack({
|
||||||
cardPlaylist,
|
cardPlaylist,
|
||||||
@@ -19,16 +20,14 @@ export function ThemePlaylistStack({
|
|||||||
handleOpen: (cardPlaylist: ThemePlaylist) => void;
|
handleOpen: (cardPlaylist: ThemePlaylist) => void;
|
||||||
allThemes: Theme[];
|
allThemes: Theme[];
|
||||||
}) {
|
}) {
|
||||||
|
const [pressed, setPressed] = useState<boolean>(false);
|
||||||
|
const handlers = useLongPress(() => {
|
||||||
|
setPressed(true);
|
||||||
|
});
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { setThemeName, setCurrentThemePlaylist, handleDelete, currentUser } =
|
const { settings, updateSetting, handleDelete } = useCommonHooks();
|
||||||
useCommonHooks();
|
|
||||||
const [indexOffset, setIndexOffset] = useState(0);
|
const [indexOffset, setIndexOffset] = useState(0);
|
||||||
const defaultLists = ['All', 'Backgrounds', 'No Backgrounds'];
|
const [movingIndex, setMovingIndex] = useState(0);
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
|
||||||
const themeList = JSON.parse(
|
|
||||||
cardPlaylist?.themeList?.name === 'Time of Day' ? '[]' : cardPlaylist.themeList,
|
|
||||||
);
|
|
||||||
|
|
||||||
const missingTheme = {
|
const missingTheme = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: 'default',
|
name: 'default',
|
||||||
@@ -41,8 +40,14 @@ export function ThemePlaylistStack({
|
|||||||
randomEnabled: 1,
|
randomEnabled: 1,
|
||||||
textShadowsEnabled: 0,
|
textShadowsEnabled: 0,
|
||||||
appShadowsEnabled: 0,
|
appShadowsEnabled: 0,
|
||||||
themeFont: currentUser.defaultFont,
|
themeFont: settings.defaultFont,
|
||||||
};
|
};
|
||||||
|
const [topTheme, setTopTheme] = useState<Theme>(missingTheme);
|
||||||
|
const defaultLists = ['All', 'Backgrounds', 'No Backgrounds'];
|
||||||
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
|
const themeList = JSON.parse(
|
||||||
|
cardPlaylist?.themeList?.name === 'Time of Day' ? '[]' : cardPlaylist.themeList,
|
||||||
|
);
|
||||||
|
|
||||||
const presentationProps = {
|
const presentationProps = {
|
||||||
initialRotationX: 60,
|
initialRotationX: 60,
|
||||||
@@ -62,7 +67,79 @@ export function ThemePlaylistStack({
|
|||||||
}
|
}
|
||||||
}, 75);
|
}, 75);
|
||||||
|
|
||||||
|
const sortedItems = useMemo(
|
||||||
|
() =>
|
||||||
|
(themeList.length > 0 &&
|
||||||
|
themeList.sort((a: number, b: number) => (a > b ? 1 : -1))) ||
|
||||||
|
[],
|
||||||
|
[themeList],
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = sortedItems?.map((item: number, index: number) => {
|
||||||
|
let spot = index + indexOffset;
|
||||||
|
if (spot <= -1) {
|
||||||
|
spot = index + indexOffset + themeList.length;
|
||||||
|
} else if (spot >= themeList.length) {
|
||||||
|
spot = index + indexOffset - themeList.length;
|
||||||
|
} else {
|
||||||
|
spot = index + indexOffset;
|
||||||
|
}
|
||||||
|
const theme: any = allThemes.find((theme: Theme) => theme.id === item);
|
||||||
return (
|
return (
|
||||||
|
<Box
|
||||||
|
key={theme.id}
|
||||||
|
style={{
|
||||||
|
transition: 'all 0.3s ease-in-out',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 16 * (spot + 1),
|
||||||
|
zIndex: themeList.length - spot,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Parallax {...(!(hover && spot === 0) ? presentationProps : { disabled: true })}>
|
||||||
|
<ThemeCardPreview key={theme.id} cardTheme={theme} />
|
||||||
|
</Parallax>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cardPlaylist.name === 'Time of Day') {
|
||||||
|
setTopTheme(
|
||||||
|
allThemes.find(
|
||||||
|
(t: Theme) => t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
|
||||||
|
) || missingTheme,
|
||||||
|
);
|
||||||
|
} else if (indexOffset > 0 && indexOffset < themeList.length) {
|
||||||
|
setMovingIndex(themeList.length - indexOffset);
|
||||||
|
setTopTheme(
|
||||||
|
allThemes.find(
|
||||||
|
(theme: Theme) => theme.id === themeList[themeList.length - indexOffset],
|
||||||
|
) || missingTheme,
|
||||||
|
);
|
||||||
|
} else if (indexOffset < 0 && Math.abs(indexOffset) < themeList.length) {
|
||||||
|
setMovingIndex(Math.abs(indexOffset));
|
||||||
|
setTopTheme(
|
||||||
|
allThemes.find((theme: Theme) => theme.id === themeList[Math.abs(indexOffset)]) ||
|
||||||
|
missingTheme,
|
||||||
|
);
|
||||||
|
} else if (indexOffset === 0) {
|
||||||
|
setMovingIndex(0);
|
||||||
|
setTopTheme(
|
||||||
|
allThemes.find((theme: Theme) => theme.id === themeList[0]) || missingTheme,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [indexOffset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
withArrow
|
||||||
|
opened={pressed}
|
||||||
|
{...handlers}
|
||||||
|
arrowSize={16}
|
||||||
|
transitionProps={{ duration: 200, timingFunction: 'ease' }}
|
||||||
|
>
|
||||||
|
<Popover.Target>
|
||||||
|
<Box>
|
||||||
<Reflection reflectionStart={75} reflectionEnd={100} shadowOpacity={0.25}>
|
<Reflection reflectionStart={75} reflectionEnd={100} shadowOpacity={0.25}>
|
||||||
<Stack
|
<Stack
|
||||||
gap={0}
|
gap={0}
|
||||||
@@ -78,8 +155,8 @@ export function ThemePlaylistStack({
|
|||||||
borderRadius: '100%',
|
borderRadius: '100%',
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentThemePlaylist(cardPlaylist.name);
|
updateSetting('currentThemePlaylist', cardPlaylist.name);
|
||||||
setThemeName('random');
|
updateSetting('theme', 'random');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{modalOpened && (
|
{modalOpened && (
|
||||||
@@ -100,85 +177,6 @@ export function ThemePlaylistStack({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{cardPlaylist?.name === 'Time of Day' ? (
|
|
||||||
<Stack
|
|
||||||
w='100%'
|
|
||||||
h='200px'
|
|
||||||
justify='center'
|
|
||||||
pos='relative'
|
|
||||||
gap={16}
|
|
||||||
style={{ overflow: 'hidden' }}
|
|
||||||
>
|
|
||||||
<Parallax
|
|
||||||
{...presentationProps}
|
|
||||||
style={{ position: 'absolute', top: -60, zIndex: 3 }}
|
|
||||||
>
|
|
||||||
<ThemeCard
|
|
||||||
cardTheme={
|
|
||||||
allThemes.find((theme: Theme) => theme.id === Number(themeList[0])) ||
|
|
||||||
missingTheme
|
|
||||||
}
|
|
||||||
allThemes={allThemes}
|
|
||||||
themePlaylists={[]}
|
|
||||||
setSelectedTheme={() => {}}
|
|
||||||
openThemeModal={() => {}}
|
|
||||||
enabled={false}
|
|
||||||
management={false}
|
|
||||||
/>
|
|
||||||
</Parallax>
|
|
||||||
<Parallax
|
|
||||||
{...presentationProps}
|
|
||||||
style={{ position: 'absolute', top: -60, zIndex: 2 }}
|
|
||||||
>
|
|
||||||
<ThemeCard
|
|
||||||
cardTheme={
|
|
||||||
allThemes.find((theme: Theme) => theme.id === Number(themeList[1])) ||
|
|
||||||
missingTheme
|
|
||||||
}
|
|
||||||
allThemes={allThemes}
|
|
||||||
themePlaylists={[]}
|
|
||||||
setSelectedTheme={() => {}}
|
|
||||||
openThemeModal={() => {}}
|
|
||||||
enabled={false}
|
|
||||||
management={false}
|
|
||||||
/>
|
|
||||||
</Parallax>
|
|
||||||
<Parallax
|
|
||||||
{...presentationProps}
|
|
||||||
style={{ position: 'absolute', top: -60, zIndex: 1 }}
|
|
||||||
>
|
|
||||||
<ThemeCard
|
|
||||||
cardTheme={
|
|
||||||
allThemes.find((theme: Theme) => theme.id === Number(themeList[2])) ||
|
|
||||||
missingTheme
|
|
||||||
}
|
|
||||||
allThemes={allThemes}
|
|
||||||
themePlaylists={[]}
|
|
||||||
setSelectedTheme={() => {}}
|
|
||||||
openThemeModal={() => {}}
|
|
||||||
enabled={false}
|
|
||||||
management={false}
|
|
||||||
/>
|
|
||||||
</Parallax>
|
|
||||||
<Parallax
|
|
||||||
{...presentationProps}
|
|
||||||
style={{ position: 'absolute', top: -60, zIndex: 0 }}
|
|
||||||
>
|
|
||||||
<ThemeCard
|
|
||||||
cardTheme={
|
|
||||||
allThemes.find((theme: Theme) => theme.id === Number(themeList[3])) ||
|
|
||||||
missingTheme
|
|
||||||
}
|
|
||||||
allThemes={allThemes}
|
|
||||||
themePlaylists={[]}
|
|
||||||
setSelectedTheme={() => {}}
|
|
||||||
openThemeModal={() => {}}
|
|
||||||
enabled={false}
|
|
||||||
management={false}
|
|
||||||
/>
|
|
||||||
</Parallax>
|
|
||||||
</Stack>
|
|
||||||
) : (
|
|
||||||
<Stack
|
<Stack
|
||||||
w='100%'
|
w='100%'
|
||||||
h='200px'
|
h='200px'
|
||||||
@@ -192,59 +190,174 @@ export function ThemePlaylistStack({
|
|||||||
}}
|
}}
|
||||||
onWheel={handleMouseWheel}
|
onWheel={handleMouseWheel}
|
||||||
>
|
>
|
||||||
{themeList
|
{cardPlaylist?.name === 'Time of Day' ? (
|
||||||
.sort((a: number, b: number) => (a > b ? 1 : -1))
|
<>
|
||||||
.map((item: number, index: number) => {
|
<Parallax
|
||||||
let spot = index + indexOffset;
|
{...(!(
|
||||||
if (spot <= -1) {
|
hover &&
|
||||||
spot = index + indexOffset + themeList.length;
|
Number(themeList[0]) ===
|
||||||
} else if (spot >= themeList.length) {
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
spot = index + indexOffset - themeList.length;
|
) && presentationProps)}
|
||||||
} else {
|
// style={{ position: 'absolute', top: 16 * 1, zIndex: 3 }}
|
||||||
spot = index + indexOffset;
|
|
||||||
}
|
|
||||||
const theme: any = allThemes.find((theme: Theme) => theme.id === item);
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={theme.id}
|
|
||||||
style={{
|
style={{
|
||||||
transition: 'all 0.3s ease-in-out',
|
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 16 * (spot + 1),
|
top: 16,
|
||||||
zIndex: themeList.length - spot,
|
zIndex: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[0]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 3
|
||||||
|
: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Parallax {...presentationProps}>
|
<ThemeCardPreview
|
||||||
<ThemeCard
|
cardTheme={
|
||||||
key={theme.id}
|
allThemes.find(
|
||||||
cardTheme={theme}
|
(theme: Theme) => theme.id === Number(themeList[0]),
|
||||||
allThemes={allThemes}
|
) || missingTheme
|
||||||
themePlaylists={[]}
|
}
|
||||||
setSelectedTheme={() => {}}
|
|
||||||
openThemeModal={() => {}}
|
|
||||||
enabled={false}
|
|
||||||
management={false}
|
|
||||||
/>
|
/>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
</Box>
|
<Parallax
|
||||||
);
|
{...(!(
|
||||||
})}
|
hover &&
|
||||||
</Stack>
|
Number(themeList[1]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
) && presentationProps)}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[1]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 16 * 2
|
||||||
|
: 16,
|
||||||
|
zIndex: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[1]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 2
|
||||||
|
: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ThemeCardPreview
|
||||||
|
cardTheme={
|
||||||
|
allThemes.find(
|
||||||
|
(theme: Theme) => theme.id === Number(themeList[1]),
|
||||||
|
) || missingTheme
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Parallax>
|
||||||
|
<Parallax
|
||||||
|
{...(!(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[2]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
) && presentationProps)}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[2]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 16 * 3
|
||||||
|
: 16,
|
||||||
|
zIndex: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[2]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 1
|
||||||
|
: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ThemeCardPreview
|
||||||
|
cardTheme={
|
||||||
|
allThemes.find(
|
||||||
|
(theme: Theme) => theme.id === Number(themeList[2]),
|
||||||
|
) || missingTheme
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Parallax>
|
||||||
|
<Parallax
|
||||||
|
{...(!(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[3]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
) && presentationProps)}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[3]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 16 * 4
|
||||||
|
: 0,
|
||||||
|
zIndex: !(
|
||||||
|
hover &&
|
||||||
|
Number(themeList[3]) ===
|
||||||
|
Number(themeList[Math.floor(new Date().getHours() / 6)])
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ThemeCardPreview
|
||||||
|
cardTheme={
|
||||||
|
allThemes.find(
|
||||||
|
(theme: Theme) => theme.id === Number(themeList[3]),
|
||||||
|
) || missingTheme
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Parallax>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
items.slice(movingIndex, movingIndex + 8)
|
||||||
)}
|
)}
|
||||||
|
</Stack>
|
||||||
<Group w={'100%'} pos={'absolute'} justify='space-between' bottom={0}>
|
</Stack>
|
||||||
|
</Reflection>
|
||||||
|
<Group
|
||||||
|
w={'100%'}
|
||||||
|
pos={'absolute'}
|
||||||
|
justify='space-between'
|
||||||
|
bottom={0}
|
||||||
|
style={{ zIndex: 1000 }}
|
||||||
|
>
|
||||||
|
<Group gap={2}>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
paddingRight: '.25rem',
|
paddingRight: '.25rem',
|
||||||
paddingLeft: '.25rem',
|
paddingLeft: '.25rem',
|
||||||
backgroundColor: 'var(--mantine-color-border-0)',
|
backgroundColor: 'var(--mantine-color-border-0)',
|
||||||
borderRadius: '4px',
|
borderRadius: '16px',
|
||||||
color: 'var(--mantine-color-primary-0)',
|
color: 'var(--mantine-color-background-0)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cardPlaylist.name}
|
{cardPlaylist.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Group gap={0} style={{ zIndex: 1000 }}>
|
{themeList.length > 0 && (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
paddingRight: '.25rem',
|
||||||
|
paddingLeft: '.25rem',
|
||||||
|
backgroundColor: 'var(--mantine-color-background-0)',
|
||||||
|
borderRadius: '16px',
|
||||||
|
color: 'var(--mantine-color-accent-0)',
|
||||||
|
minWidth: '1.5rem',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{themeList.length}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
<Group gap={0}>
|
||||||
{!defaultLists.includes(cardPlaylist.name) && (
|
{!defaultLists.includes(cardPlaylist.name) && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
style={{
|
style={{
|
||||||
@@ -278,7 +391,30 @@ export function ThemePlaylistStack({
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Box>
|
||||||
</Reflection>
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown
|
||||||
|
bg={'var(--mantine-color-primary-0)'}
|
||||||
|
p={0}
|
||||||
|
style={{ border: 'none' }}
|
||||||
|
onClick={() => {
|
||||||
|
pressed && setPressed(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Overlay
|
||||||
|
w={'calc(300%)'}
|
||||||
|
h={'calc(300%)'}
|
||||||
|
pos={'absolute'}
|
||||||
|
left={'-100%'}
|
||||||
|
top={'-100%'}
|
||||||
|
zIndex={-1}
|
||||||
|
opacity={0.8}
|
||||||
|
bg={
|
||||||
|
'radial-gradient(circle, var(--mantine-color-primary-0) 0%, transparent 60%)'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ThemeModalContent selectedTheme={topTheme} />
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ export function ThemeCard({
|
|||||||
}) {
|
}) {
|
||||||
const { hovered, ref: hoverRef } = useHover();
|
const { hovered, ref: hoverRef } = useHover();
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
const { currentTheme, setCurrentTheme, setCurrentThemePlaylist, handleDelete } =
|
const { settings, updateSetting, handleDelete } = useCommonHooks();
|
||||||
useCommonHooks();
|
|
||||||
const [hoverPreview, setHoverPreview] = useDebouncedState(false, 1000);
|
const [hoverPreview, setHoverPreview] = useDebouncedState(false, 1000);
|
||||||
const [transition, setTransition] = useDebouncedState(false, 200);
|
const [transition, setTransition] = useDebouncedState(false, 200);
|
||||||
|
|
||||||
@@ -113,7 +112,7 @@ export function ThemeCard({
|
|||||||
<Button
|
<Button
|
||||||
w={'275px'}
|
w={'275px'}
|
||||||
h={'115px'}
|
h={'115px'}
|
||||||
disabled={currentTheme === 'random'}
|
disabled={settings.theme === 'random'}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
cardTheme?.backgroundImage?.toLocaleLowerCase() === 'none'
|
cardTheme?.backgroundImage?.toLocaleLowerCase() === 'none'
|
||||||
@@ -137,12 +136,12 @@ export function ThemeCard({
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (enabled !== false) {
|
if (enabled !== false) {
|
||||||
setCurrentTheme(cardTheme.name);
|
updateSetting('theme', cardTheme.name);
|
||||||
setCurrentThemePlaylist('All');
|
updateSetting('currentThemePlaylist', 'All');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hovered && enabled && currentTheme !== 'random' && (
|
{hovered && enabled && settings.theme !== 'random' && (
|
||||||
<Overlay
|
<Overlay
|
||||||
style={{
|
style={{
|
||||||
opacity: !hoverPreview || transition ? 0.5 : 0,
|
opacity: !hoverPreview || transition ? 0.5 : 0,
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { Box, Button, Group, Stack, Text } from '@mantine/core';
|
||||||
|
import type { Theme } from 'types';
|
||||||
|
import { MiniAppItem } from './components/MiniAppItem';
|
||||||
|
import { MiniGroupItem } from './components/MiniGroupItem';
|
||||||
|
|
||||||
|
export function ThemeCardPreview({ cardTheme }: { cardTheme: Theme }) {
|
||||||
|
return (
|
||||||
|
<Group gap={0} align='center' justify='center' display={'grid'} pos={'relative'}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: -10,
|
||||||
|
left: 6,
|
||||||
|
paddingRight: '.25rem',
|
||||||
|
paddingLeft: '.25rem',
|
||||||
|
backgroundColor: cardTheme.backgroundColor,
|
||||||
|
borderRadius: '4px',
|
||||||
|
color:
|
||||||
|
cardTheme.borderColor === cardTheme.primaryColor
|
||||||
|
? cardTheme.accentColor
|
||||||
|
: cardTheme.primaryColor,
|
||||||
|
zIndex: 3,
|
||||||
|
boxShadow: `2px -2px ${cardTheme.borderColor}`,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
maxWidth: 275,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cardTheme.name}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
w={'275px'}
|
||||||
|
h={'115px'}
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
cardTheme?.backgroundImage?.toLocaleLowerCase() === 'none'
|
||||||
|
? undefined
|
||||||
|
: `linear-gradient(${cardTheme?.backgroundColor}33, ${cardTheme?.backgroundColor}33), url(../../trigger/api/backgroundImages/thumbs/${cardTheme?.backgroundImage})`,
|
||||||
|
backgroundSize:
|
||||||
|
cardTheme?.backgroundImage?.toLocaleLowerCase() === 'none'
|
||||||
|
? undefined
|
||||||
|
: 'cover',
|
||||||
|
backgroundColor:
|
||||||
|
cardTheme?.backgroundImage?.toLocaleLowerCase() === 'none'
|
||||||
|
? cardTheme?.backgroundColor
|
||||||
|
: undefined,
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
border: `solid ${cardTheme.borderColor}`,
|
||||||
|
transition: 'all .25s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group w={'100%'} gap={0} display={'flex'} align='center' justify='center'>
|
||||||
|
<Stack align='center' gap={12}>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: '8px',
|
||||||
|
width: '100px',
|
||||||
|
backgroundColor: cardTheme.searchColor,
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderBottom: `solid 1px ${cardTheme.borderColor}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Group justify='space-between'>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: '.5rem',
|
||||||
|
width: '100px',
|
||||||
|
backgroundColor: cardTheme.accentColor,
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: '.5rem',
|
||||||
|
width: '50px',
|
||||||
|
backgroundColor: cardTheme.primaryColor,
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group w={'100%'} mb={-4}>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: '2px',
|
||||||
|
width: '40px',
|
||||||
|
backgroundColor: cardTheme.primaryColor,
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group w={'100%'} gap={10} justify='space-between'>
|
||||||
|
<MiniAppItem cardTheme={cardTheme} />
|
||||||
|
<MiniAppItem cardTheme={cardTheme} />
|
||||||
|
<MiniAppItem cardTheme={cardTheme} />
|
||||||
|
<MiniAppItem cardTheme={cardTheme} />
|
||||||
|
</Group>
|
||||||
|
<Group w={'100%'} mb={-4}>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: '2px',
|
||||||
|
width: '32px',
|
||||||
|
backgroundColor: cardTheme.primaryColor,
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group w={'100%'} gap={10} justify='space-between'>
|
||||||
|
<MiniGroupItem cardTheme={cardTheme} />
|
||||||
|
<MiniGroupItem cardTheme={cardTheme} />
|
||||||
|
<MiniGroupItem cardTheme={cardTheme} />
|
||||||
|
<MiniGroupItem cardTheme={cardTheme} />
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -29,14 +29,13 @@ export function ThemeManagementPill({
|
|||||||
transition: boolean;
|
transition: boolean;
|
||||||
setTransition: (transition: boolean) => void;
|
setTransition: (transition: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { currentTheme, currentThemePlaylist, handleThemeChange, theme } =
|
const { settings, handleThemeChange, theme } = useCommonHooks();
|
||||||
useCommonHooks();
|
|
||||||
const [shiftEdit, setShiftEdit] = useState<number>(0);
|
const [shiftEdit, setShiftEdit] = useState<number>(0);
|
||||||
const [shiftShuffle, setShiftShuffle] = useState<number>(0);
|
const [shiftShuffle, setShiftShuffle] = useState<number>(0);
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{currentTheme === 'random' && cardTheme.name === theme.name ? (
|
{settings.theme === 'random' && cardTheme.name === theme.name ? (
|
||||||
<Group>
|
<Group>
|
||||||
<Group
|
<Group
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
@@ -81,16 +80,16 @@ export function ThemeManagementPill({
|
|||||||
setShiftShuffle(0);
|
setShiftShuffle(0);
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentThemePlaylist === 'All') {
|
if (settings.currentThemePlaylist === 'All') {
|
||||||
handleThemeChange(allThemes, false);
|
handleThemeChange(allThemes, false);
|
||||||
} else if (currentThemePlaylist === 'Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter(
|
allThemes.filter(
|
||||||
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
(a: any) => !a.backgroundImage.toLowerCase().includes('none'),
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else if (currentThemePlaylist === 'No Backgrounds') {
|
} else if (settings.currentThemePlaylist === 'No Backgrounds') {
|
||||||
handleThemeChange(
|
handleThemeChange(
|
||||||
allThemes.filter((a: any) =>
|
allThemes.filter((a: any) =>
|
||||||
a.backgroundImage.toLowerCase().includes('none'),
|
a.backgroundImage.toLowerCase().includes('none'),
|
||||||
@@ -100,7 +99,7 @@ export function ThemeManagementPill({
|
|||||||
} else {
|
} else {
|
||||||
const themeList = JSON.parse(
|
const themeList = JSON.parse(
|
||||||
themePlaylists?.find(
|
themePlaylists?.find(
|
||||||
(t: ThemePlaylist) => t?.name === currentThemePlaylist,
|
(t: ThemePlaylist) => t?.name === settings.currentThemePlaylist,
|
||||||
)?.themeList ?? '[]',
|
)?.themeList ?? '[]',
|
||||||
);
|
);
|
||||||
const filteredThemes = allThemes.filter((t: Theme) =>
|
const filteredThemes = allThemes.filter((t: Theme) =>
|
||||||
@@ -203,6 +202,8 @@ export function ThemeManagementPill({
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
endpoint: 'api/themes',
|
endpoint: 'api/themes',
|
||||||
body: { ...cardTheme, randomEnabled: !cardTheme.randomEnabled },
|
body: { ...cardTheme, randomEnabled: !cardTheme.randomEnabled },
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export { ThemeCard } from './ThemeCard';
|
export { ThemeCard } from './ThemeCard';
|
||||||
|
export { ThemeCardPreview } from './ThemeCardPreview';
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function ThemeModal({
|
|||||||
themePlaylists: ThemePlaylist[];
|
themePlaylists: ThemePlaylist[];
|
||||||
}) {
|
}) {
|
||||||
const themesEndpoint = 'api/themes';
|
const themesEndpoint = 'api/themes';
|
||||||
const { currentUser, isMobile } = useCommonHooks();
|
const { settings, isMobile } = useCommonHooks();
|
||||||
const [defaultTheme, setDefaultTheme] = useState<Theme>({ ...selectedTheme });
|
const [defaultTheme, setDefaultTheme] = useState<Theme>({ ...selectedTheme });
|
||||||
const [designerTheme, setDesignerTheme] = useState<Theme>({
|
const [designerTheme, setDesignerTheme] = useState<Theme>({
|
||||||
...selectedTheme,
|
...selectedTheme,
|
||||||
@@ -68,7 +68,7 @@ export function ThemeModal({
|
|||||||
randomEnabled: 1,
|
randomEnabled: 1,
|
||||||
textShadowsEnabled: 0,
|
textShadowsEnabled: 0,
|
||||||
appShadowsEnabled: 0,
|
appShadowsEnabled: 0,
|
||||||
themeFont: currentUser.defaultFont,
|
themeFont: settings.defaultFont,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,6 +77,8 @@ export function ThemeModal({
|
|||||||
method: designerTheme?.id === undefined ? 'POST' : 'PATCH',
|
method: designerTheme?.id === undefined ? 'POST' : 'PATCH',
|
||||||
endpoint: themesEndpoint,
|
endpoint: themesEndpoint,
|
||||||
body: designerTheme,
|
body: designerTheme,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function ThemeChanges({
|
|||||||
designerTheme: Theme;
|
designerTheme: Theme;
|
||||||
setDesignerTheme: (theme: Theme) => void;
|
setDesignerTheme: (theme: Theme) => void;
|
||||||
}) {
|
}) {
|
||||||
const { currentUser, getObjectDiff } = useCommonHooks();
|
const { settings, getObjectDiff } = useCommonHooks();
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
gap={0}
|
gap={0}
|
||||||
@@ -148,7 +148,7 @@ export default function ThemeChanges({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{defaultTheme[key as keyof Theme] ||
|
{defaultTheme[key as keyof Theme] ||
|
||||||
`Default (${currentUser.defaultFont})`}
|
`Default (${settings.defaultFont})`}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>→</Text>
|
<Text>→</Text>
|
||||||
<Text
|
<Text
|
||||||
@@ -158,7 +158,7 @@ export default function ThemeChanges({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{designerTheme[key as keyof Theme] ||
|
{designerTheme[key as keyof Theme] ||
|
||||||
`Default (${currentUser.defaultFont})`}
|
`Default (${settings.defaultFont})`}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
@@ -197,9 +197,11 @@ export default function ThemeChanges({
|
|||||||
<ColorSwatch
|
<ColorSwatch
|
||||||
h={50}
|
h={50}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard
|
||||||
defaultTheme[key as keyof Theme] as string,
|
.writeText(defaultTheme[key as keyof Theme] as string)
|
||||||
);
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: (
|
title: (
|
||||||
<Text fw={600} c={'var(--mantine-color-accent-0)'}>
|
<Text fw={600} c={'var(--mantine-color-accent-0)'}>
|
||||||
@@ -250,9 +252,11 @@ export default function ThemeChanges({
|
|||||||
<ColorSwatch
|
<ColorSwatch
|
||||||
h={50}
|
h={50}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard
|
||||||
defaultTheme[key as keyof Theme] as string,
|
.writeText(defaultTheme[key as keyof Theme] as string)
|
||||||
);
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: (
|
title: (
|
||||||
<Text fw={600} c={'var(--mantine-color-accent-0)'}>
|
<Text fw={600} c={'var(--mantine-color-accent-0)'}>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function ThemeEditor({
|
|||||||
duplicate: boolean;
|
duplicate: boolean;
|
||||||
setDuplicate: (duplicate: boolean) => void;
|
setDuplicate: (duplicate: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { currentUser, isMobile } = useCommonHooks();
|
const { settings, isMobile } = useCommonHooks();
|
||||||
const [colorId, setColorId] = useState<string>('');
|
const [colorId, setColorId] = useState<string>('');
|
||||||
const [colorValue, setColorValue] = useState('#000000');
|
const [colorValue, setColorValue] = useState('#000000');
|
||||||
const [rgba, setRgba] = useState<{ r: number; g: number; b: number; a?: number }>({
|
const [rgba, setRgba] = useState<{ r: number; g: number; b: number; a?: number }>({
|
||||||
@@ -80,7 +80,7 @@ export function ThemeEditor({
|
|||||||
} else if (font && !customFontEnabled) {
|
} else if (font && !customFontEnabled) {
|
||||||
setDesignerTheme({
|
setDesignerTheme({
|
||||||
...designerTheme,
|
...designerTheme,
|
||||||
themeFont: currentUser.defaultFont || 'Jost',
|
themeFont: settings.defaultFont || 'Jost',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [font]);
|
}, [font]);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { selectClasses, textClasses } from 'styles';
|
|||||||
import type { Theme } from 'types';
|
import type { Theme } from 'types';
|
||||||
|
|
||||||
export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
|
export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
|
||||||
const { currentUser, textShadowStyle, largeTextShadowStyle } = useCommonHooks();
|
const { settings, textShadowStyle, largeTextShadowStyle } = useCommonHooks();
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
style={{
|
style={{
|
||||||
@@ -31,10 +31,11 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
|
|||||||
'--mantine-color-border-0': selectedTheme?.borderColor,
|
'--mantine-color-border-0': selectedTheme?.borderColor,
|
||||||
'--mantine-color-search-0': selectedTheme?.searchColor,
|
'--mantine-color-search-0': selectedTheme?.searchColor,
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
fontFamily: selectedTheme.themeFont ?? currentUser.defaultFont,
|
fontFamily: selectedTheme.themeFont ?? settings.defaultFont,
|
||||||
}}
|
}}
|
||||||
align='center'
|
align='center'
|
||||||
gap={0}
|
gap={0}
|
||||||
|
miw={600}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
disabled
|
disabled
|
||||||
@@ -46,7 +47,7 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
|
|||||||
color: selectedTheme?.primaryColor,
|
color: selectedTheme?.primaryColor,
|
||||||
borderBottom: `thin solid ${selectedTheme?.borderColor}`,
|
borderBottom: `thin solid ${selectedTheme?.borderColor}`,
|
||||||
backgroundColor: `color-mix(in srgb, ${selectedTheme?.searchColor}, transparent 50%)`,
|
backgroundColor: `color-mix(in srgb, ${selectedTheme?.searchColor}, transparent 50%)`,
|
||||||
fontFamily: selectedTheme.themeFont ?? currentUser.defaultFont,
|
fontFamily: selectedTheme.themeFont ?? settings.defaultFont,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
data={[
|
data={[
|
||||||
@@ -86,22 +87,6 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
|
|||||||
>
|
>
|
||||||
Applications
|
Applications
|
||||||
</Text>
|
</Text>
|
||||||
<Stack
|
|
||||||
style={{
|
|
||||||
gap: '.25rem',
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
borderBottom: '0px solid',
|
|
||||||
display: 'flex',
|
|
||||||
gridColumnGap: '5px',
|
|
||||||
gridRowGap: '5px',
|
|
||||||
gridTemplateColumns: '1fr 1fr 1fr',
|
|
||||||
gridTemplateRows: '64px',
|
|
||||||
padding: 0,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignContent: 'center',
|
|
||||||
}}
|
|
||||||
w={'100%'}
|
|
||||||
>
|
|
||||||
<Group
|
<Group
|
||||||
style={{
|
style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@@ -140,7 +125,6 @@ export function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
|
||||||
<Stack gap={0} w={'100%'}>
|
<Stack gap={0} w={'100%'}>
|
||||||
<Text
|
<Text
|
||||||
className={textClasses.sectionTitle}
|
className={textClasses.sectionTitle}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function ThemeOptions({
|
|||||||
duplicate: boolean;
|
duplicate: boolean;
|
||||||
randomColor: (withAlpha?: boolean) => string;
|
randomColor: (withAlpha?: boolean) => string;
|
||||||
}) {
|
}) {
|
||||||
const { currentUser } = useCommonHooks();
|
const { settings } = useCommonHooks();
|
||||||
return (
|
return (
|
||||||
<Stack h={'100%'} justify='space-between' align='center' gap={4}>
|
<Stack h={'100%'} justify='space-between' align='center' gap={4}>
|
||||||
<Stack w={'100%'} align='center' gap={0} h={'min-content'}>
|
<Stack w={'100%'} align='center' gap={0} h={'min-content'}>
|
||||||
@@ -137,7 +137,7 @@ export default function ThemeOptions({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCustomFontEnabled(!customFontEnabled);
|
setCustomFontEnabled(!customFontEnabled);
|
||||||
setFont(
|
setFont(
|
||||||
customFontEnabled ? currentUser.defaultFont : defaultTheme.themeFont,
|
customFontEnabled ? settings.defaultFont : defaultTheme.themeFont,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
@@ -163,7 +163,7 @@ export default function ThemeOptions({
|
|||||||
autoLoad
|
autoLoad
|
||||||
defaultValue={
|
defaultValue={
|
||||||
designerTheme.themeFont === null
|
designerTheme.themeFont === null
|
||||||
? currentUser.defaultFont
|
? settings.defaultFont
|
||||||
: designerTheme.themeFont
|
: designerTheme.themeFont
|
||||||
}
|
}
|
||||||
value={(pickerFont: string) => {
|
value={(pickerFont: string) => {
|
||||||
@@ -183,7 +183,7 @@ export default function ThemeOptions({
|
|||||||
color={'red'}
|
color={'red'}
|
||||||
size={32}
|
size={32}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFont(currentUser.defaultFont);
|
setFont(settings.defaultFont);
|
||||||
setCustomFontEnabled(false);
|
setCustomFontEnabled(false);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ export function ThemePreviewMiddleware({
|
|||||||
bg={'var(--mantine-color-primary-0)'}
|
bg={'var(--mantine-color-primary-0)'}
|
||||||
p={0}
|
p={0}
|
||||||
style={{ border: 'none' }}
|
style={{ border: 'none' }}
|
||||||
|
onClick={() => {
|
||||||
|
pressed && setPressed(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Overlay
|
<Overlay
|
||||||
w={'calc(300%)'}
|
w={'calc(300%)'}
|
||||||
|
|||||||
@@ -24,15 +24,37 @@ import { ThemeCard } from './ThemeCard';
|
|||||||
import { ThemeModal } from './ThemeModal';
|
import { ThemeModal } from './ThemeModal';
|
||||||
|
|
||||||
export function Themes() {
|
export function Themes() {
|
||||||
|
const { settings, updateSetting, theme, isMobile } = useCommonHooks();
|
||||||
const backgroundsEndpoint = 'api/backgrounds';
|
const backgroundsEndpoint = 'api/backgrounds';
|
||||||
const { data: allThemeData, isFetching: allThemeDataIsFetching } = useQueryAPI({
|
const { data: allThemeData, isFetching: allThemeDataIsFetching } = useQueryAPI({
|
||||||
endpoint: 'api/themes',
|
endpoint: 'api/themes',
|
||||||
});
|
});
|
||||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const allThemes: Theme[] = allThemeData?.filter((a: Theme) =>
|
const allThemes: Theme[] = allThemeData
|
||||||
|
?.filter((a: Theme) =>
|
||||||
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
||||||
);
|
)
|
||||||
|
.sort((a: Theme, b: Theme) => {
|
||||||
|
switch (settings.themesSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? 1 : -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
const { data: backgroundData, isFetching: backgroundDataIsFetching } = useQueryAPI({
|
const { data: backgroundData, isFetching: backgroundDataIsFetching } = useQueryAPI({
|
||||||
endpoint: backgroundsEndpoint,
|
endpoint: backgroundsEndpoint,
|
||||||
});
|
});
|
||||||
@@ -62,16 +84,6 @@ export function Themes() {
|
|||||||
),
|
),
|
||||||
] as string[];
|
] as string[];
|
||||||
|
|
||||||
const {
|
|
||||||
currentTheme,
|
|
||||||
currentUser,
|
|
||||||
setCurrentTheme,
|
|
||||||
theme,
|
|
||||||
isMobile,
|
|
||||||
currentThemePlaylist,
|
|
||||||
setCurrentThemePlaylist,
|
|
||||||
} = useCommonHooks();
|
|
||||||
|
|
||||||
const [selectedTheme, setSelectedTheme] = useState<Theme>({
|
const [selectedTheme, setSelectedTheme] = useState<Theme>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: 'default',
|
name: 'default',
|
||||||
@@ -84,7 +96,7 @@ export function Themes() {
|
|||||||
randomEnabled: 1,
|
randomEnabled: 1,
|
||||||
textShadowsEnabled: 0,
|
textShadowsEnabled: 0,
|
||||||
appShadowsEnabled: 0,
|
appShadowsEnabled: 0,
|
||||||
themeFont: currentUser.defaultFont,
|
themeFont: settings.defaultFont,
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isNew, setIsNew] = useState(false);
|
const [isNew, setIsNew] = useState(false);
|
||||||
@@ -139,6 +151,11 @@ export function Themes() {
|
|||||||
searchPlaceholder={'Search Themes...'}
|
searchPlaceholder={'Search Themes...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.themesSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('themesSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack mt={isMobile ? '11rem' : '7rem'} align='center'>
|
<Stack mt={isMobile ? '11rem' : '7rem'} align='center'>
|
||||||
<LoadingOverlay
|
<LoadingOverlay
|
||||||
@@ -159,7 +176,7 @@ export function Themes() {
|
|||||||
>
|
>
|
||||||
<Overlay
|
<Overlay
|
||||||
display={
|
display={
|
||||||
currentTheme === 'random' && debouncedSearch === '' ? 'flex' : 'none'
|
settings.theme === 'random' && debouncedSearch === '' ? 'flex' : 'none'
|
||||||
}
|
}
|
||||||
blur={5}
|
blur={5}
|
||||||
style={{
|
style={{
|
||||||
@@ -174,7 +191,7 @@ export function Themes() {
|
|||||||
{allThemes?.length > 0 ? (
|
{allThemes?.length > 0 ? (
|
||||||
allThemes.map((cardTheme: Theme) => (
|
allThemes.map((cardTheme: Theme) => (
|
||||||
<Group key={cardTheme.name} pos={'relative'}>
|
<Group key={cardTheme.name} pos={'relative'}>
|
||||||
{cardTheme.name === currentTheme && (
|
{cardTheme.name === settings.theme && (
|
||||||
<FiChevronRight
|
<FiChevronRight
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -194,11 +211,12 @@ export function Themes() {
|
|||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
zIndex:
|
zIndex:
|
||||||
currentTheme === 'random' &&
|
settings.theme === 'random' &&
|
||||||
(themePlaylists
|
(themePlaylists
|
||||||
.find(
|
.find(
|
||||||
(t: ThemePlaylist) =>
|
(t: ThemePlaylist) =>
|
||||||
t.name.toLowerCase() === currentThemePlaylist.toLowerCase(),
|
t.name.toLowerCase() ===
|
||||||
|
settings.currentThemePlaylist.toLowerCase(),
|
||||||
)
|
)
|
||||||
?.themeList.includes(`${cardTheme.id}`) ||
|
?.themeList.includes(`${cardTheme.id}`) ||
|
||||||
cardTheme.name === theme.name)
|
cardTheme.name === theme.name)
|
||||||
@@ -285,7 +303,9 @@ export function Themes() {
|
|||||||
fontSize: isMobile ? '.8rem' : '1.2rem',
|
fontSize: isMobile ? '.8rem' : '1.2rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentThemePlaylist === 'All' ? 'All' : currentThemePlaylist}
|
{settings.currentThemePlaylist === 'All'
|
||||||
|
? 'All'
|
||||||
|
: settings.currentThemePlaylist}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Divider
|
<Divider
|
||||||
@@ -295,11 +315,13 @@ export function Themes() {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentThemePlaylist === 'All') {
|
if (settings.currentThemePlaylist === 'All') {
|
||||||
navigate(`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`);
|
void navigate(
|
||||||
|
`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setCurrentThemePlaylist('All');
|
updateSetting('currentThemePlaylist', 'All');
|
||||||
setCurrentTheme(theme.name);
|
updateSetting('theme', theme.name);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
@@ -307,7 +329,7 @@ export function Themes() {
|
|||||||
padding: isMobile ? 0 : undefined,
|
padding: isMobile ? 0 : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentThemePlaylist === 'All' ? (
|
{settings.currentThemePlaylist === 'All' ? (
|
||||||
isMobile ? (
|
isMobile ? (
|
||||||
<FiMousePointer />
|
<FiMousePointer />
|
||||||
) : (
|
) : (
|
||||||
@@ -323,11 +345,11 @@ export function Themes() {
|
|||||||
className={toolTipClasses.default}
|
className={toolTipClasses.default}
|
||||||
classNames={toolTipClasses}
|
classNames={toolTipClasses}
|
||||||
label='Shuffle is always on when a playlist is active.'
|
label='Shuffle is always on when a playlist is active.'
|
||||||
disabled={currentThemePlaylist === 'All'}
|
disabled={settings.currentThemePlaylist === 'All'}
|
||||||
>
|
>
|
||||||
<Group>
|
<Group>
|
||||||
<Switch
|
<Switch
|
||||||
disabled={currentThemePlaylist !== 'All'}
|
disabled={settings.currentThemePlaylist !== 'All'}
|
||||||
label={
|
label={
|
||||||
isMobile ? (
|
isMobile ? (
|
||||||
<FaDice size={20} color={'var(--mantine-color-primary-0)'} />
|
<FaDice size={20} color={'var(--mantine-color-primary-0)'} />
|
||||||
@@ -336,10 +358,10 @@ export function Themes() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
labelPosition='left'
|
labelPosition='left'
|
||||||
checked={currentTheme === 'random'}
|
checked={settings.theme === 'random'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentTheme(e.target.checked ? 'random' : theme.name);
|
updateSetting('theme', e.target.checked ? 'random' : theme.name);
|
||||||
setCurrentThemePlaylist('All');
|
updateSetting('currentThemePlaylist', 'All');
|
||||||
}}
|
}}
|
||||||
className={switchClasses.default}
|
className={switchClasses.default}
|
||||||
classNames={switchClasses}
|
classNames={switchClasses}
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ function DebouncedWolClient({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debounced && Object.keys(debounced).length) {
|
if (debounced && Object.keys(debounced).length) {
|
||||||
mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced });
|
mutateAsync({ method: 'PATCH', endpoint: 'api/WolClients', body: debounced }).catch(
|
||||||
|
(error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [debounced]);
|
}, [debounced]);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import DebouncedWolClient from './DebouncedWolClient';
|
|||||||
import WolClientSettingsItem from './WolClientSettingsItem';
|
import WolClientSettingsItem from './WolClientSettingsItem';
|
||||||
|
|
||||||
export function WolClients() {
|
export function WolClients() {
|
||||||
|
const { isMobile, theme, settings, updateSetting } = useCommonHooks();
|
||||||
const defaultWolClient: WolClient = {
|
const defaultWolClient: WolClient = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
hostname: '',
|
hostname: '',
|
||||||
@@ -41,13 +42,37 @@ export function WolClients() {
|
|||||||
const [searchValue, setSearchValue] = useState<string | number>('');
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||||
const wolClients: WolClient[] =
|
const wolClients: WolClient[] =
|
||||||
wolClientsData.filter(
|
wolClientsData
|
||||||
|
?.filter(
|
||||||
(a: WolClient) =>
|
(a: WolClient) =>
|
||||||
a.hostname.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
a.hostname.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
||||||
a.macAddress.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
a.macAddress.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
|
||||||
) || [];
|
)
|
||||||
|
?.sort((a: WolClient, b: WolClient) => {
|
||||||
|
switch (settings.wolClientsSettingsSort) {
|
||||||
|
case 'A>Z':
|
||||||
|
return a.hostname.toLocaleLowerCase() > b.hostname.toLocaleLowerCase()
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
case 'Z>A':
|
||||||
|
return a.hostname.toLocaleLowerCase() < b.hostname.toLocaleLowerCase()
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
|
case 'Newest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id < b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 'Oldest':
|
||||||
|
if (a.id && b.id) {
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}) || [];
|
||||||
const { mutateAsync } = useMutationAPI();
|
const { mutateAsync } = useMutationAPI();
|
||||||
const { isMobile, theme } = useCommonHooks();
|
|
||||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -77,6 +102,8 @@ export function WolClients() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
body: values,
|
body: values,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})}
|
})}
|
||||||
@@ -151,6 +178,11 @@ export function WolClients() {
|
|||||||
searchPlaceholder={'Search Clients...'}
|
searchPlaceholder={'Search Clients...'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
|
sortBy={settings.wolClientsSettingsSort}
|
||||||
|
setSortBy={(value) => {
|
||||||
|
updateSetting('wolClientsSettingsSort', value);
|
||||||
|
}}
|
||||||
|
customSearchEnabled={false}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
mt={isMobile ? '10rem' : '6rem'}
|
mt={isMobile ? '10rem' : '6rem'}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type { Message } from './message';
|
|||||||
export type { PersonalData } from './personalData';
|
export type { PersonalData } from './personalData';
|
||||||
export type { Provider } from './provider';
|
export type { Provider } from './provider';
|
||||||
export type { SearchHistoryItem, SearchShortcut } from './search';
|
export type { SearchHistoryItem, SearchShortcut } from './search';
|
||||||
|
export type { Settings } from './settings';
|
||||||
export type { Theme, ThemePlaylist } from './theme';
|
export type { Theme, ThemePlaylist } from './theme';
|
||||||
export { ThemeProperties } from './theme';
|
export { ThemeProperties } from './theme';
|
||||||
export type { User } from './user';
|
export type { User } from './user';
|
||||||
|
|||||||
35
src/types/settings.ts
Normal file
35
src/types/settings.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
export interface Settings {
|
||||||
|
theme: string;
|
||||||
|
appFilter: string[];
|
||||||
|
appSort: string;
|
||||||
|
appDensity: number;
|
||||||
|
appScale: number;
|
||||||
|
bookmarkSort: string;
|
||||||
|
bookmarkFilter: string[];
|
||||||
|
bookmarkDensity: number;
|
||||||
|
bookmarkScale: number;
|
||||||
|
currentThemePlaylist: string;
|
||||||
|
defaultProviderLabel: string;
|
||||||
|
defaultProviderURL: string;
|
||||||
|
defaultProviderPrefix: string;
|
||||||
|
defaultProviderIcon: string;
|
||||||
|
timeFormat: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
physicalAddress: string;
|
||||||
|
enableWol: number;
|
||||||
|
enableGuacamole: number;
|
||||||
|
guacamoleHost: string;
|
||||||
|
nickname: string;
|
||||||
|
defaultFont: string;
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
messageSettingsSort: string;
|
||||||
|
appSettingsSort: string;
|
||||||
|
bookmarkSettingsSort: string;
|
||||||
|
themesSettingsSort: string;
|
||||||
|
providerSettingsSort: string;
|
||||||
|
wolClientsSettingsSort: string;
|
||||||
|
guacamoleClientsSettingsSort: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user