Migrate to Mantine v8 and Design Changes (#961)

* mantine v8 migration

* various design changes and improvements
This commit is contained in:
Jeff 2025-06-24 00:04:36 -07:00 committed by GitHub
parent bea55d48a8
commit c1330d92b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
473 changed files with 12469 additions and 11607 deletions

View file

@ -1,68 +1,27 @@
import { Center, Flex, Group, Stack } from '@mantine/core';
import { useSetState } from '@mantine/hooks';
import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'framer-motion';
import clsx from 'clsx';
import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'motion/react';
import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { RiAlbumFill } from 'react-icons/ri';
import { generatePath } from 'react-router';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Badge, Text, TextTitle } from '/@/renderer/components';
import styles from './full-screen-player-image.module.css';
import { useFastAverageColor } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes';
import { useFullScreenPlayerStore, usePlayerData, usePlayerStore } from '/@/renderer/store';
import { usePlayerData, usePlayerStore } from '/@/renderer/store';
import { useSettingsStore } from '/@/renderer/store/settings.store';
import { Badge } from '/@/shared/components/badge/badge';
import { Center } from '/@/shared/components/center/center';
import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { Image } from '/@/shared/components/image/image';
import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text';
import { PlayerData, QueueSong } from '/@/shared/types/domain-types';
const Image = styled(motion.img)<any>`
position: absolute;
max-width: 100%;
height: 100%;
object-fit: ${({ $useAspectRatio }) => ($useAspectRatio ? 'contain' : 'cover')};
object-position: 50% 100%;
filter: drop-shadow(0 0 5px rgb(0 0 0 / 40%)) drop-shadow(0 0 5px rgb(0 0 0 / 40%));
border-radius: 5px;
`;
const ImageContainer = styled(motion.div)`
position: relative;
display: flex;
align-items: flex-end;
justify-content: center;
max-width: 100%;
height: 65%;
aspect-ratio: 1/1;
margin-bottom: 1rem;
`;
interface TransparentMetadataContainer {
opacity?: number;
}
const MetadataContainer = styled(Stack)<TransparentMetadataContainer>`
padding: 1rem;
border-radius: 5px;
h1 {
font-size: 3.5vh;
}
`;
const PlayerContainer = styled(Flex)`
@media screen and (height <= 640px) {
.full-screen-player-image-metadata {
display: none;
height: 100%;
margin-bottom: 0;
}
${ImageContainer} {
height: 100%;
margin-bottom: 0;
}
}
`;
const imageVariants: Variants = {
closed: {
opacity: 0,
@ -93,22 +52,22 @@ const scaleImageUrl = (imageSize: number, url?: null | string) => {
.replace(/&height=\d+/, `&height=${imageSize}`);
};
const ImageWithPlaceholder = ({
useAspectRatio,
...props
}: HTMLMotionProps<'img'> & { placeholder?: string; useAspectRatio: boolean }) => {
const MotionImage = motion.create(Image);
const ImageWithPlaceholder = ({ ...props }: HTMLMotionProps<'img'> & { placeholder?: string }) => {
if (!props.src) {
return (
<Center
sx={{
background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)',
style={{
background: 'var(--theme-colors-surface)',
borderRadius: 'var(--theme-card-default-radius)',
height: '100%',
width: '100%',
}}
>
<RiAlbumFill
color="var(--placeholder-fg)"
<Icon
color="muted"
icon="itemAlbum"
size="25%"
/>
</Center>
@ -116,8 +75,8 @@ const ImageWithPlaceholder = ({
}
return (
<Image
$useAspectRatio={useAspectRatio}
<MotionImage
className={styles.image}
{...props}
/>
);
@ -130,7 +89,6 @@ export const FullScreenPlayerImage = () => {
const albumArtRes = useSettingsStore((store) => store.general.albumArtRes);
const { queue } = usePlayerData();
const { useImageAspectRatio } = useFullScreenPlayerStore();
const currentSong = queue.current;
const { background } = useFastAverageColor({
algorithm: 'dominant',
@ -195,14 +153,17 @@ export const FullScreenPlayerImage = () => {
}, [imageState, mainImageDimensions.idealSize, queue, setImageState]);
return (
<PlayerContainer
<Flex
align="center"
className="full-screen-player-image-container"
className={clsx(styles.playerContainer, 'full-screen-player-image-container')}
direction="column"
justify="flex-start"
p="1rem"
>
<ImageContainer ref={mainImageRef}>
<div
className={styles.imageContainer}
ref={mainImageRef}
>
<AnimatePresence
initial={false}
mode="sync"
@ -216,9 +177,8 @@ export const FullScreenPlayerImage = () => {
exit="closed"
initial="closed"
key={imageKey}
placeholder="var(--placeholder-bg)"
placeholder="var(--theme-colors-foreground-muted)"
src={imageState.topImage || ''}
useAspectRatio={useImageAspectRatio}
variants={imageVariants}
/>
)}
@ -232,62 +192,55 @@ export const FullScreenPlayerImage = () => {
exit="closed"
initial="closed"
key={imageKey}
placeholder="var(--placeholder-bg)"
placeholder="var(--theme-colors-foreground-muted)"
src={imageState.bottomImage || ''}
useAspectRatio={useImageAspectRatio}
variants={imageVariants}
/>
)}
</AnimatePresence>
</ImageContainer>
<MetadataContainer
className="full-screen-player-image-metadata"
</div>
<Stack
className={styles.metadataContainer}
gap="xs"
maw="100%"
spacing="xs"
>
<TextTitle
align="center"
fw={900}
order={1}
overflow="hidden"
style={{
textShadow: 'var(--fullscreen-player-text-shadow)',
}}
w="100%"
weight={900}
>
{currentSong?.name}
</TextTitle>
<TextTitle
$link
align="center"
component={Link}
fw={600}
isLink
order={3}
overflow="hidden"
style={{
textShadow: 'var(--fullscreen-player-text-shadow)',
textShadow: 'var(--theme-fullscreen-player-text-shadow)',
}}
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
albumId: currentSong?.albumId || '',
})}
w="100%"
weight={600}
>
{currentSong?.album}{' '}
</TextTitle>
<TextTitle
align="center"
key="fs-artists"
order={3}
style={{
textShadow: 'var(--fullscreen-player-text-shadow)',
textShadow: 'var(--theme-fullscreen-player-text-shadow)',
}}
>
{currentSong?.artists?.map((artist, index) => (
<Fragment key={`fs-artist-${artist.id}`}>
{index > 0 && (
<Text
$secondary
sx={{
isMuted
style={{
display: 'inline-block',
padding: '0 0.5rem',
}}
@ -296,16 +249,16 @@ export const FullScreenPlayerImage = () => {
</Text>
)}
<Text
$link
$secondary
component={Link}
fw={600}
isLink
isMuted
style={{
textShadow: 'var(--fullscreen-player-text-shadow)',
textShadow: 'var(--theme-fullscreen-player-text-shadow)',
}}
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: artist.id,
})}
weight={600}
>
{artist.name}
</Text>
@ -313,8 +266,8 @@ export const FullScreenPlayerImage = () => {
))}
</TextTitle>
<Group
justify="center"
mt="sm"
position="center"
>
{currentSong?.container && (
<Badge size="lg">
@ -325,7 +278,7 @@ export const FullScreenPlayerImage = () => {
<Badge size="lg">{currentSong?.releaseYear}</Badge>
)}
</Group>
</MetadataContainer>
</PlayerContainer>
</Stack>
</Flex>
);
};