mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(album): co-locate album feature into features/album
This commit is contained in:
@@ -27,7 +27,7 @@ vi.mock('../utils/network/subsonicNetworkGuard', () => ({
|
||||
|
||||
import axios from 'axios';
|
||||
import { pingWithCredentials, pingWithCredentialsForProfile, ping } from './subsonic';
|
||||
import { getAlbumInfo2 } from './subsonicAlbumInfo';
|
||||
import { getAlbumInfo2 } from '@/features/album';
|
||||
import { getStarred } from './subsonicStarRating';
|
||||
import { search } from './subsonicSearch';
|
||||
import { getAlbum, getMusicDirectory, getMusicFolders, getMusicIndexes, getRandomSongs, getSong } from './subsonicLibrary';
|
||||
|
||||
@@ -10,7 +10,7 @@ import { resolveStartRoute } from '@/features/sidebar';
|
||||
// Route-level lazy loading: keeps the non-page graph (shell, player, stores) in
|
||||
// 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 Albums = lazy(() => import('@/features/album/pages/Albums'));
|
||||
const Artists = lazy(() => import('@/features/artist/pages/Artists'));
|
||||
const ArtistDetail = lazy(() => import('@/features/artist/pages/ArtistDetail'));
|
||||
const Composers = lazy(() => import('../pages/Composers'));
|
||||
@@ -19,10 +19,10 @@ const NewReleases = lazy(() => import('../pages/NewReleases'));
|
||||
const Favorites = lazy(() => import('@/features/favorites/pages/Favorites'));
|
||||
const RandomMix = lazy(() => import('../pages/RandomMix'));
|
||||
const RandomLanding = lazy(() => import('../pages/RandomLanding'));
|
||||
const AlbumDetail = lazy(() => import('../pages/AlbumDetail'));
|
||||
const AlbumDetail = lazy(() => import('@/features/album/pages/AlbumDetail'));
|
||||
const MostPlayed = lazy(() => import('../pages/MostPlayed'));
|
||||
const LosslessAlbums = lazy(() => import('../pages/LosslessAlbums'));
|
||||
const RandomAlbums = lazy(() => import('../pages/RandomAlbums'));
|
||||
const LosslessAlbums = lazy(() => import('@/features/album/pages/LosslessAlbums'));
|
||||
const RandomAlbums = lazy(() => import('@/features/album/pages/RandomAlbums'));
|
||||
const LuckyMixPage = lazy(() => import('../pages/LuckyMix'));
|
||||
const Playlists = lazy(() => import('../pages/Playlists'));
|
||||
const PlaylistDetail = lazy(() => import('../pages/PlaylistDetail'));
|
||||
@@ -33,7 +33,7 @@ const Help = lazy(() => import('../pages/Help'));
|
||||
const WhatsNew = lazy(() => import('../pages/WhatsNew'));
|
||||
const DeviceSync = lazy(() => import('@/features/deviceSync/pages/DeviceSync'));
|
||||
const OfflineLibrary = lazy(() => import('@/features/offline/pages/OfflineLibrary'));
|
||||
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
||||
const LabelAlbums = lazy(() => import('@/features/album/pages/LabelAlbums'));
|
||||
const SearchBrowsePage = lazy(() => import('@/features/search/pages/SearchBrowsePage'));
|
||||
const FolderBrowser = lazy(() => import('../pages/FolderBrowser'));
|
||||
const InternetRadio = lazy(() => import('@/features/radio/pages/InternetRadio'));
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ export default function MainApp() {
|
||||
const handleExport = async (since: number) => {
|
||||
setExportPickerOpen(false);
|
||||
try {
|
||||
const { exportNewAlbumsImage } = await import('../utils/export/exportNewAlbums');
|
||||
const { exportNewAlbumsImage } = await import('@/features/album');
|
||||
const result = await exportNewAlbumsImage(since);
|
||||
if (result) {
|
||||
const files = result.paths.length > 1 ? ` (${result.paths.length} Dateien)` : '';
|
||||
|
||||
@@ -24,8 +24,8 @@ import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
|
||||
import { formatHumanHoursMinutes } from '../utils/format/formatHumanDuration';
|
||||
import AlbumRow from './AlbumRow';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
|
||||
const ANCHOR_HISTORY_KEY_PREFIX = 'psysonic_because_anchor_history:';
|
||||
const PICKS_HISTORY_KEY_PREFIX = 'psysonic_because_picks:';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { Play, ListPlus, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { CoverArtImage } from '../cover/CoverArtImage';
|
||||
import { useAlbumCoverRef } from '../cover/useLibraryCoverRef';
|
||||
@@ -22,7 +22,7 @@ import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
|
||||
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album';
|
||||
|
||||
const INTERVAL_MS = 10000;
|
||||
const HERO_ALBUM_COUNT = 8;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { COVER_DENSE_RAIL_CELL_CSS_PX } from '../cover/layoutSizes';
|
||||
import { enqueueAndPlay } from '../utils/playback/playSong';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { useNavigateToArtist } from '@/features/artist';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import React, { memo } from 'react';
|
||||
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { useNavigateToArtist } from '@/features/artist';
|
||||
import { Play, ListPlus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { useNavigateToAlbum } from '@/features/album';
|
||||
import { useNavigateToArtist } from '@/features/artist';
|
||||
import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
|
||||
import { queueSongStar } from '../../store/pendingStarSync';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import type { SubsonicPlaylist, SubsonicSong } from '../../api/subsonicTypes';
|
||||
import type { ZipDownload } from '@/features/offline';
|
||||
import type { AlbumOfflineStatus } from '../../hooks/useAlbumOfflineState';
|
||||
import type { AlbumOfflineStatus } from '@/features/album';
|
||||
import { dequeueOfflinePin } from '@/features/offline';
|
||||
import { useThemeStore } from '../../store/themeStore';
|
||||
import { usePlaylistLayoutStore, type PlaylistLayoutItemId } from '../../store/playlistLayoutStore';
|
||||
|
||||
@@ -11,7 +11,7 @@ import SongRail from '../SongRail';
|
||||
import { playSongNow } from '../../utils/playback/playSong';
|
||||
import { ndListSongs, ndInvalidateSongsCache } from '../../api/navidromeBrowse';
|
||||
import { usePerfProbeFlags } from '../../utils/perf/perfFlags';
|
||||
import { useNavigateToAlbum } from '../../hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { useNavigateToArtist } from '@/features/artist';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { coverStorageKeyFromRef } from './storageKeys';
|
||||
import { resolveCoverDisplayTier } from './tiers';
|
||||
import type { CoverArtRef, CoverArtTier, CoverPrefetchPriority, CoverSurfaceKind } from './types';
|
||||
import { useThemeStore } from '../store/themeStore';
|
||||
import { deriveAlbumArtistRefs } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { deriveAlbumArtistRefs } from '@/features/album';
|
||||
import { getHeroBackdropUpgrade, recordHeroBackdropUpgrade } from './heroBackdropMemory';
|
||||
|
||||
export type CoverWarmItem = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { api } from './subsonicClient';
|
||||
import type { AlbumInfo } from './subsonicTypes';
|
||||
import { api } from '@/api/subsonicClient';
|
||||
import type { AlbumInfo } from '@/api/subsonicTypes';
|
||||
|
||||
export async function getAlbumInfo2(albumId: string): Promise<AlbumInfo | null> {
|
||||
try {
|
||||
@@ -1,29 +1,29 @@
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigateToAlbum } from '../hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album/hooks/useNavigateToAlbum';
|
||||
import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||
import { isOfflinePinComplete } from '@/features/offline';
|
||||
import { CoverArtImage } from '../cover/CoverArtImage';
|
||||
import { useAlbumCoverRef } from '../cover/useLibraryCoverRef';
|
||||
import { coverStorageKeyFromRef } from '../cover/storageKeys';
|
||||
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 { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { useAlbumCoverRef } from '@/cover/useLibraryCoverRef';
|
||||
import { coverStorageKeyFromRef } from '@/cover/storageKeys';
|
||||
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 '@/features/artist';
|
||||
import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { isAlbumRecentlyAdded } from '../utils/albumRecency';
|
||||
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { coverServerScopeForServerId } from '../cover/serverScope';
|
||||
import { appendServerQuery } from '../utils/navigation/detailServerScope';
|
||||
import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '@/utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '@/hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from '@/components/LongPressWaveOverlay';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency';
|
||||
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import { coverServerScopeForServerId } from '@/cover/serverScope';
|
||||
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
|
||||
|
||||
interface AlbumCardProps {
|
||||
album: SubsonicAlbum;
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { ListPlus, Search, X } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { useSelectionStore } from '../../store/selectionStore';
|
||||
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { AddToPlaylistSubmenu } from '@/components/ContextMenu';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
|
||||
interface Props {
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../test/helpers/renderWithProviders';
|
||||
import AlbumHeader from './AlbumHeader';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
|
||||
import AlbumHeader from '@/features/album/components/AlbumHeader';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
|
||||
const navigate = vi.fn();
|
||||
|
||||
@@ -13,14 +13,14 @@ vi.mock('react-router-dom', async importActual => {
|
||||
});
|
||||
|
||||
// Genre-unrelated dependencies — stub so the test stays focused on the meta row.
|
||||
vi.mock('../cover/useLibraryCoverRef', () => ({ useAlbumCoverRef: () => undefined }));
|
||||
vi.mock('../cover/lightbox', () => ({ useCoverLightboxSrc: () => ({ open: vi.fn(), lightbox: null }) }));
|
||||
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('@/cover/useLibraryCoverRef', () => ({ useAlbumCoverRef: () => undefined }));
|
||||
vi.mock('@/cover/lightbox', () => ({ useCoverLightboxSrc: () => ({ open: vi.fn(), lightbox: null }) }));
|
||||
vi.mock('@/features/album/hooks/useAlbumDetailBack', () => ({ useAlbumDetailBack: () => vi.fn() }));
|
||||
vi.mock('@/hooks/useIsMobile', () => ({ useIsMobile: () => false }));
|
||||
vi.mock('@/store/themeStore', () => ({ useThemeStore: () => false }));
|
||||
vi.mock('@/components/StarRating', () => ({ default: () => null }));
|
||||
vi.mock('@/features/artist', () => ({ OpenArtistRefInline: () => null }));
|
||||
vi.mock('../cover/CoverArtImage', () => ({ CoverArtImage: () => null }));
|
||||
vi.mock('@/cover/CoverArtImage', () => ({ CoverArtImage: () => null }));
|
||||
|
||||
function baseProps() {
|
||||
return {
|
||||
@@ -1,27 +1,27 @@
|
||||
import type { EntityRatingSupportLevel, SubsonicItemGenre, SubsonicOpenArtistRef, SubsonicSong } from '../api/subsonicTypes';
|
||||
import type { EntityRatingSupportLevel, SubsonicItemGenre, SubsonicOpenArtistRef, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { type CSSProperties, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Play, Heart, X, ChevronLeft, Download, ListPlus, HardDriveDownload, Share2, Highlighter, Loader2, Shuffle } from 'lucide-react';
|
||||
import { CoverArtImage } from '../cover/CoverArtImage';
|
||||
import { useAlbumCoverRef } from '../cover/useLibraryCoverRef';
|
||||
import { useCoverLightboxSrc } from '../cover/lightbox';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { useAlbumCoverRef } from '@/cover/useLibraryCoverRef';
|
||||
import { useCoverLightboxSrc } from '@/cover/lightbox';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useAlbumDetailBack } from '../hooks/useAlbumDetailBack';
|
||||
import { useThemeStore } from '../store/themeStore';
|
||||
import StarRating from './StarRating';
|
||||
import { copyEntityShareLink } from '../utils/share/copyEntityShareLink';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { isAlbumRecentlyAdded } from '../utils/albumRecency';
|
||||
import { formatLongDuration } from '../utils/format/formatDuration';
|
||||
import { formatMb } from '../utils/format/formatBytes';
|
||||
import { sanitizeHtml } from '../utils/sanitizeHtml';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useAlbumDetailBack } from '@/features/album/hooks/useAlbumDetailBack';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import StarRating from '@/components/StarRating';
|
||||
import { copyEntityShareLink } from '@/utils/share/copyEntityShareLink';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency';
|
||||
import { formatLongDuration } from '@/utils/format/formatDuration';
|
||||
import { formatMb } from '@/utils/format/formatBytes';
|
||||
import { sanitizeHtml } from '@/utils/sanitizeHtml';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
import { deriveAlbumGenreTags } from '../utils/library/genreTags';
|
||||
import { genreColor } from '../utils/library/genreColor';
|
||||
import { deriveAlbumGenreTags } from '@/utils/library/genreTags';
|
||||
import { genreColor } from '@/utils/library/genreColor';
|
||||
|
||||
/** True when the album artist label means "no single artist" — `getArtistInfo`
|
||||
* has nothing meaningful to return for these, so the Artist Bio entry is hidden.
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import React, { useRef, useState, useEffect, useLayoutEffect, useMemo } from 'react';
|
||||
import AlbumCard from './AlbumCard';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
+13
-13
@@ -1,23 +1,23 @@
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { useTracklistColumns } from '../utils/useTracklistColumns';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useTracklistColumns } from '@/utils/useTracklistColumns';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useSelectionStore } from '../store/selectionStore';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import {
|
||||
COLUMNS,
|
||||
type SortKey,
|
||||
} from '../utils/componentHelpers/albumTrackListHelpers';
|
||||
import { useAlbumTrackListSelection } from '../hooks/useAlbumTrackListSelection';
|
||||
import { TrackRow } from './albumTrackList/TrackRow';
|
||||
import { AlbumTrackListMobile } from './albumTrackList/AlbumTrackListMobile';
|
||||
import { TracklistColumnPicker } from './albumTrackList/TracklistColumnPicker';
|
||||
import { TracklistHeaderRow } from './albumTrackList/TracklistHeaderRow';
|
||||
} from '@/features/album/utils/albumTrackListHelpers';
|
||||
import { useAlbumTrackListSelection } from '@/features/album/hooks/useAlbumTrackListSelection';
|
||||
import { TrackRow } from '@/features/album/components/TrackRow';
|
||||
import { AlbumTrackListMobile } from '@/features/album/components/AlbumTrackListMobile';
|
||||
import { TracklistColumnPicker } from '@/components/albumTrackList/TracklistColumnPicker';
|
||||
import { TracklistHeaderRow } from '@/features/album/components/TracklistHeaderRow';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
|
||||
export type { SortKey } from '../utils/componentHelpers/albumTrackListHelpers';
|
||||
export type { SortKey } from '@/features/album/utils/albumTrackListHelpers';
|
||||
|
||||
interface AlbumTrackListProps {
|
||||
songs: SubsonicSong[];
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { AudioLines } from 'lucide-react';
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import type { Track } from '../../store/playerStoreTypes';
|
||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||
import { formatLongDuration } from '../../utils/format/formatDuration';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { formatLongDuration } from '@/utils/format/formatDuration';
|
||||
|
||||
interface Props {
|
||||
discNums: number[];
|
||||
+7
-7
@@ -1,12 +1,12 @@
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ndListLosslessAlbumsPage } from '../api/navidromeBrowse';
|
||||
import AlbumRow from './AlbumRow';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { runLocalLosslessAlbums } from '../utils/library/browseTextSearch';
|
||||
import { LOSSLESS_MODE_QUERY } from '../utils/library/losslessMode';
|
||||
import { ndListLosslessAlbumsPage } from '@/api/navidromeBrowse';
|
||||
import AlbumRow from '@/features/album/components/AlbumRow';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { runLocalLosslessAlbums } from '@/utils/library/browseTextSearch';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
|
||||
interface Props {
|
||||
disableArtwork?: boolean;
|
||||
+13
-13
@@ -2,20 +2,20 @@ import React from 'react';
|
||||
import { AudioLines, ChevronRight, Heart, Play, Square } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { ColDef } from '../../utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import type { Track } from '../../store/playerStoreTypes';
|
||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||
import { useSelectionStore } from '../../store/selectionStore';
|
||||
import { useThemeStore } from '../../store/themeStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
|
||||
import StarRating from '../StarRating';
|
||||
import { codecLabel, type ColKey } from '../../utils/componentHelpers/albumTrackListHelpers';
|
||||
import { formatLongDuration } from '../../utils/format/formatDuration';
|
||||
import { formatLastSeen } from '../../utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '../../i18n';
|
||||
import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
|
||||
import StarRating from '@/components/StarRating';
|
||||
import { codecLabel, type ColKey } from '@/features/album/utils/albumTrackListHelpers';
|
||||
import { formatLongDuration } from '@/utils/format/formatDuration';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '@/i18n';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs';
|
||||
import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
|
||||
|
||||
type ContextMenuFn = (
|
||||
x: number,
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { ColDef } from '../../utils/useTracklistColumns';
|
||||
import { CENTERED_COLS, isSortable, type ColKey, type SortKey } from '../../utils/componentHelpers/albumTrackListHelpers';
|
||||
import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import { CENTERED_COLS, isSortable, type ColKey, type SortKey } from '@/features/album/utils/albumTrackListHelpers';
|
||||
|
||||
interface Props {
|
||||
visibleCols: readonly ColDef[];
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
|
||||
// Keep pagination termination rules aligned with the hook implementation.
|
||||
function resolveHasMoreAfterPage(
|
||||
@@ -5,16 +5,16 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import {
|
||||
coverTrafficBeginGridPagination,
|
||||
coverTrafficEndGridPagination,
|
||||
coverTrafficGridPaginationDepth,
|
||||
} from '../cover/coverTraffic';
|
||||
import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '../cover/ensureQueue';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '../utils/library/albumCompilation';
|
||||
import type { AlbumCompFilter } from '../utils/library/albumCompilation';
|
||||
} from '@/cover/coverTraffic';
|
||||
import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/utils/library/albumCompilation';
|
||||
import type { AlbumCompFilter } from '@/utils/library/albumCompilation';
|
||||
import {
|
||||
albumBrowseHasGenreFilter,
|
||||
albumBrowseHasServerFilters,
|
||||
@@ -24,22 +24,22 @@ import {
|
||||
fetchLocalAlbumCatalogChunk,
|
||||
type AlbumBrowseQuery,
|
||||
type GenreFilterOption,
|
||||
} from '../utils/library/albumBrowseLoad';
|
||||
import { libraryScopeForServer } from '../api/subsonicClient';
|
||||
} from '@/utils/library/albumBrowseLoad';
|
||||
import { libraryScopeForServer } from '@/api/subsonicClient';
|
||||
import {
|
||||
ALBUM_YEAR_FILTER_DEBOUNCE_MS,
|
||||
resolveAlbumYearBounds,
|
||||
} from '../utils/library/albumYearFilter';
|
||||
} from '@/utils/library/albumYearFilter';
|
||||
import { loadOfflineAlbumBrowseInitial } from '@/features/offline';
|
||||
import { useOfflineBrowseReloadToken } from '@/features/offline';
|
||||
import {
|
||||
fetchAlbumBrowseCatalogChunk,
|
||||
mergeAlbumCatalogChunk,
|
||||
} from '../utils/library/albumBrowseCatalogChunk';
|
||||
} from '@/utils/library/albumBrowseCatalogChunk';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { useClientSliceInfiniteScroll } from './useClientSliceInfiniteScroll';
|
||||
import { useDebouncedValue } from './useDebouncedValue';
|
||||
import { useInpageScrollSentinel } from './useInpageScrollSentinel';
|
||||
import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
|
||||
|
||||
const PAGE_SIZE = 30;
|
||||
const CLIENT_SLICE_PAGE_SIZE = 60;
|
||||
+5
-5
@@ -3,7 +3,7 @@ import { useLocation, useNavigationType, type NavigationType } from 'react-route
|
||||
import {
|
||||
ALBUMS_INPAGE_SCROLL_VIEWPORT_ID,
|
||||
readInpageScrollTop,
|
||||
} from '../constants/appScroll';
|
||||
} from '@/constants/appScroll';
|
||||
import {
|
||||
DEFAULT_ALBUM_BROWSE_RETURN_FILTERS,
|
||||
type AlbumBrowseCompFilter,
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
albumBrowseSurfaceForPath,
|
||||
isAlbumDetailPath,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from '../store/albumBrowseSessionStore';
|
||||
import type { AlbumBrowseSort } from '../utils/library/browseTextSearch';
|
||||
import { shouldRestoreAlbumBrowseSession } from '../utils/navigation/albumDetailNavigation';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
} from '@/features/album/store/albumBrowseSessionStore';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/browseTextSearch';
|
||||
import { shouldRestoreAlbumBrowseSession } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
|
||||
const ALBUMS_SURFACE: AlbumBrowseSurface = 'albums';
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useLayoutEffect, useRef, type RefObject } from 'react';
|
||||
import type { AlbumBrowseScrollSnapshot } from './useAlbumBrowseFilters';
|
||||
import type { AlbumBrowseScrollSnapshot } from '@/features/album/hooks/useAlbumBrowseFilters';
|
||||
|
||||
type Args = {
|
||||
scrollSnapshotRef: RefObject<AlbumBrowseScrollSnapshot>;
|
||||
+2
-2
@@ -6,8 +6,8 @@ import {
|
||||
peekGenreDetailScrollRestore,
|
||||
type AlbumBrowseSurface,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from '../store/albumBrowseSessionStore';
|
||||
import { shouldRestoreAlbumBrowseSession } from '../utils/navigation/albumDetailNavigation';
|
||||
} from '@/features/album/store/albumBrowseSessionStore';
|
||||
import { shouldRestoreAlbumBrowseSession } from '@/utils/navigation/albumDetailNavigation';
|
||||
|
||||
type PendingScroll = {
|
||||
scrollTop: number;
|
||||
+2
-2
@@ -3,8 +3,8 @@ import {
|
||||
ALBUM_YEAR_MAX,
|
||||
ALBUM_YEAR_MIN,
|
||||
type AlbumCatalogYearRange,
|
||||
} from '../utils/library/albumYearFilter';
|
||||
import { fetchAlbumCatalogYearBounds } from '../utils/library/albumCatalogYearBounds';
|
||||
} from '@/utils/library/albumYearFilter';
|
||||
import { fetchAlbumCatalogYearBounds } from '@/utils/library/albumCatalogYearBounds';
|
||||
|
||||
const DEFAULT: AlbumCatalogYearRange = { min: ALBUM_YEAR_MIN, max: ALBUM_YEAR_MAX };
|
||||
|
||||
+4
-4
@@ -3,11 +3,11 @@ import React from 'react';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { useAlbumDetailBack } from './useAlbumDetailBack';
|
||||
import { navigateAlbumDetailBack } from '../utils/navigation/albumDetailNavigation';
|
||||
import { useAlbumDetailBack } from '@/features/album/hooks/useAlbumDetailBack';
|
||||
import { navigateAlbumDetailBack } from '@/utils/navigation/albumDetailNavigation';
|
||||
|
||||
vi.mock('../utils/navigation/albumDetailNavigation', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('../utils/navigation/albumDetailNavigation')>();
|
||||
vi.mock('@/utils/navigation/albumDetailNavigation', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('@/utils/navigation/albumDetailNavigation')>();
|
||||
return {
|
||||
...mod,
|
||||
navigateAlbumDetailBack: vi.fn(mod.navigateAlbumDetailBack),
|
||||
@@ -3,7 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
navigateAlbumDetailBack,
|
||||
readAlbumDetailReturnTo,
|
||||
} from '../utils/navigation/albumDetailNavigation';
|
||||
} from '@/utils/navigation/albumDetailNavigation';
|
||||
|
||||
/** Leave album/artist detail for the page that opened it (or history back as fallback). */
|
||||
export function useAlbumDetailBack(fallback = '/') {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import {
|
||||
loadAlbumFromLibraryIndex,
|
||||
loadArtistFromLibraryIndex,
|
||||
@@ -16,12 +16,12 @@ import {
|
||||
loadArtistFromLocalPlayback,
|
||||
offlineLocalBrowseEnabled,
|
||||
} from '@/features/offline';
|
||||
import { readDetailServerId } from '../utils/navigation/detailServerScope';
|
||||
import { libraryIsReady } from '../utils/library/libraryReady';
|
||||
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import {
|
||||
shouldAttemptSubsonicForActiveServer,
|
||||
shouldAttemptSubsonicForServer,
|
||||
} from '../utils/network/subsonicNetworkGuard';
|
||||
} from '@/utils/network/subsonicNetworkGuard';
|
||||
|
||||
type AlbumPayload = ResolvedAlbum;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
|
||||
export type AlbumSortKey = 'natural' | 'title' | 'artist' | 'album' | 'favorite' | 'rating' | 'duration' | 'playCount' | 'lastPlayed' | 'bpm';
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState, type RefObject } from 'react';
|
||||
import { useLocation, useNavigationType, type NavigationType } from 'react-router-dom';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import {
|
||||
DEFAULT_ALBUM_BROWSE_RETURN_FILTERS,
|
||||
type AlbumBrowseReturnFilters,
|
||||
@@ -8,14 +8,14 @@ import {
|
||||
albumBrowseSurfaceForPath,
|
||||
isAlbumDetailPath,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from '../store/albumBrowseSessionStore';
|
||||
import { shouldRestoreAlbumBrowseSession } from '../utils/navigation/albumDetailNavigation';
|
||||
import { useLiveSearchScopeStore } from '../store/liveSearchScopeStore';
|
||||
} from '@/features/album/store/albumBrowseSessionStore';
|
||||
import { shouldRestoreAlbumBrowseSession } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
import {
|
||||
inpageScrollViewportIdForSurface,
|
||||
readInpageScrollTop,
|
||||
} from '../constants/appScroll';
|
||||
import type { AlbumBrowseScrollSnapshot } from './useAlbumBrowseFilters';
|
||||
} from '@/constants/appScroll';
|
||||
import type { AlbumBrowseScrollSnapshot } from '@/features/album/hooks/useAlbumBrowseFilters';
|
||||
|
||||
export type AlbumGridBrowseSnapshot = {
|
||||
albums: SubsonicAlbum[];
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useOfflineJobStore } from '@/features/offline';
|
||||
import { useAlbumOfflineState } from './useAlbumOfflineState';
|
||||
import { useAlbumOfflineState } from '@/features/album/hooks/useAlbumOfflineState';
|
||||
|
||||
describe('useAlbumOfflineState', () => {
|
||||
beforeEach(() => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||
import { useOfflineJobStore } from '@/features/offline';
|
||||
import { isOfflinePinComplete } from '@/features/offline';
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { useSelectionStore } from '../store/selectionStore';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useDragDrop } from '@/contexts/DragDropContext';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
|
||||
interface UseAlbumTrackListSelectionArgs {
|
||||
songs: SubsonicSong[];
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
BROWSE_TEXT_DEBOUNCE_NETWORK_MS,
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
raceBrowseWithLocalFallback,
|
||||
runLocalBrowseAlbums,
|
||||
runNetworkBrowseAlbums,
|
||||
} from '../utils/library/browseTextSearch';
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '@/features/offline';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import type { AlbumBrowseSort } from '../utils/library/albumBrowseSort';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
|
||||
import {
|
||||
fetchGenreAlbumPage,
|
||||
GENRE_ALBUM_CATALOG_CHUNK,
|
||||
GENRE_ALBUM_FIRST_PAGE,
|
||||
} from '../utils/library/genreAlbumBrowse';
|
||||
import { useClientSliceInfiniteScroll } from './useClientSliceInfiniteScroll';
|
||||
import { useInpageScrollSentinel } from './useInpageScrollSentinel';
|
||||
} from '@/utils/library/genreAlbumBrowse';
|
||||
import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll';
|
||||
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
|
||||
|
||||
const CLIENT_SLICE_PAGE_SIZE = GENRE_ALBUM_FIRST_PAGE;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { navigateToAlbumDetail } from '../utils/navigation/albumDetailNavigation';
|
||||
import { navigateToAlbumDetail } from '@/utils/navigation/albumDetailNavigation';
|
||||
|
||||
/** Navigate to album detail, remembering the current page for the back button. */
|
||||
export function useNavigateToAlbum() {
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Album feature — the Albums/Random/Lossless/Label browse pages + AlbumDetail
|
||||
* (all lazy via deep `pages/*`, not re-exported), album cards/rows/header, the
|
||||
* album track list + its mobile/header/row pieces, album detail + browse hooks,
|
||||
* the album session store, per-album offline status, and album export helpers.
|
||||
*
|
||||
* Stays OUT (library-core / cross-cutting, consumed by this feature, not owned):
|
||||
* the `utils/library/album*` browse query engine (shared with offline + other
|
||||
* browse surfaces → M4), `albumDetailNavigation` (cross-cutting nav → M4),
|
||||
* `playAlbum` (playback action → playback), `starredAlbumIndexSync` (index
|
||||
* sync, core), the album context-menu items (context-menu subsystem), the
|
||||
* shared `TracklistColumnPicker` (also used by favorites), and `cover/*`.
|
||||
*/
|
||||
export * from './api/subsonicAlbumInfo';
|
||||
export * from './hooks/useAlbumBrowseData';
|
||||
export * from './hooks/useAlbumBrowseFilters';
|
||||
export * from './hooks/useAlbumBrowseScrollReset';
|
||||
export * from './hooks/useAlbumBrowseScrollRestore';
|
||||
export * from './hooks/useAlbumCatalogYearBounds';
|
||||
export * from './hooks/useAlbumDetailBack';
|
||||
export * from './hooks/useAlbumDetailData';
|
||||
export * from './hooks/useAlbumDetailSort';
|
||||
export * from './hooks/useAlbumGridBrowseFilters';
|
||||
export * from './hooks/useAlbumOfflineState';
|
||||
export * from './hooks/useAlbumTrackListSelection';
|
||||
export * from './hooks/useBrowseAlbumTextSearch';
|
||||
export * from './hooks/useGenreAlbumBrowse';
|
||||
export * from './hooks/useNavigateToAlbum';
|
||||
export * from './store/albumBrowseSessionStore';
|
||||
export * from './utils/albumDetailHelpers';
|
||||
export * from './utils/albumRecency';
|
||||
export * from './utils/albumTrackListHelpers';
|
||||
export * from './utils/deriveAlbumHeaderArtistRefs';
|
||||
export * from './utils/exportAlbumCard';
|
||||
export * from './utils/exportNewAlbums';
|
||||
export { default as AlbumCard } from './components/AlbumCard';
|
||||
export { default as AlbumHeader } from './components/AlbumHeader';
|
||||
export { default as AlbumRow } from './components/AlbumRow';
|
||||
export { default as AlbumTrackList } from './components/AlbumTrackList';
|
||||
export { default as LosslessAlbumsRail } from './components/LosslessAlbumsRail';
|
||||
@@ -1,51 +1,51 @@
|
||||
import { buildDownloadUrl } from '../api/subsonicStreamUrl';
|
||||
import { setRating, star, unstar } from '../api/subsonicStarRating';
|
||||
import { queueSongStar, queueSongRating } from '../store/pendingStarSync';
|
||||
import { getAlbumForServer } from '../api/subsonicLibrary';
|
||||
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 '@/features/artist';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { shuffleArray } from '../utils/playback/shuffleArray';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { shuffleArray } from '@/utils/playback/shuffleArray';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { useAlbumDetailData } from '../hooks/useAlbumDetailData';
|
||||
import { useAlbumOfflineState } from '../hooks/useAlbumOfflineState';
|
||||
import { useAlbumDetailSort } from '../hooks/useAlbumDetailSort';
|
||||
import { useAlbumDetailData } from '@/features/album/hooks/useAlbumDetailData';
|
||||
import { useAlbumOfflineState } from '@/features/album/hooks/useAlbumOfflineState';
|
||||
import { useAlbumDetailSort } from '@/features/album/hooks/useAlbumDetailSort';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { useOfflineStore } from '@/features/offline';
|
||||
import { useOfflineJobStore } from '@/features/offline';
|
||||
import { isOfflinePinComplete } from '@/features/offline';
|
||||
import { dequeueOfflinePin } from '@/features/offline';
|
||||
import { reconcileLibraryTierForAlbum } from '@/features/offline';
|
||||
import { shouldAttemptSubsonicForServer } from '../utils/network/subsonicNetworkGuard';
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import AlbumHeader from '../components/AlbumHeader';
|
||||
import AlbumTrackList from '../components/AlbumTrackList';
|
||||
import { AlbumDetailToolbar } from '../components/albumDetail/AlbumDetailToolbar';
|
||||
import { useCoverArt } from '../cover/useCoverArt';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import AlbumHeader from '@/features/album/components/AlbumHeader';
|
||||
import AlbumTrackList from '@/features/album/components/AlbumTrackList';
|
||||
import { AlbumDetailToolbar } from '@/features/album/components/AlbumDetailToolbar';
|
||||
import { useCoverArt } from '@/cover/useCoverArt';
|
||||
import {
|
||||
forgetAlbumDistinctDiscCovers,
|
||||
rememberAlbumDistinctDiscCovers,
|
||||
} from '../cover/ref';
|
||||
import { useAlbumCoverRef } from '../cover/useLibraryCoverRef';
|
||||
} from '@/cover/ref';
|
||||
import { useAlbumCoverRef } from '@/cover/useLibraryCoverRef';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { useSelectionStore } from '../store/selectionStore';
|
||||
import { sanitizeFilename } from '../utils/componentHelpers/albumDetailHelpers';
|
||||
import { albumArtistDisplayName, deriveAlbumHeaderArtistRefs } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers } from '../cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import LosslessModeBanner from '../components/LosslessModeBanner';
|
||||
import { isLosslessSuffix } from '../utils/library/losslessFormats';
|
||||
import { isLosslessMode } from '../utils/library/losslessMode';
|
||||
import { readDetailServerId } from '../utils/navigation/detailServerScope';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { sanitizeFilename } from '@/features/album/utils/albumDetailHelpers';
|
||||
import { albumArtistDisplayName, deriveAlbumHeaderArtistRefs } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers } from '@/cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import LosslessModeBanner from '@/components/LosslessModeBanner';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
import { isLosslessMode } from '@/utils/library/losslessMode';
|
||||
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { offlineActionPolicy } from '@/features/offline';
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import { buildDownloadUrl } from '../api/subsonicStreamUrl';
|
||||
import { buildDownloadUrl } from '@/api/subsonicStreamUrl';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useState, useEffect, useLayoutEffect, useRef, useMemo } from 'react';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import { albumGridWarmCovers, coverDisplayCssPxForAlbumGrid } from '../cover/layoutSizes';
|
||||
import { useLibraryCoverPrefetch } from '../cover/useLibraryCoverPrefetch';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { clampLibraryGridMaxColumns } from '../store/authStoreHelpers';
|
||||
import { computeCardGridColumnCount } from '../utils/cardGridLayout';
|
||||
import GenreFilterBar from '../components/GenreFilterBar';
|
||||
import YearFilterButton from '../components/YearFilterButton';
|
||||
import StarFilterButton from '../components/StarFilterButton';
|
||||
import LosslessFilterButton from '../components/LosslessFilterButton';
|
||||
import SortDropdown from '../components/SortDropdown';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { albumGridWarmCovers, coverDisplayCssPxForAlbumGrid } from '@/cover/layoutSizes';
|
||||
import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { clampLibraryGridMaxColumns } from '@/store/authStoreHelpers';
|
||||
import { computeCardGridColumnCount } from '@/utils/cardGridLayout';
|
||||
import GenreFilterBar from '@/components/GenreFilterBar';
|
||||
import YearFilterButton from '@/components/YearFilterButton';
|
||||
import StarFilterButton from '@/components/StarFilterButton';
|
||||
import LosslessFilterButton from '@/components/LosslessFilterButton';
|
||||
import SortDropdown from '@/components/SortDropdown';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useOfflineStore } from '@/features/offline';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { Download, HardDriveDownload, Disc3, ListPlus } from 'lucide-react';
|
||||
import SelectionToggleButton from '../components/SelectionToggleButton';
|
||||
import FilterQuickClear from '../components/FilterQuickClear';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { useRangeSelection } from '../hooks/useRangeSelection';
|
||||
import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '../components/InpageScrollSentinel';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import SelectionToggleButton from '@/components/SelectionToggleButton';
|
||||
import FilterQuickClear from '@/components/FilterQuickClear';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight';
|
||||
import { useInpageScrollViewport } from '@/hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '@/components/InpageScrollSentinel';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import { ALBUMS_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { useAlbumBrowseFilters, useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '../hooks/useAlbumBrowseFilters';
|
||||
import { useAlbumBrowseData } from '../hooks/useAlbumBrowseData';
|
||||
import { useAlbumBrowseScrollRestore } from '../hooks/useAlbumBrowseScrollRestore';
|
||||
import { useAlbumBrowseScrollReset } from '../hooks/useAlbumBrowseScrollReset';
|
||||
import { useBrowseAlbumTextSearch } from '../hooks/useBrowseAlbumTextSearch';
|
||||
import { peekAlbumBrowseScrollRestore } from '../store/albumBrowseSessionStore';
|
||||
import { readAlbumBrowseRestore } from '../utils/navigation/albumDetailNavigation';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { useAlbumCatalogYearBounds } from '../hooks/useAlbumCatalogYearBounds';
|
||||
import type { AlbumBrowseSort } from '../utils/library/albumBrowseSort';
|
||||
import { LOSSLESS_MODE_QUERY } from '../utils/library/losslessMode';
|
||||
import { resolveAlbumYearBounds } from '../utils/library/albumYearFilter';
|
||||
import { ALBUMS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { useAlbumBrowseFilters, useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '@/features/album/hooks/useAlbumBrowseFilters';
|
||||
import { useAlbumBrowseData } from '@/features/album/hooks/useAlbumBrowseData';
|
||||
import { useAlbumBrowseScrollRestore } from '@/features/album/hooks/useAlbumBrowseScrollRestore';
|
||||
import { useAlbumBrowseScrollReset } from '@/features/album/hooks/useAlbumBrowseScrollReset';
|
||||
import { useBrowseAlbumTextSearch } from '@/features/album/hooks/useBrowseAlbumTextSearch';
|
||||
import { peekAlbumBrowseScrollRestore } from '@/features/album/store/albumBrowseSessionStore';
|
||||
import { readAlbumBrowseRestore } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import { useAlbumCatalogYearBounds } from '@/features/album/hooks/useAlbumCatalogYearBounds';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { resolveAlbumYearBounds } from '@/utils/library/albumYearFilter';
|
||||
import {
|
||||
filterAlbumsByCompilation,
|
||||
filterAlbumsByGenres,
|
||||
filterAlbumsByStarred,
|
||||
filterAlbumsByYearBounds,
|
||||
} from '../utils/library/albumBrowseFilters';
|
||||
import { useScopedBrowseSearchQuery } from '../store/liveSearchScopeStore';
|
||||
} from '@/utils/library/albumBrowseFilters';
|
||||
import { useScopedBrowseSearchQuery } from '@/store/liveSearchScopeStore';
|
||||
|
||||
type SortType = AlbumBrowseSort;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { search } from '../api/subsonicSearch';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import { search } from '@/api/subsonicSearch';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers } from '../cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers } from '@/cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
|
||||
export default function LabelAlbums() {
|
||||
const { t } = useTranslation();
|
||||
@@ -1,44 +1,44 @@
|
||||
import { buildDownloadUrl } from '../api/subsonicStreamUrl';
|
||||
import { buildDownloadUrl } from '@/api/subsonicStreamUrl';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import { LOSSLESS_MODE_QUERY } from '../utils/library/losslessMode';
|
||||
import { ndListLosslessAlbumsPage } from '../api/navidromeBrowse';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { ndListLosslessAlbumsPage } from '@/api/navidromeBrowse';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useOfflineStore } from '@/features/offline';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { useRangeSelection } from '../hooks/useRangeSelection';
|
||||
import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { Download, HardDriveDownload, ListPlus } from 'lucide-react';
|
||||
import SelectionToggleButton from '../components/SelectionToggleButton';
|
||||
import { albumGridWarmCovers } from '../cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import SelectionToggleButton from '@/components/SelectionToggleButton';
|
||||
import { albumGridWarmCovers } from '@/cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import { LOSSLESS_ALBUMS_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useInpageScrollSentinel } from '../hooks/useInpageScrollSentinel';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '../components/InpageScrollSentinel';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import SortDropdown from '../components/SortDropdown';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { LOSSLESS_ALBUMS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
|
||||
import { useInpageScrollViewport } from '@/hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '@/components/InpageScrollSentinel';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import SortDropdown from '@/components/SortDropdown';
|
||||
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import {
|
||||
albumBrowseSortForServer,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from '../store/albumBrowseSessionStore';
|
||||
} from '@/features/album/store/albumBrowseSessionStore';
|
||||
import {
|
||||
runLocalAlbumBrowsePage,
|
||||
sortSubsonicAlbums,
|
||||
type AlbumBrowseSort,
|
||||
} from '../utils/library/browseTextSearch';
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
|
||||
/** Local index page size — SQLite is cheap; larger pages than the network walk. */
|
||||
const LOCAL_PAGE_SIZE = 30;
|
||||
@@ -1,43 +1,43 @@
|
||||
import { buildDownloadUrl } from '../api/subsonicStreamUrl';
|
||||
import { getAlbumsByGenre } from '../api/subsonicGenres';
|
||||
import { getAlbumList } from '../api/subsonicLibrary';
|
||||
import { buildDownloadUrl } from '@/api/subsonicStreamUrl';
|
||||
import { getAlbumsByGenre } from '@/api/subsonicGenres';
|
||||
import { getAlbumList } from '@/api/subsonicLibrary';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import { shuffleArray } from '../utils/playback/shuffleArray';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { shuffleArray } from '@/utils/playback/shuffleArray';
|
||||
import React, { useEffect, useLayoutEffect, useState, useCallback, useRef } from 'react';
|
||||
import { RefreshCw, Download, HardDriveDownload } from 'lucide-react';
|
||||
import SelectionToggleButton from '../components/SelectionToggleButton';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import GenreFilterBar from '../components/GenreFilterBar';
|
||||
import SelectionToggleButton from '@/components/SelectionToggleButton';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import GenreFilterBar from '@/components/GenreFilterBar';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter';
|
||||
import { runLocalRandomAlbums, runLocalAlbumsByGenres } from '../utils/library/browseTextSearch';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '@/utils/mix/mixRatingFilter';
|
||||
import { runLocalRandomAlbums, runLocalAlbumsByGenres } from '@/utils/library/browseTextSearch';
|
||||
import { useOfflineStore } from '@/features/offline';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
import { useRangeSelection } from '../hooks/useRangeSelection';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers, COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../cover/layoutSizes';
|
||||
import { useRangeSelection } from '@/hooks/useRangeSelection';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers, COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
|
||||
import {
|
||||
primeAlbumCoversForDisplay,
|
||||
} from '../cover/warmDiskPeek';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
} from '@/cover/warmDiskPeek';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import { RANDOM_ALBUMS_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import { useAlbumGridBrowseFilters, type AlbumGridBrowseSnapshot } from '../hooks/useAlbumGridBrowseFilters';
|
||||
import { useAlbumBrowseScrollRestore } from '../hooks/useAlbumBrowseScrollRestore';
|
||||
import { useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '../hooks/useAlbumBrowseFilters';
|
||||
import { readAlbumBrowseRestore } from '../utils/navigation/albumDetailNavigation';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { RANDOM_ALBUMS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight';
|
||||
import { useInpageScrollViewport } from '@/hooks/useInpageScrollViewport';
|
||||
import { useAlbumGridBrowseFilters, type AlbumGridBrowseSnapshot } from '@/features/album/hooks/useAlbumGridBrowseFilters';
|
||||
import { useAlbumBrowseScrollRestore } from '@/features/album/hooks/useAlbumBrowseScrollRestore';
|
||||
import { useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '@/features/album/hooks/useAlbumBrowseFilters';
|
||||
import { readAlbumBrowseRestore } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
|
||||
const ALBUM_COUNT = 30;
|
||||
/** Extra pool when mix rating filter is on so we can still fill the grid after filtering. */
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import {
|
||||
DEFAULT_ALBUM_BROWSE_RETURN_FILTERS,
|
||||
DEFAULT_ALBUM_BROWSE_SORT,
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
peekGenreDetailScrollRestore,
|
||||
stashGenreDetailReturnFilters,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from './albumBrowseSessionStore';
|
||||
} from '@/features/album/store/albumBrowseSessionStore';
|
||||
|
||||
describe('albumBrowseSessionStore', () => {
|
||||
beforeEach(() => {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||
import type { AlbumBrowseSort } from '../utils/library/browseTextSearch';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/browseTextSearch';
|
||||
|
||||
export const DEFAULT_ALBUM_BROWSE_SORT: AlbumBrowseSort = 'alphabeticalByName';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { ColDef } from '../useTracklistColumns';
|
||||
import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
|
||||
export function codecLabel(song: { suffix?: string; bitRate?: number }, showBitrate: boolean): string {
|
||||
const parts: string[] = [];
|
||||
+2
-2
@@ -3,8 +3,8 @@ import {
|
||||
albumArtistDisplayName,
|
||||
deriveAlbumArtistRefs,
|
||||
deriveAlbumHeaderArtistRefs,
|
||||
} from './deriveAlbumHeaderArtistRefs';
|
||||
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
||||
} from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { makeSubsonicSong } from '@/test/helpers/factories';
|
||||
|
||||
const baseAlbum = (): SubsonicAlbum => ({
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { SubsonicAlbum, SubsonicOpenArtistRef, SubsonicSong } from '../../api/subsonicTypes';
|
||||
import type { SubsonicAlbum, SubsonicOpenArtistRef, SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { coerceOpenArtistRefs } from '@/features/artist';
|
||||
|
||||
function nonEmpty(refs: SubsonicOpenArtistRef[]): refs is SubsonicOpenArtistRef[] {
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import React from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { coverArtRef } from '../../cover/ref';
|
||||
import { loadCoverBlobForExport } from '../../cover/integrations/export';
|
||||
import PsysonicLogo from '../../components/PsysonicLogo';
|
||||
import { coverArtRef } from '@/cover/ref';
|
||||
import { loadCoverBlobForExport } from '@/cover/integrations/export';
|
||||
import PsysonicLogo from '@/components/PsysonicLogo';
|
||||
|
||||
export type ExportFormat = 'story' | 'square' | 'twitter';
|
||||
export type ExportGridSize = 3 | 4 | 5;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getAlbumList } from '../../api/subsonicLibrary';
|
||||
import { coverArtRef } from '../../cover/ref';
|
||||
import { loadCoverBlobForExport } from '../../cover/integrations/export';
|
||||
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
||||
import { albumArtistDisplayName } from '../album/deriveAlbumHeaderArtistRefs';
|
||||
import { getAlbumList } from '@/api/subsonicLibrary';
|
||||
import { coverArtRef } from '@/cover/ref';
|
||||
import { loadCoverBlobForExport } from '@/cover/integrations/export';
|
||||
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import { writeFile } from '@tauri-apps/plugin-fs';
|
||||
import { downloadDir, join } from '@tauri-apps/api/path';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
// Catppuccin Macchiato palette
|
||||
const M = {
|
||||
crust: '#181926',
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAlbumDetailBack } from '@/hooks/useAlbumDetailBack';
|
||||
import { useAlbumDetailBack } from '@/features/album';
|
||||
import {
|
||||
ArrowLeft, Camera, Check, HardDriveDownload, Heart,
|
||||
Loader2, Play, Radio, Share2, Shuffle, Users,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
isArtistsBrowsePath,
|
||||
useArtistBrowseSessionStore,
|
||||
} from '@/features/artist/store/artistBrowseSessionStore';
|
||||
import { isArtistDetailPath } from '@/store/albumBrowseSessionStore';
|
||||
import { isArtistDetailPath } from '@/features/album';
|
||||
import { shouldRestoreArtistBrowseSession } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 '@/features/album';
|
||||
import { ArrowDownUp } from 'lucide-react';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useFavoritesData } from '@/features/favorites/hooks/useFavoritesData';
|
||||
import { useFavoritesSongFiltering } from '@/features/favorites/hooks/useFavoritesSongFiltering';
|
||||
import { useFavoritesSelection } from '@/features/favorites/hooks/useFavoritesSelection';
|
||||
import { useBulkPlPickerOutsideClick } from '@/hooks/useBulkPlPickerOutsideClick';
|
||||
import AlbumRow from '@/components/AlbumRow';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import { ArtistRow } from '@/features/artist';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -22,13 +22,13 @@ import {
|
||||
} from '@/utils/library/libraryDevLog';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useNavigateToAlbum } from '@/hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { Search, Disc3, Users, Music, TextSearch, Database, Globe } from 'lucide-react';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { albumArtistDisplayName } from '@/utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage';
|
||||
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { albumCoverRefForSong } from '@/cover/ref';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { albumArtistDisplayName } from '@/utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
import { useShareSearch } from '@/features/search/hooks/useShareSearch';
|
||||
import ShareSearchResults from '@/features/search/components/ShareSearchResults';
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { KeyboardEvent, MouseEvent } from 'react';
|
||||
import { ALL_NAV_ITEMS } from '@/config/navItems';
|
||||
import type { LiveSearchScope } from '@/store/liveSearchScopeStore';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/store/albumBrowseSessionStore';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/features/album';
|
||||
import { isTracksBrowsePath } from '@/store/advancedSearchSessionStore';
|
||||
import { isArtistsBrowsePath } from '@/features/artist';
|
||||
import { isComposersBrowsePath } from '@/store/composerBrowseSessionStore';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/store/albumBrowseSessionStore';
|
||||
import { isAlbumsBrowsePath, isNewReleasesBrowsePath } from '@/features/album';
|
||||
import { isArtistsBrowsePath } from '@/features/artist';
|
||||
import { isTracksBrowsePath } from '@/store/advancedSearchSessionStore';
|
||||
import { isComposersBrowsePath } from '@/store/composerBrowseSessionStore';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigateToAlbum } from '@/hooks/useNavigateToAlbum';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import {
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSta
|
||||
import { useLocation, useNavigate, useNavigationType, useSearchParams } from 'react-router-dom';
|
||||
import { SlidersVertical, Search, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AlbumRow from '@/components/AlbumRow';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import { ArtistRow } from '@/features/artist';
|
||||
import PagedSongList from '@/components/PagedSongList';
|
||||
import CustomSelect from '@/ui/CustomSelect';
|
||||
@@ -15,7 +15,7 @@ import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { isAdvancedSearchLeaveTargetPath } from '@/store/albumBrowseSessionStore';
|
||||
import { isAdvancedSearchLeaveTargetPath } from '@/features/album';
|
||||
import {
|
||||
isAdvancedSearchPath,
|
||||
isAdvancedSearchPanelPath,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
renderAlbumCardCanvas,
|
||||
ExportFormat,
|
||||
ExportGridSize,
|
||||
} from '@/utils/export/exportAlbumCard';
|
||||
} from '@/features/album';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Share2 } from 'lucide-react';
|
||||
import { formatHumanHoursMinutes } from '@/utils/format/formatHumanDuration';
|
||||
import AlbumRow from '@/components/AlbumRow';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import StatsExportModal from '@/features/stats/components/StatsExportModal';
|
||||
import PlayerStatisticsPanel from '@/features/stats/components/PlayerStatisticsPanel';
|
||||
import StatisticsTabBar from '@/features/stats/components/StatisticsTabBar';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState, type RefObject } from 'react';
|
||||
import { useLocation, useNavigationType, type NavigationType } from 'react-router-dom';
|
||||
import { isComposerDetailPath } from '../store/albumBrowseSessionStore';
|
||||
import { isComposerDetailPath } from '@/features/album';
|
||||
import {
|
||||
DEFAULT_COMPOSER_BROWSE_RETURN_STATE,
|
||||
type ComposerBrowseReturnState,
|
||||
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
peekGenreDetailScrollRestore,
|
||||
stashGenreDetailReturnFilters,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from '../store/albumBrowseSessionStore';
|
||||
} from '@/features/album';
|
||||
import { shouldRestoreAlbumBrowseSession } from '../utils/navigation/albumDetailNavigation';
|
||||
import type { AlbumBrowseScrollSnapshot } from './useAlbumBrowseFilters';
|
||||
import type { AlbumBrowseScrollSnapshot } from '@/features/album';
|
||||
|
||||
/** Genre detail: locked genre filter + leave/restore session (same contract as All Albums). */
|
||||
export function useGenreDetailBrowse(
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SubsonicArtist, SubsonicAlbum, SubsonicArtistInfo } from '../api/s
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { ndListAlbumsByArtistRole } from '../api/navidromeBrowse';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import { AlbumCard } from '@/features/album';
|
||||
import { ArtistHeroCover } from '../cover/artistHero';
|
||||
import { coverArtRef } from '../cover/ref';
|
||||
import { useCoverLightboxSrc } from '../cover/lightbox';
|
||||
|
||||
@@ -2,16 +2,16 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ArrowLeft, Disc3, Play, ListPlus, Loader2 } from 'lucide-react';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import { AlbumCard } from '@/features/album';
|
||||
import { LongPressWaveOverlay } from '../components/LongPressWaveOverlay';
|
||||
import InpageScrollSentinel from '../components/InpageScrollSentinel';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
import { GENRE_DETAIL_INPAGE_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { albumGridWarmCovers } from '../cover/layoutSizes';
|
||||
import { useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '../hooks/useAlbumBrowseFilters';
|
||||
import { useGenreAlbumBrowse } from '../hooks/useGenreAlbumBrowse';
|
||||
import { useAlbumBrowseScrollRestore } from '../hooks/useAlbumBrowseScrollRestore';
|
||||
import { useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '@/features/album';
|
||||
import { useGenreAlbumBrowse } from '@/features/album';
|
||||
import { useAlbumBrowseScrollRestore } from '@/features/album';
|
||||
import { useGenreDetailBrowse } from '../hooks/useGenreDetailBrowse';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@ import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsoni
|
||||
import { runLocalRandomSongs } from '../utils/library/browseTextSearch';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Hero from '../components/Hero';
|
||||
import AlbumRow from '../components/AlbumRow';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import SongRail from '../components/SongRail';
|
||||
import BecauseYouLikeRail from '../components/BecauseYouLikeRail';
|
||||
import LosslessAlbumsRail from '../components/LosslessAlbumsRail';
|
||||
import { LosslessAlbumsRail } from '@/features/album';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from '../components/LongPressWaveOverlay';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { dedupeById } from '../utils/dedupeById';
|
||||
import { useEffect, useLayoutEffect, useState, useCallback, useRef, useMemo } from 'react';
|
||||
import { Download, HardDriveDownload } from 'lucide-react';
|
||||
import SelectionToggleButton from '../components/SelectionToggleButton';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import { AlbumCard } from '@/features/album';
|
||||
import GenreFilterBar from '../components/GenreFilterBar';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
@@ -29,13 +29,13 @@ import { useAsyncInpagePagination } from '../hooks/useAsyncInpagePagination';
|
||||
import { useInpageScrollSentinel } from '../hooks/useInpageScrollSentinel';
|
||||
import { useInpageScrollViewport } from '../hooks/useInpageScrollViewport';
|
||||
import InpageScrollSentinel from '../components/InpageScrollSentinel';
|
||||
import { useAlbumGridBrowseFilters, type AlbumGridBrowseSnapshot } from '../hooks/useAlbumGridBrowseFilters';
|
||||
import { useAlbumBrowseScrollRestore } from '../hooks/useAlbumBrowseScrollRestore';
|
||||
import { useAlbumBrowseScrollReset } from '../hooks/useAlbumBrowseScrollReset';
|
||||
import { useBrowseAlbumTextSearch } from '../hooks/useBrowseAlbumTextSearch';
|
||||
import { useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '../hooks/useAlbumBrowseFilters';
|
||||
import { useAlbumGridBrowseFilters, type AlbumGridBrowseSnapshot } from '@/features/album';
|
||||
import { useAlbumBrowseScrollRestore } from '@/features/album';
|
||||
import { useAlbumBrowseScrollReset } from '@/features/album';
|
||||
import { useBrowseAlbumTextSearch } from '@/features/album';
|
||||
import { useAlbumBrowseScrollSnapshotSync, type AlbumBrowseScrollSnapshot } from '@/features/album';
|
||||
import { readAlbumBrowseRestore } from '../utils/navigation/albumDetailNavigation';
|
||||
import { albumArtistDisplayName } from '../utils/album/deriveAlbumHeaderArtistRefs';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { filterAlbumsByGenres } from '../utils/library/albumBrowseFilters';
|
||||
import { useScopedBrowseSearchQuery } from '../store/liveSearchScopeStore';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import { usePlaylistStore } from '../store/playlistStore';
|
||||
import { useOfflineStore } from '@/features/offline';
|
||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||
import { useAlbumOfflineState } from '../hooks/useAlbumOfflineState';
|
||||
import { useAlbumOfflineState } from '@/features/album';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { useZipDownloadStore } from '@/features/offline';
|
||||
|
||||
@@ -47,7 +47,7 @@ vi.mock('@/features/artist', () => ({
|
||||
getSimilarSongs2: vi.fn(async () => []),
|
||||
getTopSongs: vi.fn(async () => []),
|
||||
}));
|
||||
vi.mock('@/api/subsonicAlbumInfo', () => ({
|
||||
vi.mock('@/features/album', () => ({
|
||||
getAlbumInfo2: vi.fn(async () => null),
|
||||
}));
|
||||
vi.mock('@/api/subsonicScrobble', () => ({
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
isAlbumDetailPath,
|
||||
isArtistDetailPath,
|
||||
isComposerDetailPath,
|
||||
} from '../../store/albumBrowseSessionStore';
|
||||
} from '@/features/album';
|
||||
import {
|
||||
peekPersistedAdvancedSearchLeaveSnapshot,
|
||||
saveAdvancedSearchLeaveSnapshot,
|
||||
|
||||
Reference in New Issue
Block a user