Updates to the stack for performance
All checks were successful
Deploy to production / build (push) Successful in 18s

This commit is contained in:
2026-10-12 16:04:09 -05:00
parent ea56aa8402
commit bd9e77c382
3 changed files with 256 additions and 188 deletions

View File

@@ -119,8 +119,6 @@ sudo pm2 start server.cjs
- New Carosel on weather description - New Carosel on weather description
- Updates to weather modal - 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)

View File

@@ -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 { ThemeCardPreview } from '../../Themes/ThemeCard'; 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 { setThemeName, setCurrentThemePlaylist, handleDelete, currentUser } =
useCommonHooks(); useCommonHooks();
const [indexOffset, setIndexOffset] = useState(0); const [indexOffset, setIndexOffset] = useState(0);
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 missingTheme = { const missingTheme = {
id: undefined, id: undefined,
name: 'default', name: 'default',
@@ -43,6 +42,12 @@ export function ThemePlaylistStack({
appShadowsEnabled: 0, appShadowsEnabled: 0,
themeFont: currentUser.defaultFont, themeFont: currentUser.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,196 +67,258 @@ export function ThemePlaylistStack({
} }
}, 75); }, 75);
return ( const sortedItems = useMemo(
<Reflection reflectionStart={75} reflectionEnd={100} shadowOpacity={0.25}> () =>
<Stack (themeList.length > 0 &&
gap={0} themeList.sort((a: number, b: number) => (a > b ? 1 : -1))) ||
w={300} [],
pos={'relative'} [themeList],
onMouseEnter={() => setHover(true)} );
onMouseLeave={() => setHover(false)}
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 (
<Box
key={theme.id}
style={{ style={{
background: hover transition: 'all 0.3s ease-in-out',
? 'radial-gradient(var(--mantine-color-search-0), transparent)' position: 'absolute',
: undefined, top: 16 * (spot + 1),
cursor: 'pointer', zIndex: themeList.length - spot,
borderRadius: '100%',
}}
onClick={() => {
setCurrentThemePlaylist(cardPlaylist.name);
setThemeName('random');
}} }}
> >
{modalOpened && ( <Parallax {...presentationProps}>
<DeleteItemModal <ThemeCardPreview key={theme.id} cardTheme={theme} />
modalOpened={modalOpened} </Parallax>
handleClose={closeModal} </Box>
handleDelete={() => handleDelete(cardPlaylist, 'api/themePlaylists')} );
title='Delete Theme Playlist' });
body={
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text>Are you sure you want to delete this Theme Playlist?</Text>
<Text
className={textClasses.deleteItem}
classNames={textClasses}
>{`"${cardPlaylist.name}"`}</Text>
<Text c='red'>This action cannot be undone!</Text>
</Stack>
}
/>
)}
{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 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find((theme: Theme) => theme.id === Number(themeList[0])) ||
missingTheme
}
/>
</Parallax>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: -60, zIndex: 2 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find((theme: Theme) => theme.id === Number(themeList[1])) ||
missingTheme
}
/>
</Parallax>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: -60, zIndex: 1 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find((theme: Theme) => theme.id === Number(themeList[2])) ||
missingTheme
}
/>
</Parallax>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: -60, zIndex: 0 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find((theme: Theme) => theme.id === Number(themeList[3])) ||
missingTheme
}
/>
</Parallax>
</Stack>
) : (
<Stack
w='100%'
h='200px'
justify='start'
pos='relative'
gap={0}
style={{
overflow: 'hidden',
transition: 'all 0.5s ease-in-out',
overscrollBehavior: 'contain',
}}
onWheel={handleMouseWheel}
>
{themeList
.sort((a: number, b: number) => (a > b ? 1 : -1))
.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 (
<Box
key={theme.id}
style={{
transition: 'all 0.3s ease-in-out',
position: 'absolute',
top: 16 * (spot + 1),
zIndex: themeList.length - spot,
}}
>
<Parallax {...presentationProps}>
<ThemeCardPreview key={theme.id} cardTheme={theme} />
</Parallax>
</Box>
);
})}
</Stack>
)}
<Group useEffect(() => {
w={'100%'} if (cardPlaylist.name === 'Time of Day') {
pos={'absolute'} setTopTheme(
justify='space-between' allThemes.find(
bottom={0} (t: Theme) => t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]),
style={{ zIndex: 1000 }} ) || missingTheme,
> );
<Text } else if (indexOffset > 0 && indexOffset < themeList.length) {
setTopTheme(
allThemes.find(
(theme: Theme) => theme.id === themeList[themeList.length - indexOffset],
) || missingTheme,
);
} else if (indexOffset < 0 && Math.abs(indexOffset) < themeList.length) {
setTopTheme(
allThemes.find((theme: Theme) => theme.id === themeList[Math.abs(indexOffset)]) ||
missingTheme,
);
} else if (indexOffset === 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>
<Reflection reflectionStart={75} reflectionEnd={100} shadowOpacity={0.25}>
<Stack
gap={0}
w={300}
pos={'relative'}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{ style={{
paddingRight: '.25rem', background: hover
paddingLeft: '.25rem', ? 'radial-gradient(var(--mantine-color-search-0), transparent)'
backgroundColor: 'var(--mantine-color-border-0)', : undefined,
borderRadius: '4px', cursor: 'pointer',
color: 'var(--mantine-color-primary-0)', borderRadius: '100%',
}}
onClick={() => {
setCurrentThemePlaylist(cardPlaylist.name);
setThemeName('random');
}} }}
> >
{cardPlaylist.name} {modalOpened && (
</Text> <DeleteItemModal
<Group gap={0}> modalOpened={modalOpened}
{!defaultLists.includes(cardPlaylist.name) && ( handleClose={closeModal}
<ActionIcon handleDelete={() => handleDelete(cardPlaylist, 'api/themePlaylists')}
title='Delete Theme Playlist'
body={
<Stack gap={0} w={'100%'} h={'150px'} align='center'>
<Text>Are you sure you want to delete this Theme Playlist?</Text>
<Text
className={textClasses.deleteItem}
classNames={textClasses}
>{`"${cardPlaylist.name}"`}</Text>
<Text c='red'>This action cannot be undone!</Text>
</Stack>
}
/>
)}
<Stack
w='100%'
h='200px'
justify='start'
pos='relative'
gap={0}
style={{
overflow: 'hidden',
transition: 'all 0.5s ease-in-out',
overscrollBehavior: 'contain',
}}
onWheel={handleMouseWheel}
>
{cardPlaylist?.name === 'Time of Day' ? (
<>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: 16 * 1, zIndex: 3 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find(
(theme: Theme) => theme.id === Number(themeList[0]),
) || missingTheme
}
/>
</Parallax>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: 16 * 2, zIndex: 2 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find(
(theme: Theme) => theme.id === Number(themeList[1]),
) || missingTheme
}
/>
</Parallax>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: 16 * 3, zIndex: 1 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find(
(theme: Theme) => theme.id === Number(themeList[2]),
) || missingTheme
}
/>
</Parallax>
<Parallax
{...presentationProps}
style={{ position: 'absolute', top: 16 * 4, zIndex: 0 }}
>
<ThemeCardPreview
cardTheme={
allThemes.find(
(theme: Theme) => theme.id === Number(themeList[3]),
) || missingTheme
}
/>
</Parallax>
</>
) : (
items
)}
</Stack>
<Group
w={'100%'}
pos={'absolute'}
justify='space-between'
bottom={0}
style={{ zIndex: 1000 }}
>
<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: '4px',
color: 'var(--mantine-color-background-0)', color: 'var(--mantine-color-primary-0)',
}}
onClick={() => {
handleOpen(cardPlaylist);
}} }}
> >
<FiEdit /> {cardPlaylist.name}
</ActionIcon> </Text>
)} <Group gap={0}>
{cardPlaylist.default === 0 && ( {!defaultLists.includes(cardPlaylist.name) && (
<ActionIcon <ActionIcon
color='red' style={{
style={{ paddingRight: '.25rem',
paddingRight: '.25rem', paddingLeft: '.25rem',
paddingLeft: '.25rem', backgroundColor: 'var(--mantine-color-border-0)',
borderRadius: '4px', borderRadius: '4px',
}} color: 'var(--mantine-color-background-0)',
onClick={() => { }}
openModal(); onClick={() => {
}} handleOpen(cardPlaylist);
> }}
<FiTrash color='black' /> >
</ActionIcon> <FiEdit />
)} </ActionIcon>
</Group> )}
</Group> {cardPlaylist.default === 0 && (
</Stack> <ActionIcon
</Reflection> color='red'
style={{
paddingRight: '.25rem',
paddingLeft: '.25rem',
borderRadius: '4px',
}}
onClick={() => {
openModal();
}}
>
<FiTrash color='black' />
</ActionIcon>
)}
</Group>
</Group>
</Stack>
</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>
); );
} }

View File

@@ -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%)'}