mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(offline): co-locate offline feature into features/offline
This commit is contained in:
@@ -57,7 +57,7 @@ export async function updatePlaylist(id: string, songIds: string[], prevCount =
|
|||||||
songIndexToRemove: Array.from({ length: prevCount }, (_, i) => i),
|
songIndexToRemove: Array.from({ length: prevCount }, (_, i) => i),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void import('../utils/offline/pinnedOfflineSync')
|
void import('@/features/offline')
|
||||||
.then(m => m.schedulePinnedPlaylistSync(id))
|
.then(m => m.schedulePinnedPlaylistSync(id))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export async function star(
|
|||||||
const indexEnabled = useLibraryIndexStore.getState().isIndexEnabled(serverId);
|
const indexEnabled = useLibraryIndexStore.getState().isIndexEnabled(serverId);
|
||||||
void refreshStarredAlbumIndexFromServer(serverId, indexEnabled).catch(() => {});
|
void refreshStarredAlbumIndexFromServer(serverId, indexEnabled).catch(() => {});
|
||||||
}
|
}
|
||||||
void import('../utils/offline/favoritesOfflineSync')
|
void import('@/features/offline')
|
||||||
.then(m => m.onFavoritesOfflineStarChange(id, type, true, serverId ?? undefined))
|
.then(m => m.onFavoritesOfflineStarChange(id, type, true, serverId ?? undefined))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ export async function unstar(
|
|||||||
const indexEnabled = useLibraryIndexStore.getState().isIndexEnabled(serverId);
|
const indexEnabled = useLibraryIndexStore.getState().isIndexEnabled(serverId);
|
||||||
void refreshStarredAlbumIndexFromServer(serverId, indexEnabled).catch(() => {});
|
void refreshStarredAlbumIndexFromServer(serverId, indexEnabled).catch(() => {});
|
||||||
}
|
}
|
||||||
void import('../utils/offline/favoritesOfflineSync')
|
void import('@/features/offline')
|
||||||
.then(m => m.onFavoritesOfflineStarChange(id, type, false, serverId ?? undefined))
|
.then(m => m.onFavoritesOfflineStarChange(id, type, false, serverId ?? undefined))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const Statistics = lazy(() => import('@/features/stats/pages/Statistics'));
|
|||||||
const Help = lazy(() => import('../pages/Help'));
|
const Help = lazy(() => import('../pages/Help'));
|
||||||
const WhatsNew = lazy(() => import('../pages/WhatsNew'));
|
const WhatsNew = lazy(() => import('../pages/WhatsNew'));
|
||||||
const DeviceSync = lazy(() => import('@/features/deviceSync/pages/DeviceSync'));
|
const DeviceSync = lazy(() => import('@/features/deviceSync/pages/DeviceSync'));
|
||||||
const OfflineLibrary = lazy(() => import('../pages/OfflineLibrary'));
|
const OfflineLibrary = lazy(() => import('@/features/offline/pages/OfflineLibrary'));
|
||||||
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
const LabelAlbums = lazy(() => import('../pages/LabelAlbums'));
|
||||||
const SearchBrowsePage = lazy(() => import('@/features/search/pages/SearchBrowsePage'));
|
const SearchBrowsePage = lazy(() => import('@/features/search/pages/SearchBrowsePage'));
|
||||||
const FolderBrowser = lazy(() => import('../pages/FolderBrowser'));
|
const FolderBrowser = lazy(() => import('../pages/FolderBrowser'));
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import AppRoutes from './AppRoutes';
|
|||||||
import FullscreenPlayer from '@/features/fullscreenPlayer';
|
import FullscreenPlayer from '@/features/fullscreenPlayer';
|
||||||
import ContextMenu from '../components/ContextMenu';
|
import ContextMenu from '../components/ContextMenu';
|
||||||
import SongInfoModal from '../components/SongInfoModal';
|
import SongInfoModal from '../components/SongInfoModal';
|
||||||
import DownloadFolderModal from '../components/DownloadFolderModal';
|
import { DownloadFolderModal } from '@/features/offline';
|
||||||
import GlobalConfirmModal from '../components/GlobalConfirmModal';
|
import GlobalConfirmModal from '../components/GlobalConfirmModal';
|
||||||
import ThemeMigrationNotice from '../components/ThemeMigrationNotice';
|
import ThemeMigrationNotice from '../components/ThemeMigrationNotice';
|
||||||
import OrbitAccountPicker from '../components/OrbitAccountPicker';
|
import OrbitAccountPicker from '../components/OrbitAccountPicker';
|
||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
} from '../constants/appScroll';
|
} from '../constants/appScroll';
|
||||||
import ConnectionIndicator from '../components/ConnectionIndicator';
|
import ConnectionIndicator from '../components/ConnectionIndicator';
|
||||||
import MusicNetworkIndicator from '../components/MusicNetworkIndicator';
|
import MusicNetworkIndicator from '../components/MusicNetworkIndicator';
|
||||||
import OfflineBanner from '../components/OfflineBanner';
|
import { OfflineBanner } from '@/features/offline';
|
||||||
import AppUpdater from '../components/AppUpdater';
|
import AppUpdater from '../components/AppUpdater';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import OrbitSessionBar from '../components/OrbitSessionBar';
|
import OrbitSessionBar from '../components/OrbitSessionBar';
|
||||||
@@ -39,8 +39,8 @@ import { useOrbitHost } from '../hooks/useOrbitHost';
|
|||||||
import { useOrbitGuest } from '../hooks/useOrbitGuest';
|
import { useOrbitGuest } from '../hooks/useOrbitGuest';
|
||||||
import { useOrbitBodyAttrs } from '../hooks/useOrbitBodyAttrs';
|
import { useOrbitBodyAttrs } from '../hooks/useOrbitBodyAttrs';
|
||||||
import { usePlatformShellSetup } from '../hooks/usePlatformShellSetup';
|
import { usePlatformShellSetup } from '../hooks/usePlatformShellSetup';
|
||||||
import { useOfflineBrowseContext } from '../hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineBrowseNavFlags } from '../utils/offline/offlineBrowseContext';
|
import { offlineBrowseNavFlags } from '@/features/offline';
|
||||||
import { useWindowFullscreenState } from '../hooks/useWindowFullscreenState';
|
import { useWindowFullscreenState } from '../hooks/useWindowFullscreenState';
|
||||||
import { useNowPlayingTrayTitle } from '../hooks/useNowPlayingTrayTitle';
|
import { useNowPlayingTrayTitle } from '../hooks/useNowPlayingTrayTitle';
|
||||||
import { usePrefetchReleaseNotes } from '../hooks/usePrefetchReleaseNotes';
|
import { usePrefetchReleaseNotes } from '../hooks/usePrefetchReleaseNotes';
|
||||||
@@ -53,8 +53,8 @@ import { useMainScrollingIndicator } from '../hooks/useMainScrollingIndicator';
|
|||||||
import { useCoverNavigationPriority } from '../hooks/useCoverNavigationPriority';
|
import { useCoverNavigationPriority } from '../hooks/useCoverNavigationPriority';
|
||||||
import { useLiveSearchRouteScope } from '@/features/search';
|
import { useLiveSearchRouteScope } from '@/features/search';
|
||||||
import { useNowPlayingPrewarm } from '@/features/nowPlaying';
|
import { useNowPlayingPrewarm } from '@/features/nowPlaying';
|
||||||
import { useOfflineAutoNav } from '../hooks/useOfflineAutoNav';
|
import { useOfflineAutoNav } from '@/features/offline';
|
||||||
import { useOfflineLibraryFilterSuspend } from '../hooks/useOfflineLibraryFilterSuspend';
|
import { useOfflineLibraryFilterSuspend } from '@/features/offline';
|
||||||
import { AppShellQueueResizerSeam } from '../components/AppShellQueueResizerSeam';
|
import { AppShellQueueResizerSeam } from '../components/AppShellQueueResizerSeam';
|
||||||
import { IS_LINUX, IS_MACOS } from '../utils/platform';
|
import { IS_LINUX, IS_MACOS } from '../utils/platform';
|
||||||
import { useConnectionStatus } from '../hooks/useConnectionStatus';
|
import { useConnectionStatus } from '../hooks/useConnectionStatus';
|
||||||
|
|||||||
+6
-6
@@ -7,18 +7,18 @@ import { WindowVisibilityProvider } from '../hooks/useWindowVisibility';
|
|||||||
import { DragDropProvider } from '../contexts/DragDropContext';
|
import { DragDropProvider } from '../contexts/DragDropContext';
|
||||||
import PasteClipboardHandler from '../components/PasteClipboardHandler';
|
import PasteClipboardHandler from '../components/PasteClipboardHandler';
|
||||||
import ExportPickerModal from '../components/ExportPickerModal';
|
import ExportPickerModal from '../components/ExportPickerModal';
|
||||||
import ZipDownloadOverlay from '../components/ZipDownloadOverlay';
|
import { ZipDownloadOverlay } from '@/features/offline';
|
||||||
import FpsOverlay from '../components/FpsOverlay';
|
import FpsOverlay from '../components/FpsOverlay';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||||
import { useGlobalShortcutsStore } from '../store/globalShortcutsStore';
|
import { useGlobalShortcutsStore } from '../store/globalShortcutsStore';
|
||||||
import { initHotCachePrefetch } from '../hotCachePrefetch';
|
import { initHotCachePrefetch } from '../hotCachePrefetch';
|
||||||
import { initLocalPlaybackInvalidation } from '../localPlaybackInvalidation';
|
import { initLocalPlaybackInvalidation } from '../localPlaybackInvalidation';
|
||||||
import { initFavoritesOfflineSync } from '../utils/offline/favoritesOfflineSync';
|
import { initFavoritesOfflineSync } from '@/features/offline';
|
||||||
import { initPinnedOfflineSync } from '../utils/offline/pinnedOfflineSync';
|
import { initPinnedOfflineSync } from '@/features/offline';
|
||||||
import { initResumeIncompleteOfflinePins, scheduleResumeIncompleteOfflinePins } from '../utils/offline/resumeIncompleteOfflinePins';
|
import { initResumeIncompleteOfflinePins, scheduleResumeIncompleteOfflinePins } from '@/features/offline';
|
||||||
import { runLegacyOfflineFileMigration } from '../utils/migrations/legacyOfflineFileMigration';
|
import { runLegacyOfflineFileMigration } from '@/features/offline';
|
||||||
import { reconcileLibraryTierForServer } from '../utils/offline/libraryTierReconcile';
|
import { reconcileLibraryTierForServer } from '@/features/offline';
|
||||||
import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer';
|
import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer';
|
||||||
import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration';
|
import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration';
|
||||||
import { bootstrapAllIndexedServers } from '../utils/library/librarySession';
|
import { bootstrapAllIndexedServers } from '../utils/library/librarySession';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useZipDownloadBridge } from '../hooks/tauriBridge/useZipDownloadBridge';
|
import { useZipDownloadBridge } from '@/features/offline';
|
||||||
import { usePreviewBridge } from '../hooks/tauriBridge/usePreviewBridge';
|
import { usePreviewBridge } from '../hooks/tauriBridge/usePreviewBridge';
|
||||||
import { useAudioDeviceBridge } from '../hooks/tauriBridge/useAudioDeviceBridge';
|
import { useAudioDeviceBridge } from '../hooks/tauriBridge/useAudioDeviceBridge';
|
||||||
import { useCliBridge } from '../hooks/tauriBridge/useCliBridge';
|
import { useCliBridge } from '../hooks/tauriBridge/useCliBridge';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||||
import { isOfflinePinComplete } from '../utils/offline/offlineLibraryHelpers';
|
import { isOfflinePinComplete } from '@/features/offline';
|
||||||
import { CoverArtImage } from '../cover/CoverArtImage';
|
import { CoverArtImage } from '../cover/CoverArtImage';
|
||||||
import { useAlbumCoverRef } from '../cover/useLibraryCoverRef';
|
import { useAlbumCoverRef } from '../cover/useLibraryCoverRef';
|
||||||
import { coverStorageKeyFromRef } from '../cover/storageKeys';
|
import { coverStorageKeyFromRef } from '../cover/storageKeys';
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { formatMb } from '../utils/format/formatBytes';
|
|||||||
import { sanitizeHtml } from '../utils/sanitizeHtml';
|
import { sanitizeHtml } from '../utils/sanitizeHtml';
|
||||||
import { OpenArtistRefInline } from './OpenArtistRefInline';
|
import { OpenArtistRefInline } from './OpenArtistRefInline';
|
||||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||||
import { offlineActionPolicy, type OfflineActionPolicy } from '../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||||
import { deriveAlbumGenreTags } from '../utils/library/genreTags';
|
import { deriveAlbumGenreTags } from '../utils/library/genreTags';
|
||||||
import { genreColor } from '../utils/library/genreColor';
|
import { genreColor } from '../utils/library/genreColor';
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { TrackRow } from './albumTrackList/TrackRow';
|
|||||||
import { AlbumTrackListMobile } from './albumTrackList/AlbumTrackListMobile';
|
import { AlbumTrackListMobile } from './albumTrackList/AlbumTrackListMobile';
|
||||||
import { TracklistColumnPicker } from './albumTrackList/TracklistColumnPicker';
|
import { TracklistColumnPicker } from './albumTrackList/TracklistColumnPicker';
|
||||||
import { TracklistHeaderRow } from './albumTrackList/TracklistHeaderRow';
|
import { TracklistHeaderRow } from './albumTrackList/TracklistHeaderRow';
|
||||||
import { offlineActionPolicy, type OfflineActionPolicy } from '../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||||
|
|
||||||
export type { SortKey } from '../utils/componentHelpers/albumTrackListHelpers';
|
export type { SortKey } from '../utils/componentHelpers/albumTrackListHelpers';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getArtist, getArtistInfo } from '../api/subsonicArtists';
|
import { getArtist, getArtistInfo } from '../api/subsonicArtists';
|
||||||
import { filterAlbumsToActiveLibrary } from '../api/subsonicLibrary';
|
import { filterAlbumsToActiveLibrary } from '../api/subsonicLibrary';
|
||||||
import { resolveAlbum, resolveMediaServerId } from '../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||||
import { songToTrack } from '../utils/playback/songToTrack';
|
import { songToTrack } from '../utils/playback/songToTrack';
|
||||||
import { shuffleArray } from '../utils/playback/shuffleArray';
|
import { shuffleArray } from '../utils/playback/shuffleArray';
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ vi.mock('@/utils/orbitBulkGuard', () => ({
|
|||||||
orbitBulkGuard: vi.fn(async () => true),
|
orbitBulkGuard: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@/hooks/useOfflineBrowseContext', () => ({
|
vi.mock('@/features/offline', () => ({
|
||||||
useOfflineBrowseContext: () => ({
|
useOfflineBrowseContext: () => ({
|
||||||
active: false,
|
active: false,
|
||||||
serverId: 'srv-1',
|
serverId: 'srv-1',
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import { useContextMenuKeyboardNav } from '../hooks/useContextMenuKeyboardNav';
|
|||||||
import { useContextMenuRating } from '../hooks/useContextMenuRating';
|
import { useContextMenuRating } from '../hooks/useContextMenuRating';
|
||||||
import { usePlaybackLibraryNavigate } from '../hooks/usePlaybackLibraryNavigate';
|
import { usePlaybackLibraryNavigate } from '../hooks/usePlaybackLibraryNavigate';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useOfflineBrowseContext } from '../hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import {
|
import {
|
||||||
offlineActionPolicy,
|
offlineActionPolicy,
|
||||||
type OfflineSurface,
|
type OfflineSurface,
|
||||||
} from '../utils/offline/offlineActionPolicy';
|
} from '@/features/offline';
|
||||||
import ContextMenuItems from './contextMenu/ContextMenuItems';
|
import ContextMenuItems from './contextMenu/ContextMenuItems';
|
||||||
|
|
||||||
function contextMenuSurfaceForType(type: string | null): OfflineSurface {
|
function contextMenuSurfaceForType(type: string | null): OfflineSurface {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Cloud, CloudOff } from 'lucide-react';
|
import { Cloud, CloudOff } from 'lucide-react';
|
||||||
import { useDevOfflineBrowseStore } from '../store/devOfflineBrowseStore';
|
import { useDevOfflineBrowseStore } from '@/features/offline';
|
||||||
|
|
||||||
/** DEV-only: simulate full offline (disconnect UI, block Subsonic, local playback only). */
|
/** DEV-only: simulate full offline (disconnect UI, block Subsonic, local playback only). */
|
||||||
export default function DevNetworkModeToggle() {
|
export default function DevNetworkModeToggle() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getRandomAlbums } from '../api/subsonicLibrary';
|
import { getRandomAlbums } from '../api/subsonicLibrary';
|
||||||
import { resolveAlbum, resolveMediaServerId } from '../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||||
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
import type { SubsonicAlbum } from '../api/subsonicTypes';
|
||||||
import { songToTrack } from '../utils/playback/songToTrack';
|
import { songToTrack } from '../utils/playback/songToTrack';
|
||||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import { useSidebarStore } from '@/features/sidebar';
|
|||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { ALL_NAV_ITEMS } from '../config/navItems';
|
import { ALL_NAV_ITEMS } from '../config/navItems';
|
||||||
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
|
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
|
||||||
import { isOfflineSidebarNavAllowed } from '../utils/offline/offlineNavPolicy';
|
import { isOfflineSidebarNavAllowed } from '@/features/offline';
|
||||||
import { useOfflineBrowseContext } from '../hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineBrowseNavFlags } from '../utils/offline/offlineBrowseContext';
|
import { offlineBrowseNavFlags } from '@/features/offline';
|
||||||
|
|
||||||
const BOTTOM_NAV_ROUTES = new Set(['/', '/albums', '/now-playing']);
|
const BOTTOM_NAV_ROUTES = new Set(['/', '/albums', '/now-playing']);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Play } from 'lucide-react';
|
import { Play } from 'lucide-react';
|
||||||
import { updatePlaylist } from '../api/subsonicPlaylists';
|
import { updatePlaylist } from '../api/subsonicPlaylists';
|
||||||
import { resolvePlaylist, resolveMediaServerId } from '../utils/offline/offlineMediaResolve';
|
import { resolvePlaylist, resolveMediaServerId } from '@/features/offline';
|
||||||
import { songToTrack } from '../utils/playback/songToTrack';
|
import { songToTrack } from '../utils/playback/songToTrack';
|
||||||
import type { Track } from '../store/playerStoreTypes';
|
import type { Track } from '../store/playerStoreTypes';
|
||||||
import { useState, useRef, useMemo } from 'react';
|
import { useState, useRef, useMemo } from 'react';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ListPlus, Search, X } from 'lucide-react';
|
|||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { useSelectionStore } from '../../store/selectionStore';
|
import { useSelectionStore } from '../../store/selectionStore';
|
||||||
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
||||||
import { offlineActionPolicy, type OfflineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
filterText: string;
|
filterText: string;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { codecLabel, type ColKey } from '../../utils/componentHelpers/albumTrack
|
|||||||
import { formatLongDuration } from '../../utils/format/formatDuration';
|
import { formatLongDuration } from '../../utils/format/formatDuration';
|
||||||
import { formatLastSeen } from '../../utils/componentHelpers/userMgmtHelpers';
|
import { formatLastSeen } from '../../utils/componentHelpers/userMgmtHelpers';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
import { offlineActionPolicy, type OfflineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||||
import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs';
|
import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs';
|
||||||
|
|
||||||
type ContextMenuFn = (
|
type ContextMenuFn = (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
Loader2, Play, Radio, Share2, Shuffle, Users,
|
Loader2, Play, Radio, Share2, Shuffle, Users,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '../../api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '../../api/subsonicTypes';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
import { useThemeStore } from '../../store/themeStore';
|
import { useThemeStore } from '../../store/themeStore';
|
||||||
import { useArtistOfflineState } from '../../hooks/useArtistOfflineState';
|
import { useArtistOfflineState } from '../../hooks/useArtistOfflineState';
|
||||||
@@ -22,7 +22,7 @@ import LastfmIcon from '../LastfmIcon';
|
|||||||
import WikipediaIcon from '../WikipediaIcon';
|
import WikipediaIcon from '../WikipediaIcon';
|
||||||
import StarRating from '../StarRating';
|
import StarRating from '../StarRating';
|
||||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||||
import { offlineActionPolicy, type OfflineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
artist: SubsonicArtist;
|
artist: SubsonicArtist;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { resolveAlbum, resolveArtist, resolveMediaServerId } from '../../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveArtist, resolveMediaServerId } from '@/features/offline';
|
||||||
import { AddToPlaylistSubmenu } from './AddToPlaylistSubmenu';
|
import { AddToPlaylistSubmenu } from './AddToPlaylistSubmenu';
|
||||||
|
|
||||||
interface AlbumProps {
|
interface AlbumProps {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Play, ListPlus, Heart, Download, ChevronRight, ChevronsRight, User, ListMusic, Star, Share2 } from 'lucide-react';
|
import { Play, ListPlus, Heart, Download, ChevronRight, ChevronsRight, User, ListMusic, Star, Share2 } from 'lucide-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { resolveAlbum, resolveMediaServerId } from '../../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||||
import { star, unstar } from '../../api/subsonicStarRating';
|
import { star, unstar } from '../../api/subsonicStarRating';
|
||||||
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
||||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ListMusic, Plus } from 'lucide-react';
|
import { ListMusic, Plus } from 'lucide-react';
|
||||||
import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '../../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
|
||||||
import type { SubsonicPlaylist } from '../../api/subsonicTypes';
|
import type { SubsonicPlaylist } from '../../api/subsonicTypes';
|
||||||
import { usePlaylistStore } from '../../store/playlistStore';
|
import { usePlaylistStore } from '../../store/playlistStore';
|
||||||
import { showToast } from '../../utils/ui/toast';
|
import { showToast } from '../../utils/ui/toast';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ListMusic, Plus } from 'lucide-react';
|
import { ListMusic, Plus } from 'lucide-react';
|
||||||
import { resolveAlbum, resolveArtist, resolveMediaServerId, resolvePlaylist } from '../../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveArtist, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
|
||||||
import { getPlaylists } from '../../api/subsonicPlaylists';
|
import { getPlaylists } from '../../api/subsonicPlaylists';
|
||||||
import type { SubsonicPlaylist } from '../../api/subsonicTypes';
|
import type { SubsonicPlaylist } from '../../api/subsonicTypes';
|
||||||
import { usePlaylistStore } from '../../store/playlistStore';
|
import { usePlaylistStore } from '../../store/playlistStore';
|
||||||
|
|||||||
@@ -2,7 +2,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 { 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 '../../hooks/useNavigateToAlbum';
|
||||||
import { useNavigateToArtist } from '../../hooks/useNavigateToArtist';
|
import { useNavigateToArtist } from '../../hooks/useNavigateToArtist';
|
||||||
import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '../../utils/offline/offlineMediaResolve';
|
import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
|
||||||
import { queueSongStar } from '../../store/pendingStarSync';
|
import { queueSongStar } from '../../store/pendingStarSync';
|
||||||
import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network';
|
import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network';
|
||||||
import type { Track } from '../../store/playerStoreTypes';
|
import type { Track } from '../../store/playerStoreTypes';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type React from 'react';
|
|||||||
import type { SubsonicAlbum, SubsonicArtist } from '../../api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicArtist } from '../../api/subsonicTypes';
|
||||||
import type { QueueItemRef, Track } from '../../store/playerStoreTypes';
|
import type { QueueItemRef, Track } from '../../store/playerStoreTypes';
|
||||||
import type { EntityShareKind } from '../../utils/share/shareLink';
|
import type { EntityShareKind } from '../../utils/share/shareLink';
|
||||||
import type { OfflineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import type { OfflineActionPolicy } from '@/features/offline';
|
||||||
|
|
||||||
export type RatingKind = 'song' | 'album' | 'artist';
|
export type RatingKind = 'song' | 'album' | 'artist';
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import {
|
|||||||
usePlayerBarLayoutStore,
|
usePlayerBarLayoutStore,
|
||||||
type PlayerBarLayoutItemId,
|
type PlayerBarLayoutItemId,
|
||||||
} from '../../store/playerBarLayoutStore';
|
} from '../../store/playerBarLayoutStore';
|
||||||
import { useOfflineBrowseContext } from '../../hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy } from '@/features/offline';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
currentTrack: Track | null;
|
currentTrack: Track | null;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
Loader2, Lock, Pencil, Play, Search, Shuffle, Sparkles, Trash2,
|
Loader2, Lock, Pencil, Play, Search, Shuffle, Sparkles, Trash2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { SubsonicPlaylist, SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicPlaylist, SubsonicSong } from '../../api/subsonicTypes';
|
||||||
import type { ZipDownload } from '../../store/zipDownloadStore';
|
import type { ZipDownload } from '@/features/offline';
|
||||||
import type { AlbumOfflineStatus } from '../../hooks/useAlbumOfflineState';
|
import type { AlbumOfflineStatus } from '../../hooks/useAlbumOfflineState';
|
||||||
import { dequeueOfflinePin } from '../../utils/offline/offlinePinQueue';
|
import { dequeueOfflinePin } from '@/features/offline';
|
||||||
import { useThemeStore } from '../../store/themeStore';
|
import { useThemeStore } from '../../store/themeStore';
|
||||||
import { usePlaylistLayoutStore, type PlaylistLayoutItemId } from '../../store/playlistLayoutStore';
|
import { usePlaylistLayoutStore, type PlaylistLayoutItemId } from '../../store/playlistLayoutStore';
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +18,7 @@ import type { CoverArtId } from '../../cover/types';
|
|||||||
import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage';
|
import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage';
|
||||||
import { PLAYLIST_MAIN_COVER_CSS_PX } from '../../hooks/usePlaylistCovers';
|
import { PLAYLIST_MAIN_COVER_CSS_PX } from '../../hooks/usePlaylistCovers';
|
||||||
import { PlaylistSmartCoverCell } from '../playlists/PlaylistCoverImages';
|
import { PlaylistSmartCoverCell } from '../playlists/PlaylistCoverImages';
|
||||||
import type { OfflineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import type { OfflineActionPolicy } from '@/features/offline';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
playlist: SubsonicPlaylist;
|
playlist: SubsonicPlaylist;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { SubsonicPlaylist } from '../../api/subsonicTypes';
|
|||||||
import {
|
import {
|
||||||
defaultSmartFilters, type SmartFilters,
|
defaultSmartFilters, type SmartFilters,
|
||||||
} from '../../utils/playlist/playlistsSmart';
|
} from '../../utils/playlist/playlistsSmart';
|
||||||
import { offlineActionPolicy, type OfflineActionPolicy } from '../../utils/offline/offlineActionPolicy';
|
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||||
import PlaylistsNewFolderButton from './PlaylistsNewFolderButton';
|
import PlaylistsNewFolderButton from './PlaylistsNewFolderButton';
|
||||||
import PlaylistsFolderViewToggle from './PlaylistsFolderViewToggle';
|
import PlaylistsFolderViewToggle from './PlaylistsFolderViewToggle';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
disableFavoritesOfflineSync,
|
disableFavoritesOfflineSync,
|
||||||
scheduleFavoritesOfflineSync,
|
scheduleFavoritesOfflineSync,
|
||||||
} from '@/utils/offline/favoritesOfflineSync';
|
} from '@/features/offline';
|
||||||
|
|
||||||
function semaphoreTooltipKey(semaphore: FavoritesOfflineSemaphore): string {
|
function semaphoreTooltipKey(semaphore: FavoritesOfflineSemaphore): string {
|
||||||
switch (semaphore) {
|
switch (semaphore) {
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import { usePlayerStore } from '@/store/playerStore';
|
|||||||
import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists';
|
import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists';
|
||||||
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
|
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
|
||||||
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
|
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
|
||||||
import { useOfflineBrowseContext } from '@/hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { useOfflineBrowseReloadToken } from '@/hooks/useOfflineBrowseReloadToken';
|
import { useOfflineBrowseReloadToken } from '@/features/offline';
|
||||||
import {
|
import {
|
||||||
loadStarredFromAllLibraryIndexes,
|
loadStarredFromAllLibraryIndexes,
|
||||||
loadStarredFromAllServersOnline,
|
loadStarredFromAllServersOnline,
|
||||||
} from '@/utils/offline/offlineStarredLoad';
|
} from '@/features/offline';
|
||||||
|
|
||||||
export interface FavoritesDataResult {
|
export interface FavoritesDataResult {
|
||||||
albums: SubsonicAlbum[];
|
albums: SubsonicAlbum[];
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useFavoritesOfflineSyncStore } from '@/store/favoritesOfflineSyncStore';
|
import { useFavoritesOfflineSyncStore } from '@/features/offline';
|
||||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useOfflineJobStore } from '@/store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline';
|
||||||
import { FAVORITES_OFFLINE_JOB_ID } from '@/utils/offline/favoritesOfflineConstants';
|
import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline';
|
||||||
import { entryBelongsToServer } from '@/utils/offline/offlineLibraryHelpers';
|
import { entryBelongsToServer } from '@/features/offline';
|
||||||
|
|
||||||
export type FavoritesOfflineUiStatus =
|
export type FavoritesOfflineUiStatus =
|
||||||
| 'disabled'
|
| 'disabled'
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { getArtistForServer, getTopSongsForServer } from '@/api/subsonicArtists'
|
|||||||
import { getAlbumForServer, getSongForServer } from '@/api/subsonicLibrary';
|
import { getAlbumForServer, getSongForServer } from '@/api/subsonicLibrary';
|
||||||
import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||||
import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/utils/offline/offlineLibraryIndexLoad';
|
import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline';
|
||||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { FolderOpen } from 'lucide-react';
|
import { FolderOpen } from 'lucide-react';
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDownloadModalStore } from '../store/downloadModalStore';
|
import { useDownloadModalStore } from '@/features/offline/store/downloadModalStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
|
|
||||||
export default function DownloadFolderModal() {
|
export default function DownloadFolderModal() {
|
||||||
const { isOpen, folder, remember, setFolder, setRemember, confirm, cancel } = useDownloadModalStore();
|
const { isOpen, folder, remember, setFolder, setRemember, confirm, cancel } = useDownloadModalStore();
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { HardDriveDownload, Heart } from 'lucide-react';
|
import { HardDriveDownload, Heart } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { formatBytes } from '../utils/format/formatBytes';
|
import { formatBytes } from '@/utils/format/formatBytes';
|
||||||
|
|
||||||
const OPEN_DELAY_MS = 450;
|
const OPEN_DELAY_MS = 450;
|
||||||
|
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { HardDriveDownload, Check, X } from 'lucide-react';
|
import { HardDriveDownload, Check, X } from 'lucide-react';
|
||||||
import { useZipDownloadStore } from '../store/zipDownloadStore';
|
import { useZipDownloadStore } from '@/features/offline/store/zipDownloadStore';
|
||||||
import { formatBytes } from '../utils/format/formatBytes';
|
import { formatBytes } from '@/utils/format/formatBytes';
|
||||||
|
|
||||||
function ZipDownloadItem({ id }: { id: string }) {
|
function ZipDownloadItem({ id }: { id: string }) {
|
||||||
const dismiss = useZipDownloadStore(s => s.dismiss);
|
const dismiss = useZipDownloadStore(s => s.dismiss);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import type { Location, NavigateFunction } from 'react-router-dom';
|
import type { Location, NavigateFunction } from 'react-router-dom';
|
||||||
import { resolveOfflineDisconnectNavAction } from '../utils/offline/offlineBrowseRouting';
|
import { resolveOfflineDisconnectNavAction } from '@/features/offline/utils/offlineBrowseRouting';
|
||||||
|
|
||||||
type ConnStatus = 'connected' | 'disconnected' | 'connecting' | 'unknown';
|
type ConnStatus = 'connected' | 'disconnected' | 'connecting' | 'unknown';
|
||||||
|
|
||||||
+6
-6
@@ -1,14 +1,14 @@
|
|||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOfflineStore } from '../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { useConnectionStatus } from './useConnectionStatus';
|
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
|
||||||
import { usePlayerStatsRecordingEnabled } from '@/features/stats';
|
import { usePlayerStatsRecordingEnabled } from '@/features/stats';
|
||||||
import { hasOfflineBrowsingContent } from '../utils/offline/favoritesOfflineBrowse';
|
import { hasOfflineBrowsingContent } from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||||
import { useOfflineBrowseActive } from '../utils/offline/offlineBrowseMode';
|
import { useOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||||
import {
|
import {
|
||||||
buildOfflineBrowseContext,
|
buildOfflineBrowseContext,
|
||||||
computeOfflineBrowseCapabilities,
|
computeOfflineBrowseCapabilities,
|
||||||
type OfflineBrowseContext,
|
type OfflineBrowseContext,
|
||||||
} from '../utils/offline/offlineBrowseContext';
|
} from '@/features/offline/utils/offlineBrowseContext';
|
||||||
|
|
||||||
/** Single subscription for shell and pages: offline browse mode + capabilities. */
|
/** Single subscription for shell and pages: offline browse mode + capabilities. */
|
||||||
export function useOfflineBrowseContext(): OfflineBrowseContext {
|
export function useOfflineBrowseContext(): OfflineBrowseContext {
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline/hooks/useOfflineBrowseContext';
|
||||||
import {
|
import {
|
||||||
restoreMusicLibraryFiltersAfterOffline,
|
restoreMusicLibraryFiltersAfterOffline,
|
||||||
suspendMusicLibraryFiltersForOffline,
|
suspendMusicLibraryFiltersForOffline,
|
||||||
} from '../utils/offline/offlineLibraryFilterSuspend';
|
} from '@/features/offline/utils/offlineLibraryFilterSuspend';
|
||||||
|
|
||||||
/** Disable scoped library browse offline; restore the picker value when back online. */
|
/** Disable scoped library browse offline; restore the picker value when back online. */
|
||||||
export function useOfflineLibraryFilterSuspend(): void {
|
export function useOfflineLibraryFilterSuspend(): void {
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { useZipDownloadStore } from '../../store/zipDownloadStore';
|
import { useZipDownloadStore } from '@/features/offline/store/zipDownloadStore';
|
||||||
|
|
||||||
/** ZIP download progress events from Rust. */
|
/** ZIP download progress events from Rust. */
|
||||||
export function useZipDownloadBridge() {
|
export function useZipDownloadBridge() {
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Offline feature — offline mode + the downloaded/pinned-library machinery:
|
||||||
|
* the offline/job/zip-download/download-modal stores, the offline-browse and
|
||||||
|
* media-resolution utils, pin-queue + sync engines, the offline overlays/banner,
|
||||||
|
* and the OfflineLibrary page (lazy via the deep path `pages/OfflineLibrary`, so
|
||||||
|
* it is not re-exported here). The favorites-offline integration layer
|
||||||
|
* (`favoritesOffline*`, `favoritesOfflineSyncStore`) lives here too — the
|
||||||
|
* offline core owns it; the favorites feature consumes it through this barrel.
|
||||||
|
*
|
||||||
|
* Stays OUT (consumed by, not owned by, offline): the per-entity offline-status
|
||||||
|
* hooks `useAlbumOfflineState` / `useArtistOfflineState` (album/artist),
|
||||||
|
* `runPlaylistZipDownload` (playlist), `ensureQueueServerPinned` (playback), and
|
||||||
|
* the `localPlayback*` byte-store substrate (decided with playback, which moves
|
||||||
|
* last). Playback-core (`playTrackAction`) and the server key-remigration infra
|
||||||
|
* consume this barrel — the correct feature→feature edge, realized early.
|
||||||
|
*/
|
||||||
|
export * from './hooks/useOfflineAutoNav';
|
||||||
|
export * from './hooks/useOfflineBrowseContext';
|
||||||
|
export * from './hooks/useOfflineBrowseReloadToken';
|
||||||
|
export * from './hooks/useOfflineLibraryFilterSuspend';
|
||||||
|
export * from './hooks/useZipDownloadBridge';
|
||||||
|
export * from './store/devOfflineBrowseStore';
|
||||||
|
export * from './store/downloadModalStore';
|
||||||
|
export * from './store/favoritesOfflineSyncStore';
|
||||||
|
export * from './store/offlineJobStore';
|
||||||
|
export * from './store/offlineStore';
|
||||||
|
export * from './store/zipDownloadStore';
|
||||||
|
export * from './utils/favoritesOfflineBrowse';
|
||||||
|
export * from './utils/favoritesOfflineConstants';
|
||||||
|
export * from './utils/favoritesOfflineSync';
|
||||||
|
export * from './utils/legacyOfflineFileMigration';
|
||||||
|
export * from './utils/libraryTierReconcile';
|
||||||
|
export * from './utils/offlineActionPolicy';
|
||||||
|
export * from './utils/offlineAlbumBrowseCatalog';
|
||||||
|
export * from './utils/offlineBrowseContext';
|
||||||
|
export * from './utils/offlineBrowseMode';
|
||||||
|
export * from './utils/offlineBrowseRouting';
|
||||||
|
export * from './utils/offlineLibraryFilterSuspend';
|
||||||
|
export * from './utils/offlineLibraryHelpers';
|
||||||
|
export * from './utils/offlineLibraryIndexLoad';
|
||||||
|
export * from './utils/offlineLocalBrowse';
|
||||||
|
export * from './utils/offlineMediaResolve';
|
||||||
|
export * from './utils/offlineNavPolicy';
|
||||||
|
export * from './utils/offlinePinQueue';
|
||||||
|
export * from './utils/offlinePlaylistBrowse';
|
||||||
|
export * from './utils/offlineStarredLoad';
|
||||||
|
export * from './utils/pinnedOfflineSync';
|
||||||
|
export * from './utils/resumeIncompleteOfflinePins';
|
||||||
|
// `OfflinePinKind` (= PinSource['kind']) is declared in both pin modules; the
|
||||||
|
// explicit re-export resolves the `export *` ambiguity (TS2308).
|
||||||
|
export type { OfflinePinKind } from './utils/offlinePinQueue';
|
||||||
|
export { OfflineLibraryDiskStat } from './components/OfflineLibraryDiskStat';
|
||||||
|
export { default as DownloadFolderModal } from './components/DownloadFolderModal';
|
||||||
|
export { default as OfflineBanner } from './components/OfflineBanner';
|
||||||
|
export { default as ZipDownloadOverlay } from './components/ZipDownloadOverlay';
|
||||||
@@ -3,16 +3,16 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { Play, HardDriveDownload, Trash2, ListPlus, ListMusic, Heart } from 'lucide-react';
|
import { Play, HardDriveDownload, Trash2, ListPlus, ListMusic, Heart } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOfflineStore } from '../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import { AlbumCoverArtImage } from '../cover/AlbumCoverArtImage';
|
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||||
import { coverServerScopeForServerId } from '../cover/serverScope';
|
import { coverServerScopeForServerId } from '@/cover/serverScope';
|
||||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||||
import { GRID_COVER_WARM_LIMIT } from '../cover/layoutSizes';
|
import { GRID_COVER_WARM_LIMIT } from '@/cover/layoutSizes';
|
||||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||||
import { OfflineLibraryDiskStat } from '../components/OfflineLibraryDiskStat';
|
import { OfflineLibraryDiskStat } from '@/features/offline/components/OfflineLibraryDiskStat';
|
||||||
import {
|
import {
|
||||||
buildOfflineCacheQueueTracks,
|
buildOfflineCacheQueueTracks,
|
||||||
buildOfflineFavoritesQueueTracks,
|
buildOfflineFavoritesQueueTracks,
|
||||||
@@ -31,16 +31,16 @@ import {
|
|||||||
offlineTrackCount,
|
offlineTrackCount,
|
||||||
type OfflineCoverQuadCell,
|
type OfflineCoverQuadCell,
|
||||||
type OfflineLibraryCard,
|
type OfflineLibraryCard,
|
||||||
} from '../utils/offline/offlineLibraryHelpers';
|
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import { shuffleArray } from '../utils/playback/shuffleArray';
|
import { shuffleArray } from '@/utils/playback/shuffleArray';
|
||||||
import { getMediaDir } from '../utils/media/mediaDir';
|
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||||
import { canonicalQueueServerKey, resolveIndexKey } from '../utils/server/serverIndexKey';
|
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
|
||||||
import { reconcileAllLibraryTiersFromDisk } from '../utils/offline/libraryTierReconcile';
|
import { reconcileAllLibraryTiersFromDisk } from '@/features/offline/utils/libraryTierReconcile';
|
||||||
import {
|
import {
|
||||||
inferPinSourcesFromLibraryIndex,
|
inferPinSourcesFromLibraryIndex,
|
||||||
restoreOfflineLibraryPinSources,
|
restoreOfflineLibraryPinSources,
|
||||||
} from '../utils/migrations/legacyOfflineFileMigration';
|
} from '@/features/offline/utils/legacyOfflineFileMigration';
|
||||||
|
|
||||||
const OFFLINE_CARD_COVER_CSS_PX = 300;
|
const OFFLINE_CARD_COVER_CSS_PX = 300;
|
||||||
const OFFLINE_CACHE_GRID_KEY = '__offline_cache__';
|
const OFFLINE_CACHE_GRID_KEY = '__offline_cache__';
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { onInvoke } from '@/test/mocks/tauri';
|
import { onInvoke } from '@/test/mocks/tauri';
|
||||||
import { useOfflineJobStore, cancelledDownloads, type DownloadJob } from './offlineJobStore';
|
import { useOfflineJobStore, cancelledDownloads, type DownloadJob } from '@/features/offline/store/offlineJobStore';
|
||||||
|
|
||||||
function job(over: Partial<DownloadJob>): DownloadJob {
|
function job(over: Partial<DownloadJob>): DownloadJob {
|
||||||
return {
|
return {
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
import { libraryUpsertSongsFromApi } from '../api/library';
|
import { libraryUpsertSongsFromApi } from '@/api/library';
|
||||||
import { buildStreamUrl } from '../api/subsonicStreamUrl';
|
import { buildStreamUrl } from '@/api/subsonicStreamUrl';
|
||||||
import { getAlbum } from '../api/subsonicLibrary';
|
import { getAlbum } from '@/api/subsonicLibrary';
|
||||||
import { getArtist } from '../api/subsonicArtists';
|
import { getArtist } from '@/api/subsonicArtists';
|
||||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useAuthStore } from './authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { showToast } from '../utils/ui/toast';
|
import { showToast } from '@/utils/ui/toast';
|
||||||
import { useOfflineJobStore, cancelledDownloads } from './offlineJobStore';
|
import { useOfflineJobStore, cancelledDownloads } from '@/features/offline/store/offlineJobStore';
|
||||||
import { useLocalPlaybackStore, type PinSource } from './localPlaybackStore';
|
import { useLocalPlaybackStore, type PinSource } from '@/store/localPlaybackStore';
|
||||||
import { getMediaDir } from '../utils/media/mediaDir';
|
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||||
import {
|
import {
|
||||||
findLocalPlaybackEntry,
|
findLocalPlaybackEntry,
|
||||||
isOfflinePinComplete,
|
isOfflinePinComplete,
|
||||||
pendingOfflinePinSongs,
|
pendingOfflinePinSongs,
|
||||||
} from '../utils/offline/offlineLibraryHelpers';
|
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
import { librarySqlServerId } from '../api/coverCache';
|
import { librarySqlServerId } from '@/api/coverCache';
|
||||||
import { resolveIndexKey, serverIndexKeyForProfile } from '../utils/server/serverIndexKey';
|
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||||
import { isSmartPlaylistName } from '../utils/componentHelpers/playlistDetailHelpers';
|
import { isSmartPlaylistName } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||||
import {
|
import {
|
||||||
enqueueOfflinePin,
|
enqueueOfflinePin,
|
||||||
registerOfflinePinExecutor,
|
registerOfflinePinExecutor,
|
||||||
removeOfflinePinTask,
|
removeOfflinePinTask,
|
||||||
type OfflinePinTask,
|
type OfflinePinTask,
|
||||||
} from '../utils/offline/offlinePinQueue';
|
} from '@/features/offline/utils/offlinePinQueue';
|
||||||
|
|
||||||
/** @deprecated Metadata lives in the library index; kept for type-compat during transition. */
|
/** @deprecated Metadata lives in the library index; kept for type-compat during transition. */
|
||||||
export interface OfflineTrackMeta {
|
export interface OfflineTrackMeta {
|
||||||
@@ -60,7 +60,7 @@ export interface OfflineAlbumMeta {
|
|||||||
type?: 'album' | 'playlist' | 'artist' | 'track';
|
type?: 'album' | 'playlist' | 'artist' | 'track';
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { DownloadJob } from './offlineJobStore';
|
export type { DownloadJob } from '@/features/offline/store/offlineJobStore';
|
||||||
|
|
||||||
function serverIndexKeyForOffline(serverId: string): string {
|
function serverIndexKeyForOffline(serverId: string): string {
|
||||||
const server = useAuthStore.getState().servers.find(s => s.id === serverId);
|
const server = useAuthStore.getState().servers.find(s => s.id === serverId);
|
||||||
+11
-11
@@ -1,28 +1,28 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import {
|
import {
|
||||||
favoritesOfflineBrowseEnabled,
|
favoritesOfflineBrowseEnabled,
|
||||||
hasOfflineBrowsingContent,
|
hasOfflineBrowsingContent,
|
||||||
} from './favoritesOfflineBrowse';
|
} from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||||
import {
|
import {
|
||||||
isOfflineSidebarLibraryNavAllowed,
|
isOfflineSidebarLibraryNavAllowed,
|
||||||
isOfflineSidebarNavAllowed,
|
isOfflineSidebarNavAllowed,
|
||||||
isOfflineSidebarSystemNavAllowed,
|
isOfflineSidebarSystemNavAllowed,
|
||||||
} from './offlineNavPolicy';
|
} from '@/features/offline/utils/offlineNavPolicy';
|
||||||
import {
|
import {
|
||||||
loadStarredFromLibraryIndex,
|
loadStarredFromLibraryIndex,
|
||||||
mergeStarredFromServers,
|
mergeStarredFromServers,
|
||||||
} from './offlineStarredLoad';
|
} from '@/features/offline/utils/offlineStarredLoad';
|
||||||
import { resolveAlbumForServer } from './offlineMediaResolve';
|
import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaResolve';
|
||||||
|
|
||||||
const isActiveServerReachableMock = vi.fn(() => true);
|
const isActiveServerReachableMock = vi.fn(() => true);
|
||||||
const shouldAttemptSubsonicForServerMock = vi.fn((_serverId: string, _trackId?: string) => true);
|
const shouldAttemptSubsonicForServerMock = vi.fn((_serverId: string, _trackId?: string) => true);
|
||||||
vi.mock('../network/activeServerReachability', () => ({
|
vi.mock('@/utils/network/activeServerReachability', () => ({
|
||||||
isActiveServerReachable: () => isActiveServerReachableMock(),
|
isActiveServerReachable: () => isActiveServerReachableMock(),
|
||||||
}));
|
}));
|
||||||
vi.mock('../network/subsonicNetworkGuard', () => ({
|
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||||
shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) =>
|
shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) =>
|
||||||
shouldAttemptSubsonicForServerMock(serverId, trackId),
|
shouldAttemptSubsonicForServerMock(serverId, trackId),
|
||||||
}));
|
}));
|
||||||
@@ -32,11 +32,11 @@ const libraryAdvancedSearchMock = vi.fn();
|
|||||||
const libraryGetTracksByAlbumMock = vi.fn();
|
const libraryGetTracksByAlbumMock = vi.fn();
|
||||||
const libraryGetTracksBatchChunkedMock = vi.fn();
|
const libraryGetTracksBatchChunkedMock = vi.fn();
|
||||||
|
|
||||||
vi.mock('../../api/subsonicLibrary', () => ({
|
vi.mock('@/api/subsonicLibrary', () => ({
|
||||||
getAlbumForServer: (...args: unknown[]) => getAlbumForServerMock(...args),
|
getAlbumForServer: (...args: unknown[]) => getAlbumForServerMock(...args),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/library', () => ({
|
vi.mock('@/api/library', () => ({
|
||||||
libraryAdvancedSearch: (...args: unknown[]) => libraryAdvancedSearchMock(...args),
|
libraryAdvancedSearch: (...args: unknown[]) => libraryAdvancedSearchMock(...args),
|
||||||
libraryGetTracksByAlbum: (...args: unknown[]) => libraryGetTracksByAlbumMock(...args),
|
libraryGetTracksByAlbum: (...args: unknown[]) => libraryGetTracksByAlbumMock(...args),
|
||||||
libraryGetTracksBatchChunked: (...args: unknown[]) => libraryGetTracksBatchChunkedMock(...args),
|
libraryGetTracksBatchChunked: (...args: unknown[]) => libraryGetTracksBatchChunkedMock(...args),
|
||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import type { OfflineAlbumMeta } from '../../store/offlineStore';
|
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||||
import { countFavoriteAutoTracks, hasAnyOfflineAlbums } from './offlineLibraryHelpers';
|
import { countFavoriteAutoTracks, hasAnyOfflineAlbums } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
|
|
||||||
/** Saved servers with a local library index (cross-server favorites scope). */
|
/** Saved servers with a local library index (cross-server favorites scope). */
|
||||||
export function favoritesServerIds(): string[] {
|
export function favoritesServerIds(): string[] {
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { FAVORITES_OFFLINE_JOB_ID } from './favoritesOfflineConstants';
|
import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants';
|
||||||
|
|
||||||
describe('favoritesOfflineConstants', () => {
|
describe('favoritesOfflineConstants', () => {
|
||||||
it('uses a stable virtual job id', () => {
|
it('uses a stable virtual job id', () => {
|
||||||
+9
-9
@@ -1,12 +1,12 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOfflineJobStore } from '../../store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline/store/offlineJobStore';
|
||||||
import { FAVORITES_OFFLINE_JOB_ID } from './favoritesOfflineConstants';
|
import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants';
|
||||||
import {
|
import {
|
||||||
mergeStarredSongsUnion,
|
mergeStarredSongsUnion,
|
||||||
onFavoritesOfflineStarChange,
|
onFavoritesOfflineStarChange,
|
||||||
} from './favoritesOfflineSync';
|
} from '@/features/offline/utils/favoritesOfflineSync';
|
||||||
|
|
||||||
const getStarredForServerMock = vi.fn(async (_serverId: string) => ({
|
const getStarredForServerMock = vi.fn(async (_serverId: string) => ({
|
||||||
artists: [],
|
artists: [],
|
||||||
@@ -16,19 +16,19 @@ const getStarredForServerMock = vi.fn(async (_serverId: string) => ({
|
|||||||
|
|
||||||
const isActiveServerReachableMock = vi.fn(() => true);
|
const isActiveServerReachableMock = vi.fn(() => true);
|
||||||
|
|
||||||
vi.mock('../network/activeServerReachability', () => ({
|
vi.mock('@/utils/network/activeServerReachability', () => ({
|
||||||
isActiveServerReachable: () => isActiveServerReachableMock(),
|
isActiveServerReachable: () => isActiveServerReachableMock(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicStarRating', () => ({
|
vi.mock('@/api/subsonicStarRating', () => ({
|
||||||
getStarredForServer: (serverId: string) => getStarredForServerMock(serverId),
|
getStarredForServer: (serverId: string) => getStarredForServerMock(serverId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicLibrary', () => ({
|
vi.mock('@/api/subsonicLibrary', () => ({
|
||||||
getAlbumForServer: vi.fn(async () => ({ songs: [] })),
|
getAlbumForServer: vi.fn(async () => ({ songs: [] })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicArtists', () => ({
|
vi.mock('@/api/subsonicArtists', () => ({
|
||||||
getArtistForServer: vi.fn(async () => ({ albums: [] })),
|
getArtistForServer: vi.fn(async () => ({ albums: [] })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
+19
-19
@@ -1,27 +1,27 @@
|
|||||||
import { libraryUpsertSongsFromApi } from '../../api/library';
|
import { libraryUpsertSongsFromApi } from '@/api/library';
|
||||||
import { librarySqlServerId } from '../../api/coverCache';
|
import { librarySqlServerId } from '@/api/coverCache';
|
||||||
import { getAlbumForServer } from '../../api/subsonicLibrary';
|
import { getAlbumForServer } from '@/api/subsonicLibrary';
|
||||||
import { getArtistForServer } from '../../api/subsonicArtists';
|
import { getArtistForServer } from '@/api/subsonicArtists';
|
||||||
import { getStarredForServer } from '../../api/subsonicStarRating';
|
import { getStarredForServer } from '@/api/subsonicStarRating';
|
||||||
import { buildStreamUrlForServer } from '../../api/subsonicStreamUrl';
|
import { buildStreamUrlForServer } from '@/api/subsonicStreamUrl';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import i18n from '../../i18n';
|
import i18n from '@/i18n';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { cancelledDownloads, useOfflineJobStore } from '../../store/offlineJobStore';
|
import { cancelledDownloads, useOfflineJobStore } from '@/features/offline/store/offlineJobStore';
|
||||||
import { useFavoritesOfflineSyncStore } from '../../store/favoritesOfflineSyncStore';
|
import { useFavoritesOfflineSyncStore } from '@/features/offline/store/favoritesOfflineSyncStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { getMediaDir } from '../media/mediaDir';
|
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||||
import { resolveIndexKey, serverIndexKeyForProfile } from '../server/serverIndexKey';
|
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||||
import { FAVORITES_OFFLINE_JOB_ID } from './favoritesOfflineConstants';
|
import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants';
|
||||||
import { isActiveServerReachable } from '../network/activeServerReachability';
|
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
|
||||||
import { favoritesServerIds } from './favoritesOfflineBrowse';
|
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||||
import { loadAlbumFromLibraryIndex } from './offlineLibraryIndexLoad';
|
import { loadAlbumFromLibraryIndex } from '@/features/offline/utils/offlineLibraryIndexLoad';
|
||||||
import {
|
import {
|
||||||
entryBelongsToServer,
|
entryBelongsToServer,
|
||||||
hasLocalLibraryBytes,
|
hasLocalLibraryBytes,
|
||||||
hasLocalFavoriteAutoBytes,
|
hasLocalFavoriteAutoBytes,
|
||||||
} from './offlineLibraryHelpers';
|
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
|
|
||||||
const CONCURRENCY = 2;
|
const CONCURRENCY = 2;
|
||||||
const DEBOUNCE_MS = 600;
|
const DEBOUNCE_MS = 600;
|
||||||
+5
-5
@@ -1,12 +1,12 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import {
|
import {
|
||||||
entryNeedsFileRelocation,
|
entryNeedsFileRelocation,
|
||||||
restoreOfflineLibraryPinSources,
|
restoreOfflineLibraryPinSources,
|
||||||
} from './legacyOfflineFileMigration';
|
} from '@/features/offline/utils/legacyOfflineFileMigration';
|
||||||
import type { LocalPlaybackEntry } from '../../store/localPlaybackStore';
|
import type { LocalPlaybackEntry } from '@/store/localPlaybackStore';
|
||||||
|
|
||||||
function entry(overrides: Partial<LocalPlaybackEntry>): LocalPlaybackEntry {
|
function entry(overrides: Partial<LocalPlaybackEntry>): LocalPlaybackEntry {
|
||||||
return {
|
return {
|
||||||
+9
-9
@@ -1,13 +1,13 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { libraryGetTracksBatch } from '../../api/library';
|
import { libraryGetTracksBatch } from '@/api/library';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOfflineStore, type OfflineAlbumMeta } from '../../store/offlineStore';
|
import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||||
import { useLocalPlaybackStore, type LocalPlaybackEntry, type PinSource } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore, type LocalPlaybackEntry, type PinSource } from '@/store/localPlaybackStore';
|
||||||
import { localPlaybackEntryKey } from '../../store/localPlaybackKeys';
|
import { localPlaybackEntryKey } from '@/store/localPlaybackKeys';
|
||||||
import { importLegacyLocalPlayback } from '../../store/localPlaybackMigration';
|
import { importLegacyLocalPlayback } from '@/store/localPlaybackMigration';
|
||||||
import { getMediaDir } from '../media/mediaDir';
|
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||||
import { resolveServerIdForIndexKey } from '../server/serverLookup';
|
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||||
import { resolveIndexKey } from '../server/serverIndexKey';
|
import { resolveIndexKey } from '@/utils/server/serverIndexKey';
|
||||||
|
|
||||||
interface LegacyOfflineMigrationResult {
|
interface LegacyOfflineMigrationResult {
|
||||||
trackId: string;
|
trackId: string;
|
||||||
+9
-9
@@ -1,17 +1,17 @@
|
|||||||
import { libraryUpsertSongsFromApi } from '../../api/library';
|
import { libraryUpsertSongsFromApi } from '@/api/library';
|
||||||
import { librarySqlServerId } from '../../api/coverCache';
|
import { librarySqlServerId } from '@/api/coverCache';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import type { LocalPlaybackEntry, PinSource } from '../../store/localPlaybackStore';
|
import type { LocalPlaybackEntry, PinSource } from '@/store/localPlaybackStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { getMediaDir } from '../media/mediaDir';
|
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||||
import { resolveIndexKey, serverIndexKeyForProfile } from '../server/serverIndexKey';
|
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||||
import {
|
import {
|
||||||
entryBelongsToServer,
|
entryBelongsToServer,
|
||||||
findLocalPlaybackEntry,
|
findLocalPlaybackEntry,
|
||||||
indexKeyBelongsToServer,
|
indexKeyBelongsToServer,
|
||||||
} from './offlineLibraryHelpers';
|
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
|
|
||||||
interface LibraryTrackProbeResult {
|
interface LibraryTrackProbeResult {
|
||||||
path: string;
|
path: string;
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { offlineActionPolicy } from './offlineActionPolicy';
|
import { offlineActionPolicy } from '@/features/offline/utils/offlineActionPolicy';
|
||||||
|
|
||||||
describe('offlineActionPolicy', () => {
|
describe('offlineActionPolicy', () => {
|
||||||
it('allows all mutations when offline browse is inactive', () => {
|
it('allows all mutations when offline browse is inactive', () => {
|
||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
import type { SubsonicAlbum } from '@/api/subsonicTypes';
|
||||||
import type { AlbumBrowseQuery } from '../library/albumBrowseTypes';
|
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
|
||||||
import { isOfflineBrowseActive } from './offlineBrowseMode';
|
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||||
import {
|
import {
|
||||||
fetchOfflineLocalAlbumCatalogChunk,
|
fetchOfflineLocalAlbumCatalogChunk,
|
||||||
offlineLocalBrowseEnabled,
|
offlineLocalBrowseEnabled,
|
||||||
} from './offlineLocalBrowse';
|
} from '@/features/offline/utils/offlineLocalBrowse';
|
||||||
|
|
||||||
type OfflineAlbumCatalogChunk = {
|
type OfflineAlbumCatalogChunk = {
|
||||||
albums: SubsonicAlbum[];
|
albums: SubsonicAlbum[];
|
||||||
+8
-8
@@ -1,24 +1,24 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import {
|
import {
|
||||||
buildOfflineBrowseContext,
|
buildOfflineBrowseContext,
|
||||||
computeOfflineBrowseCapabilities,
|
computeOfflineBrowseCapabilities,
|
||||||
offlineBrowseNavFlags,
|
offlineBrowseNavFlags,
|
||||||
} from './offlineBrowseContext';
|
} from '@/features/offline/utils/offlineBrowseContext';
|
||||||
|
|
||||||
vi.mock('./offlineLocalBrowse', () => ({
|
vi.mock('@/features/offline/utils/offlineLocalBrowse', () => ({
|
||||||
offlineLocalBrowseEnabled: vi.fn(() => false),
|
offlineLocalBrowseEnabled: vi.fn(() => false),
|
||||||
countLocalBrowsableTracks: vi.fn(() => 0),
|
countLocalBrowsableTracks: vi.fn(() => 0),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlinePlaylistBrowse', () => ({
|
vi.mock('@/features/offline/utils/offlinePlaylistBrowse', () => ({
|
||||||
playlistsOfflineBrowseEnabled: vi.fn(() => false),
|
playlistsOfflineBrowseEnabled: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { offlineLocalBrowseEnabled } from './offlineLocalBrowse';
|
import { offlineLocalBrowseEnabled } from '@/features/offline/utils/offlineLocalBrowse';
|
||||||
import { playlistsOfflineBrowseEnabled } from './offlinePlaylistBrowse';
|
import { playlistsOfflineBrowseEnabled } from '@/features/offline/utils/offlinePlaylistBrowse';
|
||||||
|
|
||||||
describe('offlineBrowseContext', () => {
|
describe('offlineBrowseContext', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
+7
-7
@@ -1,10 +1,10 @@
|
|||||||
import type { ConnectionStatus } from '../../hooks/useConnectionStatus';
|
import type { ConnectionStatus } from '@/hooks/useConnectionStatus';
|
||||||
import type { OfflineAlbumMeta } from '../../store/offlineStore';
|
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||||
import { favoritesOfflineBrowseEnabled } from './favoritesOfflineBrowse';
|
import { favoritesOfflineBrowseEnabled } from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||||
import { hasOfflineBrowseCapability } from './offlineBrowseRouting';
|
import { hasOfflineBrowseCapability } from '@/features/offline/utils/offlineBrowseRouting';
|
||||||
import { offlineLocalBrowseEnabled } from './offlineLocalBrowse';
|
import { offlineLocalBrowseEnabled } from '@/features/offline/utils/offlineLocalBrowse';
|
||||||
import { playlistsOfflineBrowseEnabled } from './offlinePlaylistBrowse';
|
import { playlistsOfflineBrowseEnabled } from '@/features/offline/utils/offlinePlaylistBrowse';
|
||||||
import { hasAnyOfflineAlbums } from './offlineLibraryHelpers';
|
import { hasAnyOfflineAlbums } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
|
|
||||||
export type OfflineBrowseCapabilities = {
|
export type OfflineBrowseCapabilities = {
|
||||||
localLibrary: boolean;
|
localLibrary: boolean;
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import { renderHook, act } from '@testing-library/react';
|
import { renderHook, act } from '@testing-library/react';
|
||||||
import { describe, expect, it, beforeEach } from 'vitest';
|
import { describe, expect, it, beforeEach } from 'vitest';
|
||||||
import { useDevOfflineBrowseStore } from '../../store/devOfflineBrowseStore';
|
import { useDevOfflineBrowseStore } from '@/features/offline/store/devOfflineBrowseStore';
|
||||||
import { resetActiveServerConnectionSnapshot } from '../network/activeServerReachability';
|
import { resetActiveServerConnectionSnapshot } from '@/utils/network/activeServerReachability';
|
||||||
import { useOfflineBrowseActive } from './offlineBrowseMode';
|
import { useOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||||
|
|
||||||
describe('useOfflineBrowseActive', () => {
|
describe('useOfflineBrowseActive', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
isDevOfflineBrowseForced,
|
isDevOfflineBrowseForced,
|
||||||
useDevOfflineBrowseStore,
|
useDevOfflineBrowseStore,
|
||||||
} from '../../store/devOfflineBrowseStore';
|
} from '@/features/offline/store/devOfflineBrowseStore';
|
||||||
import { useConnectionStatus } from '../../hooks/useConnectionStatus';
|
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
|
||||||
import { isActiveServerReachable } from '../network/activeServerReachability';
|
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
|
||||||
|
|
||||||
/** True when browse/detail pages should use local-bytes-only data sources. */
|
/** True when browse/detail pages should use local-bytes-only data sources. */
|
||||||
export function isOfflineBrowseActive(): boolean {
|
export function isOfflineBrowseActive(): boolean {
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import {
|
|||||||
hasOfflineBrowseCapability,
|
hasOfflineBrowseCapability,
|
||||||
isPathOfflineBrowsable,
|
isPathOfflineBrowsable,
|
||||||
resolveOfflineDisconnectNavAction,
|
resolveOfflineDisconnectNavAction,
|
||||||
} from './offlineBrowseRouting';
|
} from '@/features/offline/utils/offlineBrowseRouting';
|
||||||
|
|
||||||
describe('offlineBrowseRouting', () => {
|
describe('offlineBrowseRouting', () => {
|
||||||
it('hasOfflineBrowseCapability is true when any offline surface exists', () => {
|
it('hasOfflineBrowseCapability is true when any offline surface exists', () => {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { isOfflineSidebarNavAllowed } from './offlineNavPolicy';
|
import { isOfflineSidebarNavAllowed } from '@/features/offline/utils/offlineNavPolicy';
|
||||||
|
|
||||||
/** Any offline browse surface the disconnect fork may use. */
|
/** Any offline browse surface the disconnect fork may use. */
|
||||||
export function hasOfflineBrowseCapability(
|
export function hasOfflineBrowseCapability(
|
||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import {
|
import {
|
||||||
resetOfflineLibraryFilterSuspendState,
|
resetOfflineLibraryFilterSuspendState,
|
||||||
restoreMusicLibraryFiltersAfterOffline,
|
restoreMusicLibraryFiltersAfterOffline,
|
||||||
suspendMusicLibraryFiltersForOffline,
|
suspendMusicLibraryFiltersForOffline,
|
||||||
} from './offlineLibraryFilterSuspend';
|
} from '@/features/offline/utils/offlineLibraryFilterSuspend';
|
||||||
|
|
||||||
describe('offlineLibraryFilterSuspend', () => {
|
describe('offlineLibraryFilterSuspend', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
|
|
||||||
let savedFilterByServer: Record<string, 'all' | string> | null = null;
|
let savedFilterByServer: Record<string, 'all' | string> | null = null;
|
||||||
|
|
||||||
+11
-11
@@ -1,8 +1,8 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { switchActiveServer } from '../server/switchActiveServer';
|
import { switchActiveServer } from '@/utils/server/switchActiveServer';
|
||||||
import {
|
import {
|
||||||
buildOfflineCacheQueueTracks,
|
buildOfflineCacheQueueTracks,
|
||||||
countFavoriteAutoTracks,
|
countFavoriteAutoTracks,
|
||||||
@@ -15,17 +15,17 @@ import {
|
|||||||
offlineAlbumCoverScope,
|
offlineAlbumCoverScope,
|
||||||
offlineTrackCount,
|
offlineTrackCount,
|
||||||
type OfflineLibraryCard,
|
type OfflineLibraryCard,
|
||||||
} from './offlineLibraryHelpers';
|
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
import * as libraryApi from '../../api/library';
|
import * as libraryApi from '@/api/library';
|
||||||
import { coverStorageKey } from '../../cover/storageKeys';
|
import { coverStorageKey } from '@/cover/storageKeys';
|
||||||
import { resolveCoverDisplayTier } from '../../cover/tiers';
|
import { resolveCoverDisplayTier } from '@/cover/tiers';
|
||||||
|
|
||||||
vi.mock('../server/switchActiveServer', () => ({
|
vi.mock('@/utils/server/switchActiveServer', () => ({
|
||||||
switchActiveServer: vi.fn(async () => true),
|
switchActiveServer: vi.fn(async () => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/library', async importOriginal => {
|
vi.mock('@/api/library', async importOriginal => {
|
||||||
const actual = await importOriginal<typeof import('../../api/library')>();
|
const actual = await importOriginal<typeof import('@/api/library')>();
|
||||||
const libraryGetTracksBatch = vi.fn();
|
const libraryGetTracksBatch = vi.fn();
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
+14
-14
@@ -1,16 +1,16 @@
|
|||||||
import type { LibraryTrackDto } from '../../api/library';
|
import type { LibraryTrackDto } from '@/api/library';
|
||||||
import { libraryGetTrack, libraryGetTracksBatchChunked } from '../../api/library';
|
import { libraryGetTrack, libraryGetTracksBatchChunked } from '@/api/library';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import type { CoverServerScope } from '../../cover/types';
|
import type { CoverServerScope } from '@/cover/types';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import type { LocalPlaybackEntry, PinnedGroup, PinSource } from '../../store/localPlaybackStore';
|
import type { LocalPlaybackEntry, PinnedGroup, PinSource } from '@/store/localPlaybackStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useOfflineStore, type OfflineAlbumMeta } from '../../store/offlineStore';
|
import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||||
import { resolveTrackCoverArtId, trackToSong } from '../library/advancedSearchLocal';
|
import { resolveTrackCoverArtId, trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||||
import { canonicalQueueServerKey, resolveIndexKey } from '../server/serverIndexKey';
|
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
|
||||||
import type { Track } from '../../store/playerStoreTypes';
|
import type { Track } from '@/store/playerStoreTypes';
|
||||||
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '../server/serverLookup';
|
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||||
import { serverIndexKeyForProfile } from '../server/serverIndexKey';
|
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||||
|
|
||||||
export interface OfflineLibraryCard {
|
export interface OfflineLibraryCard {
|
||||||
serverIndexKey: string;
|
serverIndexKey: string;
|
||||||
@@ -165,7 +165,7 @@ export async function syncAlbumLibraryTierFromDisk(
|
|||||||
songs: SubsonicSong[],
|
songs: SubsonicSong[],
|
||||||
pinSource?: PinSource,
|
pinSource?: PinSource,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const { reconcileLibraryTierForAlbum } = await import('./libraryTierReconcile');
|
const { reconcileLibraryTierForAlbum } = await import('@/features/offline/utils/libraryTierReconcile');
|
||||||
const result = await reconcileLibraryTierForAlbum(serverId, songs, pinSource ?? {
|
const result = await reconcileLibraryTierForAlbum(serverId, songs, pinSource ?? {
|
||||||
kind: 'album',
|
kind: 'album',
|
||||||
sourceId: albumId,
|
sourceId: albumId,
|
||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
import { libraryAdvancedSearch, libraryGetTracksByAlbum } from '../../api/library';
|
import { libraryAdvancedSearch, libraryGetTracksByAlbum } from '@/api/library';
|
||||||
import type {
|
import type {
|
||||||
SubsonicAlbum,
|
SubsonicAlbum,
|
||||||
SubsonicArtist,
|
SubsonicArtist,
|
||||||
SubsonicSong,
|
SubsonicSong,
|
||||||
} from '../../api/subsonicTypes';
|
} from '@/api/subsonicTypes';
|
||||||
import {
|
import {
|
||||||
albumToAlbum,
|
albumToAlbum,
|
||||||
artistToArtist,
|
artistToArtist,
|
||||||
trackToSong,
|
trackToSong,
|
||||||
} from '../library/advancedSearchLocal';
|
} from '@/utils/library/advancedSearchLocal';
|
||||||
|
|
||||||
export async function loadAlbumFromLibraryIndex(
|
export async function loadAlbumFromLibraryIndex(
|
||||||
serverId: string,
|
serverId: string,
|
||||||
+6
-6
@@ -1,19 +1,19 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import type { LibraryTrackDto } from '../../api/library';
|
import type { LibraryTrackDto } from '@/api/library';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import {
|
import {
|
||||||
countLocalBrowsableTracks,
|
countLocalBrowsableTracks,
|
||||||
fetchOfflineLocalBrowsableSongPage,
|
fetchOfflineLocalBrowsableSongPage,
|
||||||
offlineLocalBrowseEnabled,
|
offlineLocalBrowseEnabled,
|
||||||
} from './offlineLocalBrowse';
|
} from '@/features/offline/utils/offlineLocalBrowse';
|
||||||
|
|
||||||
const { libraryGetTracksBatchChunkedMock } = vi.hoisted(() => ({
|
const { libraryGetTracksBatchChunkedMock } = vi.hoisted(() => ({
|
||||||
libraryGetTracksBatchChunkedMock: vi.fn(async (): Promise<LibraryTrackDto[]> => []),
|
libraryGetTracksBatchChunkedMock: vi.fn(async (): Promise<LibraryTrackDto[]> => []),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/library', () => ({
|
vi.mock('@/api/library', () => ({
|
||||||
libraryGetTracksBatchChunked: libraryGetTracksBatchChunkedMock,
|
libraryGetTracksBatchChunked: libraryGetTracksBatchChunkedMock,
|
||||||
libraryGetTracksByAlbum: vi.fn(async () => []),
|
libraryGetTracksByAlbum: vi.fn(async () => []),
|
||||||
libraryAdvancedSearch: vi.fn(async () => ({ albums: [], artists: [], tracks: [] })),
|
libraryAdvancedSearch: vi.fn(async () => ({ albums: [], artists: [], tracks: [] })),
|
||||||
+13
-13
@@ -1,26 +1,26 @@
|
|||||||
import type { LibraryTrackDto } from '../../api/library';
|
import type { LibraryTrackDto } from '@/api/library';
|
||||||
import { libraryAdvancedSearch, libraryGetTracksBatchChunked, libraryGetTracksByAlbum } from '../../api/library';
|
import { libraryAdvancedSearch, libraryGetTracksBatchChunked, libraryGetTracksByAlbum } from '@/api/library';
|
||||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import type { LocalPlaybackEntry } from '../../store/localPlaybackStore';
|
import type { LocalPlaybackEntry } from '@/store/localPlaybackStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import {
|
import {
|
||||||
albumToAlbum,
|
albumToAlbum,
|
||||||
artistToArtist,
|
artistToArtist,
|
||||||
resolveTrackCoverArtId,
|
resolveTrackCoverArtId,
|
||||||
trackToSong,
|
trackToSong,
|
||||||
} from '../library/advancedSearchLocal';
|
} from '@/utils/library/advancedSearchLocal';
|
||||||
import { albumIsCompilationFromTrackDtos } from '../library/albumCompilation';
|
import { albumIsCompilationFromTrackDtos } from '@/utils/library/albumCompilation';
|
||||||
import {
|
import {
|
||||||
filterAlbumsByCompilation,
|
filterAlbumsByCompilation,
|
||||||
filterAlbumsByGenres,
|
filterAlbumsByGenres,
|
||||||
filterAlbumsByStarred,
|
filterAlbumsByStarred,
|
||||||
filterAlbumsByYearBounds,
|
filterAlbumsByYearBounds,
|
||||||
} from '../library/albumBrowseFilters';
|
} from '@/utils/library/albumBrowseFilters';
|
||||||
import type { AlbumBrowseQuery } from '../library/albumBrowseTypes';
|
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
|
||||||
import { sortSubsonicAlbums } from '../library/albumBrowseSort';
|
import { sortSubsonicAlbums } from '@/utils/library/albumBrowseSort';
|
||||||
import { isLosslessSuffix } from '../library/losslessFormats';
|
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||||
import { entryBelongsToServer } from './offlineLibraryHelpers';
|
import { entryBelongsToServer } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
|
|
||||||
function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] {
|
function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] {
|
||||||
return [...songs].sort((a, b) => a.title.localeCompare(b.title));
|
return [...songs].sort((a, b) => a.title.localeCompare(b.title));
|
||||||
+11
-11
@@ -1,11 +1,11 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import {
|
import {
|
||||||
resolveAlbum,
|
resolveAlbum,
|
||||||
resolveAlbumForActiveServer,
|
resolveAlbumForActiveServer,
|
||||||
resolveArtist,
|
resolveArtist,
|
||||||
resolvePlaylist,
|
resolvePlaylist,
|
||||||
} from './offlineMediaResolve';
|
} from '@/features/offline/utils/offlineMediaResolve';
|
||||||
|
|
||||||
const isOfflineBrowseActiveMock = vi.fn(() => false);
|
const isOfflineBrowseActiveMock = vi.fn(() => false);
|
||||||
const offlineLocalBrowseEnabledMock = vi.fn((_serverId: string) => false);
|
const offlineLocalBrowseEnabledMock = vi.fn((_serverId: string) => false);
|
||||||
@@ -21,11 +21,11 @@ const getArtistForServerMock = vi.fn((_serverId: string, _artistId: string) => (
|
|||||||
const getPlaylistForServerMock = vi.fn((_serverId: string, _playlistId: string) => ({}));
|
const getPlaylistForServerMock = vi.fn((_serverId: string, _playlistId: string) => ({}));
|
||||||
const libraryIsReadyMock = vi.fn(async (_serverId: string) => false);
|
const libraryIsReadyMock = vi.fn(async (_serverId: string) => false);
|
||||||
|
|
||||||
vi.mock('./offlineBrowseMode', () => ({
|
vi.mock('@/features/offline/utils/offlineBrowseMode', () => ({
|
||||||
isOfflineBrowseActive: () => isOfflineBrowseActiveMock(),
|
isOfflineBrowseActive: () => isOfflineBrowseActiveMock(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlineLocalBrowse', () => ({
|
vi.mock('@/features/offline/utils/offlineLocalBrowse', () => ({
|
||||||
offlineLocalBrowseEnabled: (id: string) => offlineLocalBrowseEnabledMock(id),
|
offlineLocalBrowseEnabled: (id: string) => offlineLocalBrowseEnabledMock(id),
|
||||||
loadAlbumFromLocalPlayback: (serverId: string, albumId: string) =>
|
loadAlbumFromLocalPlayback: (serverId: string, albumId: string) =>
|
||||||
loadAlbumFromLocalPlaybackMock(serverId, albumId),
|
loadAlbumFromLocalPlaybackMock(serverId, albumId),
|
||||||
@@ -33,36 +33,36 @@ vi.mock('./offlineLocalBrowse', () => ({
|
|||||||
loadArtistFromLocalPlaybackMock(serverId, artistId),
|
loadArtistFromLocalPlaybackMock(serverId, artistId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlineLibraryIndexLoad', () => ({
|
vi.mock('@/features/offline/utils/offlineLibraryIndexLoad', () => ({
|
||||||
loadAlbumFromLibraryIndex: (...args: unknown[]) => loadAlbumFromLibraryIndexMock(...args),
|
loadAlbumFromLibraryIndex: (...args: unknown[]) => loadAlbumFromLibraryIndexMock(...args),
|
||||||
loadArtistFromLibraryIndex: (...args: unknown[]) => loadArtistFromLibraryIndexMock(...args),
|
loadArtistFromLibraryIndex: (...args: unknown[]) => loadArtistFromLibraryIndexMock(...args),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlinePlaylistBrowse', () => ({
|
vi.mock('@/features/offline/utils/offlinePlaylistBrowse', () => ({
|
||||||
playlistsOfflineBrowseEnabled: (id: string) => playlistsOfflineBrowseEnabledMock(id),
|
playlistsOfflineBrowseEnabled: (id: string) => playlistsOfflineBrowseEnabledMock(id),
|
||||||
loadOfflineBrowsablePlaylist: (playlistId: string, serverId: string) =>
|
loadOfflineBrowsablePlaylist: (playlistId: string, serverId: string) =>
|
||||||
loadOfflineBrowsablePlaylistMock(playlistId, serverId),
|
loadOfflineBrowsablePlaylistMock(playlistId, serverId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../network/subsonicNetworkGuard', () => ({
|
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||||
shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) =>
|
shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) =>
|
||||||
shouldAttemptSubsonicForServerMock(serverId, trackId),
|
shouldAttemptSubsonicForServerMock(serverId, trackId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicLibrary', () => ({
|
vi.mock('@/api/subsonicLibrary', () => ({
|
||||||
getAlbumForServer: (serverId: string, albumId: string) => getAlbumForServerMock(serverId, albumId),
|
getAlbumForServer: (serverId: string, albumId: string) => getAlbumForServerMock(serverId, albumId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicArtists', () => ({
|
vi.mock('@/api/subsonicArtists', () => ({
|
||||||
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
|
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicPlaylists', () => ({
|
vi.mock('@/api/subsonicPlaylists', () => ({
|
||||||
getPlaylistForServer: (serverId: string, playlistId: string) =>
|
getPlaylistForServer: (serverId: string, playlistId: string) =>
|
||||||
getPlaylistForServerMock(serverId, playlistId),
|
getPlaylistForServerMock(serverId, playlistId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../library/libraryReady', () => ({
|
vi.mock('@/utils/library/libraryReady', () => ({
|
||||||
libraryIsReady: (serverId: string) => libraryIsReadyMock(serverId),
|
libraryIsReady: (serverId: string) => libraryIsReadyMock(serverId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
+11
-11
@@ -1,29 +1,29 @@
|
|||||||
import { getAlbumForServer } from '../../api/subsonicLibrary';
|
import { getAlbumForServer } from '@/api/subsonicLibrary';
|
||||||
import { getArtistForServer } from '../../api/subsonicArtists';
|
import { getArtistForServer } from '@/api/subsonicArtists';
|
||||||
import { getPlaylistForServer } from '../../api/subsonicPlaylists';
|
import { getPlaylistForServer } from '@/api/subsonicPlaylists';
|
||||||
import type {
|
import type {
|
||||||
SubsonicAlbum,
|
SubsonicAlbum,
|
||||||
SubsonicArtist,
|
SubsonicArtist,
|
||||||
SubsonicPlaylist,
|
SubsonicPlaylist,
|
||||||
SubsonicSong,
|
SubsonicSong,
|
||||||
} from '../../api/subsonicTypes';
|
} from '@/api/subsonicTypes';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { shouldAttemptSubsonicForServer } from '../network/subsonicNetworkGuard';
|
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||||
import { isOfflineBrowseActive } from './offlineBrowseMode';
|
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||||
import { libraryIsReady } from '../library/libraryReady';
|
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||||
import {
|
import {
|
||||||
loadAlbumFromLibraryIndex,
|
loadAlbumFromLibraryIndex,
|
||||||
loadArtistFromLibraryIndex,
|
loadArtistFromLibraryIndex,
|
||||||
} from './offlineLibraryIndexLoad';
|
} from '@/features/offline/utils/offlineLibraryIndexLoad';
|
||||||
import {
|
import {
|
||||||
loadAlbumFromLocalPlayback,
|
loadAlbumFromLocalPlayback,
|
||||||
loadArtistFromLocalPlayback,
|
loadArtistFromLocalPlayback,
|
||||||
offlineLocalBrowseEnabled,
|
offlineLocalBrowseEnabled,
|
||||||
} from './offlineLocalBrowse';
|
} from '@/features/offline/utils/offlineLocalBrowse';
|
||||||
import {
|
import {
|
||||||
loadOfflineBrowsablePlaylist,
|
loadOfflineBrowsablePlaylist,
|
||||||
playlistsOfflineBrowseEnabled,
|
playlistsOfflineBrowseEnabled,
|
||||||
} from './offlinePlaylistBrowse';
|
} from '@/features/offline/utils/offlinePlaylistBrowse';
|
||||||
|
|
||||||
export type ResolvedAlbum = { album: SubsonicAlbum; songs: SubsonicSong[] };
|
export type ResolvedAlbum = { album: SubsonicAlbum; songs: SubsonicSong[] };
|
||||||
|
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { cancelledDownloads, useOfflineJobStore } from '../../store/offlineJobStore';
|
import { cancelledDownloads, useOfflineJobStore } from '@/features/offline/store/offlineJobStore';
|
||||||
import {
|
import {
|
||||||
clearOfflinePinTasks,
|
clearOfflinePinTasks,
|
||||||
dequeueOfflinePin,
|
dequeueOfflinePin,
|
||||||
enqueueOfflinePin,
|
enqueueOfflinePin,
|
||||||
isAlbumPinQueued,
|
isAlbumPinQueued,
|
||||||
registerOfflinePinExecutor,
|
registerOfflinePinExecutor,
|
||||||
} from './offlinePinQueue';
|
} from '@/features/offline/utils/offlinePinQueue';
|
||||||
|
|
||||||
describe('offlinePinQueue', () => {
|
describe('offlinePinQueue', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import type { PinSource } from '../../store/localPlaybackStore';
|
import type { PinSource } from '@/store/localPlaybackStore';
|
||||||
import {
|
import {
|
||||||
cancelledDownloads,
|
cancelledDownloads,
|
||||||
useOfflineJobStore,
|
useOfflineJobStore,
|
||||||
type OfflinePinQueueEntry,
|
type OfflinePinQueueEntry,
|
||||||
} from '../../store/offlineJobStore';
|
} from '@/features/offline/store/offlineJobStore';
|
||||||
|
|
||||||
export type OfflinePinKind = PinSource['kind'];
|
export type OfflinePinKind = PinSource['kind'];
|
||||||
|
|
||||||
+7
-7
@@ -1,16 +1,16 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import * as libraryApi from '../../api/library';
|
import * as libraryApi from '@/api/library';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import {
|
import {
|
||||||
fetchOfflineBrowsablePlaylists,
|
fetchOfflineBrowsablePlaylists,
|
||||||
loadOfflineBrowsablePlaylist,
|
loadOfflineBrowsablePlaylist,
|
||||||
playlistsOfflineBrowseEnabled,
|
playlistsOfflineBrowseEnabled,
|
||||||
} from './offlinePlaylistBrowse';
|
} from '@/features/offline/utils/offlinePlaylistBrowse';
|
||||||
|
|
||||||
vi.mock('../../api/library', () => ({
|
vi.mock('@/api/library', () => ({
|
||||||
libraryGetTracksBatchChunked: vi.fn(),
|
libraryGetTracksBatchChunked: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
+8
-8
@@ -1,15 +1,15 @@
|
|||||||
import { libraryGetTracksBatchChunked } from '../../api/library';
|
import { libraryGetTracksBatchChunked } from '@/api/library';
|
||||||
import type { SubsonicPlaylist, SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicPlaylist, SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useLibraryIndexStore } from '../../store/libraryIndexStore';
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||||
import type { PinnedGroup } from '../../store/localPlaybackStore';
|
import type { PinnedGroup } from '@/store/localPlaybackStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { trackToSong } from '../library/advancedSearchLocal';
|
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||||
import { isManualOfflinePlaylist } from './pinnedOfflineSync';
|
import { isManualOfflinePlaylist } from '@/features/offline/utils/pinnedOfflineSync';
|
||||||
import {
|
import {
|
||||||
hasLocalLibraryBytes,
|
hasLocalLibraryBytes,
|
||||||
indexKeyBelongsToServer,
|
indexKeyBelongsToServer,
|
||||||
resolveOfflineAlbumMeta,
|
resolveOfflineAlbumMeta,
|
||||||
} from './offlineLibraryHelpers';
|
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
|
|
||||||
function listPlaylistPinnedGroupsForServer(serverId: string): PinnedGroup[] {
|
function listPlaylistPinnedGroupsForServer(serverId: string): PinnedGroup[] {
|
||||||
return useLocalPlaybackStore.getState()
|
return useLocalPlaybackStore.getState()
|
||||||
+9
-9
@@ -1,24 +1,24 @@
|
|||||||
import { getStarredForServer } from '../../api/subsonicStarRating';
|
import { getStarredForServer } from '@/api/subsonicStarRating';
|
||||||
import { libraryAdvancedSearch } from '../../api/library';
|
import { libraryAdvancedSearch } from '@/api/library';
|
||||||
import type {
|
import type {
|
||||||
StarredResults,
|
StarredResults,
|
||||||
SubsonicAlbum,
|
SubsonicAlbum,
|
||||||
SubsonicArtist,
|
SubsonicArtist,
|
||||||
SubsonicSong,
|
SubsonicSong,
|
||||||
} from '../../api/subsonicTypes';
|
} from '@/api/subsonicTypes';
|
||||||
import { isActiveServerReachable } from '../network/activeServerReachability';
|
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
|
||||||
import {
|
import {
|
||||||
albumToAlbum,
|
albumToAlbum,
|
||||||
trackToSong,
|
trackToSong,
|
||||||
} from '../library/advancedSearchLocal';
|
} from '@/utils/library/advancedSearchLocal';
|
||||||
import { dedupeById } from '../dedupeById';
|
import { dedupeById } from '@/utils/dedupeById';
|
||||||
import { isOfflineBrowseActive } from './offlineBrowseMode';
|
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||||
import { favoritesServerIds } from './favoritesOfflineBrowse';
|
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||||
import {
|
import {
|
||||||
buildAlbumFromTracks,
|
buildAlbumFromTracks,
|
||||||
fetchBrowsableLocalTrackDtos,
|
fetchBrowsableLocalTrackDtos,
|
||||||
offlineLocalBrowseEnabled,
|
offlineLocalBrowseEnabled,
|
||||||
} from './offlineLocalBrowse';
|
} from '@/features/offline/utils/offlineLocalBrowse';
|
||||||
|
|
||||||
function tagStarredWithServer(starred: StarredResults, serverId: string): StarredResults {
|
function tagStarredWithServer(starred: StarredResults, serverId: string): StarredResults {
|
||||||
const withServer = <T extends { id: string }>(items: T[]): (T & { serverId: string })[] =>
|
const withServer = <T extends { id: string }>(items: T[]): (T & { serverId: string })[] =>
|
||||||
+14
-14
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useOfflineJobStore } from '../../store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline/store/offlineJobStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import {
|
import {
|
||||||
isManualOfflinePlaylist,
|
isManualOfflinePlaylist,
|
||||||
isPlaylistPinnedOffline,
|
isPlaylistPinnedOffline,
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
scheduleSyncPinnedAlbumsAndArtists,
|
scheduleSyncPinnedAlbumsAndArtists,
|
||||||
syncAllPinnedPlaylists,
|
syncAllPinnedPlaylists,
|
||||||
syncPinnedArtistIfNeeded,
|
syncPinnedArtistIfNeeded,
|
||||||
} from './pinnedOfflineSync';
|
} from '@/features/offline/utils/pinnedOfflineSync';
|
||||||
import { SMART_PREFIX } from '../componentHelpers/playlistDetailHelpers';
|
import { SMART_PREFIX } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||||
|
|
||||||
const getPlaylistMock = vi.fn();
|
const getPlaylistMock = vi.fn();
|
||||||
const getAlbumForServerMock = vi.fn();
|
const getAlbumForServerMock = vi.fn();
|
||||||
@@ -24,31 +24,31 @@ const isReachableMock = vi.fn(() => true);
|
|||||||
const enqueueMock = vi.fn((_task: unknown) => true);
|
const enqueueMock = vi.fn((_task: unknown) => true);
|
||||||
const invokeMock = vi.fn(async (_cmd: string, _args?: unknown) => ({}));
|
const invokeMock = vi.fn(async (_cmd: string, _args?: unknown) => ({}));
|
||||||
|
|
||||||
vi.mock('../network/activeServerReachability', () => ({
|
vi.mock('@/utils/network/activeServerReachability', () => ({
|
||||||
isActiveServerReachable: () => isReachableMock(),
|
isActiveServerReachable: () => isReachableMock(),
|
||||||
onActiveServerBecameReachable: () => () => {},
|
onActiveServerBecameReachable: () => () => {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicPlaylists', () => ({
|
vi.mock('@/api/subsonicPlaylists', () => ({
|
||||||
getPlaylistForServer: (serverId: string, id: string) => getPlaylistMock(serverId, id),
|
getPlaylistForServer: (serverId: string, id: string) => getPlaylistMock(serverId, id),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicLibrary', () => ({
|
vi.mock('@/api/subsonicLibrary', () => ({
|
||||||
getAlbumForServer: (serverId: string, id: string) => getAlbumForServerMock(serverId, id),
|
getAlbumForServer: (serverId: string, id: string) => getAlbumForServerMock(serverId, id),
|
||||||
filterSongsToServerLibrary: (songs: SubsonicSong[]) => filterSongsMock(songs),
|
filterSongsToServerLibrary: (songs: SubsonicSong[]) => filterSongsMock(songs),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/subsonicArtists', () => ({
|
vi.mock('@/api/subsonicArtists', () => ({
|
||||||
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
|
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/library', () => ({
|
vi.mock('@/api/library', () => ({
|
||||||
libraryGetTracksByAlbum: vi.fn(async () => []),
|
libraryGetTracksByAlbum: vi.fn(async () => []),
|
||||||
subscribeLibrarySyncIdle: vi.fn(async () => () => {}),
|
subscribeLibrarySyncIdle: vi.fn(async () => () => {}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlinePinQueue', async (importOriginal) => {
|
vi.mock('@/features/offline/utils/offlinePinQueue', async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import('./offlinePinQueue')>();
|
const actual = await importOriginal<typeof import('@/features/offline/utils/offlinePinQueue')>();
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
enqueueOfflinePin: (task: unknown) => enqueueMock(task),
|
enqueueOfflinePin: (task: unknown) => enqueueMock(task),
|
||||||
+17
-17
@@ -1,24 +1,24 @@
|
|||||||
import { libraryGetTracksByAlbum, subscribeLibrarySyncIdle } from '../../api/library';
|
import { libraryGetTracksByAlbum, subscribeLibrarySyncIdle } from '@/api/library';
|
||||||
import { getAlbumForServer, filterSongsToServerLibrary } from '../../api/subsonicLibrary';
|
import { getAlbumForServer, filterSongsToServerLibrary } from '@/api/subsonicLibrary';
|
||||||
import { getPlaylistForServer } from '../../api/subsonicPlaylists';
|
import { getPlaylistForServer } from '@/api/subsonicPlaylists';
|
||||||
import { getArtistForServer } from '../../api/subsonicArtists';
|
import { getArtistForServer } from '@/api/subsonicArtists';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import type { PinSource } from '../../store/localPlaybackStore';
|
import type { PinSource } from '@/store/localPlaybackStore';
|
||||||
import { useLocalPlaybackStore } from '../../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { usePlaylistStore } from '../../store/playlistStore';
|
import { usePlaylistStore } from '@/store/playlistStore';
|
||||||
import { isSmartPlaylistName } from '../componentHelpers/playlistDetailHelpers';
|
import { isSmartPlaylistName } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||||
import { getMediaDir } from '../media/mediaDir';
|
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||||
import {
|
import {
|
||||||
isActiveServerReachable,
|
isActiveServerReachable,
|
||||||
onActiveServerBecameReachable,
|
onActiveServerBecameReachable,
|
||||||
} from '../network/activeServerReachability';
|
} from '@/utils/network/activeServerReachability';
|
||||||
import { resolveIndexKey, serverIndexKeyForProfile } from '../server/serverIndexKey';
|
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||||
import { resolveServerIdForIndexKey } from '../server/serverLookup';
|
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||||
import { findLocalPlaybackEntry } from './offlineLibraryHelpers';
|
import { findLocalPlaybackEntry } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
import { enqueueOfflinePin } from './offlinePinQueue';
|
import { enqueueOfflinePin } from '@/features/offline/utils/offlinePinQueue';
|
||||||
|
|
||||||
export type OfflinePinKind = PinSource['kind'];
|
export type OfflinePinKind = PinSource['kind'];
|
||||||
|
|
||||||
+7
-7
@@ -1,29 +1,29 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { resumeIncompleteOfflinePins } from './resumeIncompleteOfflinePins';
|
import { resumeIncompleteOfflinePins } from '@/features/offline/utils/resumeIncompleteOfflinePins';
|
||||||
|
|
||||||
const isActiveServerReachableMock = vi.fn(() => true);
|
const isActiveServerReachableMock = vi.fn(() => true);
|
||||||
const isOfflinePinCompleteMock = vi.fn((_albumId: string, _serverId: string) => false);
|
const isOfflinePinCompleteMock = vi.fn((_albumId: string, _serverId: string) => false);
|
||||||
const resolveAlbumForServerMock = vi.fn();
|
const resolveAlbumForServerMock = vi.fn();
|
||||||
const downloadAlbumMock = vi.fn();
|
const downloadAlbumMock = vi.fn();
|
||||||
|
|
||||||
vi.mock('../network/activeServerReachability', () => ({
|
vi.mock('@/utils/network/activeServerReachability', () => ({
|
||||||
isActiveServerReachable: () => isActiveServerReachableMock(),
|
isActiveServerReachable: () => isActiveServerReachableMock(),
|
||||||
onActiveServerBecameReachable: () => () => {},
|
onActiveServerBecameReachable: () => () => {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlineLibraryHelpers', () => ({
|
vi.mock('@/features/offline/utils/offlineLibraryHelpers', () => ({
|
||||||
isOfflinePinComplete: (albumId: string, serverId: string) =>
|
isOfflinePinComplete: (albumId: string, serverId: string) =>
|
||||||
isOfflinePinCompleteMock(albumId, serverId),
|
isOfflinePinCompleteMock(albumId, serverId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./offlineMediaResolve', () => ({
|
vi.mock('@/features/offline/utils/offlineMediaResolve', () => ({
|
||||||
resolveAlbumForServer: (serverId: string, albumId: string) =>
|
resolveAlbumForServer: (serverId: string, albumId: string) =>
|
||||||
resolveAlbumForServerMock(serverId, albumId),
|
resolveAlbumForServerMock(serverId, albumId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../api/library', () => ({
|
vi.mock('@/api/library', () => ({
|
||||||
libraryGetTracksBatchChunked: vi.fn(async () => []),
|
libraryGetTracksBatchChunked: vi.fn(async () => []),
|
||||||
LIBRARY_TRACKS_BATCH_LIMIT: 100,
|
LIBRARY_TRACKS_BATCH_LIMIT: 100,
|
||||||
}));
|
}));
|
||||||
+12
-12
@@ -1,15 +1,15 @@
|
|||||||
import { libraryGetTracksBatchChunked } from '../../api/library';
|
import { libraryGetTracksBatchChunked } from '@/api/library';
|
||||||
import { getPlaylist } from '../../api/subsonicPlaylists';
|
import { getPlaylist } from '@/api/subsonicPlaylists';
|
||||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
import type { SubsonicSong } from '@/api/subsonicTypes';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import type { OfflineAlbumMeta } from '../../store/offlineStore';
|
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||||
import { useOfflineStore } from '../../store/offlineStore';
|
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||||
import { trackToSong } from '../library/advancedSearchLocal';
|
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||||
import { isActiveServerReachable, onActiveServerBecameReachable } from '../network/activeServerReachability';
|
import { isActiveServerReachable, onActiveServerBecameReachable } from '@/utils/network/activeServerReachability';
|
||||||
import { shouldAttemptSubsonicForServer } from '../network/subsonicNetworkGuard';
|
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||||
import { resolveServerIdForIndexKey } from '../server/serverLookup';
|
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||||
import { isOfflinePinComplete } from './offlineLibraryHelpers';
|
import { isOfflinePinComplete } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||||
import { resolveAlbumForServer } from './offlineMediaResolve';
|
import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaResolve';
|
||||||
|
|
||||||
const DEBOUNCE_MS = 800;
|
const DEBOUNCE_MS = 800;
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useState, useRef, useEffect, useMemo } from 'react';
|
import { useState, useRef, useEffect, useMemo } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { usePlayerStore } from '@/store/playerStore';
|
import { usePlayerStore } from '@/store/playerStore';
|
||||||
import { useOfflineJobStore } from '@/store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline';
|
||||||
import { clearOfflinePinTasks } from '@/utils/offline/offlinePinQueue';
|
import { clearOfflinePinTasks } from '@/features/offline';
|
||||||
import { useDeviceSyncJobStore } from '@/features/deviceSync';
|
import { useDeviceSyncJobStore } from '@/features/deviceSync';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useSidebarStore } from '@/features/sidebar/store/sidebarStore';
|
import { useSidebarStore } from '@/features/sidebar/store/sidebarStore';
|
||||||
@@ -23,9 +23,9 @@ import { useSidebarNewReleasesUnread } from '@/features/sidebar/hooks/useSidebar
|
|||||||
import { useSidebarNavDnd } from '@/features/sidebar/hooks/useSidebarNavDnd';
|
import { useSidebarNavDnd } from '@/features/sidebar/hooks/useSidebarNavDnd';
|
||||||
import { useSidebarLibraryDropdown } from '@/features/sidebar/hooks/useSidebarLibraryDropdown';
|
import { useSidebarLibraryDropdown } from '@/features/sidebar/hooks/useSidebarLibraryDropdown';
|
||||||
import { useSidebarScrollVisible } from '@/features/sidebar/hooks/useSidebarScrollVisible';
|
import { useSidebarScrollVisible } from '@/features/sidebar/hooks/useSidebarScrollVisible';
|
||||||
import { isOfflineSidebarNavAllowed } from '@/utils/offline/offlineNavPolicy';
|
import { isOfflineSidebarNavAllowed } from '@/features/offline';
|
||||||
import { useOfflineBrowseContext } from '@/hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineBrowseNavFlags } from '@/utils/offline/offlineBrowseContext';
|
import { offlineBrowseNavFlags } from '@/features/offline';
|
||||||
import { useSidebarPerfProbe } from '@/features/sidebar/hooks/useSidebarPerfProbe';
|
import { useSidebarPerfProbe } from '@/features/sidebar/hooks/useSidebarPerfProbe';
|
||||||
import SidebarPerfProbeModal from '@/features/sidebar/components/SidebarPerfProbeModal';
|
import SidebarPerfProbeModal from '@/features/sidebar/components/SidebarPerfProbeModal';
|
||||||
import SidebarNavBody from '@/features/sidebar/components/SidebarNavBody';
|
import SidebarNavBody from '@/features/sidebar/components/SidebarNavBody';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useOfflineBrowseContext } from '@/hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { usePlayerStatsRecordingEnabled } from '@/features/stats/hooks/usePlayerStatsRecordingEnabled';
|
import { usePlayerStatsRecordingEnabled } from '@/features/stats/hooks/usePlayerStatsRecordingEnabled';
|
||||||
|
|
||||||
export default function StatisticsTabBar() {
|
export default function StatisticsTabBar() {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { getMusicNetworkRuntime, type RecentTrack, type StatsPeriod, type TopItem } from '@/music-network';
|
import { getMusicNetworkRuntime, type RecentTrack, type StatsPeriod, type TopItem } from '@/music-network';
|
||||||
import { useOfflineBrowseContext } from '@/hooks/useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { usePlayerStatsRecordingEnabled } from '@/features/stats/hooks/usePlayerStatsRecordingEnabled';
|
import { usePlayerStatsRecordingEnabled } from '@/features/stats/hooks/usePlayerStatsRecordingEnabled';
|
||||||
import { useEnrichmentPrimaryLabel } from '@/hooks/useEnrichmentPrimaryLabel';
|
import { useEnrichmentPrimaryLabel } from '@/hooks/useEnrichmentPrimaryLabel';
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ import {
|
|||||||
ALBUM_YEAR_FILTER_DEBOUNCE_MS,
|
ALBUM_YEAR_FILTER_DEBOUNCE_MS,
|
||||||
resolveAlbumYearBounds,
|
resolveAlbumYearBounds,
|
||||||
} from '../utils/library/albumYearFilter';
|
} from '../utils/library/albumYearFilter';
|
||||||
import { loadOfflineAlbumBrowseInitial } from '../utils/offline/offlineAlbumBrowseCatalog';
|
import { loadOfflineAlbumBrowseInitial } from '@/features/offline';
|
||||||
import { useOfflineBrowseReloadToken } from './useOfflineBrowseReloadToken';
|
import { useOfflineBrowseReloadToken } from '@/features/offline';
|
||||||
import {
|
import {
|
||||||
fetchAlbumBrowseCatalogChunk,
|
fetchAlbumBrowseCatalogChunk,
|
||||||
mergeAlbumCatalogChunk,
|
mergeAlbumCatalogChunk,
|
||||||
} from '../utils/library/albumBrowseCatalogChunk';
|
} from '../utils/library/albumBrowseCatalogChunk';
|
||||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { useClientSliceInfiniteScroll } from './useClientSliceInfiniteScroll';
|
import { useClientSliceInfiniteScroll } from './useClientSliceInfiniteScroll';
|
||||||
import { useDebouncedValue } from './useDebouncedValue';
|
import { useDebouncedValue } from './useDebouncedValue';
|
||||||
import { useInpageScrollSentinel } from './useInpageScrollSentinel';
|
import { useInpageScrollSentinel } from './useInpageScrollSentinel';
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import { useAuthStore } from '../store/authStore';
|
|||||||
import {
|
import {
|
||||||
loadAlbumFromLibraryIndex,
|
loadAlbumFromLibraryIndex,
|
||||||
loadArtistFromLibraryIndex,
|
loadArtistFromLibraryIndex,
|
||||||
} from '../utils/offline/offlineLibraryIndexLoad';
|
} from '@/features/offline';
|
||||||
import {
|
import {
|
||||||
resolveAlbum,
|
resolveAlbum,
|
||||||
resolveArtist,
|
resolveArtist,
|
||||||
type ResolvedAlbum,
|
type ResolvedAlbum,
|
||||||
} from '../utils/offline/offlineMediaResolve';
|
} from '@/features/offline';
|
||||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import {
|
import {
|
||||||
loadArtistFromLocalPlayback,
|
loadArtistFromLocalPlayback,
|
||||||
offlineLocalBrowseEnabled,
|
offlineLocalBrowseEnabled,
|
||||||
} from '../utils/offline/offlineLocalBrowse';
|
} from '@/features/offline';
|
||||||
import { readDetailServerId } from '../utils/navigation/detailServerScope';
|
import { readDetailServerId } from '../utils/navigation/detailServerScope';
|
||||||
import { libraryIsReady } from '../utils/library/libraryReady';
|
import { libraryIsReady } from '../utils/library/libraryReady';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline';
|
||||||
import { useAlbumOfflineState } from './useAlbumOfflineState';
|
import { useAlbumOfflineState } from './useAlbumOfflineState';
|
||||||
|
|
||||||
describe('useAlbumOfflineState', () => {
|
describe('useAlbumOfflineState', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline';
|
||||||
import { isOfflinePinComplete } from '../utils/offline/offlineLibraryHelpers';
|
import { isOfflinePinComplete } from '@/features/offline';
|
||||||
|
|
||||||
export type AlbumOfflineStatus = 'none' | 'queued' | 'downloading' | 'cached';
|
export type AlbumOfflineStatus = 'none' | 'queued' | 'downloading' | 'cached';
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import type {
|
|||||||
} from '../api/subsonicTypes';
|
} from '../api/subsonicTypes';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useConnectionStatus } from './useConnectionStatus';
|
import { useConnectionStatus } from './useConnectionStatus';
|
||||||
import { loadArtistFromLibraryIndex } from '../utils/offline/offlineLibraryIndexLoad';
|
import { loadArtistFromLibraryIndex } from '@/features/offline';
|
||||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '../utils/offline/offlineLocalBrowse';
|
import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline';
|
||||||
import { readDetailServerId } from '../utils/navigation/detailServerScope';
|
import { readDetailServerId } from '../utils/navigation/detailServerScope';
|
||||||
import { runLocalArtistLosslessBrowse } from '../utils/library/browseTextSearch';
|
import { runLocalArtistLosslessBrowse } from '../utils/library/browseTextSearch';
|
||||||
import { isLosslessSuffix } from '../utils/library/losslessFormats';
|
import { isLosslessSuffix } from '../utils/library/losslessFormats';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline';
|
||||||
import { useArtistOfflineState } from './useArtistOfflineState';
|
import { useArtistOfflineState } from './useArtistOfflineState';
|
||||||
|
|
||||||
describe('useArtistOfflineState', () => {
|
describe('useArtistOfflineState', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
import { useLocalPlaybackStore } from '../store/localPlaybackStore';
|
||||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
import { useOfflineJobStore } from '@/features/offline';
|
||||||
import { isOfflinePinComplete } from '../utils/offline/offlineLibraryHelpers';
|
import { isOfflinePinComplete } from '@/features/offline';
|
||||||
|
|
||||||
export type ArtistOfflineStatus = 'none' | 'queued' | 'downloading' | 'cached';
|
export type ArtistOfflineStatus = 'none' | 'queued' | 'downloading' | 'cached';
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import {
|
|||||||
fetchLocalArtistCatalogChunk,
|
fetchLocalArtistCatalogChunk,
|
||||||
fetchNetworkStarredArtists,
|
fetchNetworkStarredArtists,
|
||||||
} from '../utils/library/browseTextSearch';
|
} from '../utils/library/browseTextSearch';
|
||||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { useOfflineBrowseReloadToken } from './useOfflineBrowseReloadToken';
|
import { useOfflineBrowseReloadToken } from '@/features/offline';
|
||||||
import {
|
import {
|
||||||
fetchOfflineLocalArtistCatalogChunk,
|
fetchOfflineLocalArtistCatalogChunk,
|
||||||
fetchOfflineLocalStarredArtists,
|
fetchOfflineLocalStarredArtists,
|
||||||
offlineLocalBrowseEnabled,
|
offlineLocalBrowseEnabled,
|
||||||
} from '../utils/offline/offlineLocalBrowse';
|
} from '@/features/offline';
|
||||||
|
|
||||||
/** Local-index artist catalog buffer grows by this many rows per background SQL chunk. */
|
/** Local-index artist catalog buffer grows by this many rows per background SQL chunk. */
|
||||||
export const ARTIST_CATALOG_CHUNK_SIZE = 200;
|
export const ARTIST_CATALOG_CHUNK_SIZE = 200;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
runLocalBrowseAlbums,
|
runLocalBrowseAlbums,
|
||||||
runNetworkBrowseAlbums,
|
runNetworkBrowseAlbums,
|
||||||
} from '../utils/library/browseTextSearch';
|
} from '../utils/library/browseTextSearch';
|
||||||
import { useOfflineBrowseContext } from './useOfflineBrowseContext';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '../utils/offline/offlineLocalBrowse';
|
import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '@/features/offline';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounced album title search with local-vs-network race when the
|
* Debounced album title search with local-vs-network race when the
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user