Make ratings/favourites a bit more customizable (#2262)

* Replace show ratings toggle with favorite/rating controls setting

Swap `genera.showRatings` (previously a simple bool) for a
`favoriteRatingControls` enum, exposed via derived `useShowRatings` /
`useShowFavorites` hooks. Settings switch becomes a select, with a
migration to a new v32 schema mapping the old boolean forward with no
user change.

* Gate favorite controls on `favoriteRatingControls` setting

Hide the playerbar and mobile fullscreen favorite buttons and the
context-menu favorite action when the setting excludes favorites,
mirroring how ratings are already gated.

* Gate favorite controls on item cards and detail list

Drill a `showFavorite` flag through the item-card tree (mirroring
`showRating`), and gate the card/detail-list favorite badges and hover
buttons on the `favoriteRatingControls` setting.

* Gate favorite controls in detail headers and album group controls

* Keep the original `showRatings` switch and add a `showFavorites`

* Gate album header favorite hander at definition
This commit is contained in:
Marc Plano-Lesay
2026-07-26 22:59:17 +10:00
committed by GitHub
parent d0710f3549
commit 07ef323ccc
15 changed files with 143 additions and 69 deletions
+3 -1
View File
@@ -1085,10 +1085,12 @@
"preferLocalLyrics": "Prefer local lyrics", "preferLocalLyrics": "Prefer local lyrics",
"showLyricsInSidebar_description": "A panel will be added to the attached play queue that displays the lyrics", "showLyricsInSidebar_description": "A panel will be added to the attached play queue that displays the lyrics",
"showLyricsInSidebar": "Show lyrics in player sidebar", "showLyricsInSidebar": "Show lyrics in player sidebar",
"showFavorites": "Show favorites",
"showFavorites_description": "Controls if the favorite (heart) buttons show up in the interface",
"showQueueInSidebar_description": "A panel will be added to the player sidebar that displays the play queue", "showQueueInSidebar_description": "A panel will be added to the player sidebar that displays the play queue",
"showQueueInSidebar": "Show play queue in player sidebar", "showQueueInSidebar": "Show play queue in player sidebar",
"showRatings_description": "Controls if the star ratings feature shows up in the interface",
"showRatings": "Show star ratings", "showRatings": "Show star ratings",
"showRatings_description": "Controls if the star ratings feature shows up in the interface",
"blurExplicitImages": "Blur explicit images", "blurExplicitImages": "Blur explicit images",
"blurExplicitImages_description": "Album and song artwork tagged as explicit will be blurred", "blurExplicitImages_description": "Album and song artwork tagged as explicit will be blurred",
"enableGridMultiSelect": "Enable grid multi-select", "enableGridMultiSelect": "Enable grid multi-select",
@@ -33,6 +33,7 @@ interface ItemCardControlsProps {
internalState?: ItemListStateActions; internalState?: ItemListStateActions;
item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined; item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined;
itemType: LibraryItem; itemType: LibraryItem;
showFavorite: boolean;
showRating: boolean; showRating: boolean;
type?: 'compact' | 'default' | 'poster'; type?: 'compact' | 'default' | 'poster';
} }
@@ -205,6 +206,7 @@ export const ItemCardControls = ({
internalState, internalState,
item, item,
itemType, itemType,
showFavorite,
showRating, showRating,
type = 'default', type = 'default',
}: ItemCardControlsProps) => { }: ItemCardControlsProps) => {
@@ -289,7 +291,7 @@ export const ItemCardControls = ({
</PlayTooltip> </PlayTooltip>
</Tooltip.Group> </Tooltip.Group>
)} )}
{controls?.onFavorite && ( {controls?.onFavorite && showFavorite && (
<FavoriteButton isFavorite={isFavorite} onClick={favoriteHandler} /> <FavoriteButton isFavorite={isFavorite} onClick={favoriteHandler} />
)} )}
{controls?.onRating && {controls?.onRating &&
@@ -19,7 +19,7 @@ import { ItemControls } from '/@/renderer/components/item-list/types';
import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists'; import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useShowRatings } from '/@/renderer/store'; import { useShowFavorites, useShowRatings } from '/@/renderer/store';
import { import {
formatDateAbsolute, formatDateAbsolute,
formatDateRelative, formatDateRelative,
@@ -90,6 +90,7 @@ export const ItemCard = ({
withControls, withControls,
}: ItemCardProps) => { }: ItemCardProps) => {
const showRatings = useShowRatings(); const showRatings = useShowRatings();
const showFavorites = useShowFavorites();
const imageUrl = getImageUrl(data); const imageUrl = getImageUrl(data);
const rows = providedRows || []; const rows = providedRows || [];
@@ -110,6 +111,7 @@ export const ItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
rows={rows} rows={rows}
showFavorite={showFavorites}
showRating={showRatings} showRating={showRatings}
withControls={withControls} withControls={withControls}
/> />
@@ -130,6 +132,7 @@ export const ItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
rows={rows} rows={rows}
showFavorite={showFavorites}
showRating={showRatings} showRating={showRatings}
withControls={withControls} withControls={withControls}
/> />
@@ -150,6 +153,7 @@ export const ItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
rows={rows} rows={rows}
showFavorite={showFavorites}
showRating={showRatings} showRating={showRatings}
withControls={withControls} withControls={withControls}
/> />
@@ -166,6 +170,7 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
imageUrl: string | undefined; imageUrl: string | undefined;
internalState?: ItemListStateActions; internalState?: ItemListStateActions;
rows: DataRow[]; rows: DataRow[];
showFavorite: boolean;
showRating: boolean; showRating: boolean;
} }
@@ -186,6 +191,7 @@ const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
isRound, isRound,
itemType, itemType,
navigationPath, navigationPath,
showFavorite,
showRating, showRating,
variant, variant,
withControls, withControls,
@@ -204,6 +210,7 @@ const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
isRound?: boolean; isRound?: boolean;
itemType: LibraryItem; itemType: LibraryItem;
navigationPath: null | string; navigationPath: null | string;
showFavorite: boolean;
showRating: boolean; showRating: boolean;
variant: 'default' | 'poster'; variant: 'default' | 'poster';
withControls?: boolean; withControls?: boolean;
@@ -259,7 +266,7 @@ const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
type="itemCard" type="itemCard"
/> />
)} )}
{isFavorite && <div className={styles.favoriteBadge} />} {showFavorite && isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>} {hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence> <AnimatePresence>
{withControls && showControls && ( {withControls && showControls && (
@@ -269,6 +276,7 @@ const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
{...(variant === 'poster' ? { internalState } : {})} {...(variant === 'poster' ? { internalState } : {})}
item={data} item={data}
itemType={itemType} itemType={itemType}
showFavorite={showFavorite}
showRating={showRating} showRating={showRating}
type={variant} type={variant}
/> />
@@ -321,6 +329,7 @@ const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
itemType, itemType,
navigationPath, navigationPath,
rows, rows,
showFavorite,
showRating, showRating,
withControls, withControls,
}: { }: {
@@ -338,6 +347,7 @@ const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
itemType: LibraryItem; itemType: LibraryItem;
navigationPath: null | string; navigationPath: null | string;
rows: DataRow[]; rows: DataRow[];
showFavorite: boolean;
showRating: boolean; showRating: boolean;
withControls?: boolean; withControls?: boolean;
}) { }) {
@@ -393,7 +403,7 @@ const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
type="itemCard" type="itemCard"
/> />
)} )}
{isFavorite && <div className={styles.favoriteBadge} />} {showFavorite && isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>} {hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence> <AnimatePresence>
{withControls && showControls && data && ( {withControls && showControls && data && (
@@ -403,6 +413,7 @@ const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
internalState={internalState} internalState={internalState}
item={data} item={data}
itemType={itemType} itemType={itemType}
showFavorite={showFavorite}
showRating={showRating} showRating={showRating}
type="compact" type="compact"
/> />
@@ -468,6 +479,7 @@ const CompactItemCard = ({
isRound, isRound,
itemType, itemType,
rows, rows,
showFavorite,
showRating, showRating,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
@@ -634,6 +646,7 @@ const CompactItemCard = ({
itemType={itemType} itemType={itemType}
navigationPath={navigationPath} navigationPath={navigationPath}
rows={rows} rows={rows}
showFavorite={showFavorite}
showRating={showRating} showRating={showRating}
withControls={withControls} withControls={withControls}
/> />
@@ -679,6 +692,7 @@ const DefaultItemCard = ({
isRound, isRound,
itemType, itemType,
rows, rows,
showFavorite,
showRating, showRating,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
@@ -777,6 +791,7 @@ const DefaultItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
navigationPath={navigationPath} navigationPath={navigationPath}
showFavorite={showFavorite}
showRating={showRating} showRating={showRating}
variant="default" variant="default"
withControls={withControls} withControls={withControls}
@@ -840,6 +855,7 @@ const PosterItemCard = ({
isRound, isRound,
itemType, itemType,
rows, rows,
showFavorite,
showRating, showRating,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
@@ -1005,6 +1021,7 @@ const PosterItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
navigationPath={navigationPath} navigationPath={navigationPath}
showFavorite={showFavorite}
showRating={showRating} showRating={showRating}
variant="poster" variant="poster"
withControls={withControls} withControls={withControls}
@@ -67,7 +67,7 @@ import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutatio
import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useSettingsStore, useShowRatings } from '/@/renderer/store'; import { useSettingsStore, useShowFavorites, useShowRatings } from '/@/renderer/store';
import { formatDurationString, formatPartialIsoDateUTC } from '/@/renderer/utils'; import { formatDurationString, formatPartialIsoDateUTC } from '/@/renderer/utils';
import { SEPARATOR_STRING } from '/@/shared/api/utils'; import { SEPARATOR_STRING } from '/@/shared/api/utils';
import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator'; import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator';
@@ -426,6 +426,7 @@ const MetadataSection = memo(
({ controls, internalState, item }: MetadataSectionProps) => { ({ controls, internalState, item }: MetadataSectionProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const showRatings = useShowRatings(); const showRatings = useShowRatings();
const showFavorites = useShowFavorites();
const [isImageHovered, setIsImageHovered] = useState(false); const [isImageHovered, setIsImageHovered] = useState(false);
const [isMetadataHovered, setIsMetadataHovered] = useState(false); const [isMetadataHovered, setIsMetadataHovered] = useState(false);
@@ -570,7 +571,7 @@ const MetadataSection = memo(
serverId={item._serverId} serverId={item._serverId}
type="itemCard" type="itemCard"
/> />
{isFavorite && <div className={styles.favoriteBadge} />} {showFavorites && isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>} {hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence> <AnimatePresence>
{controls && isImageHovered && ( {controls && isImageHovered && (
@@ -580,6 +581,7 @@ const MetadataSection = memo(
internalState={internalState} internalState={internalState}
item={item} item={item}
itemType={item._itemType} itemType={item._itemType}
showFavorite={showFavorites}
showRating={true} showRating={true}
type="compact" type="compact"
/> />
@@ -9,7 +9,7 @@ import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
import { useShowRatings } from '/@/renderer/store'; import { useShowFavorites, useShowRatings } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Rating } from '/@/shared/components/rating/rating'; import { Rating } from '/@/shared/components/rating/rating';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types'; import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
@@ -35,6 +35,7 @@ interface AlbumGroupControlsProps {
export const AlbumGroupControls = ({ albumId, serverId, serverType }: AlbumGroupControlsProps) => { export const AlbumGroupControls = ({ albumId, serverId, serverType }: AlbumGroupControlsProps) => {
const showRatingsSetting = useShowRatings(); const showRatingsSetting = useShowRatings();
const showFavorites = useShowFavorites();
const detailQuery = useAlbumGroupAlbum(albumId, serverId); const detailQuery = useAlbumGroupAlbum(albumId, serverId);
const setFavorite = useSetFavorite(); const setFavorite = useSetFavorite();
const setRating = useSetRating(); const setRating = useSetRating();
@@ -77,6 +78,7 @@ export const AlbumGroupControls = ({ albumId, serverId, serverType }: AlbumGroup
return ( return (
<div className={styles.controls}> <div className={styles.controls}>
{showFavorites && (
<ActionIcon <ActionIcon
className={styles.favorite} className={styles.favorite}
disabled={isMutatingFavorite} disabled={isMutatingFavorite}
@@ -94,6 +96,7 @@ export const AlbumGroupControls = ({ albumId, serverId, serverType }: AlbumGroup
size="xs" size="xs"
variant="transparent" variant="transparent"
/> />
)}
{showRating && ( {showRating && (
<Rating <Rating
className={styles.rating} className={styles.rating}
@@ -18,7 +18,7 @@ import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favori
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating'; import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useShowRatings } from '/@/renderer/store'; import { useCurrentServer, useShowFavorites, useShowRatings } from '/@/renderer/store';
import { useArtistRadioCount, usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { useArtistRadioCount, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDurationString, formatPartialIsoDateUTC, formatSizeString } from '/@/renderer/utils'; import { formatDurationString, formatPartialIsoDateUTC, formatSizeString } from '/@/renderer/utils';
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types'; import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
@@ -34,6 +34,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const server = useCurrentServer(); const server = useCurrentServer();
const showRatings = useShowRatings(); const showRatings = useShowRatings();
const showFavorites = useShowFavorites();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const albumRadioCount = useArtistRadioCount(); const albumRadioCount = useArtistRadioCount();
const detailQuery = useQuery( const detailQuery = useQuery(
@@ -51,7 +52,8 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
const setRating = useSetRating(); const setRating = useSetRating();
const setFavorite = useSetFavorite(); const setFavorite = useSetFavorite();
const handleFavorite = () => { const handleFavorite = showFavorites
? () => {
if (!detailQuery?.data) return; if (!detailQuery?.data) return;
setFavorite( setFavorite(
detailQuery.data._serverId, detailQuery.data._serverId,
@@ -59,7 +61,8 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
LibraryItem.ALBUM, LibraryItem.ALBUM,
!detailQuery.data.userFavorite, !detailQuery.data.userFavorite,
); );
}; }
: undefined;
const handleUpdateRating = showRating const handleUpdateRating = showRating
? (rating: number) => { ? (rating: number) => {
@@ -19,7 +19,7 @@ import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-
import { useFastAverageColor } from '/@/renderer/hooks'; import { useFastAverageColor } from '/@/renderer/hooks';
import { queryClient } from '/@/renderer/lib/react-query'; import { queryClient } from '/@/renderer/lib/react-query';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer, useShowFavorites } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDurationString } from '/@/renderer/utils'; import { formatDurationString } from '/@/renderer/utils';
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify'; import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
@@ -38,6 +38,7 @@ const DummyAlbumDetailRoute = () => {
const { albumId } = useParams() as { albumId: string }; const { albumId } = useParams() as { albumId: string };
const server = useCurrentServer(); const server = useCurrentServer();
const showFavorites = useShowFavorites();
const queryKey = queryKeys.songs.detail(server?.id || '', albumId); const queryKey = queryKeys.songs.detail(server?.id || '', albumId);
const detailQuery = useSuspenseQuery({ const detailQuery = useSuspenseQuery({
queryFn: ({ signal }) => { queryFn: ({ signal }) => {
@@ -178,6 +179,7 @@ const DummyAlbumDetailRoute = () => {
<Group gap="sm" justify="space-between"> <Group gap="sm" justify="space-between">
<Group> <Group>
<DefaultPlayButton onClick={() => handlePlay()} /> <DefaultPlayButton onClick={() => handlePlay()} />
{showFavorites && (
<ActionIcon <ActionIcon
icon="favorite" icon="favorite"
iconProps={{ iconProps={{
@@ -192,6 +194,7 @@ const DummyAlbumDetailRoute = () => {
onClick={handleFavorite} onClick={handleFavorite}
variant="subtle" variant="subtle"
/> />
)}
<ActionIcon <ActionIcon
icon="ellipsisHorizontal" icon="ellipsisHorizontal"
onClick={() => { onClick={() => {
@@ -19,7 +19,7 @@ import {
import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite'; import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite';
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating'; import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useAppStore, useCurrentServer, useShowRatings } from '/@/renderer/store'; import { useAppStore, useCurrentServer, useShowFavorites, useShowRatings } from '/@/renderer/store';
import { useArtistReleaseTypeItems, usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { useArtistReleaseTypeItems, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDurationString } from '/@/renderer/utils'; import { formatDurationString } from '/@/renderer/utils';
import { hasFeature, SEPARATOR_STRING, sortAlbumList } from '/@/shared/api/utils'; import { hasFeature, SEPARATOR_STRING, sortAlbumList } from '/@/shared/api/utils';
@@ -105,6 +105,7 @@ export const AlbumArtistDetailHeader = forwardRef<HTMLDivElement, AlbumArtistDet
const routeId = (artistId || albumArtistId) as string; const routeId = (artistId || albumArtistId) as string;
const server = useCurrentServer(); const server = useCurrentServer();
const showRatings = useShowRatings(); const showRatings = useShowRatings();
const showFavorites = useShowFavorites();
const { t } = useTranslation(); const { t } = useTranslation();
const detailQuery = useSuspenseQuery( const detailQuery = useSuspenseQuery(
artistsQueries.albumArtistDetail({ artistsQueries.albumArtistDetail({
@@ -299,7 +300,7 @@ export const AlbumArtistDetailHeader = forwardRef<HTMLDivElement, AlbumArtistDet
</Group> </Group>
<LibraryHeaderMenu <LibraryHeaderMenu
favorite={detailQuery.data?.userFavorite} favorite={detailQuery.data?.userFavorite}
onFavorite={handleFavorite} onFavorite={showFavorites ? handleFavorite : undefined}
onMore={handleMoreOptions} onMore={handleMoreOptions}
onPlay={(type) => handlePlay(type)} onPlay={(type) => handlePlay(type)}
onRating={showRating ? handleUpdateRating : undefined} onRating={showRating ? handleUpdateRating : undefined}
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
import { useCurrentServerId } from '/@/renderer/store'; import { useCurrentServerId, useShowFavorites } from '/@/renderer/store';
import { ContextMenu } from '/@/shared/components/context-menu/context-menu'; import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
@@ -15,6 +15,7 @@ interface SetFavoriteActionProps {
export const SetFavoriteAction = ({ ids, itemType }: SetFavoriteActionProps) => { export const SetFavoriteAction = ({ ids, itemType }: SetFavoriteActionProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const serverId = useCurrentServerId(); const serverId = useCurrentServerId();
const showFavorites = useShowFavorites();
const createFavoriteMutation = useCreateFavorite({}); const createFavoriteMutation = useCreateFavorite({});
const deleteFavoriteMutation = useDeleteFavorite({}); const deleteFavoriteMutation = useDeleteFavorite({});
@@ -43,6 +44,10 @@ export const SetFavoriteAction = ({ ids, itemType }: SetFavoriteActionProps) =>
}); });
}, [deleteFavoriteMutation, ids, itemType, serverId]); }, [deleteFavoriteMutation, ids, itemType, serverId]);
if (!showFavorites) {
return null;
}
return ( return (
<ContextMenu.Submenu> <ContextMenu.Submenu>
<ContextMenu.SubmenuTarget> <ContextMenu.SubmenuTarget>
@@ -19,6 +19,7 @@ interface MobileFullscreenPlayerMetadataProps {
radioArtist?: string; radioArtist?: string;
radioStationName?: string; radioStationName?: string;
radioTitle?: string; radioTitle?: string;
showFavorite?: boolean;
showRating?: boolean; showRating?: boolean;
} }
@@ -30,6 +31,7 @@ export const MobileFullscreenPlayerMetadata = memo(
radioArtist, radioArtist,
radioStationName, radioStationName,
radioTitle, radioTitle,
showFavorite,
showRating, showRating,
}: MobileFullscreenPlayerMetadataProps) => { }: MobileFullscreenPlayerMetadataProps) => {
const isRadio = radioTitle !== undefined || radioStationName !== undefined; const isRadio = radioTitle !== undefined || radioStationName !== undefined;
@@ -77,6 +79,7 @@ export const MobileFullscreenPlayerMetadata = memo(
)} )}
{!isRadio && ( {!isRadio && (
<Group align="center" className={styles.actionsRow} gap="xs"> <Group align="center" className={styles.actionsRow} gap="xs">
{showFavorite && (
<ActionIcon <ActionIcon
icon="favorite" icon="favorite"
iconProps={{ iconProps={{
@@ -87,6 +90,7 @@ export const MobileFullscreenPlayerMetadata = memo(
size="sm" size="sm"
variant="subtle" variant="subtle"
/> />
)}
{showRating && ( {showRating && (
<Rating onChange={onUpdateRating} size="sm" value={rating || 0} /> <Rating onChange={onUpdateRating} size="sm" value={rating || 0} />
)} )}
@@ -35,10 +35,11 @@ import {
useCurrentServer, useCurrentServer,
useFullScreenPlayerStore, useFullScreenPlayerStore,
useFullScreenPlayerStoreActions, useFullScreenPlayerStoreActions,
useGeneralSettings,
usePlayerData, usePlayerData,
usePlayerSong, usePlayerSong,
useSetFullScreenPlayerStore, useSetFullScreenPlayerStore,
useShowFavorites,
useShowRatings,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
@@ -387,7 +388,8 @@ export const MobileFullscreenPlayer = () => {
const isPlayingRadio = isRadioActive && isRadioPlaying; const isPlayingRadio = isRadioActive && isRadioPlaying;
const effectiveDynamicBackground = dynamicBackground && !isPlayingRadio; const effectiveDynamicBackground = dynamicBackground && !isPlayingRadio;
const setFavorite = useSetFavorite(); const setFavorite = useSetFavorite();
const { showRatings: showRatingsSetting } = useGeneralSettings(); const showRatingsSetting = useShowRatings();
const showFavorites = useShowFavorites();
const setRating = useSetRating(); const setRating = useSetRating();
const [isPageHovered, setIsPageHovered] = useState(false); const [isPageHovered, setIsPageHovered] = useState(false);
@@ -482,6 +484,7 @@ export const MobileFullscreenPlayer = () => {
radioArtist={isPlayingRadio ? (radioMetadata?.artist ?? undefined) : undefined} radioArtist={isPlayingRadio ? (radioMetadata?.artist ?? undefined) : undefined}
radioStationName={isPlayingRadio ? (stationName ?? undefined) : undefined} radioStationName={isPlayingRadio ? (stationName ?? undefined) : undefined}
radioTitle={isPlayingRadio ? (radioMetadata?.title ?? undefined) : undefined} radioTitle={isPlayingRadio ? (radioMetadata?.title ?? undefined) : undefined}
showFavorite={showFavorites}
showRating={showRating} showRating={showRating}
/> />
<MobileFullscreenPlayerProgress currentSong={currentSong} /> <MobileFullscreenPlayerProgress currentSong={currentSong} />
@@ -24,7 +24,6 @@ import {
useAutoDJSettings, useAutoDJSettings,
useCurrentServer, useCurrentServer,
useFullScreenPlayerStore, useFullScreenPlayerStore,
useGeneralSettings,
useHotkeySettings, useHotkeySettings,
usePlaybackSettings, usePlaybackSettings,
usePlaybackType, usePlaybackType,
@@ -34,6 +33,8 @@ import {
usePlayerVolume, usePlayerVolume,
useSetFullScreenPlayerStore, useSetFullScreenPlayerStore,
useSettingsStoreActions, useSettingsStoreActions,
useShowFavorites,
useShowRatings,
useSidebarRightExpanded, useSidebarRightExpanded,
useSideQueueType, useSideQueueType,
useVolumeMax, useVolumeMax,
@@ -86,7 +87,8 @@ const calculateVolumeDown = (volume: number, volumeWheelStep: number) => {
}; };
export const RightControls = () => { export const RightControls = () => {
const { showRatings } = useGeneralSettings(); const showRatings = useShowRatings();
const showFavorites = useShowFavorites();
return ( return (
<Flex align="flex-end" direction="column" h="100%" px="1rem" py="0.5rem"> <Flex align="flex-end" direction="column" h="100%" px="1rem" py="0.5rem">
<Group h="calc(100% / 3)"> <Group h="calc(100% / 3)">
@@ -97,7 +99,7 @@ export const RightControls = () => {
<SleepTimerButton /> <SleepTimerButton />
<PlayerConfig /> <PlayerConfig />
<LyricsButton /> <LyricsButton />
<FavoriteButton /> {showFavorites && <FavoriteButton />}
<QueueButton /> <QueueButton />
<VolumeButton /> <VolumeButton />
</Group> </Group>
@@ -562,6 +562,26 @@ export const ApplicationSettings = memo(() => {
isHidden: settings.sideQueueType !== 'sideQueue', isHidden: settings.sideQueueType !== 'sideQueue',
title: t('setting.sidePlayQueueLayout'), title: t('setting.sidePlayQueueLayout'),
}, },
{
control: (
<Switch
defaultChecked={settings.showFavorites}
onChange={(e) => {
setSettings({
general: {
...settings,
showFavorites: e.currentTarget.checked,
},
});
}}
/>
),
description: t('setting.showFavorites', {
context: 'description',
}),
isHidden: false,
title: t('setting.showFavorites'),
},
{ {
control: ( control: (
<Switch <Switch
@@ -247,6 +247,7 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
path: ['general', 'showLyricsInSidebar'], path: ['general', 'showLyricsInSidebar'],
type: 'bool', type: 'bool',
}, },
{ key: 'FS_GENERAL_SHOW_FAVORITES', path: ['general', 'showFavorites'], type: 'bool' },
{ {
key: 'FS_GENERAL_SHOW_QUEUE_IN_SIDEBAR', key: 'FS_GENERAL_SHOW_QUEUE_IN_SIDEBAR',
path: ['general', 'showQueueInSidebar'], path: ['general', 'showQueueInSidebar'],
+7 -1
View File
@@ -537,6 +537,7 @@ export const GeneralSettingsSchema = z.object({
primaryShade: z.number().min(0).max(9), primaryShade: z.number().min(0).max(9),
qobuz: z.boolean(), qobuz: z.boolean(),
resume: z.boolean(), resume: z.boolean(),
showFavorites: z.boolean(),
showLyricsInSidebar: z.boolean(), showLyricsInSidebar: z.boolean(),
showQueueInSidebar: z.boolean(), showQueueInSidebar: z.boolean(),
showRatings: z.boolean(), showRatings: z.boolean(),
@@ -991,6 +992,7 @@ export type DataGridProps = {
export type DataTableProps = z.infer<typeof ItemTableListPropsSchema>; export type DataTableProps = z.infer<typeof ItemTableListPropsSchema>;
export type ItemDetailListProps = z.infer<typeof ItemDetailListPropsSchema>; export type ItemDetailListProps = z.infer<typeof ItemDetailListPropsSchema>;
export type ItemListSettings = { export type ItemListSettings = {
detail?: ItemDetailListProps; detail?: ItemDetailListProps;
display: ListDisplayType; display: ListDisplayType;
@@ -1005,7 +1007,6 @@ export type PlayerFilter = z.infer<typeof PlayerFilterSchema>;
export type PlayerFilterField = z.infer<typeof PlayerFilterFieldSchema>; export type PlayerFilterField = z.infer<typeof PlayerFilterFieldSchema>;
export type PlayerFilterOperator = z.infer<typeof PlayerFilterOperatorSchema>; export type PlayerFilterOperator = z.infer<typeof PlayerFilterOperatorSchema>;
export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> { export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> {
actions: { actions: {
addCollection: (collection: SavedCollection) => void; addCollection: (collection: SavedCollection) => void;
@@ -1031,6 +1032,7 @@ export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> {
}; };
} }
export interface SettingsState extends z.infer<typeof SettingsStateSchema> {} export interface SettingsState extends z.infer<typeof SettingsStateSchema> {}
export type SidebarItemType = z.infer<typeof SidebarItemTypeSchema>; export type SidebarItemType = z.infer<typeof SidebarItemTypeSchema>;
export type SideQueueLayout = z.infer<typeof SideQueueLayoutSchema>; export type SideQueueLayout = z.infer<typeof SideQueueLayoutSchema>;
@@ -1314,6 +1316,7 @@ const initialState: SettingsState = {
primaryShade: 6, primaryShade: 6,
qobuz: true, qobuz: true,
resume: true, resume: true,
showFavorites: true,
showLyricsInSidebar: true, showLyricsInSidebar: true,
showQueueInSidebar: true, showQueueInSidebar: true,
showRatings: true, showRatings: true,
@@ -2937,6 +2940,9 @@ export const usePlayerbarOpenDrawer = () =>
export const useShowRatings = () => useSettingsStore((state) => state.general.showRatings, shallow); export const useShowRatings = () => useSettingsStore((state) => state.general.showRatings, shallow);
export const useShowFavorites = () =>
useSettingsStore((state) => state.general.showFavorites, shallow);
export const useArtistRadioCount = () => export const useArtistRadioCount = () =>
useSettingsStore((state) => state.general.artistRadioCount, shallow); useSettingsStore((state) => state.general.artistRadioCount, shallow);