mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 05:12:57 +02:00
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:
@@ -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,23 +78,25 @@ export const AlbumGroupControls = ({ albumId, serverId, serverType }: AlbumGroup
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
<ActionIcon
|
{showFavorites && (
|
||||||
className={styles.favorite}
|
<ActionIcon
|
||||||
disabled={isMutatingFavorite}
|
className={styles.favorite}
|
||||||
icon="favorite"
|
disabled={isMutatingFavorite}
|
||||||
iconProps={{
|
icon="favorite"
|
||||||
color: album.userFavorite ? 'primary' : 'muted',
|
iconProps={{
|
||||||
fill: album.userFavorite ? 'primary' : undefined,
|
color: album.userFavorite ? 'primary' : 'muted',
|
||||||
size: 'xs',
|
fill: album.userFavorite ? 'primary' : undefined,
|
||||||
}}
|
size: 'xs',
|
||||||
onClick={handleFavorite}
|
}}
|
||||||
onDoubleClick={(event) => {
|
onClick={handleFavorite}
|
||||||
event.stopPropagation();
|
onDoubleClick={(event) => {
|
||||||
event.preventDefault();
|
event.stopPropagation();
|
||||||
}}
|
event.preventDefault();
|
||||||
size="xs"
|
}}
|
||||||
variant="transparent"
|
size="xs"
|
||||||
/>
|
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,15 +52,17 @@ 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;
|
? () => {
|
||||||
setFavorite(
|
if (!detailQuery?.data) return;
|
||||||
detailQuery.data._serverId,
|
setFavorite(
|
||||||
[detailQuery.data.id],
|
detailQuery.data._serverId,
|
||||||
LibraryItem.ALBUM,
|
[detailQuery.data.id],
|
||||||
!detailQuery.data.userFavorite,
|
LibraryItem.ALBUM,
|
||||||
);
|
!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,20 +179,22 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
<Group gap="sm" justify="space-between">
|
<Group gap="sm" justify="space-between">
|
||||||
<Group>
|
<Group>
|
||||||
<DefaultPlayButton onClick={() => handlePlay()} />
|
<DefaultPlayButton onClick={() => handlePlay()} />
|
||||||
<ActionIcon
|
{showFavorites && (
|
||||||
icon="favorite"
|
<ActionIcon
|
||||||
iconProps={{
|
icon="favorite"
|
||||||
fill: detailQuery?.data?.userFavorite
|
iconProps={{
|
||||||
? 'primary'
|
fill: detailQuery?.data?.userFavorite
|
||||||
: undefined,
|
? 'primary'
|
||||||
}}
|
: undefined,
|
||||||
loading={
|
}}
|
||||||
createFavoriteMutation.isPending ||
|
loading={
|
||||||
deleteFavoriteMutation.isPending
|
createFavoriteMutation.isPending ||
|
||||||
}
|
deleteFavoriteMutation.isPending
|
||||||
onClick={handleFavorite}
|
}
|
||||||
variant="subtle"
|
onClick={handleFavorite}
|
||||||
/>
|
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,16 +79,18 @@ export const MobileFullscreenPlayerMetadata = memo(
|
|||||||
)}
|
)}
|
||||||
{!isRadio && (
|
{!isRadio && (
|
||||||
<Group align="center" className={styles.actionsRow} gap="xs">
|
<Group align="center" className={styles.actionsRow} gap="xs">
|
||||||
<ActionIcon
|
{showFavorite && (
|
||||||
icon="favorite"
|
<ActionIcon
|
||||||
iconProps={{
|
icon="favorite"
|
||||||
fill: isFavorite ? 'primary' : undefined,
|
iconProps={{
|
||||||
size: 'md',
|
fill: isFavorite ? 'primary' : undefined,
|
||||||
}}
|
size: 'md',
|
||||||
onClick={onToggleFavorite}
|
}}
|
||||||
size="sm"
|
onClick={onToggleFavorite}
|
||||||
variant="subtle"
|
size="sm"
|
||||||
/>
|
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'],
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user