From 40fb5ba91605daecf9a67b63e1e59175d98e2b07 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 6 Sep 2025 00:45:59 -0700 Subject: [PATCH] add show album / album artist context menu items (#1105) --- src/i18n/locales/en.json | 2 + src/renderer/app.tsx | 9 ++--- .../context-menu/context-menu-items.tsx | 9 +++++ .../context-menu/context-menu-provider.tsx | 40 +++++++++++++++++++ src/renderer/features/context-menu/events.ts | 4 ++ src/renderer/layouts/default-layout.tsx | 5 ++- 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 8693bf5a..20e066e4 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -364,6 +364,8 @@ "setRating": "$t(action.setRating)", "playShuffled": "$t(player.shuffle)", "shareItem": "share item", + "goToAlbum": "go to $t(entity.album_one)", + "goToAlbumArtist": "go to $t(entity.albumArtist_one)", "showDetails": "get info" }, "fullscreenPlayer": { diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index f3f077b4..85c95774 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -16,7 +16,6 @@ import 'overlayscrollbars/overlayscrollbars.css'; import '/styles/overlayscrollbars.css'; import i18n from '/@/i18n/i18n'; -import { ContextMenuProvider } from '/@/renderer/features/context-menu'; import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc'; import { PlayQueueHandlerContext } from '/@/renderer/features/player'; import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context'; @@ -193,11 +192,9 @@ export const App = () => { - - - - {' '} - + + + diff --git a/src/renderer/features/context-menu/context-menu-items.tsx b/src/renderer/features/context-menu/context-menu-items.tsx index 8b5a622d..2b16572c 100644 --- a/src/renderer/features/context-menu/context-menu-items.tsx +++ b/src/renderer/features/context-menu/context-menu-items.tsx @@ -12,6 +12,8 @@ export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { disabled: false, divider: true, id: 'deselectAll' }, { id: 'download' }, { divider: true, id: 'shareItem' }, + { id: 'goToAlbum' }, + { id: 'goToAlbumArtist' }, { divider: true, id: 'showDetails' }, ]; @@ -27,6 +29,8 @@ export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { children: true, disabled: false, divider: true, id: 'setRating' }, { id: 'download' }, { divider: true, id: 'shareItem' }, + { id: 'goToAlbum' }, + { id: 'goToAlbumArtist' }, { divider: true, id: 'showDetails' }, ]; @@ -51,6 +55,8 @@ export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { children: true, disabled: false, id: 'setRating' }, { id: 'download' }, { divider: true, id: 'shareItem' }, + { id: 'goToAlbum' }, + { id: 'goToAlbumArtist' }, { divider: true, id: 'showDetails' }, ]; @@ -66,6 +72,8 @@ export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { children: true, disabled: false, id: 'setRating' }, { id: 'download' }, { divider: true, id: 'shareItem' }, + { id: 'goToAlbum' }, + { id: 'goToAlbumArtist' }, { divider: true, id: 'showDetails' }, ]; @@ -79,6 +87,7 @@ export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'removeFromFavorites' }, { children: true, disabled: false, divider: true, id: 'setRating' }, { divider: true, id: 'shareItem' }, + { id: 'goToAlbumArtist' }, { divider: true, id: 'showDetails' }, ]; diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index a294eb6b..39732b44 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -19,6 +19,7 @@ import { useState, } from 'react'; import { useTranslation } from 'react-i18next'; +import { generatePath, useNavigate } from 'react-router-dom'; import { api } from '/@/renderer/api'; import { controller } from '/@/renderer/api/controller'; @@ -34,6 +35,7 @@ import { updateSong } from '/@/renderer/features/player/update-remote-song'; import { useDeletePlaylist } from '/@/renderer/features/playlists'; import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation'; import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared'; +import { AppRoute } from '/@/renderer/router/routes'; import { getServerById, useAuthStore, @@ -131,6 +133,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { }); const handlePlayQueueAdd = usePlayQueueAdd(); + const navigate = useNavigate(); const openContextMenu = useCallback( (args: OpenContextMenuProps) => { @@ -734,6 +737,24 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { } }, [ctx.data, server]); + const handleGoToAlbum = useCallback(() => { + const item = ctx.data[0]; + if (item.albumId) { + navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: item.albumId })); + } + }, [ctx.data, navigate]); + + const handleGoToAlbumArtist = useCallback(() => { + const item = ctx.data[0]; + if (item.albumArtists && item.albumArtists.length > 0) { + navigate( + generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { + albumArtistId: item.albumArtists[0].id, + }), + ); + } + }, [ctx.data, navigate]); + const contextMenuItems: Record = useMemo(() => { return { addToFavorites: { @@ -772,6 +793,23 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { leftIcon: , onClick: handleDownload, }, + goToAlbum: { + disabled: ctx.data?.length !== 1 || !ctx.data[0]?.albumId, + id: 'goToAlbum', + label: t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' }), + leftIcon: , + onClick: handleGoToAlbum, + }, + goToAlbumArtist: { + disabled: + ctx.data?.length !== 1 || + !ctx.data[0]?.albumArtists || + ctx.data[0]?.albumArtists?.length === 0, + id: 'goToAlbumArtist', + label: t('page.contextMenu.goToAlbumArtist', { postProcess: 'sentenceCase' }), + leftIcon: , + onClick: handleGoToAlbumArtist, + }, moveToBottomOfQueue: { id: 'moveToBottomOfQueue', label: t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' }), @@ -888,6 +926,8 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { handleRemoveSelected, server, handleShareItem, + handleGoToAlbum, + handleGoToAlbumArtist, handleOpenItemDetails, handlePlay, handleUpdateRating, diff --git a/src/renderer/features/context-menu/events.ts b/src/renderer/features/context-menu/events.ts index 28529c17..6165ea13 100644 --- a/src/renderer/features/context-menu/events.ts +++ b/src/renderer/features/context-menu/events.ts @@ -15,6 +15,8 @@ export type ContextMenuItemType = | 'deletePlaylist' | 'deselectAll' | 'download' + | 'goToAlbum' + | 'goToAlbumArtist' | 'moveToBottomOfQueue' | 'moveToNextOfQueue' | 'moveToTopOfQueue' @@ -57,6 +59,8 @@ export const CONFIGURABLE_CONTEXT_MENU_ITEMS: ContextMenuItemType[] = [ 'setRating', 'download', 'shareItem', + 'goToAlbum', + 'goToAlbumArtist', 'showDetails', ]; diff --git a/src/renderer/layouts/default-layout.tsx b/src/renderer/layouts/default-layout.tsx index 7473776d..846057fa 100644 --- a/src/renderer/layouts/default-layout.tsx +++ b/src/renderer/layouts/default-layout.tsx @@ -6,6 +6,7 @@ import { useNavigate } from 'react-router'; import styles from './default-layout.module.css'; +import { ContextMenuProvider } from '/@/renderer/features/context-menu'; import { CommandPalette } from '/@/renderer/features/search/components/command-palette'; import { MainContent } from '/@/renderer/layouts/default-layout/main-content'; import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar'; @@ -73,7 +74,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => { ]); return ( - <> +
{
- +
); };