From 9ffc42688c365bf60a55d8a5a3cc4eca43523d82 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Tue, 30 Jun 2026 01:27:01 +0200 Subject: [PATCH] refactor(artist): co-locate artist feature into features/artist --- src/api/subsonic.async.test.ts | 2 +- src/api/subsonicRatings.test.ts | 4 +- src/api/subsonicRatings.ts | 2 +- src/app/AppRoutes.tsx | 4 +- src/components/AlbumCard.tsx | 2 +- src/components/AlbumHeader.test.tsx | 2 +- src/components/AlbumHeader.tsx | 2 +- src/components/BecauseYouLikeRail.tsx | 2 +- src/components/MobilePlayerView.tsx | 2 +- src/components/PlayerBar.tsx | 2 +- src/components/SongCard.test.tsx | 5 +- src/components/SongCard.tsx | 4 +- src/components/SongRow.tsx | 2 +- .../contextMenu/SongContextItems.tsx | 2 +- src/components/playerBar/PlayerTrackInfo.tsx | 2 +- .../queuePanel/QueueCurrentTrack.tsx | 2 +- src/components/tracks/TracksPageChrome.tsx | 4 +- .../artist/api/subsonicArtists.router.test.ts | 10 ++-- src/features/artist/api/subsonicArtists.ts | 14 ++--- .../artist/components/ArtistAvatars.tsx | 10 ++-- .../artist/components/ArtistCardLocal.tsx | 14 ++--- .../artist/components/ArtistDetailHero.tsx | 30 +++++----- .../components/ArtistDetailSimilarArtists.tsx | 4 +- .../components/ArtistDetailTopTracks.tsx | 14 ++--- src/features/artist/components/ArtistRow.tsx | 4 +- .../artist/components/ArtistTopTrackCover.tsx | 8 +-- .../artist/components/ArtistsGridView.tsx | 10 ++-- .../artist/components/ArtistsListView.tsx | 8 +-- .../artist/components/OpenArtistRefInline.tsx | 2 +- .../components/topSongAlbumForCover.test.ts | 2 +- .../artist/components/topSongAlbumForCover.ts | 2 +- .../artist/hooks/useArtistDetailData.test.ts | 12 ++-- .../artist/hooks/useArtistDetailData.ts | 16 +++--- .../artist/hooks/useArtistInfoBatch.ts | 6 +- .../hooks/useArtistOfflineState.test.ts | 4 +- .../artist/hooks/useArtistOfflineState.ts | 2 +- .../artist/hooks/useArtistSimilarArtists.ts | 8 +-- .../artist/hooks/useArtistsBrowseCatalog.ts | 8 +-- .../artist/hooks/useArtistsBrowseFilters.ts | 10 ++-- .../hooks/useArtistsBrowseScrollRestore.ts | 4 +- .../artist/hooks/useArtistsFiltering.ts | 6 +- .../artist/hooks/useArtistsInfiniteScroll.ts | 2 +- .../artist/hooks/useBrowseArtistTextSearch.ts | 4 +- .../artist/hooks/useNavigateToArtist.ts | 2 +- src/features/artist/index.ts | 41 ++++++++++++++ src/features/artist/pages/ArtistDetail.tsx | 50 ++++++++--------- src/features/artist/pages/Artists.tsx | 56 +++++++++---------- .../store/artistAlbumYearSortStore.test.ts | 2 +- .../artist/store/artistAlbumYearSortStore.ts | 2 +- .../store/artistBrowseSessionStore.test.ts | 2 +- .../artist/store/artistBrowseSessionStore.ts | 2 +- .../artist/utils/artistsHelpers.test.ts | 2 +- src/features/artist/utils/artistsHelpers.ts | 2 +- .../artist/utils/openArtistRefs.test.ts | 2 +- src/features/artist/utils/openArtistRefs.ts | 2 +- .../artist/utils/runArtistDetailActions.ts | 14 ++--- .../artist/utils/runArtistDetailPlay.test.ts | 4 +- .../artist/utils/runArtistDetailPlay.ts | 10 ++-- .../artist/utils/sortArtistAlbums.test.ts | 4 +- src/features/artist/utils/sortArtistAlbums.ts | 2 +- .../deviceSync/hooks/useDeviceSyncBrowser.ts | 2 +- .../favorites/components/FavoriteSongRow.tsx | 2 +- src/features/favorites/pages/Favorites.tsx | 2 +- .../miniPlayer/components/MiniMeta.tsx | 2 +- src/features/nowPlaying/components/Hero.tsx | 2 +- .../nowPlaying/components/NowPlayingInfo.tsx | 2 +- .../hooks/useNowPlayingFetchers.test.ts | 4 +- .../nowPlaying/hooks/useNowPlayingFetchers.ts | 2 +- src/features/nowPlaying/pages/NowPlaying.tsx | 2 +- .../utils/nowPlayingMetadataResolve.test.ts | 2 +- .../utils/nowPlayingMetadataResolve.ts | 2 +- src/features/offline/store/offlineStore.ts | 2 +- .../utils/favoritesOfflineSync.test.ts | 2 +- .../offline/utils/favoritesOfflineSync.ts | 2 +- .../offline/utils/offlineMediaResolve.test.ts | 2 +- .../offline/utils/offlineMediaResolve.ts | 2 +- .../offline/utils/pinnedOfflineSync.test.ts | 2 +- .../offline/utils/pinnedOfflineSync.ts | 2 +- .../search/components/liveSearchScope.ts | 2 +- .../search/hooks/useLiveSearchRouteScope.ts | 2 +- .../search/pages/SearchBrowsePage.tsx | 2 +- .../components/ArtistLayoutCustomizer.tsx | 2 +- .../components/PersonalisationTab.tsx | 2 +- src/features/stats/api/subsonicStatistics.ts | 2 +- src/hooks/tauriBridge/useCliBridge.ts | 2 +- src/pages/AlbumDetail.tsx | 2 +- src/pages/ComposerDetail.tsx | 2 +- src/pages/Composers.tsx | 6 +- src/pages/Home.tsx | 2 +- src/store/composerBrowseSessionStore.ts | 2 +- src/store/nextAction.ts | 2 +- src/store/nextActionOrbitRadio.test.ts | 2 +- src/store/playerStore.persistence.test.ts | 2 +- .../album/deriveAlbumHeaderArtistRefs.ts | 2 +- .../componentHelpers/contextMenuActions.ts | 2 +- src/utils/migrations/advancedModeMigration.ts | 2 +- src/utils/mix/luckyMix.ts | 2 +- src/utils/mix/luckyMixHelpers.ts | 2 +- .../buildInfiniteQueueCandidates.test.ts | 7 ++- .../playback/buildInfiniteQueueCandidates.ts | 2 +- src/utils/playback/songToTrack.ts | 2 +- src/utils/playback/trackArtistRefs.ts | 2 +- .../share/enqueueShareSearchPayload.test.ts | 2 +- 103 files changed, 301 insertions(+), 254 deletions(-) create mode 100644 src/features/artist/index.ts diff --git a/src/api/subsonic.async.test.ts b/src/api/subsonic.async.test.ts index 87d5befc..cad59075 100644 --- a/src/api/subsonic.async.test.ts +++ b/src/api/subsonic.async.test.ts @@ -31,7 +31,7 @@ import { getAlbumInfo2 } from './subsonicAlbumInfo'; import { getStarred } from './subsonicStarRating'; import { search } from './subsonicSearch'; import { getAlbum, getMusicDirectory, getMusicFolders, getMusicIndexes, getRandomSongs, getSong } from './subsonicLibrary'; -import { getArtists, getTopSongs } from './subsonicArtists'; +import { getArtists, getTopSongs } from '@/features/artist'; import { useAuthStore } from '@/store/authStore'; import { resetAuthStore } from '@/test/helpers/storeReset'; diff --git a/src/api/subsonicRatings.test.ts b/src/api/subsonicRatings.test.ts index a27863c8..295d6e77 100644 --- a/src/api/subsonicRatings.test.ts +++ b/src/api/subsonicRatings.test.ts @@ -1,12 +1,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -vi.mock('./subsonicArtists', () => ({ getArtist: vi.fn() })); +vi.mock('@/features/artist', () => ({ getArtist: vi.fn() })); vi.mock('./subsonicLibrary', () => ({ getAlbum: vi.fn() })); vi.mock('../utils/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForActiveServer: vi.fn(() => true), })); -import { getArtist } from './subsonicArtists'; +import { getArtist } from '@/features/artist'; import { invalidateEntityUserRatingCaches, prefetchArtistUserRatings } from './subsonicRatings'; beforeEach(() => { diff --git a/src/api/subsonicRatings.ts b/src/api/subsonicRatings.ts index 74f6f036..710b7442 100644 --- a/src/api/subsonicRatings.ts +++ b/src/api/subsonicRatings.ts @@ -1,4 +1,4 @@ -import { getArtist } from './subsonicArtists'; +import { getArtist } from '@/features/artist'; import { getAlbum } from './subsonicLibrary'; import { shouldAttemptSubsonicForActiveServer } from '../utils/network/subsonicNetworkGuard'; diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 18b14ec7..4b418f99 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -11,8 +11,8 @@ import { resolveStartRoute } from '@/features/sidebar'; // the entry chunk; each page is fetched when its route is first visited. const Home = lazy(() => import('../pages/Home')); const Albums = lazy(() => import('../pages/Albums')); -const Artists = lazy(() => import('../pages/Artists')); -const ArtistDetail = lazy(() => import('../pages/ArtistDetail')); +const Artists = lazy(() => import('@/features/artist/pages/Artists')); +const ArtistDetail = lazy(() => import('@/features/artist/pages/ArtistDetail')); const Composers = lazy(() => import('../pages/Composers')); const ComposerDetail = lazy(() => import('../pages/ComposerDetail')); const NewReleases = lazy(() => import('../pages/NewReleases')); diff --git a/src/components/AlbumCard.tsx b/src/components/AlbumCard.tsx index db9e50be..67f93556 100644 --- a/src/components/AlbumCard.tsx +++ b/src/components/AlbumCard.tsx @@ -15,7 +15,7 @@ import type { CoverPrefetchPriority } from '../cover/types'; import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../cover/layoutSizes'; import { resolveCoverDisplayTier } from '../cover/tiers'; import { acquireUrl } from '../utils/imageCache/urlPool'; -import { OpenArtistRefInline } from './OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum'; import { useLongPressAction } from '../hooks/useLongPressAction'; import { LongPressWaveOverlay } from './LongPressWaveOverlay'; diff --git a/src/components/AlbumHeader.test.tsx b/src/components/AlbumHeader.test.tsx index 84de801c..f7c489c6 100644 --- a/src/components/AlbumHeader.test.tsx +++ b/src/components/AlbumHeader.test.tsx @@ -19,7 +19,7 @@ vi.mock('../hooks/useAlbumDetailBack', () => ({ useAlbumDetailBack: () => vi.fn( vi.mock('../hooks/useIsMobile', () => ({ useIsMobile: () => false })); vi.mock('../store/themeStore', () => ({ useThemeStore: () => false })); vi.mock('./StarRating', () => ({ default: () => null })); -vi.mock('./OpenArtistRefInline', () => ({ OpenArtistRefInline: () => null })); +vi.mock('@/features/artist', () => ({ OpenArtistRefInline: () => null })); vi.mock('../cover/CoverArtImage', () => ({ CoverArtImage: () => null })); function baseProps() { diff --git a/src/components/AlbumHeader.tsx b/src/components/AlbumHeader.tsx index 34c82c9a..b477169f 100644 --- a/src/components/AlbumHeader.tsx +++ b/src/components/AlbumHeader.tsx @@ -17,7 +17,7 @@ import { isAlbumRecentlyAdded } from '../utils/albumRecency'; import { formatLongDuration } from '../utils/format/formatDuration'; import { formatMb } from '../utils/format/formatBytes'; import { sanitizeHtml } from '../utils/sanitizeHtml'; -import { OpenArtistRefInline } from './OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline'; import { deriveAlbumGenreTags } from '../utils/library/genreTags'; diff --git a/src/components/BecauseYouLikeRail.tsx b/src/components/BecauseYouLikeRail.tsx index 5039244a..4bfdf938 100644 --- a/src/components/BecauseYouLikeRail.tsx +++ b/src/components/BecauseYouLikeRail.tsx @@ -1,4 +1,4 @@ -import { getArtist, getArtistInfo } from '../api/subsonicArtists'; +import { getArtist, getArtistInfo } from '@/features/artist'; import { filterAlbumsToActiveLibrary } from '../api/subsonicLibrary'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; import type { SubsonicAlbum } from '../api/subsonicTypes'; diff --git a/src/components/MobilePlayerView.tsx b/src/components/MobilePlayerView.tsx index c99d5b2b..74bc942d 100644 --- a/src/components/MobilePlayerView.tsx +++ b/src/components/MobilePlayerView.tsx @@ -15,7 +15,7 @@ import { } from 'lucide-react'; import { usePlayerStore } from '../store/playerStore'; import { useCachedUrl } from '@/ui/CachedImage'; -import { OpenArtistRefInline } from './OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import { formatTrackTime } from '../utils/format/formatDuration'; import { resolveQueueTrack } from '../utils/library/queueTrackView'; import { diff --git a/src/components/PlayerBar.tsx b/src/components/PlayerBar.tsx index 8ea006ef..190db3b0 100644 --- a/src/components/PlayerBar.tsx +++ b/src/components/PlayerBar.tsx @@ -22,7 +22,7 @@ import PlaybackDelayModal from './PlaybackDelayModal'; import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; import { usePreviewStore } from '../store/previewStore'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; -import { coerceOpenArtistRefs } from '../utils/openArtistRefs'; +import { coerceOpenArtistRefs } from '@/features/artist'; import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs'; import { PlayerTrackInfo } from './playerBar/PlayerTrackInfo'; import { PlayerTransportControls } from './playerBar/PlayerTransportControls'; diff --git a/src/components/SongCard.test.tsx b/src/components/SongCard.test.tsx index 3cc41f9c..1c40dd69 100644 --- a/src/components/SongCard.test.tsx +++ b/src/components/SongCard.test.tsx @@ -7,7 +7,10 @@ import type { SubsonicSong } from '../api/subsonicTypes'; const navigateToArtist = vi.fn(); -vi.mock('../hooks/useNavigateToArtist', () => ({ +// Only navigate-to-artist is stubbed; mock that submodule directly so the +// barrel re-exports the stub while coerceOpenArtistRefs (used by +// trackArtistRefs, a different submodule) stays real. +vi.mock('@/features/artist/hooks/useNavigateToArtist', () => ({ useNavigateToArtist: () => navigateToArtist, })); diff --git a/src/components/SongCard.tsx b/src/components/SongCard.tsx index f304308b..3d0c0490 100644 --- a/src/components/SongCard.tsx +++ b/src/components/SongCard.tsx @@ -12,8 +12,8 @@ import { enqueueAndPlay } from '../utils/playback/playSong'; import { useDragDrop } from '../contexts/DragDropContext'; import { useOrbitSongRowBehavior } from '@/features/orbit'; import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum'; -import { useNavigateToArtist } from '../hooks/useNavigateToArtist'; -import { OpenArtistRefInline } from './OpenArtistRefInline'; +import { useNavigateToArtist } from '@/features/artist'; +import { OpenArtistRefInline } from '@/features/artist'; import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs'; interface SongCardProps { diff --git a/src/components/SongRow.tsx b/src/components/SongRow.tsx index c7d86c5e..7af2bf76 100644 --- a/src/components/SongRow.tsx +++ b/src/components/SongRow.tsx @@ -2,7 +2,7 @@ import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/playback/songToTrack'; import React, { memo } from 'react'; import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum'; -import { useNavigateToArtist } from '../hooks/useNavigateToArtist'; +import { useNavigateToArtist } from '@/features/artist'; import { Play, ListPlus } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { usePlayerStore } from '../store/playerStore'; diff --git a/src/components/contextMenu/SongContextItems.tsx b/src/components/contextMenu/SongContextItems.tsx index f0b7d777..066c44de 100644 --- a/src/components/contextMenu/SongContextItems.tsx +++ b/src/components/contextMenu/SongContextItems.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import { Play, ListPlus, Radio, Heart, ChevronRight, ChevronsRight, User, Disc3, ListMusic, Info, Sparkles, Star, Trash2, HeartCrack, Share2, Orbit as OrbitIcon } from 'lucide-react'; import { useNavigateToAlbum } from '../../hooks/useNavigateToAlbum'; -import { useNavigateToArtist } from '../../hooks/useNavigateToArtist'; +import { useNavigateToArtist } from '@/features/artist'; import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '@/features/offline'; import { queueSongStar } from '../../store/pendingStarSync'; import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network'; diff --git a/src/components/playerBar/PlayerTrackInfo.tsx b/src/components/playerBar/PlayerTrackInfo.tsx index 14987383..4de84b51 100644 --- a/src/components/playerBar/PlayerTrackInfo.tsx +++ b/src/components/playerBar/PlayerTrackInfo.tsx @@ -10,7 +10,7 @@ import { albumCoverRef } from '../../cover/ref'; import { useAlbumCoverRef } from '../../cover/useLibraryCoverRef'; import { usePlaybackTrackCoverRef } from '../../cover/useLibraryCoverRef'; import MarqueeText from '../MarqueeText'; -import { OpenArtistRefInline } from '../OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import StarRating from '../StarRating'; import { PlaybackBufferingOverlay } from '../playback/PlaybackBufferingOverlay'; import { usePlayerStore } from '../../store/playerStore'; diff --git a/src/components/queuePanel/QueueCurrentTrack.tsx b/src/components/queuePanel/QueueCurrentTrack.tsx index 0533af8e..578b2d3b 100644 --- a/src/components/queuePanel/QueueCurrentTrack.tsx +++ b/src/components/queuePanel/QueueCurrentTrack.tsx @@ -15,7 +15,7 @@ import { useQueueTrackEnrichment } from '../../hooks/useQueueTrackEnrichment'; import { QueueLufsTargetMenu } from './QueueLufsTargetMenu'; import { PlaybackBufferingOverlay } from '../playback/PlaybackBufferingOverlay'; import { CoverArtImage } from '../../cover/CoverArtImage'; -import { OpenArtistRefInline } from '../OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import { usePlaybackTrackCoverRef } from '../../cover/useLibraryCoverRef'; import { usePlayerStore } from '../../store/playerStore'; import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs'; diff --git a/src/components/tracks/TracksPageChrome.tsx b/src/components/tracks/TracksPageChrome.tsx index df232dd8..c9a7a5d9 100644 --- a/src/components/tracks/TracksPageChrome.tsx +++ b/src/components/tracks/TracksPageChrome.tsx @@ -12,8 +12,8 @@ import { playSongNow } from '../../utils/playback/playSong'; import { ndListSongs, ndInvalidateSongsCache } from '../../api/navidromeBrowse'; import { usePerfProbeFlags } from '../../utils/perf/perfFlags'; import { useNavigateToAlbum } from '../../hooks/useNavigateToAlbum'; -import { useNavigateToArtist } from '../../hooks/useNavigateToArtist'; -import { OpenArtistRefInline } from '../OpenArtistRefInline'; +import { useNavigateToArtist } from '@/features/artist'; +import { OpenArtistRefInline } from '@/features/artist'; import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs'; const RANDOM_RAIL_SIZE = 18; diff --git a/src/features/artist/api/subsonicArtists.router.test.ts b/src/features/artist/api/subsonicArtists.router.test.ts index 478d9464..32cbc1fe 100644 --- a/src/features/artist/api/subsonicArtists.router.test.ts +++ b/src/features/artist/api/subsonicArtists.router.test.ts @@ -1,21 +1,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -vi.mock('./subsonicClient', () => ({ +vi.mock('@/api/subsonicClient', () => ({ api: vi.fn(), apiForServer: vi.fn(), libraryFilterParams: () => ({}), libraryFilterParamsForServer: () => ({}), })); -vi.mock('./subsonicLibrary', () => ({ +vi.mock('@/api/subsonicLibrary', () => ({ filterSongsToActiveLibrary: async (songs: unknown[]) => songs, filterSongsToServerLibrary: async (songs: unknown[]) => songs, similarSongsRequestCount: (count: number) => count, })); -import { api } from './subsonicClient'; -import { fetchSimilarTracksRouted } from './subsonicArtists'; -import { useAuthStore } from '../store/authStore'; +import { api } from '@/api/subsonicClient'; +import { fetchSimilarTracksRouted } from '@/features/artist/api/subsonicArtists'; +import { useAuthStore } from '@/store/authStore'; const SID = 'srv-router'; const apiMock = vi.mocked(api); diff --git a/src/features/artist/api/subsonicArtists.ts b/src/features/artist/api/subsonicArtists.ts index bb489c52..2a91ccf8 100644 --- a/src/features/artist/api/subsonicArtists.ts +++ b/src/features/artist/api/subsonicArtists.ts @@ -1,18 +1,18 @@ -import { useAuthStore } from '../store/authStore'; -import { api, apiForServer, libraryFilterParams, libraryFilterParamsForServer } from './subsonicClient'; -import { filterSongsToServerLibrary } from './subsonicLibrary'; -import { filterSongsToActiveLibrary, similarSongsRequestCount } from './subsonicLibrary'; +import { useAuthStore } from '@/store/authStore'; +import { api, apiForServer, libraryFilterParams, libraryFilterParamsForServer } from '@/api/subsonicClient'; +import { filterSongsToServerLibrary } from '@/api/subsonicLibrary'; +import { filterSongsToActiveLibrary, similarSongsRequestCount } from '@/api/subsonicLibrary'; import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS, OP_SIMILAR_TRACKS, -} from '../serverCapabilities/catalog'; -import { resolveCallRoutesForServer } from '../serverCapabilities/storeView'; +} from '@/serverCapabilities/catalog'; +import { resolveCallRoutesForServer } from '@/serverCapabilities/storeView'; import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo, SubsonicSong, -} from './subsonicTypes'; +} from '@/api/subsonicTypes'; export async function getArtists(): Promise { type ArtistIndexEntry = { artist?: SubsonicArtist | SubsonicArtist[] }; diff --git a/src/features/artist/components/ArtistAvatars.tsx b/src/features/artist/components/ArtistAvatars.tsx index 3e8ea6e3..b169180f 100644 --- a/src/features/artist/components/ArtistAvatars.tsx +++ b/src/features/artist/components/ArtistAvatars.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import type { SubsonicArtist } from '../../api/subsonicTypes'; -import { ArtistCoverArtImage } from '../../cover/ArtistCoverArtImage'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; +import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage'; import { COVER_DENSE_ARTIST_LIST_CSS_PX, COVER_DENSE_GRID_MIN_CELL_CSS_PX, -} from '../../cover/layoutSizes'; -import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '../../constants/appScroll'; -import { nameColor, nameInitial } from '../../utils/componentHelpers/artistsHelpers'; +} from '@/cover/layoutSizes'; +import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; +import { nameColor, nameInitial } from '@/features/artist/utils/artistsHelpers'; interface AvatarProps { artist: SubsonicArtist; diff --git a/src/features/artist/components/ArtistCardLocal.tsx b/src/features/artist/components/ArtistCardLocal.tsx index ee292f71..e007d131 100644 --- a/src/features/artist/components/ArtistCardLocal.tsx +++ b/src/features/artist/components/ArtistCardLocal.tsx @@ -1,13 +1,13 @@ -import type { SubsonicArtist } from '../api/subsonicTypes'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; import React, { useMemo } from 'react'; import { Users } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { CoverArtImage } from '../cover/CoverArtImage'; -import { useArtistCoverRef } from '../cover/useLibraryCoverRef'; -import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../cover/layoutSizes'; -import { useNavigateToArtist } from '../hooks/useNavigateToArtist'; -import { coverServerScopeForServerId } from '../cover/serverScope'; -import { appendServerQuery } from '../utils/navigation/detailServerScope'; +import { CoverArtImage } from '@/cover/CoverArtImage'; +import { useArtistCoverRef } from '@/cover/useLibraryCoverRef'; +import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes'; +import { useNavigateToArtist } from '@/features/artist/hooks/useNavigateToArtist'; +import { coverServerScopeForServerId } from '@/cover/serverScope'; +import { appendServerQuery } from '@/utils/navigation/detailServerScope'; interface Props { artist: SubsonicArtist; diff --git a/src/features/artist/components/ArtistDetailHero.tsx b/src/features/artist/components/ArtistDetailHero.tsx index 8934aeaf..dc88a0bb 100644 --- a/src/features/artist/components/ArtistDetailHero.tsx +++ b/src/features/artist/components/ArtistDetailHero.tsx @@ -1,26 +1,26 @@ import React, { useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAlbumDetailBack } from '../../hooks/useAlbumDetailBack'; +import { useAlbumDetailBack } from '@/hooks/useAlbumDetailBack'; import { ArrowLeft, Camera, Check, HardDriveDownload, Heart, Loader2, Play, Radio, Share2, Shuffle, Users, } from 'lucide-react'; -import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '../../api/subsonicTypes'; +import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '@/api/subsonicTypes'; import { useOfflineStore } from '@/features/offline'; -import { useAuthStore } from '../../store/authStore'; -import { useThemeStore } from '../../store/themeStore'; -import { useArtistOfflineState } from '../../hooks/useArtistOfflineState'; -import { useIsMobile } from '../../hooks/useIsMobile'; -import { ArtistHeroCover } from '../../cover/artistHero'; -import { useArtistBanner, useArtistFanart } from '../../cover/useArtistFanart'; -import { backdropFromConfig } from '../../cover/artistBackdrop'; -import { usePlaybackCoverArt } from '../../cover/usePlaybackCoverArt'; +import { useAuthStore } from '@/store/authStore'; +import { useThemeStore } from '@/store/themeStore'; +import { useArtistOfflineState } from '@/features/artist/hooks/useArtistOfflineState'; +import { useIsMobile } from '@/hooks/useIsMobile'; +import { ArtistHeroCover } from '@/cover/artistHero'; +import { useArtistBanner, useArtistFanart } from '@/cover/useArtistFanart'; +import { backdropFromConfig } from '@/cover/artistBackdrop'; +import { usePlaybackCoverArt } from '@/cover/usePlaybackCoverArt'; import { useCachedUrl } from '@/ui/CachedImage'; -import { useCoverLightboxSrc } from '../../cover/lightbox'; -import type { CoverArtRef } from '../../cover/types'; -import LastfmIcon from '../LastfmIcon'; -import WikipediaIcon from '../WikipediaIcon'; -import StarRating from '../StarRating'; +import { useCoverLightboxSrc } from '@/cover/lightbox'; +import type { CoverArtRef } from '@/cover/types'; +import LastfmIcon from '@/components/LastfmIcon'; +import WikipediaIcon from '@/components/WikipediaIcon'; +import StarRating from '@/components/StarRating'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline'; diff --git a/src/features/artist/components/ArtistDetailSimilarArtists.tsx b/src/features/artist/components/ArtistDetailSimilarArtists.tsx index 2137db63..33354438 100644 --- a/src/features/artist/components/ArtistDetailSimilarArtists.tsx +++ b/src/features/artist/components/ArtistDetailSimilarArtists.tsx @@ -2,8 +2,8 @@ import React, { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { ChevronDown, ChevronUp } from 'lucide-react'; -import type { SubsonicArtist } from '../../api/subsonicTypes'; -import { useIsMobile } from '../../hooks/useIsMobile'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; +import { useIsMobile } from '@/hooks/useIsMobile'; interface Props { marginTop: string; diff --git a/src/features/artist/components/ArtistDetailTopTracks.tsx b/src/features/artist/components/ArtistDetailTopTracks.tsx index 37255b7e..36f2daff 100644 --- a/src/features/artist/components/ArtistDetailTopTracks.tsx +++ b/src/features/artist/components/ArtistDetailTopTracks.tsx @@ -1,14 +1,14 @@ import React, { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { AudioLines, ChevronRight, Play, Square } from 'lucide-react'; -import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; -import { usePlayerStore } from '../../store/playerStore'; -import { previewInputFromSong, usePreviewStore } from '../../store/previewStore'; +import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes'; +import { usePlayerStore } from '@/store/playerStore'; +import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; import { useOrbitSongRowBehavior } from '@/features/orbit'; -import { songToTrack } from '../../utils/playback/songToTrack'; -import { formatTrackTime } from '../../utils/format/formatDuration'; -import ArtistTopTrackCover from './ArtistTopTrackCover'; -import { topSongAlbumForCover } from './topSongAlbumForCover'; +import { songToTrack } from '@/utils/playback/songToTrack'; +import { formatTrackTime } from '@/utils/format/formatDuration'; +import ArtistTopTrackCover from '@/features/artist/components/ArtistTopTrackCover'; +import { topSongAlbumForCover } from '@/features/artist/components/topSongAlbumForCover'; interface Props { topSongs: SubsonicSong[]; diff --git a/src/features/artist/components/ArtistRow.tsx b/src/features/artist/components/ArtistRow.tsx index 492f8cf4..9a98baad 100644 --- a/src/features/artist/components/ArtistRow.tsx +++ b/src/features/artist/components/ArtistRow.tsx @@ -1,6 +1,6 @@ -import type { SubsonicArtist } from '../api/subsonicTypes'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; import React, { useRef, useState, useEffect, useLayoutEffect } from 'react'; -import ArtistCardLocal from './ArtistCardLocal'; +import ArtistCardLocal from '@/features/artist/components/ArtistCardLocal'; import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; diff --git a/src/features/artist/components/ArtistTopTrackCover.tsx b/src/features/artist/components/ArtistTopTrackCover.tsx index 128b0972..3c2c09ca 100644 --- a/src/features/artist/components/ArtistTopTrackCover.tsx +++ b/src/features/artist/components/ArtistTopTrackCover.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { CoverArtImage } from '../../cover/CoverArtImage'; -import { useAlbumCoverRef } from '../../cover/useLibraryCoverRef'; -import { COVER_ARTIST_TOP_TRACK_CSS_PX } from '../../cover/layoutSizes'; -import type { TopSongAlbumCoverSource } from './topSongAlbumForCover'; +import { CoverArtImage } from '@/cover/CoverArtImage'; +import { useAlbumCoverRef } from '@/cover/useLibraryCoverRef'; +import { COVER_ARTIST_TOP_TRACK_CSS_PX } from '@/cover/layoutSizes'; +import type { TopSongAlbumCoverSource } from '@/features/artist/components/topSongAlbumForCover'; /** 32px album thumb — same cover ref path as {@link AlbumCard} on artist pages. */ export default function ArtistTopTrackCover({ album }: { album: TopSongAlbumCoverSource }) { diff --git a/src/features/artist/components/ArtistsGridView.tsx b/src/features/artist/components/ArtistsGridView.tsx index 6358c75a..0cb94f00 100644 --- a/src/features/artist/components/ArtistsGridView.tsx +++ b/src/features/artist/components/ArtistsGridView.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { Check } from 'lucide-react'; import type { TFunction } from 'i18next'; -import type { SubsonicArtist } from '../../api/subsonicTypes'; -import type { PlayerState } from '../../store/playerStoreTypes'; -import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '../../constants/appScroll'; -import { VirtualCardGrid } from '../VirtualCardGrid'; -import { ArtistCardAvatar } from './ArtistAvatars'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; +import type { PlayerState } from '@/store/playerStoreTypes'; +import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; +import { VirtualCardGrid } from '@/components/VirtualCardGrid'; +import { ArtistCardAvatar } from '@/features/artist/components/ArtistAvatars'; interface TileProps { artist: SubsonicArtist; diff --git a/src/features/artist/components/ArtistsListView.tsx b/src/features/artist/components/ArtistsListView.tsx index b80db2e5..2d07453a 100644 --- a/src/features/artist/components/ArtistsListView.tsx +++ b/src/features/artist/components/ArtistsListView.tsx @@ -1,10 +1,10 @@ import React from 'react'; import type { Virtualizer } from '@tanstack/react-virtual'; import type { TFunction } from 'i18next'; -import type { SubsonicArtist } from '../../api/subsonicTypes'; -import type { PlayerState } from '../../store/playerStoreTypes'; -import { OTHER_BUCKET, type ArtistListFlatRow } from '../../utils/componentHelpers/artistsHelpers'; -import { ArtistRowAvatar } from './ArtistAvatars'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; +import type { PlayerState } from '@/store/playerStoreTypes'; +import { OTHER_BUCKET, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers'; +import { ArtistRowAvatar } from '@/features/artist/components/ArtistAvatars'; interface RowProps { artist: SubsonicArtist; diff --git a/src/features/artist/components/OpenArtistRefInline.tsx b/src/features/artist/components/OpenArtistRefInline.tsx index 77022df7..f314b3e0 100644 --- a/src/features/artist/components/OpenArtistRefInline.tsx +++ b/src/features/artist/components/OpenArtistRefInline.tsx @@ -1,5 +1,5 @@ import React, { Fragment } from 'react'; -import type { SubsonicOpenArtistRef } from '../api/subsonicTypes'; +import type { SubsonicOpenArtistRef } from '@/api/subsonicTypes'; interface Props { refs: SubsonicOpenArtistRef[]; diff --git a/src/features/artist/components/topSongAlbumForCover.test.ts b/src/features/artist/components/topSongAlbumForCover.test.ts index 0a106693..a9e6020f 100644 --- a/src/features/artist/components/topSongAlbumForCover.test.ts +++ b/src/features/artist/components/topSongAlbumForCover.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { topSongAlbumForCover, topSongAlbumsForCoverWarm, artistDetailCoverWarmAlbums } from './topSongAlbumForCover'; +import { topSongAlbumForCover, topSongAlbumsForCoverWarm, artistDetailCoverWarmAlbums } from '@/features/artist/components/topSongAlbumForCover'; describe('topSongAlbumForCover', () => { it('uses the artist album row when albumId matches', () => { diff --git a/src/features/artist/components/topSongAlbumForCover.ts b/src/features/artist/components/topSongAlbumForCover.ts index 844e3c9e..6f492f7c 100644 --- a/src/features/artist/components/topSongAlbumForCover.ts +++ b/src/features/artist/components/topSongAlbumForCover.ts @@ -1,4 +1,4 @@ -import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; +import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes'; export type TopSongAlbumCoverSource = Pick; diff --git a/src/features/artist/hooks/useArtistDetailData.test.ts b/src/features/artist/hooks/useArtistDetailData.test.ts index 10334e3f..acf9e28c 100644 --- a/src/features/artist/hooks/useArtistDetailData.test.ts +++ b/src/features/artist/hooks/useArtistDetailData.test.ts @@ -13,14 +13,14 @@ import { renderHook, act, waitFor } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { SubsonicArtistInfo } from '../api/subsonicTypes'; +import type { SubsonicArtistInfo } from '@/api/subsonicTypes'; -vi.mock('../api/subsonicArtists'); -vi.mock('../api/subsonicSearch'); +vi.mock('@/features/artist/api/subsonicArtists'); +vi.mock('@/api/subsonicSearch'); -import { getArtist, getArtistInfo, getTopSongs } from '../api/subsonicArtists'; -import { search } from '../api/subsonicSearch'; -import { useArtistDetailData } from './useArtistDetailData'; +import { getArtist, getArtistInfo, getTopSongs } from '@/features/artist/api/subsonicArtists'; +import { search } from '@/api/subsonicSearch'; +import { useArtistDetailData } from '@/features/artist/hooks/useArtistDetailData'; const mockArtistInfo = vi.mocked(getArtistInfo) as unknown as { mockImplementation: (impl: (id: string) => Promise) => void; diff --git a/src/features/artist/hooks/useArtistDetailData.ts b/src/features/artist/hooks/useArtistDetailData.ts index 9ce6c7ac..ee4c241c 100644 --- a/src/features/artist/hooks/useArtistDetailData.ts +++ b/src/features/artist/hooks/useArtistDetailData.ts @@ -1,18 +1,18 @@ import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { search } from '../api/subsonicSearch'; -import { getArtist, getArtistForServer, getArtistInfo, getTopSongs } from '../api/subsonicArtists'; +import { search } from '@/api/subsonicSearch'; +import { getArtist, getArtistForServer, getArtistInfo, getTopSongs } from '@/features/artist/api/subsonicArtists'; import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo, SubsonicSong, -} from '../api/subsonicTypes'; -import { useAuthStore } from '../store/authStore'; -import { useConnectionStatus } from './useConnectionStatus'; +} from '@/api/subsonicTypes'; +import { useAuthStore } from '@/store/authStore'; +import { useConnectionStatus } from '@/hooks/useConnectionStatus'; import { loadArtistFromLibraryIndex } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline'; import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline'; -import { readDetailServerId } from '../utils/navigation/detailServerScope'; -import { runLocalArtistLosslessBrowse } from '../utils/library/browseTextSearch'; -import { isLosslessSuffix } from '../utils/library/losslessFormats'; +import { readDetailServerId } from '@/utils/navigation/detailServerScope'; +import { runLocalArtistLosslessBrowse } from '@/utils/library/browseTextSearch'; +import { isLosslessSuffix } from '@/utils/library/losslessFormats'; export interface UseArtistDetailDataOptions { /** When true, albums and top tracks are limited to lossless containers (local index preferred). */ diff --git a/src/features/artist/hooks/useArtistInfoBatch.ts b/src/features/artist/hooks/useArtistInfoBatch.ts index 5da34665..6ea0d85b 100644 --- a/src/features/artist/hooks/useArtistInfoBatch.ts +++ b/src/features/artist/hooks/useArtistInfoBatch.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; -import { getArtistInfoForServer } from '../api/subsonicArtists'; -import type { SubsonicArtistInfo, SubsonicOpenArtistRef } from '../api/subsonicTypes'; -import { makeCache } from '../utils/cache/nowPlayingCache'; +import { getArtistInfoForServer } from '@/features/artist/api/subsonicArtists'; +import type { SubsonicArtistInfo, SubsonicOpenArtistRef } from '@/api/subsonicTypes'; +import { makeCache } from '@/utils/cache/nowPlayingCache'; const artistInfoCache = makeCache(); diff --git a/src/features/artist/hooks/useArtistOfflineState.test.ts b/src/features/artist/hooks/useArtistOfflineState.test.ts index 8d5c1508..f8927ea9 100644 --- a/src/features/artist/hooks/useArtistOfflineState.test.ts +++ b/src/features/artist/hooks/useArtistOfflineState.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { renderHook } from '@testing-library/react'; -import { useLocalPlaybackStore } from '../store/localPlaybackStore'; +import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { useOfflineJobStore } from '@/features/offline'; -import { useArtistOfflineState } from './useArtistOfflineState'; +import { useArtistOfflineState } from '@/features/artist/hooks/useArtistOfflineState'; describe('useArtistOfflineState', () => { beforeEach(() => { diff --git a/src/features/artist/hooks/useArtistOfflineState.ts b/src/features/artist/hooks/useArtistOfflineState.ts index 5588ab98..16bad8cc 100644 --- a/src/features/artist/hooks/useArtistOfflineState.ts +++ b/src/features/artist/hooks/useArtistOfflineState.ts @@ -1,4 +1,4 @@ -import { useLocalPlaybackStore } from '../store/localPlaybackStore'; +import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { useOfflineJobStore } from '@/features/offline'; import { isOfflinePinComplete } from '@/features/offline'; diff --git a/src/features/artist/hooks/useArtistSimilarArtists.ts b/src/features/artist/hooks/useArtistSimilarArtists.ts index 6fd31e46..81fba5d4 100644 --- a/src/features/artist/hooks/useArtistSimilarArtists.ts +++ b/src/features/artist/hooks/useArtistSimilarArtists.ts @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; -import { getMusicNetworkRuntime } from '../music-network'; -import { search } from '../api/subsonicSearch'; -import type { SubsonicArtist, SubsonicArtistInfo } from '../api/subsonicTypes'; -import { useAuthStore } from '../store/authStore'; +import { getMusicNetworkRuntime } from '@/music-network'; +import { search } from '@/api/subsonicSearch'; +import type { SubsonicArtist, SubsonicArtistInfo } from '@/api/subsonicTypes'; +import { useAuthStore } from '@/store/authStore'; export interface ArtistSimilarArtistsResult { similarArtists: SubsonicArtist[]; diff --git a/src/features/artist/hooks/useArtistsBrowseCatalog.ts b/src/features/artist/hooks/useArtistsBrowseCatalog.ts index f49348ce..436bce89 100644 --- a/src/features/artist/hooks/useArtistsBrowseCatalog.ts +++ b/src/features/artist/hooks/useArtistsBrowseCatalog.ts @@ -1,11 +1,11 @@ -import { getArtists } from '../api/subsonicArtists'; -import type { SubsonicArtist } from '../api/subsonicTypes'; +import { getArtists } from '@/features/artist/api/subsonicArtists'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { dedupeById } from '../utils/dedupeById'; +import { dedupeById } from '@/utils/dedupeById'; import { fetchLocalArtistCatalogChunk, fetchNetworkStarredArtists, -} from '../utils/library/browseTextSearch'; +} from '@/utils/library/browseTextSearch'; import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseReloadToken } from '@/features/offline'; import { diff --git a/src/features/artist/hooks/useArtistsBrowseFilters.ts b/src/features/artist/hooks/useArtistsBrowseFilters.ts index 88543bfe..30735609 100644 --- a/src/features/artist/hooks/useArtistsBrowseFilters.ts +++ b/src/features/artist/hooks/useArtistsBrowseFilters.ts @@ -1,16 +1,16 @@ import { useEffect, useRef, useState, type RefObject } from 'react'; import { useLocation, useNavigationType, type NavigationType } from 'react-router-dom'; -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { DEFAULT_ARTIST_BROWSE_RETURN_STATE, type ArtistBrowseReturnState, type ArtistBrowseViewMode, isArtistsBrowsePath, useArtistBrowseSessionStore, -} from '../store/artistBrowseSessionStore'; -import { isArtistDetailPath } from '../store/albumBrowseSessionStore'; -import { shouldRestoreArtistBrowseSession } from '../utils/navigation/albumDetailNavigation'; -import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore'; +} from '@/features/artist/store/artistBrowseSessionStore'; +import { isArtistDetailPath } from '@/store/albumBrowseSessionStore'; +import { shouldRestoreArtistBrowseSession } from '@/utils/navigation/albumDetailNavigation'; +import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore'; export type ArtistBrowseScrollSnapshot = { scrollTop: number; diff --git a/src/features/artist/hooks/useArtistsBrowseScrollRestore.ts b/src/features/artist/hooks/useArtistsBrowseScrollRestore.ts index 10d2cd4d..e9c001ac 100644 --- a/src/features/artist/hooks/useArtistsBrowseScrollRestore.ts +++ b/src/features/artist/hooks/useArtistsBrowseScrollRestore.ts @@ -3,8 +3,8 @@ import { useLocation, useNavigationType, type NavigationType } from 'react-route import { peekArtistBrowseScrollRestore, useArtistBrowseSessionStore, -} from '../store/artistBrowseSessionStore'; -import { shouldRestoreArtistBrowseSession } from '../utils/navigation/albumDetailNavigation'; +} from '@/features/artist/store/artistBrowseSessionStore'; +import { shouldRestoreArtistBrowseSession } from '@/utils/navigation/albumDetailNavigation'; type PendingScroll = { scrollTop: number; diff --git a/src/features/artist/hooks/useArtistsFiltering.ts b/src/features/artist/hooks/useArtistsFiltering.ts index 17fb40e7..046f6a45 100644 --- a/src/features/artist/hooks/useArtistsFiltering.ts +++ b/src/features/artist/hooks/useArtistsFiltering.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; -import type { SubsonicArtist } from '../api/subsonicTypes'; -import { usePlayerStore } from '../store/playerStore'; -import { ALL_SENTINEL, artistLetterBucket, compareBuckets, type ArtistListFlatRow } from '../utils/componentHelpers/artistsHelpers'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; +import { usePlayerStore } from '@/store/playerStore'; +import { ALL_SENTINEL, artistLetterBucket, compareBuckets, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers'; interface UseArtistsFilteringArgs { artists: SubsonicArtist[]; diff --git a/src/features/artist/hooks/useArtistsInfiniteScroll.ts b/src/features/artist/hooks/useArtistsInfiniteScroll.ts index aadbf682..422711ed 100644 --- a/src/features/artist/hooks/useArtistsInfiniteScroll.ts +++ b/src/features/artist/hooks/useArtistsInfiniteScroll.ts @@ -6,4 +6,4 @@ export { useClientSliceInfiniteScroll as useArtistsInfiniteScroll, type UseClientSliceInfiniteScrollArgs as UseArtistsInfiniteScrollArgs, type UseClientSliceInfiniteScrollResult as UseArtistsInfiniteScrollResult, -} from './useClientSliceInfiniteScroll'; +} from '@/hooks/useClientSliceInfiniteScroll'; diff --git a/src/features/artist/hooks/useBrowseArtistTextSearch.ts b/src/features/artist/hooks/useBrowseArtistTextSearch.ts index 97a6236c..517ed461 100644 --- a/src/features/artist/hooks/useBrowseArtistTextSearch.ts +++ b/src/features/artist/hooks/useBrowseArtistTextSearch.ts @@ -1,4 +1,4 @@ -import type { SubsonicArtist } from '../api/subsonicTypes'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; import { useEffect, useRef, useState } from 'react'; import { BROWSE_TEXT_DEBOUNCE_NETWORK_MS, @@ -8,7 +8,7 @@ import { runLocalBrowseArtists, runNetworkBrowseArtists, type LibrarySearchSurface, -} from '../utils/library/browseTextSearch'; +} from '@/utils/library/browseTextSearch'; import { useOfflineBrowseContext } from '@/features/offline'; import { offlineLocalBrowseEnabled, searchOfflineLocalArtists } from '@/features/offline'; diff --git a/src/features/artist/hooks/useNavigateToArtist.ts b/src/features/artist/hooks/useNavigateToArtist.ts index 5a108e68..9096959b 100644 --- a/src/features/artist/hooks/useNavigateToArtist.ts +++ b/src/features/artist/hooks/useNavigateToArtist.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { navigateToArtistDetail } from '../utils/navigation/albumDetailNavigation'; +import { navigateToArtistDetail } from '@/utils/navigation/albumDetailNavigation'; /** Navigate to artist detail, remembering the current page for the back button. */ export function useNavigateToArtist() { diff --git a/src/features/artist/index.ts b/src/features/artist/index.ts new file mode 100644 index 00000000..7cca687a --- /dev/null +++ b/src/features/artist/index.ts @@ -0,0 +1,41 @@ +/** + * Artist feature — the Artists overview + ArtistDetail (both lazy via their deep + * `pages/*` paths, not re-exported), artist cards/rows/avatars, the detail hero + + * similar-artists + top-tracks UI, the artist Subsonic API, browse catalog/ + * filter/scroll hooks, per-artist offline status, and the open-artist-ref nav + * cluster. `sortArtistAlbums` moved in from `utils/library/` (artist-only sort, + * not the shared index query engine). + * + * Stays OUT (other owners): `deriveAlbumHeaderArtistRefs` (album header), + * `playArtistShuffled` + `trackArtistRefs` (playback/track helpers), the + * `*ContextItems`/`*ToPlaylistSubmenu` items (the cross-cutting context-menu + * subsystem, shared with album), and `PlaylistArtistCell` (playlist). + */ +export * from './api/subsonicArtists'; +export * from './hooks/useArtistDetailData'; +export * from './hooks/useArtistInfoBatch'; +export * from './hooks/useArtistOfflineState'; +export * from './hooks/useArtistsBrowseCatalog'; +export * from './hooks/useArtistsBrowseFilters'; +export * from './hooks/useArtistsBrowseScrollReset'; +export * from './hooks/useArtistsBrowseScrollRestore'; +export * from './hooks/useArtistsFiltering'; +export * from './hooks/useArtistSimilarArtists'; +export * from './hooks/useArtistsInfiniteScroll'; +export * from './hooks/useBrowseArtistTextSearch'; +export * from './hooks/useNavigateToArtist'; +export * from './store/artistAlbumYearSortStore'; +export * from './store/artistBrowseSessionStore'; +export * from './store/artistLayoutStore'; +export * from './utils/artistsHelpers'; +export * from './utils/openArtistRefs'; +export * from './utils/runArtistDetailActions'; +export * from './utils/runArtistDetailPlay'; +export * from './utils/sortArtistAlbums'; +export { OpenArtistRefInline } from './components/OpenArtistRefInline'; +export { default as ArtistCardLocal } from './components/ArtistCardLocal'; +export { default as ArtistDetailHero } from './components/ArtistDetailHero'; +export { default as ArtistDetailSimilarArtists } from './components/ArtistDetailSimilarArtists'; +export { default as ArtistDetailTopTracks } from './components/ArtistDetailTopTracks'; +export { default as ArtistRow } from './components/ArtistRow'; +export { default as ArtistTopTrackCover } from './components/ArtistTopTrackCover'; diff --git a/src/features/artist/pages/ArtistDetail.tsx b/src/features/artist/pages/ArtistDetail.tsx index f468ed7a..2d065d3d 100644 --- a/src/features/artist/pages/ArtistDetail.tsx +++ b/src/features/artist/pages/ArtistDetail.tsx @@ -1,44 +1,44 @@ -import { useCoverArt } from '../cover/useCoverArt'; -import { useArtistCoverRef } from '../cover/useLibraryCoverRef'; -import type { SubsonicArtist, SubsonicAlbum } from '../api/subsonicTypes'; +import { useCoverArt } from '@/cover/useCoverArt'; +import { useArtistCoverRef } from '@/cover/useLibraryCoverRef'; +import type { SubsonicArtist, SubsonicAlbum } from '@/api/subsonicTypes'; import { useEffect, useState, Fragment, useMemo } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; -import AlbumCard from '../components/AlbumCard'; +import AlbumCard from '@/components/AlbumCard'; import { ArrowDownUp } from 'lucide-react'; import { open } from '@tauri-apps/plugin-shell'; -import { usePlayerStore } from '../store/playerStore'; -import { useAuthStore } from '../store/authStore'; +import { usePlayerStore } from '@/store/playerStore'; +import { useAuthStore } from '@/store/authStore'; import { useTranslation } from 'react-i18next'; -import { useArtistLayoutStore, type ArtistSectionId } from '../store/artistLayoutStore'; +import { useArtistLayoutStore, type ArtistSectionId } from '@/features/artist/store/artistLayoutStore'; import { DEFAULT_ARTIST_ALBUM_YEAR_ORDER, useArtistAlbumYearSortStore, -} from '../store/artistAlbumYearSortStore'; +} from '@/features/artist/store/artistAlbumYearSortStore'; -import { useArtistDetailData } from '../hooks/useArtistDetailData'; -import { useArtistSimilarArtists } from '../hooks/useArtistSimilarArtists'; +import { useArtistDetailData } from '@/features/artist/hooks/useArtistDetailData'; +import { useArtistSimilarArtists } from '@/features/artist/hooks/useArtistSimilarArtists'; import { runArtistDetailPlayAll, runArtistDetailPlayTopSong, runArtistDetailShuffle, runArtistDetailStartRadio, -} from '../utils/componentHelpers/runArtistDetailPlay'; +} from '@/features/artist/utils/runArtistDetailPlay'; import { useOfflineBrowseContext } from '@/features/offline'; import { offlineActionPolicy } from '@/features/offline'; import { runArtistEntityRating, runArtistToggleStar, runArtistShare, runArtistImageUpload, -} from '../utils/componentHelpers/runArtistDetailActions'; -import ArtistDetailHero from '../components/artistDetail/ArtistDetailHero'; -import ArtistDetailTopTracks from '../components/artistDetail/ArtistDetailTopTracks'; -import ArtistDetailSimilarArtists from '../components/artistDetail/ArtistDetailSimilarArtists'; +} from '@/features/artist/utils/runArtistDetailActions'; +import ArtistDetailHero from '@/features/artist/components/ArtistDetailHero'; +import ArtistDetailTopTracks from '@/features/artist/components/ArtistDetailTopTracks'; +import ArtistDetailSimilarArtists from '@/features/artist/components/ArtistDetailSimilarArtists'; import { ArtistCard } from '@/features/nowPlaying'; -import LosslessModeBanner from '../components/LosslessModeBanner'; -import { usePerfProbeFlags } from '../utils/perf/perfFlags'; -import { albumGridWarmCovers, COVER_DENSE_GRID_MIN_CELL_CSS_PX, GRID_COVER_WARM_LIMIT } from '../cover/layoutSizes'; -import { artistDetailCoverWarmAlbums } from '../components/artistDetail/topSongAlbumForCover'; -import { useLibraryCoverPrefetch } from '../cover/useLibraryCoverPrefetch'; -import { useWarmGridCovers } from '../hooks/useWarmGridCovers'; -import { VirtualCardGrid } from '../components/VirtualCardGrid'; -import { LOSSLESS_MODE_QUERY } from '../utils/library/losslessMode'; -import { sortArtistAlbumsByYear } from '../utils/library/sortArtistAlbums'; -import { readDetailServerId } from '../utils/navigation/detailServerScope'; +import LosslessModeBanner from '@/components/LosslessModeBanner'; +import { usePerfProbeFlags } from '@/utils/perf/perfFlags'; +import { albumGridWarmCovers, COVER_DENSE_GRID_MIN_CELL_CSS_PX, GRID_COVER_WARM_LIMIT } from '@/cover/layoutSizes'; +import { artistDetailCoverWarmAlbums } from '@/features/artist/components/topSongAlbumForCover'; +import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch'; +import { useWarmGridCovers } from '@/hooks/useWarmGridCovers'; +import { VirtualCardGrid } from '@/components/VirtualCardGrid'; +import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode'; +import { sortArtistAlbumsByYear } from '@/features/artist/utils/sortArtistAlbums'; +import { readDetailServerId } from '@/utils/navigation/detailServerScope'; export default function ArtistDetail() { diff --git a/src/features/artist/pages/Artists.tsx b/src/features/artist/pages/Artists.tsx index 6ac04033..c1c227de 100644 --- a/src/features/artist/pages/Artists.tsx +++ b/src/features/artist/pages/Artists.tsx @@ -1,17 +1,17 @@ import { useEffect, useState, useCallback, useRef } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { LayoutGrid, List, Images } from 'lucide-react'; -import SelectionToggleButton from '../components/SelectionToggleButton'; -import StarFilterButton from '../components/StarFilterButton'; +import SelectionToggleButton from '@/components/SelectionToggleButton'; +import StarFilterButton from '@/components/StarFilterButton'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; -import { usePlayerStore } from '../store/playerStore'; -import { useAuthStore } from '../store/authStore'; +import { usePlayerStore } from '@/store/playerStore'; +import { useAuthStore } from '@/store/authStore'; import { useTranslation } from 'react-i18next'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { APP_MAIN_SCROLL_VIEWPORT_ID, ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; -import { useElementClientHeightById, useElementClientHeightForElement } from '../hooks/useResizeClientHeight'; -import { useVirtualizerScrollMargin } from '../hooks/useVirtualizerScrollMargin'; -import { usePerfProbeFlags } from '../utils/perf/perfFlags'; +import { APP_MAIN_SCROLL_VIEWPORT_ID, ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; +import { useElementClientHeightById, useElementClientHeightForElement } from '@/hooks/useResizeClientHeight'; +import { useVirtualizerScrollMargin } from '@/hooks/useVirtualizerScrollMargin'; +import { usePerfProbeFlags } from '@/utils/perf/perfFlags'; import { ALL_SENTINEL, ALPHABET, @@ -19,27 +19,27 @@ import { ARTIST_LIST_LAST_IN_LETTER_EST, ARTIST_LIST_LETTER_ROW_EST, ARTIST_LIST_ROW_EST, -} from '../utils/componentHelpers/artistsHelpers'; -import { useArtistsFiltering } from '../hooks/useArtistsFiltering'; -import { useLibraryIgnoredArticles } from '../hooks/useLibraryIgnoredArticles'; -import { useArtistsBrowseCatalog } from '../hooks/useArtistsBrowseCatalog'; -import { useBrowseArtistTextSearch } from '../hooks/useBrowseArtistTextSearch'; -import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight'; -import { useClientSliceInfiniteScroll } from '../hooks/useClientSliceInfiniteScroll'; -import { useInpageScrollSentinel } from '../hooks/useInpageScrollSentinel'; -import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport'; -import { ArtistsGridView } from '../components/artists/ArtistsGridView'; -import { ArtistsListView } from '../components/artists/ArtistsListView'; -import InpageScrollSentinel from '../components/InpageScrollSentinel'; -import { useArtistsBrowseFilters, type ArtistBrowseScrollSnapshot } from '../hooks/useArtistsBrowseFilters'; -import { useArtistsBrowseScrollRestore } from '../hooks/useArtistsBrowseScrollRestore'; -import { useArtistsBrowseScrollReset } from '../hooks/useArtistsBrowseScrollReset'; -import { useNavigateToArtist } from '../hooks/useNavigateToArtist'; -import { peekArtistBrowseScrollRestore } from '../store/artistBrowseSessionStore'; -import { readArtistBrowseRestore } from '../utils/navigation/albumDetailNavigation'; +} from '@/features/artist/utils/artistsHelpers'; +import { useArtistsFiltering } from '@/features/artist/hooks/useArtistsFiltering'; +import { useLibraryIgnoredArticles } from '@/hooks/useLibraryIgnoredArticles'; +import { useArtistsBrowseCatalog } from '@/features/artist/hooks/useArtistsBrowseCatalog'; +import { useBrowseArtistTextSearch } from '@/features/artist/hooks/useBrowseArtistTextSearch'; +import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight'; +import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll'; +import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel'; +import { useInpageScrollViewport } from '@/hooks/useInpageScrollViewport'; +import { ArtistsGridView } from '@/features/artist/components/ArtistsGridView'; +import { ArtistsListView } from '@/features/artist/components/ArtistsListView'; +import InpageScrollSentinel from '@/components/InpageScrollSentinel'; +import { useArtistsBrowseFilters, type ArtistBrowseScrollSnapshot } from '@/features/artist/hooks/useArtistsBrowseFilters'; +import { useArtistsBrowseScrollRestore } from '@/features/artist/hooks/useArtistsBrowseScrollRestore'; +import { useArtistsBrowseScrollReset } from '@/features/artist/hooks/useArtistsBrowseScrollReset'; +import { useNavigateToArtist } from '@/features/artist/hooks/useNavigateToArtist'; +import { peekArtistBrowseScrollRestore } from '@/features/artist/store/artistBrowseSessionStore'; +import { readArtistBrowseRestore } from '@/utils/navigation/albumDetailNavigation'; -import { useScopedBrowseSearchQuery } from '../store/liveSearchScopeStore'; -import { useLibraryIndexStore } from '../store/libraryIndexStore'; +import { useScopedBrowseSearchQuery } from '@/store/liveSearchScopeStore'; +import { useLibraryIndexStore } from '@/store/libraryIndexStore'; export default function Artists() { const perfFlags = usePerfProbeFlags(); diff --git a/src/features/artist/store/artistAlbumYearSortStore.test.ts b/src/features/artist/store/artistAlbumYearSortStore.test.ts index 12e49c1f..937296d9 100644 --- a/src/features/artist/store/artistAlbumYearSortStore.test.ts +++ b/src/features/artist/store/artistAlbumYearSortStore.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, beforeEach } from 'vitest'; import { DEFAULT_ARTIST_ALBUM_YEAR_ORDER, useArtistAlbumYearSortStore, -} from './artistAlbumYearSortStore'; +} from '@/features/artist/store/artistAlbumYearSortStore'; describe('artistAlbumYearSortStore', () => { beforeEach(() => { diff --git a/src/features/artist/store/artistAlbumYearSortStore.ts b/src/features/artist/store/artistAlbumYearSortStore.ts index b51654d5..82e98e10 100644 --- a/src/features/artist/store/artistAlbumYearSortStore.ts +++ b/src/features/artist/store/artistAlbumYearSortStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -import type { ArtistAlbumYearOrder } from '../utils/library/sortArtistAlbums'; +import type { ArtistAlbumYearOrder } from '@/features/artist/utils/sortArtistAlbums'; export const DEFAULT_ARTIST_ALBUM_YEAR_ORDER: ArtistAlbumYearOrder = 'yearDesc'; diff --git a/src/features/artist/store/artistBrowseSessionStore.test.ts b/src/features/artist/store/artistBrowseSessionStore.test.ts index 2a42c196..cfbfd99b 100644 --- a/src/features/artist/store/artistBrowseSessionStore.test.ts +++ b/src/features/artist/store/artistBrowseSessionStore.test.ts @@ -3,7 +3,7 @@ import { DEFAULT_ARTIST_BROWSE_RETURN_STATE, peekArtistBrowseScrollRestore, useArtistBrowseSessionStore, -} from './artistBrowseSessionStore'; +} from '@/features/artist/store/artistBrowseSessionStore'; describe('artistBrowseSessionStore', () => { beforeEach(() => { diff --git a/src/features/artist/store/artistBrowseSessionStore.ts b/src/features/artist/store/artistBrowseSessionStore.ts index 39823494..0013e90b 100644 --- a/src/features/artist/store/artistBrowseSessionStore.ts +++ b/src/features/artist/store/artistBrowseSessionStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { ALL_SENTINEL } from '../utils/componentHelpers/artistsHelpers'; +import { ALL_SENTINEL } from '@/features/artist/utils/artistsHelpers'; export type ArtistBrowseViewMode = 'grid' | 'list'; diff --git a/src/features/artist/utils/artistsHelpers.test.ts b/src/features/artist/utils/artistsHelpers.test.ts index 06a4f1bf..aabe705f 100644 --- a/src/features/artist/utils/artistsHelpers.test.ts +++ b/src/features/artist/utils/artistsHelpers.test.ts @@ -8,7 +8,7 @@ import { ALPHABET, sortKeyFromDisplayName, stripLeadingArticles, -} from './artistsHelpers'; +} from '@/features/artist/utils/artistsHelpers'; describe('stripLeadingArticles', () => { it('strips The from Beatles', () => { diff --git a/src/features/artist/utils/artistsHelpers.ts b/src/features/artist/utils/artistsHelpers.ts index a6aac807..bf6f1033 100644 --- a/src/features/artist/utils/artistsHelpers.ts +++ b/src/features/artist/utils/artistsHelpers.ts @@ -1,4 +1,4 @@ -import type { SubsonicArtist } from '../../api/subsonicTypes'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; export const ALL_SENTINEL = 'ALL'; /** Catch-all bucket for names that start with neither an A–Z letter nor a digit diff --git a/src/features/artist/utils/openArtistRefs.test.ts b/src/features/artist/utils/openArtistRefs.test.ts index 7516c045..5cceed4c 100644 --- a/src/features/artist/utils/openArtistRefs.test.ts +++ b/src/features/artist/utils/openArtistRefs.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { coerceOpenArtistRefs } from './openArtistRefs'; +import { coerceOpenArtistRefs } from '@/features/artist/utils/openArtistRefs'; describe('coerceOpenArtistRefs', () => { it('returns an empty array for nullish input', () => { diff --git a/src/features/artist/utils/openArtistRefs.ts b/src/features/artist/utils/openArtistRefs.ts index 6e6b19e6..52f46a64 100644 --- a/src/features/artist/utils/openArtistRefs.ts +++ b/src/features/artist/utils/openArtistRefs.ts @@ -1,4 +1,4 @@ -import type { SubsonicOpenArtistRef } from '../api/subsonicTypes'; +import type { SubsonicOpenArtistRef } from '@/api/subsonicTypes'; /** Subsonic JSON may return one ref object instead of a one-element array. */ export function coerceOpenArtistRefs( diff --git a/src/features/artist/utils/runArtistDetailActions.ts b/src/features/artist/utils/runArtistDetailActions.ts index cab83030..d75f142a 100644 --- a/src/features/artist/utils/runArtistDetailActions.ts +++ b/src/features/artist/utils/runArtistDetailActions.ts @@ -1,12 +1,12 @@ import type React from 'react'; import type { TFunction } from 'i18next'; -import { uploadArtistImage } from '../../api/subsonicPlaylists'; -import { setRating, star, unstar } from '../../api/subsonicStarRating'; -import type { SubsonicArtist } from '../../api/subsonicTypes'; -import { useAuthStore } from '../../store/authStore'; -import { copyEntityShareLink } from '../share/copyEntityShareLink'; -import { invalidateCoverArt } from '../imageCache'; -import { showToast } from '../ui/toast'; +import { uploadArtistImage } from '@/api/subsonicPlaylists'; +import { setRating, star, unstar } from '@/api/subsonicStarRating'; +import type { SubsonicArtist } from '@/api/subsonicTypes'; +import { useAuthStore } from '@/store/authStore'; +import { copyEntityShareLink } from '@/utils/share/copyEntityShareLink'; +import { invalidateCoverArt } from '@/utils/imageCache'; +import { showToast } from '@/utils/ui/toast'; export interface RunArtistEntityRatingDeps { artist: SubsonicArtist | null; diff --git a/src/features/artist/utils/runArtistDetailPlay.test.ts b/src/features/artist/utils/runArtistDetailPlay.test.ts index 839e13e9..6e0c161f 100644 --- a/src/features/artist/utils/runArtistDetailPlay.test.ts +++ b/src/features/artist/utils/runArtistDetailPlay.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { SubsonicAlbum } from '../../api/subsonicTypes'; +import type { SubsonicAlbum } from '@/api/subsonicTypes'; import * as offlineMediaResolve from '@/features/offline'; -import { fetchArtistDetailTracks, runArtistDetailPlayTopSong } from './runArtistDetailPlay'; +import { fetchArtistDetailTracks, runArtistDetailPlayTopSong } from '@/features/artist/utils/runArtistDetailPlay'; vi.mock('@/features/offline', () => ({ resolveAlbum: vi.fn(), diff --git a/src/features/artist/utils/runArtistDetailPlay.ts b/src/features/artist/utils/runArtistDetailPlay.ts index 7f5342ab..9c23d412 100644 --- a/src/features/artist/utils/runArtistDetailPlay.ts +++ b/src/features/artist/utils/runArtistDetailPlay.ts @@ -1,9 +1,9 @@ import type { TFunction } from 'i18next'; -import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; -import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../../api/subsonicTypes'; -import type { Track } from '../../store/playerStoreTypes'; -import { songToTrack } from '../playback/songToTrack'; -import { runBulkPlayAll, runBulkShuffle } from '../playback/runBulkPlay'; +import { getSimilarSongs2, getTopSongs } from '@/features/artist/api/subsonicArtists'; +import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/api/subsonicTypes'; +import type { Track } from '@/store/playerStoreTypes'; +import { songToTrack } from '@/utils/playback/songToTrack'; +import { runBulkPlayAll, runBulkShuffle } from '@/utils/playback/runBulkPlay'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; /** Ordered artist discography tracks for play-all / shuffle (network or local bytes). */ diff --git a/src/features/artist/utils/sortArtistAlbums.test.ts b/src/features/artist/utils/sortArtistAlbums.test.ts index 14d62f58..c7d43bee 100644 --- a/src/features/artist/utils/sortArtistAlbums.test.ts +++ b/src/features/artist/utils/sortArtistAlbums.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import type { SubsonicAlbum } from '../../api/subsonicTypes'; -import { sortArtistAlbumsByYear } from './sortArtistAlbums'; +import type { SubsonicAlbum } from '@/api/subsonicTypes'; +import { sortArtistAlbumsByYear } from '@/features/artist/utils/sortArtistAlbums'; const album = (id: string, name: string, year?: number): SubsonicAlbum => ({ id, diff --git a/src/features/artist/utils/sortArtistAlbums.ts b/src/features/artist/utils/sortArtistAlbums.ts index 3833f436..133711c1 100644 --- a/src/features/artist/utils/sortArtistAlbums.ts +++ b/src/features/artist/utils/sortArtistAlbums.ts @@ -1,4 +1,4 @@ -import type { SubsonicAlbum } from '../../api/subsonicTypes'; +import type { SubsonicAlbum } from '@/api/subsonicTypes'; export type ArtistAlbumYearOrder = 'yearDesc' | 'yearAsc'; diff --git a/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts b/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts index ed7da0b8..b55ba5ff 100644 --- a/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts +++ b/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { getPlaylists } from '@/api/subsonicPlaylists'; -import { getArtists, getArtist } from '@/api/subsonicArtists'; +import { getArtists, getArtist } from '@/features/artist'; import { getAlbumList } from '@/api/subsonicLibrary'; import { search as searchSubsonic } from '@/api/subsonicSearch'; import type { diff --git a/src/features/favorites/components/FavoriteSongRow.tsx b/src/features/favorites/components/FavoriteSongRow.tsx index c4924cc8..cb193f4f 100644 --- a/src/features/favorites/components/FavoriteSongRow.tsx +++ b/src/features/favorites/components/FavoriteSongRow.tsx @@ -8,7 +8,7 @@ import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; import i18n from '@/i18n'; import { formatTrackTime } from '@/utils/format/formatDuration'; import StarRating from '@/components/StarRating'; -import { OpenArtistRefInline } from '@/components/OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; export interface FavoriteSongRowCallbacks { diff --git a/src/features/favorites/pages/Favorites.tsx b/src/features/favorites/pages/Favorites.tsx index 44e979d4..ebf6b9d0 100644 --- a/src/features/favorites/pages/Favorites.tsx +++ b/src/features/favorites/pages/Favorites.tsx @@ -10,7 +10,7 @@ import { useFavoritesSongFiltering } from '@/features/favorites/hooks/useFavorit import { useFavoritesSelection } from '@/features/favorites/hooks/useFavoritesSelection'; import { useBulkPlPickerOutsideClick } from '@/hooks/useBulkPlPickerOutsideClick'; import AlbumRow from '@/components/AlbumRow'; -import ArtistRow from '@/components/ArtistRow'; +import { ArtistRow } from '@/features/artist'; import { usePlayerStore } from '@/store/playerStore'; import { useTranslation } from 'react-i18next'; import { useSelectionStore } from '@/store/selectionStore'; diff --git a/src/features/miniPlayer/components/MiniMeta.tsx b/src/features/miniPlayer/components/MiniMeta.tsx index 611b2ef2..07469d81 100644 --- a/src/features/miniPlayer/components/MiniMeta.tsx +++ b/src/features/miniPlayer/components/MiniMeta.tsx @@ -1,6 +1,6 @@ import { emit } from '@tauri-apps/api/event'; import CachedImage from '@/ui/CachedImage'; -import { OpenArtistRefInline } from '@/components/OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import type { MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge'; interface Props { diff --git a/src/features/nowPlaying/components/Hero.tsx b/src/features/nowPlaying/components/Hero.tsx index 2639bd93..7dbb1ab5 100644 --- a/src/features/nowPlaying/components/Hero.tsx +++ b/src/features/nowPlaying/components/Hero.tsx @@ -5,7 +5,7 @@ import { CoverArtImage } from '@/cover/CoverArtImage'; import type { CoverArtRef } from '@/cover/types'; import type { ArtistStats, TrackStats } from '@/music-network'; import type { SubsonicOpenArtistRef } from '@/api/subsonicTypes'; -import { OpenArtistRefInline } from '@/components/OpenArtistRefInline'; +import { OpenArtistRefInline } from '@/features/artist'; import { formatTrackTime } from '@/utils/format/formatDuration'; import { useEnrichmentPrimaryLabel } from '@/hooks/useEnrichmentPrimaryLabel'; import { useEnrichmentPrimaryIcon } from '@/hooks/useEnrichmentPrimaryIcon'; diff --git a/src/features/nowPlaying/components/NowPlayingInfo.tsx b/src/features/nowPlaying/components/NowPlayingInfo.tsx index 976614f4..f5cdb3c1 100644 --- a/src/features/nowPlaying/components/NowPlayingInfo.tsx +++ b/src/features/nowPlaying/components/NowPlayingInfo.tsx @@ -1,5 +1,5 @@ import { getSongForServer } from '@/api/subsonicLibrary'; -import { getArtistInfoForServer } from '@/api/subsonicArtists'; +import { getArtistInfoForServer } from '@/features/artist'; import type { SubsonicArtistInfo, SubsonicSong } from '@/api/subsonicTypes'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts b/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts index 917fb02a..cf1a4a5e 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts @@ -13,7 +13,7 @@ import { renderHook, act, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { SubsonicArtistInfo, SubsonicSong, SubsonicAlbum, SubsonicArtist } from '@/api/subsonicTypes'; -vi.mock('@/api/subsonicArtists'); +vi.mock('@/features/artist'); vi.mock('@/api/subsonicLibrary'); vi.mock('@/api/bandsintown'); vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ @@ -21,7 +21,7 @@ vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ })); import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; -import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/api/subsonicArtists'; +import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/features/artist'; import { getAlbumForServer, getSongForServer } from '@/api/subsonicLibrary'; import { fetchBandsintownEvents } from '@/api/bandsintown'; import { useNowPlayingFetchers, type NowPlayingFetchersDeps } from '@/features/nowPlaying/hooks/useNowPlayingFetchers'; diff --git a/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts b/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts index 3404d7db..5ae0dea0 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { getArtistInfoForServer } from '@/api/subsonicArtists'; +import { getArtistInfoForServer } from '@/features/artist'; import type { SubsonicAlbum, SubsonicArtistInfo, SubsonicSong } from '@/api/subsonicTypes'; import { resolveNpAlbum, resolveNpDiscography, resolveNpSongMeta, resolveNpTopSongs } from '@/features/nowPlaying/utils/nowPlayingMetadataResolve'; import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown'; diff --git a/src/features/nowPlaying/pages/NowPlaying.tsx b/src/features/nowPlaying/pages/NowPlaying.tsx index 49fb1d3a..e2864036 100644 --- a/src/features/nowPlaying/pages/NowPlaying.tsx +++ b/src/features/nowPlaying/pages/NowPlaying.tsx @@ -37,7 +37,7 @@ import TourCard from '@/features/nowPlaying/components/TourCard'; import DiscographyCard from '@/features/nowPlaying/components/DiscographyCard'; import { useNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers'; import { useNowPlayingStarLove } from '@/features/nowPlaying/hooks/useNowPlayingStarLove'; -import { useArtistInfoBatch } from '@/hooks/useArtistInfoBatch'; +import { useArtistInfoBatch } from '@/features/artist'; import { primaryTrackArtistRef, resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; import type { ArtistCardTab } from '@/features/nowPlaying/components/ArtistCard'; diff --git a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts index 7155d935..5a138c07 100644 --- a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts +++ b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts @@ -9,7 +9,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { onInvoke } from '@/test/mocks/tauri'; import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import type { LibraryAdvancedSearchResponse } from '@/api/library'; -import * as subsonicArtists from '@/api/subsonicArtists'; +import * as subsonicArtists from '@/features/artist'; import * as subsonicLibrary from '@/api/subsonicLibrary'; // Network reachability is decided by the guard; mock it so we can test both arms. diff --git a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts index 06652451..89281b3a 100644 --- a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts +++ b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts @@ -17,7 +17,7 @@ * is intentionally absent here. */ import { libraryGetTrack, libraryGetTracksByAlbum } from '@/api/library'; -import { getArtistForServer, getTopSongsForServer } from '@/api/subsonicArtists'; +import { getArtistForServer, getTopSongsForServer } from '@/features/artist'; import { getAlbumForServer, getSongForServer } from '@/api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes'; import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; diff --git a/src/features/offline/store/offlineStore.ts b/src/features/offline/store/offlineStore.ts index fc32d4ce..cf26103b 100644 --- a/src/features/offline/store/offlineStore.ts +++ b/src/features/offline/store/offlineStore.ts @@ -1,7 +1,7 @@ import { libraryUpsertSongsFromApi } from '@/api/library'; import { buildStreamUrl } from '@/api/subsonicStreamUrl'; import { getAlbum } from '@/api/subsonicLibrary'; -import { getArtist } from '@/api/subsonicArtists'; +import { getArtist } from '@/features/artist'; import type { SubsonicSong } from '@/api/subsonicTypes'; import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; diff --git a/src/features/offline/utils/favoritesOfflineSync.test.ts b/src/features/offline/utils/favoritesOfflineSync.test.ts index ff6756ae..40ff1cbb 100644 --- a/src/features/offline/utils/favoritesOfflineSync.test.ts +++ b/src/features/offline/utils/favoritesOfflineSync.test.ts @@ -28,7 +28,7 @@ vi.mock('@/api/subsonicLibrary', () => ({ getAlbumForServer: vi.fn(async () => ({ songs: [] })), })); -vi.mock('@/api/subsonicArtists', () => ({ +vi.mock('@/features/artist', () => ({ getArtistForServer: vi.fn(async () => ({ albums: [] })), })); diff --git a/src/features/offline/utils/favoritesOfflineSync.ts b/src/features/offline/utils/favoritesOfflineSync.ts index 98f874df..8b1e310e 100644 --- a/src/features/offline/utils/favoritesOfflineSync.ts +++ b/src/features/offline/utils/favoritesOfflineSync.ts @@ -1,7 +1,7 @@ import { libraryUpsertSongsFromApi } from '@/api/library'; import { librarySqlServerId } from '@/api/coverCache'; import { getAlbumForServer } from '@/api/subsonicLibrary'; -import { getArtistForServer } from '@/api/subsonicArtists'; +import { getArtistForServer } from '@/features/artist'; import { getStarredForServer } from '@/api/subsonicStarRating'; import { buildStreamUrlForServer } from '@/api/subsonicStreamUrl'; import type { SubsonicSong } from '@/api/subsonicTypes'; diff --git a/src/features/offline/utils/offlineMediaResolve.test.ts b/src/features/offline/utils/offlineMediaResolve.test.ts index bf5b0458..ea088c08 100644 --- a/src/features/offline/utils/offlineMediaResolve.test.ts +++ b/src/features/offline/utils/offlineMediaResolve.test.ts @@ -53,7 +53,7 @@ vi.mock('@/api/subsonicLibrary', () => ({ getAlbumForServer: (serverId: string, albumId: string) => getAlbumForServerMock(serverId, albumId), })); -vi.mock('@/api/subsonicArtists', () => ({ +vi.mock('@/features/artist', () => ({ getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId), })); diff --git a/src/features/offline/utils/offlineMediaResolve.ts b/src/features/offline/utils/offlineMediaResolve.ts index 808f4b20..d121273c 100644 --- a/src/features/offline/utils/offlineMediaResolve.ts +++ b/src/features/offline/utils/offlineMediaResolve.ts @@ -1,5 +1,5 @@ import { getAlbumForServer } from '@/api/subsonicLibrary'; -import { getArtistForServer } from '@/api/subsonicArtists'; +import { getArtistForServer } from '@/features/artist'; import { getPlaylistForServer } from '@/api/subsonicPlaylists'; import type { SubsonicAlbum, diff --git a/src/features/offline/utils/pinnedOfflineSync.test.ts b/src/features/offline/utils/pinnedOfflineSync.test.ts index 18e74ef7..70911276 100644 --- a/src/features/offline/utils/pinnedOfflineSync.test.ts +++ b/src/features/offline/utils/pinnedOfflineSync.test.ts @@ -38,7 +38,7 @@ vi.mock('@/api/subsonicLibrary', () => ({ filterSongsToServerLibrary: (songs: SubsonicSong[]) => filterSongsMock(songs), })); -vi.mock('@/api/subsonicArtists', () => ({ +vi.mock('@/features/artist', () => ({ getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId), })); diff --git a/src/features/offline/utils/pinnedOfflineSync.ts b/src/features/offline/utils/pinnedOfflineSync.ts index 62ac8a82..6bccd984 100644 --- a/src/features/offline/utils/pinnedOfflineSync.ts +++ b/src/features/offline/utils/pinnedOfflineSync.ts @@ -1,7 +1,7 @@ import { libraryGetTracksByAlbum, subscribeLibrarySyncIdle } from '@/api/library'; import { getAlbumForServer, filterSongsToServerLibrary } from '@/api/subsonicLibrary'; import { getPlaylistForServer } from '@/api/subsonicPlaylists'; -import { getArtistForServer } from '@/api/subsonicArtists'; +import { getArtistForServer } from '@/features/artist'; import type { SubsonicSong } from '@/api/subsonicTypes'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '@/store/authStore'; diff --git a/src/features/search/components/liveSearchScope.ts b/src/features/search/components/liveSearchScope.ts index 315f3504..73407867 100644 --- a/src/features/search/components/liveSearchScope.ts +++ b/src/features/search/components/liveSearchScope.ts @@ -3,7 +3,7 @@ import { ALL_NAV_ITEMS } from '@/config/navItems'; import type { LiveSearchScope } from '@/store/liveSearchScopeStore'; import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/store/albumBrowseSessionStore'; import { isTracksBrowsePath } from '@/store/advancedSearchSessionStore'; -import { isArtistsBrowsePath } from '@/store/artistBrowseSessionStore'; +import { isArtistsBrowsePath } from '@/features/artist'; import { isComposersBrowsePath } from '@/store/composerBrowseSessionStore'; export const SCOPE_NAV_ITEM: Record = { diff --git a/src/features/search/hooks/useLiveSearchRouteScope.ts b/src/features/search/hooks/useLiveSearchRouteScope.ts index 32e39ce3..755e6369 100644 --- a/src/features/search/hooks/useLiveSearchRouteScope.ts +++ b/src/features/search/hooks/useLiveSearchRouteScope.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/store/albumBrowseSessionStore'; -import { isArtistsBrowsePath } from '@/store/artistBrowseSessionStore'; +import { isArtistsBrowsePath } from '@/features/artist'; import { isTracksBrowsePath } from '@/store/advancedSearchSessionStore'; import { isComposersBrowsePath } from '@/store/composerBrowseSessionStore'; import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore'; diff --git a/src/features/search/pages/SearchBrowsePage.tsx b/src/features/search/pages/SearchBrowsePage.tsx index cb68cd30..524df6ca 100644 --- a/src/features/search/pages/SearchBrowsePage.tsx +++ b/src/features/search/pages/SearchBrowsePage.tsx @@ -7,7 +7,7 @@ import { useLocation, useNavigate, useNavigationType, useSearchParams } from 're import { SlidersVertical, Search, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import AlbumRow from '@/components/AlbumRow'; -import ArtistRow from '@/components/ArtistRow'; +import { ArtistRow } from '@/features/artist'; import PagedSongList from '@/components/PagedSongList'; import CustomSelect from '@/ui/CustomSelect'; import StarFilterButton from '@/components/StarFilterButton'; diff --git a/src/features/settings/components/ArtistLayoutCustomizer.tsx b/src/features/settings/components/ArtistLayoutCustomizer.tsx index e77c4c5d..5c3b33fc 100644 --- a/src/features/settings/components/ArtistLayoutCustomizer.tsx +++ b/src/features/settings/components/ArtistLayoutCustomizer.tsx @@ -1,6 +1,6 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useArtistLayoutStore, type ArtistSectionConfig, type ArtistSectionId } from '@/store/artistLayoutStore'; +import { useArtistLayoutStore, type ArtistSectionConfig, type ArtistSectionId } from '@/features/artist'; import { useListReorderDnd } from '@/hooks/useListReorderDnd'; import { applyListReorderById, type ListReorderDropTarget } from '@/utils/componentHelpers/listReorder'; import { ReorderGripHandle } from '@/features/settings/components/ReorderGripHandle'; diff --git a/src/features/settings/components/PersonalisationTab.tsx b/src/features/settings/components/PersonalisationTab.tsx index fd850345..932fd4e5 100644 --- a/src/features/settings/components/PersonalisationTab.tsx +++ b/src/features/settings/components/PersonalisationTab.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { Disc3, LayoutGrid, ListOrdered, ListTodo, PanelLeft, RotateCcw, Users } from 'lucide-react'; -import { useArtistLayoutStore } from '@/store/artistLayoutStore'; +import { useArtistLayoutStore } from '@/features/artist'; import { useAuthStore } from '@/store/authStore'; import type { QueueDisplayMode } from '@/store/authStoreTypes'; import { useHomeStore } from '@/store/homeStore'; diff --git a/src/features/stats/api/subsonicStatistics.ts b/src/features/stats/api/subsonicStatistics.ts index bfa2e1d2..fc3e97b0 100644 --- a/src/features/stats/api/subsonicStatistics.ts +++ b/src/features/stats/api/subsonicStatistics.ts @@ -1,6 +1,6 @@ import { useAuthStore } from '@/store/authStore'; import { genreTagsFor } from '@/utils/library/genreTags'; -import { getArtists } from '@/api/subsonicArtists'; +import { getArtists } from '@/features/artist'; import { getAlbumList, getRandomSongs } from '@/api/subsonicLibrary'; import type { StatisticsFormatSample, diff --git a/src/hooks/tauriBridge/useCliBridge.ts b/src/hooks/tauriBridge/useCliBridge.ts index e26f5cc8..4d9a0dd4 100644 --- a/src/hooks/tauriBridge/useCliBridge.ts +++ b/src/hooks/tauriBridge/useCliBridge.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { listen } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api/core'; import type { NavigateFunction } from 'react-router-dom'; -import { getSimilarSongs } from '../../api/subsonicArtists'; +import { getSimilarSongs } from '@/features/artist'; import { getMusicFolders } from '../../api/subsonicLibrary'; import { search as subsonicSearch } from '../../api/subsonicSearch'; import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../../utils/mix/mixRatingFilter'; diff --git a/src/pages/AlbumDetail.tsx b/src/pages/AlbumDetail.tsx index 7ea86709..92c82e26 100644 --- a/src/pages/AlbumDetail.tsx +++ b/src/pages/AlbumDetail.tsx @@ -2,7 +2,7 @@ import { buildDownloadUrl } from '../api/subsonicStreamUrl'; import { setRating, star, unstar } from '../api/subsonicStarRating'; import { queueSongStar, queueSongRating } from '../store/pendingStarSync'; import { getAlbumForServer } from '../api/subsonicLibrary'; -import { getArtistInfo } from '../api/subsonicArtists'; +import { getArtistInfo } from '@/features/artist'; import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/playback/songToTrack'; import { shuffleArray } from '../utils/playback/shuffleArray'; diff --git a/src/pages/ComposerDetail.tsx b/src/pages/ComposerDetail.tsx index 7b9f5fe3..3a3e38ed 100644 --- a/src/pages/ComposerDetail.tsx +++ b/src/pages/ComposerDetail.tsx @@ -1,5 +1,5 @@ import { star, unstar } from '../api/subsonicStarRating'; -import { getArtist, getArtistInfo } from '../api/subsonicArtists'; +import { getArtist, getArtistInfo } from '@/features/artist'; import type { SubsonicArtist, SubsonicAlbum, SubsonicArtistInfo } from '../api/subsonicTypes'; import { useEffect, useState, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; diff --git a/src/pages/Composers.tsx b/src/pages/Composers.tsx index f49fc50e..4dfffdf4 100644 --- a/src/pages/Composers.tsx +++ b/src/pages/Composers.tsx @@ -11,16 +11,16 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { APP_MAIN_SCROLL_VIEWPORT_ID, COMPOSERS_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; import { useElementClientHeightById, useElementClientHeightForElement } from '../hooks/useResizeClientHeight'; import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight'; -import { useBrowseArtistTextSearch } from '../hooks/useBrowseArtistTextSearch'; +import { useBrowseArtistTextSearch } from '@/features/artist'; import { useComposersBrowseFilters, type ComposerBrowseScrollSnapshot } from '../hooks/useComposersBrowseFilters'; import { useComposersBrowseScrollRestore } from '../hooks/useComposersBrowseScrollRestore'; -import { useArtistsBrowseScrollReset } from '../hooks/useArtistsBrowseScrollReset'; +import { useArtistsBrowseScrollReset } from '@/features/artist'; import { useNavigateToComposer } from '../hooks/useNavigateToComposer'; import { peekComposerBrowseScrollRestore } from '../store/composerBrowseSessionStore'; import { useScopedBrowseSearchQuery } from '../store/liveSearchScopeStore'; import { readComposerBrowseRestore } from '../utils/navigation/albumDetailNavigation'; import { filterArtistsWithRoleAlbumCredits } from '../utils/library/composerBrowse'; -import { ALL_SENTINEL, artistLetterBucket } from '../utils/componentHelpers/artistsHelpers'; +import { ALL_SENTINEL, artistLetterBucket } from '@/features/artist'; import { useLibraryIgnoredArticles } from '../hooks/useLibraryIgnoredArticles'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { VirtualCardGrid } from '../components/VirtualCardGrid'; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index a968eec1..f16ae818 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,4 +1,4 @@ -import { getArtists } from '../api/subsonicArtists'; +import { getArtists } from '@/features/artist'; import { getAlbumList, getRandomSongs } from '../api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsonicTypes'; import { runLocalRandomSongs } from '../utils/library/browseTextSearch'; diff --git a/src/store/composerBrowseSessionStore.ts b/src/store/composerBrowseSessionStore.ts index 8287c0ff..c815b4a2 100644 --- a/src/store/composerBrowseSessionStore.ts +++ b/src/store/composerBrowseSessionStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { ALL_SENTINEL } from '../utils/componentHelpers/artistsHelpers'; +import { ALL_SENTINEL } from '@/features/artist'; export type ComposerBrowseViewMode = 'grid' | 'list'; diff --git a/src/store/nextAction.ts b/src/store/nextAction.ts index 7ce2c777..a9699ef3 100644 --- a/src/store/nextAction.ts +++ b/src/store/nextAction.ts @@ -1,4 +1,4 @@ -import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists'; +import { getSimilarSongs2, getTopSongs } from '@/features/artist'; import { invoke } from '@tauri-apps/api/core'; import { buildInfiniteQueueCandidates } from '../utils/playback/buildInfiniteQueueCandidates'; import { songToTrack } from '../utils/playback/songToTrack'; diff --git a/src/store/nextActionOrbitRadio.test.ts b/src/store/nextActionOrbitRadio.test.ts index 660e1503..101342d1 100644 --- a/src/store/nextActionOrbitRadio.test.ts +++ b/src/store/nextActionOrbitRadio.test.ts @@ -7,7 +7,7 @@ const { inOrbit, getSimilarSongs2, getTopSongs } = vi.hoisted(() => ({ getTopSongs: vi.fn(() => Promise.resolve([])), })); -vi.mock('../api/subsonicArtists', () => ({ getSimilarSongs2, getTopSongs })); +vi.mock('@/features/artist', () => ({ getSimilarSongs2, getTopSongs })); vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(() => Promise.resolve()) })); vi.mock('@/features/orbit', () => ({ isInOrbitSession: () => inOrbit.value })); vi.mock('./authStore', () => ({ diff --git a/src/store/playerStore.persistence.test.ts b/src/store/playerStore.persistence.test.ts index c057e300..4051c5ae 100644 --- a/src/store/playerStore.persistence.test.ts +++ b/src/store/playerStore.persistence.test.ts @@ -43,7 +43,7 @@ vi.mock('@/api/subsonicLibrary', () => ({ getSong: vi.fn(async () => null), getRandomSongs: vi.fn(async () => []), })); -vi.mock('@/api/subsonicArtists', () => ({ +vi.mock('@/features/artist', () => ({ getSimilarSongs2: vi.fn(async () => []), getTopSongs: vi.fn(async () => []), })); diff --git a/src/utils/album/deriveAlbumHeaderArtistRefs.ts b/src/utils/album/deriveAlbumHeaderArtistRefs.ts index 5fa11818..1314d820 100644 --- a/src/utils/album/deriveAlbumHeaderArtistRefs.ts +++ b/src/utils/album/deriveAlbumHeaderArtistRefs.ts @@ -1,5 +1,5 @@ import type { SubsonicAlbum, SubsonicOpenArtistRef, SubsonicSong } from '../../api/subsonicTypes'; -import { coerceOpenArtistRefs } from '../openArtistRefs'; +import { coerceOpenArtistRefs } from '@/features/artist'; function nonEmpty(refs: SubsonicOpenArtistRef[]): refs is SubsonicOpenArtistRef[] { return refs.length > 0; diff --git a/src/utils/componentHelpers/contextMenuActions.ts b/src/utils/componentHelpers/contextMenuActions.ts index dcfa268c..76a5b484 100644 --- a/src/utils/componentHelpers/contextMenuActions.ts +++ b/src/utils/componentHelpers/contextMenuActions.ts @@ -1,6 +1,6 @@ import { join } from '@tauri-apps/api/path'; import { invoke } from '@tauri-apps/api/core'; -import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '../../api/subsonicArtists'; +import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '@/features/artist'; import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../mix/mixRatingFilter'; import { buildDownloadUrl } from '../../api/subsonicStreamUrl'; import { useAuthStore } from '../../store/authStore'; diff --git a/src/utils/migrations/advancedModeMigration.ts b/src/utils/migrations/advancedModeMigration.ts index 25297630..af4974c8 100644 --- a/src/utils/migrations/advancedModeMigration.ts +++ b/src/utils/migrations/advancedModeMigration.ts @@ -1,5 +1,5 @@ import { useAuthStore } from '../../store/authStore'; -import { DEFAULT_ARTIST_SECTIONS, useArtistLayoutStore } from '../../store/artistLayoutStore'; +import { DEFAULT_ARTIST_SECTIONS, useArtistLayoutStore } from '@/features/artist'; import { DEFAULT_QUEUE_TOOLBAR_BUTTONS, useQueueToolbarStore } from '../../store/queueToolbarStore'; import { DEFAULT_PLAYLIST_LAYOUT_ITEMS, usePlaylistLayoutStore } from '../../store/playlistLayoutStore'; diff --git a/src/utils/mix/luckyMix.ts b/src/utils/mix/luckyMix.ts index 45bdbcb4..89c3554e 100644 --- a/src/utils/mix/luckyMix.ts +++ b/src/utils/mix/luckyMix.ts @@ -1,4 +1,4 @@ -import { fetchSimilarTracksRouted } from '../../api/subsonicArtists'; +import { fetchSimilarTracksRouted } from '@/features/artist'; import { filterSongsToActiveLibrary, getRandomSongs } from '../../api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; import type { QueueItemRef } from '../../store/playerStoreTypes'; diff --git a/src/utils/mix/luckyMixHelpers.ts b/src/utils/mix/luckyMixHelpers.ts index d83528b0..3d1f7e32 100644 --- a/src/utils/mix/luckyMixHelpers.ts +++ b/src/utils/mix/luckyMixHelpers.ts @@ -1,4 +1,4 @@ -import { getTopSongs } from '../../api/subsonicArtists'; +import { getTopSongs } from '@/features/artist'; import { filterSongsToActiveLibrary, getAlbumList, getRandomSongs } from '../../api/subsonicLibrary'; import { resolveAlbumForActiveServer } from '@/features/offline'; import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; diff --git a/src/utils/playback/buildInfiniteQueueCandidates.test.ts b/src/utils/playback/buildInfiniteQueueCandidates.test.ts index 92da8c87..a5734c78 100644 --- a/src/utils/playback/buildInfiniteQueueCandidates.test.ts +++ b/src/utils/playback/buildInfiniteQueueCandidates.test.ts @@ -6,12 +6,15 @@ * refactor (2026-05-12). This test pins the artist-first / random-fallback * order, the dedup contract against existingIds, and the autoAdded flag. */ -import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; +import { getSimilarSongs2, getTopSongs } from '@/features/artist'; import { getRandomSongs } from '../../api/subsonicLibrary'; import type { Track } from '../../store/playerStoreTypes'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -vi.mock('../../api/subsonicArtists', () => ({ +// Mock only the artist Subsonic API submodule (the pre-move target was +// `api/subsonicArtists`); the barrel re-exports it, so consumers still get the +// stubs while `coerceOpenArtistRefs` (used by songToTrack) stays real. +vi.mock('@/features/artist/api/subsonicArtists', () => ({ getSimilarSongs2: vi.fn(), getTopSongs: vi.fn(), })); diff --git a/src/utils/playback/buildInfiniteQueueCandidates.ts b/src/utils/playback/buildInfiniteQueueCandidates.ts index 33ef62ff..5ca8778b 100644 --- a/src/utils/playback/buildInfiniteQueueCandidates.ts +++ b/src/utils/playback/buildInfiniteQueueCandidates.ts @@ -1,4 +1,4 @@ -import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; +import { getSimilarSongs2, getTopSongs } from '@/features/artist'; import { getRandomSongs } from '../../api/subsonicLibrary'; import type { Track } from '../../store/playerStoreTypes'; import { diff --git a/src/utils/playback/songToTrack.ts b/src/utils/playback/songToTrack.ts index 16f033f9..7d992afe 100644 --- a/src/utils/playback/songToTrack.ts +++ b/src/utils/playback/songToTrack.ts @@ -1,6 +1,6 @@ import type { SubsonicSong } from '../../api/subsonicTypes'; import type { Track } from '../../store/playerStoreTypes'; -import { coerceOpenArtistRefs } from '../openArtistRefs'; +import { coerceOpenArtistRefs } from '@/features/artist'; import { activeServerProfileId } from './trackServerScope'; export function songToTrack(song: SubsonicSong): Track { diff --git a/src/utils/playback/trackArtistRefs.ts b/src/utils/playback/trackArtistRefs.ts index 6d41c24c..3acf91ba 100644 --- a/src/utils/playback/trackArtistRefs.ts +++ b/src/utils/playback/trackArtistRefs.ts @@ -1,6 +1,6 @@ import type { SubsonicOpenArtistRef } from '../../api/subsonicTypes'; import type { Track } from '../../store/playerStoreTypes'; -import { coerceOpenArtistRefs } from '../openArtistRefs'; +import { coerceOpenArtistRefs } from '@/features/artist'; type TrackArtistFields = Pick; diff --git a/src/utils/share/enqueueShareSearchPayload.test.ts b/src/utils/share/enqueueShareSearchPayload.test.ts index f4e917d3..ba299b8a 100644 --- a/src/utils/share/enqueueShareSearchPayload.test.ts +++ b/src/utils/share/enqueueShareSearchPayload.test.ts @@ -27,7 +27,7 @@ vi.mock('../../api/subsonicLibrary', () => ({ getSong: mocks.getSong, })); -vi.mock('../../api/subsonicArtists', () => ({ +vi.mock('@/features/artist', () => ({ getArtist: mocks.getArtist, }));