mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
optimize library headers (#1374)
This commit is contained in:
@@ -578,10 +578,10 @@
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"addLast": "add last",
|
||||
"addNext": "add next",
|
||||
"addLastShuffled": "add last (shuffled)",
|
||||
"addNextShuffled": "add next (shuffled)",
|
||||
"addLast": "last",
|
||||
"addNext": "next",
|
||||
"addLastShuffled": "last (shuffled)",
|
||||
"addNextShuffled": "next (shuffled)",
|
||||
"holdToShuffle": "hold to shuffle",
|
||||
"favorite": "favorite",
|
||||
"lyrics": "lyrics",
|
||||
|
||||
@@ -368,8 +368,10 @@ export const AlbumDetailContent = () => {
|
||||
<div className={styles.contentContainer} ref={ref}>
|
||||
<div className={styles.detailContainer}>
|
||||
{comment && (
|
||||
<Spoiler hideLabel={true} maxHeight={32} showLabel={true}>
|
||||
{replaceURLWithHTMLLinks(comment)}
|
||||
<Spoiler maxHeight={75}>
|
||||
<Text
|
||||
dangerouslySetInnerHTML={{ __html: replaceURLWithHTMLLinks(comment) }}
|
||||
/>
|
||||
</Spoiler>
|
||||
)}
|
||||
<div className={styles.contentLayout}>
|
||||
|
||||
@@ -89,7 +89,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
|
||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||
title={detailQuery?.data?.name || ''}
|
||||
>
|
||||
<Stack gap="xl" w="100%">
|
||||
<Stack gap="md" w="100%">
|
||||
{(firstAlbumArtist || releaseYear) && (
|
||||
<Group className={styles.metadataGroup}>
|
||||
{firstAlbumArtist && (
|
||||
@@ -121,7 +121,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
|
||||
favorite={detailQuery?.data?.userFavorite}
|
||||
onFavorite={handleFavorite}
|
||||
onMore={handleMoreOptions}
|
||||
onPlay={() => handlePlay(Play.NOW)}
|
||||
onPlay={(type) => handlePlay(type)}
|
||||
onRating={handleUpdateRating}
|
||||
onShuffle={() => handlePlay(Play.SHUFFLE)}
|
||||
rating={detailQuery?.data?.userRating || 0}
|
||||
|
||||
@@ -211,7 +211,13 @@ const DummyAlbumDetailRoute = () => {
|
||||
)}
|
||||
{comment && (
|
||||
<section>
|
||||
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
|
||||
<Spoiler maxHeight={75}>
|
||||
<Text
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replaceURLWithHTMLLinks(comment),
|
||||
}}
|
||||
/>
|
||||
</Spoiler>
|
||||
</section>
|
||||
)}
|
||||
<section>
|
||||
|
||||
@@ -14,20 +14,14 @@ import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
|
||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||
import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel';
|
||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { DefaultPlayButton } from '/@/renderer/features/shared/components/play-button';
|
||||
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { ArtistItem, useCurrentServer, usePlayerSong } from '/@/renderer/store';
|
||||
import {
|
||||
useGeneralSettings,
|
||||
usePlayButtonBehavior,
|
||||
useSettingsStore,
|
||||
} from '/@/renderer/store/settings.store';
|
||||
import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
import { sanitize } from '/@/renderer/utils/sanitize';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
@@ -52,58 +46,35 @@ import {
|
||||
import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types';
|
||||
|
||||
interface AlbumArtistActionButtonsProps {
|
||||
albumCount: null | number | undefined;
|
||||
artistDiscographyLink: string;
|
||||
artistSongsLink: string;
|
||||
onFavorite: () => void;
|
||||
onMoreOptions: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onPlay: () => void;
|
||||
userFavorite?: boolean;
|
||||
}
|
||||
|
||||
const AlbumArtistActionButtons = ({
|
||||
albumCount,
|
||||
artistDiscographyLink,
|
||||
artistSongsLink,
|
||||
onFavorite,
|
||||
onMoreOptions,
|
||||
onPlay,
|
||||
userFavorite,
|
||||
}: AlbumArtistActionButtonsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group gap="md">
|
||||
<DefaultPlayButton disabled={albumCount === 0} onClick={onPlay} />
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
icon="favorite"
|
||||
iconProps={{
|
||||
fill: userFavorite ? 'primary' : undefined,
|
||||
}}
|
||||
onClick={onFavorite}
|
||||
size="lg"
|
||||
variant="transparent"
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="ellipsisHorizontal"
|
||||
onClick={onMoreOptions}
|
||||
size="lg"
|
||||
variant="transparent"
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group gap="md">
|
||||
<Group gap="lg">
|
||||
<Button
|
||||
component={Link}
|
||||
p={0}
|
||||
size="compact-md"
|
||||
to={artistDiscographyLink}
|
||||
variant="subtle"
|
||||
variant="transparent"
|
||||
>
|
||||
{String(t('page.albumArtistDetail.viewDiscography')).toUpperCase()}
|
||||
</Button>
|
||||
<Button component={Link} size="compact-md" to={artistSongsLink} variant="subtle">
|
||||
<Button
|
||||
component={Link}
|
||||
p={0}
|
||||
size="compact-md"
|
||||
to={artistSongsLink}
|
||||
variant="transparent"
|
||||
>
|
||||
{String(t('page.albumArtistDetail.viewAllTracks')).toUpperCase()}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -175,8 +146,8 @@ const AlbumArtistMetadataBiography = ({
|
||||
artist: artistName,
|
||||
})}
|
||||
</TextTitle>
|
||||
<Spoiler maxHeight={50}>
|
||||
<div dangerouslySetInnerHTML={{ __html: sanitizedBiography }}></div>
|
||||
<Spoiler>
|
||||
<Text dangerouslySetInnerHTML={{ __html: sanitizedBiography }} />
|
||||
</Spoiler>
|
||||
</section>
|
||||
);
|
||||
@@ -440,7 +411,6 @@ export const AlbumArtistDetailContent = () => {
|
||||
};
|
||||
const routeId = (artistId || albumArtistId) as string;
|
||||
const { ref, ...cq } = useContainerQuery();
|
||||
const { addToQueueByFetch, setFavorite } = usePlayer();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const [enabledItem, itemOrder] = useMemo(() => {
|
||||
@@ -506,21 +476,11 @@ export const AlbumArtistDetailContent = () => {
|
||||
sortBy: AlbumListSort.RELEASE_DATE,
|
||||
sortOrder: SortOrder.DESC,
|
||||
title: (
|
||||
<Group align="flex-end">
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.recentReleases', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</TextTitle>
|
||||
<Button
|
||||
component={Link}
|
||||
size="compact-md"
|
||||
to={artistDiscographyLink}
|
||||
variant="subtle"
|
||||
>
|
||||
{String(t('page.albumArtistDetail.viewDiscography')).toUpperCase()}
|
||||
</Button>
|
||||
</Group>
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.recentReleases', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</TextTitle>
|
||||
),
|
||||
uniqueId: 'recentReleases',
|
||||
},
|
||||
@@ -560,7 +520,6 @@ export const AlbumArtistDetailContent = () => {
|
||||
},
|
||||
];
|
||||
}, [
|
||||
artistDiscographyLink,
|
||||
detailQuery.data?.similarArtists,
|
||||
enabledItem.compilations,
|
||||
enabledItem.recentAlbums,
|
||||
@@ -573,37 +532,6 @@ export const AlbumArtistDetailContent = () => {
|
||||
t,
|
||||
]);
|
||||
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const handlePlay = async (playType?: Play) => {
|
||||
if (!server?.id) return;
|
||||
addToQueueByFetch(
|
||||
server.id,
|
||||
[routeId],
|
||||
albumArtistId ? LibraryItem.ALBUM_ARTIST : LibraryItem.ARTIST,
|
||||
playType || playButtonBehavior,
|
||||
);
|
||||
};
|
||||
|
||||
const handleFavorite = () => {
|
||||
if (!detailQuery.data) return;
|
||||
setFavorite(
|
||||
detailQuery.data._serverId,
|
||||
[detailQuery.data.id],
|
||||
LibraryItem.ALBUM_ARTIST,
|
||||
!detailQuery.data.userFavorite,
|
||||
);
|
||||
};
|
||||
|
||||
const handleMoreOptions = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (!detailQuery.data) return;
|
||||
ContextMenuController.call({
|
||||
cmd: { items: [detailQuery.data], type: LibraryItem.ALBUM_ARTIST },
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
|
||||
const albumCount = detailQuery.data?.albumCount;
|
||||
const biography =
|
||||
detailQuery.data?.biography && enabledItem.biography ? detailQuery.data.biography : null;
|
||||
const showGenres = detailQuery.data?.genres ? detailQuery.data.genres.length !== 0 : false;
|
||||
@@ -618,13 +546,8 @@ export const AlbumArtistDetailContent = () => {
|
||||
<div className={styles.contentContainer} ref={ref}>
|
||||
<div className={styles.detailContainer}>
|
||||
<AlbumArtistActionButtons
|
||||
albumCount={albumCount}
|
||||
artistDiscographyLink={artistDiscographyLink}
|
||||
artistSongsLink={artistSongsLink}
|
||||
onFavorite={handleFavorite}
|
||||
onMoreOptions={handleMoreOptions}
|
||||
onPlay={() => handlePlay(playButtonBehavior)}
|
||||
userFavorite={detailQuery.data?.userFavorite}
|
||||
/>
|
||||
<Grid gutter="xl">
|
||||
{showGenres && (
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.metadata-group {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
@container (min-width: $mantine-breakpoint-sm) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,24 @@ import { forwardRef, Fragment, Ref } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import styles from './album-artist-detail-header.module.css';
|
||||
|
||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { LibraryHeader } from '/@/renderer/features/shared/components/library-header';
|
||||
import {
|
||||
LibraryHeader,
|
||||
LibraryHeaderMenu,
|
||||
} from '/@/renderer/features/shared/components/library-header';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { formatDurationString } from '/@/renderer/utils';
|
||||
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 { LibraryItem, ServerType } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivElement>) => {
|
||||
const { albumArtistId, artistId } = useParams() as {
|
||||
@@ -56,7 +63,28 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivEleme
|
||||
},
|
||||
];
|
||||
|
||||
const { setRating } = usePlayer();
|
||||
const { addToQueueByFetch, setFavorite, setRating } = usePlayer();
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const handlePlay = (type?: Play) => {
|
||||
if (!server?.id || !routeId) return;
|
||||
addToQueueByFetch(
|
||||
server.id,
|
||||
[routeId],
|
||||
LibraryItem.ALBUM_ARTIST,
|
||||
type || playButtonBehavior,
|
||||
);
|
||||
};
|
||||
|
||||
const handleFavorite = () => {
|
||||
if (!detailQuery?.data) return;
|
||||
setFavorite(
|
||||
detailQuery.data._serverId,
|
||||
[detailQuery.data.id],
|
||||
LibraryItem.ALBUM_ARTIST,
|
||||
!detailQuery.data.userFavorite,
|
||||
);
|
||||
};
|
||||
|
||||
const handleUpdateRating = (rating: number) => {
|
||||
if (!detailQuery?.data) return;
|
||||
@@ -78,6 +106,14 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivEleme
|
||||
);
|
||||
};
|
||||
|
||||
const handleMoreOptions = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (!detailQuery?.data) return;
|
||||
ContextMenuController.call({
|
||||
cmd: { items: [detailQuery.data], type: LibraryItem.ALBUM_ARTIST },
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
|
||||
const showRating = detailQuery?.data?._serverType === ServerType.NAVIDROME;
|
||||
|
||||
return (
|
||||
@@ -87,8 +123,8 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivEleme
|
||||
ref={ref}
|
||||
title={detailQuery?.data?.name || ''}
|
||||
>
|
||||
<Stack>
|
||||
<Group>
|
||||
<Stack gap="md" w="100%">
|
||||
<Group className={styles.metadataGroup}>
|
||||
{metadataItems
|
||||
.filter((i) => i.enabled)
|
||||
.map((item, index) => (
|
||||
@@ -97,17 +133,16 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivEleme
|
||||
<Text isMuted={item.secondary}>{item.value}</Text>
|
||||
</Fragment>
|
||||
))}
|
||||
{showRating && (
|
||||
<>
|
||||
<Text isNoSelect>•</Text>
|
||||
<Rating
|
||||
onChange={handleUpdateRating}
|
||||
readOnly={detailQuery?.isFetching}
|
||||
value={detailQuery?.data?.userRating || 0}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Group>
|
||||
<LibraryHeaderMenu
|
||||
favorite={detailQuery?.data?.userFavorite}
|
||||
onFavorite={handleFavorite}
|
||||
onMore={handleMoreOptions}
|
||||
onPlay={(type) => handlePlay(type)}
|
||||
onRating={showRating ? handleUpdateRating : undefined}
|
||||
onShuffle={() => handlePlay(Play.SHUFFLE)}
|
||||
rating={detailQuery?.data?.userRating || 0}
|
||||
/>
|
||||
</Stack>
|
||||
</LibraryHeader>
|
||||
);
|
||||
|
||||
@@ -228,8 +228,8 @@ const AlbumArtistPropertyMapping: ItemDetailRow<AlbumArtist>[] = [
|
||||
label: 'common.biography',
|
||||
render: (artist) =>
|
||||
artist.biography ? (
|
||||
<Spoiler maxHeight={50}>
|
||||
<div dangerouslySetInnerHTML={{ __html: sanitize(artist.biography) }} />
|
||||
<Spoiler>
|
||||
<Text dangerouslySetInnerHTML={{ __html: sanitize(artist.biography) }} />
|
||||
</Spoiler>
|
||||
) : null,
|
||||
},
|
||||
|
||||
@@ -91,7 +91,7 @@ export const PlaylistDetailSongListHeader = ({
|
||||
title={detailQuery?.data?.name}
|
||||
>
|
||||
<LibraryHeaderMenu
|
||||
onPlay={() => handlePlay(Play.NOW)}
|
||||
onPlay={(type) => handlePlay(type)}
|
||||
onShuffle={() => handlePlay(Play.SHUFFLE)}
|
||||
/>
|
||||
</LibraryHeader>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
grid-template-areas: 'image info';
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: 225px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
justify-items: start;
|
||||
height: auto;
|
||||
min-height: 340px;
|
||||
@@ -98,7 +98,7 @@
|
||||
.title {
|
||||
display: flex;
|
||||
margin: var(--theme-spacing-sm) 0;
|
||||
font-size: clamp(2rem, 3.5dvw, 3.25rem);
|
||||
font-size: clamp(1.75rem, 3dvw, 2.75rem);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@ import { Link } from 'react-router';
|
||||
import styles from './library-header.module.css';
|
||||
|
||||
import {
|
||||
PlayLastTextButton,
|
||||
PlayNextTextButton,
|
||||
PlayTextButton,
|
||||
WideShuffleButton,
|
||||
} from '/@/renderer/features/shared/components/play-button';
|
||||
import { LONG_PRESS_PLAY_BEHAVIOR } from '/@/renderer/features/shared/components/play-button-group';
|
||||
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
|
||||
import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
|
||||
import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
|
||||
import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
|
||||
@@ -20,6 +23,7 @@ import { Image } from '/@/shared/components/image/image';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
interface LibraryHeaderProps {
|
||||
children?: ReactNode;
|
||||
@@ -145,36 +149,36 @@ export const LibraryHeader = forwardRef(
|
||||
|
||||
const calculateTitleSize = (title: string) => {
|
||||
const titleLength = title.length;
|
||||
let baseSize = '3.5dvw';
|
||||
let baseSize = '3dvw';
|
||||
|
||||
if (titleLength > 20) {
|
||||
baseSize = '3dvw';
|
||||
}
|
||||
|
||||
if (titleLength > 30) {
|
||||
baseSize = '2.75dvw';
|
||||
}
|
||||
|
||||
if (titleLength > 40) {
|
||||
baseSize = '2.5dvw';
|
||||
}
|
||||
|
||||
if (titleLength > 50) {
|
||||
if (titleLength > 30) {
|
||||
baseSize = '2.25dvw';
|
||||
}
|
||||
|
||||
if (titleLength > 60) {
|
||||
if (titleLength > 40) {
|
||||
baseSize = '2dvw';
|
||||
}
|
||||
|
||||
return `clamp(2rem, ${baseSize}, 3.25rem)`;
|
||||
if (titleLength > 50) {
|
||||
baseSize = '1.875dvw';
|
||||
}
|
||||
|
||||
if (titleLength > 60) {
|
||||
baseSize = '1.75dvw';
|
||||
}
|
||||
|
||||
return `clamp(1.75rem, ${baseSize}, 2.75rem)`;
|
||||
};
|
||||
|
||||
interface LibraryHeaderMenuProps {
|
||||
favorite?: boolean;
|
||||
onFavorite?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onMore?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onPlay?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onPlay?: (type: Play) => void;
|
||||
onRating?: (rating: number) => void;
|
||||
onShuffle?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
rating?: number;
|
||||
@@ -186,7 +190,6 @@ export const LibraryHeaderMenu = ({
|
||||
onMore,
|
||||
onPlay,
|
||||
onRating,
|
||||
onShuffle,
|
||||
rating,
|
||||
}: LibraryHeaderMenuProps) => {
|
||||
const isMutatingRating = useIsMutatingRating();
|
||||
@@ -194,11 +197,43 @@ export const LibraryHeaderMenu = ({
|
||||
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
|
||||
const isMutatingFavorite = isMutatingCreateFavorite || isMutatingDeleteFavorite;
|
||||
|
||||
const handlePlayNow = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay?.(Play.NOW);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay?.(LONG_PRESS_PLAY_BEHAVIOR[Play.NOW]);
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlayNext = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay?.(Play.NEXT);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay?.(LONG_PRESS_PLAY_BEHAVIOR[Play.NEXT]);
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlayLast = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay?.(Play.LAST);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay?.(LONG_PRESS_PLAY_BEHAVIOR[Play.LAST]);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.libraryHeaderMenu}>
|
||||
<Group wrap="nowrap">
|
||||
{onPlay && <PlayTextButton onClick={onPlay} />}
|
||||
{onShuffle && <WideShuffleButton onClick={onShuffle} />}
|
||||
{onPlay && <PlayTextButton {...handlePlayNow.handlers} {...handlePlayNow.props} />}
|
||||
{onPlay && (
|
||||
<PlayNextTextButton {...handlePlayNext.handlers} {...handlePlayNext.props} />
|
||||
)}
|
||||
{onPlay && (
|
||||
<PlayLastTextButton {...handlePlayLast.handlers} {...handlePlayLast.props} />
|
||||
)}
|
||||
</Group>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
{onRating && (
|
||||
|
||||
@@ -50,10 +50,26 @@
|
||||
padding-left: var(--theme-spacing-xl);
|
||||
background: white;
|
||||
border-radius: var(--theme-radius-xl);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
|
||||
&[data-variant='subtle'] {
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus-visible {
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wide-text-button.unthemed {
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
|
||||
&[data-variant='subtle'] {
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
}
|
||||
|
||||
@mixin light {
|
||||
background: black;
|
||||
|
||||
@@ -62,8 +78,11 @@
|
||||
fill: white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten(black, 10%);
|
||||
&[data-variant='subtle']:hover,
|
||||
&[data-variant='subtle']:active,
|
||||
&[data-variant='subtle']:focus-visible {
|
||||
background: lighten(black, 10%) !important;
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +94,11 @@
|
||||
fill: black;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: darken(white, 20%);
|
||||
&[data-variant='subtle']:hover,
|
||||
&[data-variant='subtle']:active,
|
||||
&[data-variant='subtle']:focus-visible {
|
||||
background: darken(white, 20%) !important;
|
||||
transition: background-color 0.2s ease-in-out !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,14 +112,16 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
svg {
|
||||
color: black;
|
||||
fill: black;
|
||||
}
|
||||
}
|
||||
|
||||
.no-fill {
|
||||
fill: none !important;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
all: unset;
|
||||
display: flex;
|
||||
|
||||
@@ -4,12 +4,14 @@ import { forwardRef, memo } from 'react';
|
||||
|
||||
import styles from './play-button.module.css';
|
||||
|
||||
import { PlayTooltip } from '/@/renderer/features/shared/components/play-button-group';
|
||||
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
|
||||
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Button, ButtonProps } from '/@/shared/components/button/button';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { AppIcon, Icon } from '/@/shared/components/icon/icon';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
export interface DefaultPlayButtonProps extends ActionIconProps {
|
||||
size?: number | string;
|
||||
@@ -36,14 +38,18 @@ export const DefaultPlayButton = forwardRef<HTMLButtonElement, DefaultPlayButton
|
||||
|
||||
DefaultPlayButton.displayName = 'DefaultPlayButton';
|
||||
|
||||
interface TextPlayButtonProps extends ButtonProps {}
|
||||
interface TextPlayButtonProps extends ButtonProps {
|
||||
onLongPress?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
showTooltip?: boolean;
|
||||
}
|
||||
|
||||
export const PlayTextButton = ({
|
||||
className,
|
||||
showTooltip = true,
|
||||
variant = 'default',
|
||||
...props
|
||||
}: TextPlayButtonProps) => {
|
||||
return (
|
||||
const button = (
|
||||
<Button
|
||||
className={clsx(styles.wideTextButton, className, {
|
||||
[styles.unthemed]: variant !== 'filled',
|
||||
@@ -57,12 +63,64 @@ export const PlayTextButton = ({
|
||||
>
|
||||
{props.children || (
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Icon fill="default" icon="mediaPlay" size="lg" />
|
||||
<Icon icon="mediaPlay" size="lg" />
|
||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||
</Group>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const hasLongPress = Boolean(
|
||||
props.onLongPress || (props as any).onMouseDown || (props as any).onTouchStart,
|
||||
);
|
||||
|
||||
if (hasLongPress && showTooltip) {
|
||||
return <PlayTooltip type={Play.NOW}>{button}</PlayTooltip>;
|
||||
}
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
export const PlayNextTextButton = ({ ...props }: TextPlayButtonProps) => {
|
||||
const button = (
|
||||
<PlayTextButton {...props} showTooltip={false}>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Icon className={styles.noFill} icon="mediaPlayNext" size="lg" />
|
||||
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||
</Group>
|
||||
</PlayTextButton>
|
||||
);
|
||||
|
||||
const hasLongPress = Boolean(
|
||||
props.onLongPress || (props as any).onMouseDown || (props as any).onTouchStart,
|
||||
);
|
||||
|
||||
if (hasLongPress) {
|
||||
return <PlayTooltip type={Play.NEXT}>{button}</PlayTooltip>;
|
||||
}
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
export const PlayLastTextButton = ({ ...props }: TextPlayButtonProps) => {
|
||||
const button = (
|
||||
<PlayTextButton {...props} showTooltip={false}>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Icon className={styles.noFill} icon="mediaPlayLast" size="lg" />
|
||||
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||
</Group>
|
||||
</PlayTextButton>
|
||||
);
|
||||
|
||||
const hasLongPress = Boolean(
|
||||
props.onLongPress || (props as any).onMouseDown || (props as any).onTouchStart,
|
||||
);
|
||||
|
||||
if (hasLongPress) {
|
||||
return <PlayTooltip type={Play.LAST}>{button}</PlayTooltip>;
|
||||
}
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
export const WideShuffleButton = ({ ...props }: TextPlayButtonProps) => {
|
||||
|
||||
@@ -143,11 +143,11 @@
|
||||
background-color: transparent;
|
||||
|
||||
@mixin dark {
|
||||
color: lighten(var(--theme-colors-foreground), 10%);
|
||||
color: darken(var(--theme-colors-foreground), 15%);
|
||||
}
|
||||
|
||||
@mixin light {
|
||||
color: darken(var(--theme-colors-foreground), 10%);
|
||||
color: lighten(var(--theme-colors-foreground), 10%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,18 @@ import styles from './spoiler.module.css';
|
||||
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
|
||||
interface SpoilerProps extends MantineSpoilerProps {
|
||||
interface SpoilerProps extends Omit<MantineSpoilerProps, 'hideLabel' | 'showLabel'> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Spoiler = ({ children, ...props }: SpoilerProps) => {
|
||||
export const Spoiler = ({ children, maxHeight = 56, ...props }: SpoilerProps) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<MantineSpoiler
|
||||
classNames={{ content: styles.spoiler, control: styles.control }}
|
||||
expanded={expanded}
|
||||
maxHeight={maxHeight}
|
||||
{...props}
|
||||
hideLabel={<Icon icon="arrowUpS" size="lg" />}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
|
||||
Reference in New Issue
Block a user