fix and update remote design

This commit is contained in:
jeffvli 2025-06-24 14:36:14 -07:00
parent ad533a1d9c
commit 6689e84f67
24 changed files with 326 additions and 453 deletions

View file

@ -1,24 +1,16 @@
import { Image, Title } from '@mantine/core';
import formatDuration from 'format-duration';
import debounce from 'lodash/debounce';
import { useCallback } from 'react';
import {
RiHeartLine,
RiPauseFill,
RiPlayFill,
RiRepeat2Line,
RiRepeatOneLine,
RiShuffleFill,
RiSkipBackFill,
RiSkipForwardFill,
RiVolumeUpFill,
} from 'react-icons/ri';
import { RiPauseFill, RiPlayFill, RiVolumeUpFill } from 'react-icons/ri';
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
import { PlayerImage } from '/@/remote/components/player-image';
import { WrappedSlider } from '/@/remote/components/wrapped-slider';
import { useInfo, useSend, useShowImage } from '/@/remote/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group';
import { Rating } from '/@/shared/components/rating/rating';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
@ -40,40 +32,115 @@ export const RemoteContainer = () => {
const debouncedSetRating = debounce(setRating, 400);
return (
<>
<Stack
gap="md"
h="100dvh"
w="100%"
>
{showImage && (
<Flex
align="center"
justify="center"
w="100%"
>
<PlayerImage src={song?.imageUrl} />
</Flex>
)}
{id && (
<>
<Title order={1}>{song.name}</Title>
<Group align="flex-end">
<Title order={2}>Album: {song.album}</Title>
<Title order={2}>Artist: {song.artistName}</Title>
</Group>
<Stack gap="xs">
<Text
fw={700}
size="xl"
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{song.name}
</Text>
<Text
isMuted
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{song.album}
</Text>
<Text
isMuted
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{song.artistName}
</Text>
<Group justify="space-between">
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
{song.releaseDate && (
<Title order={3}>
Released: {new Date(song.releaseDate).toLocaleDateString()}
</Title>
<Text isMuted>{new Date(song.releaseDate).toLocaleDateString()}</Text>
)}
<Title order={3}>Plays: {song.playCount}</Title>
<Text isMuted>Plays: {song.playCount}</Text>
</Group>
</>
</Stack>
)}
<Group
gap={0}
grow
>
<RemoteButton
<ActionIcon
disabled={!id}
icon="favorite"
iconProps={{
fill: song?.userFavorite ? 'primary' : 'default',
}}
onClick={() => {
if (!id) return;
send({ event: 'favorite', favorite: !song.userFavorite, id });
}}
tooltip={{
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
}}
variant="transparent"
/>
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
<div style={{ margin: 'auto' }}>
<Tooltip
label="Double click to clear"
openDelay={1000}
>
<Rating
onChange={debouncedSetRating}
onDoubleClick={() => debouncedSetRating(0)}
style={{ margin: 'auto' }}
value={song.userRating ?? 0}
/>
</Tooltip>
</div>
)}
</Group>
<Group
gap="xs"
grow
>
<ActionIcon
disabled={!id}
icon="mediaPrevious"
iconProps={{
fill: 'default',
size: 'lg',
}}
onClick={() => send({ event: 'previous' })}
tooltip={{
label: 'Previous track',
}}
variant="default"
>
<RiSkipBackFill size={25} />
</RemoteButton>
<RemoteButton
/>
<ActionIcon
disabled={!id}
onClick={() => {
if (status === PlayerStatus.PLAYING) {
@ -92,34 +159,50 @@ export const RemoteContainer = () => {
) : (
<RiPlayFill size={25} />
)}
</RemoteButton>
<RemoteButton
</ActionIcon>
<ActionIcon
disabled={!id}
icon="mediaNext"
iconProps={{
fill: 'default',
size: 'lg',
}}
onClick={() => send({ event: 'next' })}
tooltip={{
label: 'Next track',
}}
variant="default"
>
<RiSkipForwardFill size={25} />
</RemoteButton>
/>
</Group>
<Group
gap={0}
gap="xs"
grow
>
<RemoteButton
isActive={shuffle || false}
<ActionIcon
icon="mediaShuffle"
iconProps={{
fill: shuffle ? 'primary' : 'default',
size: 'lg',
}}
onClick={() => send({ event: 'shuffle' })}
tooltip={{
label: shuffle ? 'Shuffle tracks' : 'Shuffle disabled',
}}
variant="default"
>
<RiShuffleFill size={25} />
</RemoteButton>
<RemoteButton
isActive={repeat !== undefined && repeat !== PlayerRepeat.NONE}
/>
<ActionIcon
icon={
repeat === undefined || repeat === PlayerRepeat.ONE
? 'mediaRepeatOne'
: 'mediaRepeat'
}
iconProps={{
fill:
repeat !== undefined && repeat !== PlayerRepeat.NONE
? 'primary'
: 'default',
size: 'lg',
}}
onClick={() => send({ event: 'repeat' })}
tooltip={{
label: `Repeat ${
@ -131,74 +214,34 @@ export const RemoteContainer = () => {
}`,
}}
variant="default"
>
{repeat === undefined || repeat === PlayerRepeat.ONE ? (
<RiRepeatOneLine size={25} />
) : (
<RiRepeat2Line size={25} />
)}
</RemoteButton>
<RemoteButton
disabled={!id}
isActive={song?.userFavorite}
onClick={() => {
if (!id) return;
send({ event: 'favorite', favorite: !song.userFavorite, id });
}}
tooltip={{
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
}}
variant="default"
>
<RiHeartLine size={25} />
</RemoteButton>
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
<div style={{ margin: 'auto' }}>
<Tooltip
label="Double click to clear"
openDelay={1000}
>
<Rating
onChange={debouncedSetRating}
onDoubleClick={() => debouncedSetRating(0)}
style={{ margin: 'auto' }}
value={song.userRating ?? 0}
/>
</Tooltip>
</div>
)}
/>
</Group>
{id && position !== undefined && (
<WrapperSlider
label={(value) => formatDuration(value * 1e3)}
leftLabel={formatDuration(position * 1e3)}
max={song.duration / 1e3}
onChangeEnd={(e) => send({ event: 'position', position: e })}
rightLabel={formatDuration(song.duration)}
value={position}
<Stack gap="lg">
{id && position !== undefined && (
<WrappedSlider
label={(value) => formatDuration(value * 1e3)}
leftLabel={formatDuration(position * 1e3)}
max={song.duration / 1e3}
onChangeEnd={(e) => send({ event: 'position', position: e })}
rightLabel={formatDuration(song.duration)}
value={position}
/>
)}
<WrappedSlider
leftLabel={<RiVolumeUpFill size={20} />}
max={100}
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
rightLabel={
<Text
fw={600}
size="xs"
>
{volume ?? 0}
</Text>
}
value={volume ?? 0}
/>
)}
<WrapperSlider
leftLabel={<RiVolumeUpFill size={20} />}
max={100}
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
rightLabel={
<Text
fw={600}
size="xs"
>
{volume ?? 0}
</Text>
}
value={volume ?? 0}
/>
{showImage && (
<Image
onError={() => send({ event: 'proxy' })}
src={song?.imageUrl?.replaceAll(/&(size|width|height=\d+)/g, '')}
/>
)}
</>
</Stack>
</Stack>
);
};