diff --git a/.github/frontend-hot-path-files.txt b/.github/frontend-hot-path-files.txt index c3bddd43..51698e1d 100644 --- a/.github/frontend-hot-path-files.txt +++ b/.github/frontend-hot-path-files.txt @@ -24,19 +24,19 @@ # - src/api/subsonic.ts (13 % — F3 covered the URL-builder + parser surface; async API endpoints need axios mocking, deferred) # ── utils (already at or above threshold) ──────────────────────────── -src/utils/coverArtRegisteredSizes.ts -src/utils/serverDisplayName.ts -src/utils/serverMagicString.ts -src/utils/shareLink.ts -src/utils/dynamicColors.ts -src/utils/resolvePlaybackUrl.ts -src/utils/copyEntityShareLink.ts +src/utils/cover/coverArtRegisteredSizes.ts +src/utils/server/serverDisplayName.ts +src/utils/server/serverMagicString.ts +src/utils/share/shareLink.ts +src/utils/ui/dynamicColors.ts +src/utils/playback/resolvePlaybackUrl.ts +src/utils/share/copyEntityShareLink.ts # ── M0: pure helpers extracted from playerStore.ts (2026-05-12) ────── -src/utils/shuffleArray.ts -src/utils/resolveReplayGainDb.ts -src/utils/songToTrack.ts -src/utils/buildInfiniteQueueCandidates.ts +src/utils/playback/shuffleArray.ts +src/utils/audio/resolveReplayGainDb.ts +src/utils/playback/songToTrack.ts +src/utils/playback/buildInfiniteQueueCandidates.ts # ── Phase B.1: pre-React bootstrap + window-kind detector (2026-05-12) ── src/app/windowKind.ts diff --git a/src/api/subsonic.ts b/src/api/subsonic.ts index 16bd33ad..8d673f73 100644 --- a/src/api/subsonic.ts +++ b/src/api/subsonic.ts @@ -5,7 +5,7 @@ import { isNavidromeAudiomuseSoftwareEligible, type InstantMixProbeResult, type SubsonicServerIdentity, -} from '../utils/subsonicServerIdentity'; +} from '../utils/server/subsonicServerIdentity'; import { SUBSONIC_CLIENT, api, diff --git a/src/api/subsonicTypes.ts b/src/api/subsonicTypes.ts index cefd20ba..1a91e6fa 100644 --- a/src/api/subsonicTypes.ts +++ b/src/api/subsonicTypes.ts @@ -1,4 +1,4 @@ -import type { SubsonicServerIdentity } from '../utils/subsonicServerIdentity'; +import type { SubsonicServerIdentity } from '../utils/server/subsonicServerIdentity'; export interface SubsonicAlbum { id: string; diff --git a/src/app/AppShell.tsx b/src/app/AppShell.tsx index ab943760..58d1fff0 100644 --- a/src/app/AppShell.tsx +++ b/src/app/AppShell.tsx @@ -50,11 +50,11 @@ import { usePlayerStore } from '../store/playerStore'; import { useThemeStore } from '../store/themeStore'; import { useFontStore } from '../store/fontStore'; import { useEqStore } from '../store/eqStore'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { persistSidebarCollapsed, readInitialSidebarCollapsed, -} from '../utils/appShellHelpers'; +} from '../utils/componentHelpers/appShellHelpers'; /** * The main webview's persistent layout: titlebar (Linux only) + sidebar + diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index e160bf6a..3439c279 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -2,7 +2,7 @@ import { initAudioListeners } from '../store/initAudioListeners'; import { lazy, Suspense, useEffect, useState } from 'react'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { invoke } from '@tauri-apps/api/core'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { WindowVisibilityProvider } from '../hooks/useWindowVisibility'; import { DragDropProvider } from '../contexts/DragDropContext'; import PasteClipboardHandler from '../components/PasteClipboardHandler'; @@ -69,7 +69,7 @@ export default function MainApp() { const handleExport = async (since: number) => { setExportPickerOpen(false); try { - const { exportNewAlbumsImage } = await import('../utils/exportNewAlbums'); + const { exportNewAlbumsImage } = await import('../utils/export/exportNewAlbums'); const result = await exportNewAlbumsImage(since); if (result) { const files = result.paths.length > 1 ? ` (${result.paths.length} Dateien)` : ''; diff --git a/src/app/MiniPlayerApp.test.tsx b/src/app/MiniPlayerApp.test.tsx index aeb30ef0..e4b71f49 100644 --- a/src/app/MiniPlayerApp.test.tsx +++ b/src/app/MiniPlayerApp.test.tsx @@ -22,7 +22,7 @@ vi.mock('../store/fontStore', () => ({ vi.mock('../store/keybindingsStore', () => ({ useKeybindingsStore: { persist: { rehydrate: keybindingsRehydrate } }, })); -vi.mock('../utils/perfFlags', () => ({ +vi.mock('../utils/perf/perfFlags', () => ({ usePerfProbeFlags: () => ({ disableTooltipPortal: true }), })); vi.mock('../i18n', () => ({ diff --git a/src/app/MiniPlayerApp.tsx b/src/app/MiniPlayerApp.tsx index 8f10c75f..3a91cc63 100644 --- a/src/app/MiniPlayerApp.tsx +++ b/src/app/MiniPlayerApp.tsx @@ -7,7 +7,7 @@ import FpsOverlay from '../components/FpsOverlay'; import { useThemeStore } from '../store/themeStore'; import { useFontStore } from '../store/fontStore'; import { useKeybindingsStore } from '../store/keybindingsStore'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import i18n from '../i18n'; /** diff --git a/src/components/AlbumCard.tsx b/src/components/AlbumCard.tsx index e75481f0..6578d635 100644 --- a/src/components/AlbumCard.tsx +++ b/src/components/AlbumCard.tsx @@ -1,7 +1,7 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { getAlbum } from '../api/subsonicLibrary'; import type { SubsonicAlbum } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { memo, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react'; @@ -10,7 +10,7 @@ import { usePlayerStore } from '../store/playerStore'; import { useOfflineStore } from '../store/offlineStore'; import { useAuthStore } from '../store/authStore'; import CachedImage from './CachedImage'; -import { playAlbum } from '../utils/playAlbum'; +import { playAlbum } from '../utils/playback/playAlbum'; import { useDragDrop } from '../contexts/DragDropContext'; import { isAlbumRecentlyAdded } from '../utils/albumRecency'; diff --git a/src/components/AlbumHeader.tsx b/src/components/AlbumHeader.tsx index 9e76b407..e6ee9a97 100644 --- a/src/components/AlbumHeader.tsx +++ b/src/components/AlbumHeader.tsx @@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next'; import { useIsMobile } from '../hooks/useIsMobile'; import { useThemeStore } from '../store/themeStore'; import StarRating from './StarRating'; -import { copyEntityShareLink } from '../utils/copyEntityShareLink'; -import { showToast } from '../utils/toast'; +import { copyEntityShareLink } from '../utils/share/copyEntityShareLink'; +import { showToast } from '../utils/ui/toast'; import { isAlbumRecentlyAdded } from '../utils/albumRecency'; function formatDuration(seconds: number): string { diff --git a/src/components/AlbumRow.tsx b/src/components/AlbumRow.tsx index 417991f2..3642bd19 100644 --- a/src/components/AlbumRow.tsx +++ b/src/components/AlbumRow.tsx @@ -4,7 +4,7 @@ import AlbumCard from './AlbumCard'; import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react'; import { NavLink, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { dedupeById } from '../utils/dedupeById'; interface Props { diff --git a/src/components/AlbumTrackList.tsx b/src/components/AlbumTrackList.tsx index ffe1d876..e5fefe7f 100644 --- a/src/components/AlbumTrackList.tsx +++ b/src/components/AlbumTrackList.tsx @@ -9,14 +9,14 @@ import { useSelectionStore } from '../store/selectionStore'; import { COLUMNS, type SortKey, -} from '../utils/albumTrackListHelpers'; +} from '../utils/componentHelpers/albumTrackListHelpers'; import { useAlbumTrackListSelection } from '../hooks/useAlbumTrackListSelection'; import { TrackRow } from './albumTrackList/TrackRow'; import { AlbumTrackListMobile } from './albumTrackList/AlbumTrackListMobile'; import { TracklistColumnPicker } from './albumTrackList/TracklistColumnPicker'; import { TracklistHeaderRow } from './albumTrackList/TracklistHeaderRow'; -export type { SortKey } from '../utils/albumTrackListHelpers'; +export type { SortKey } from '../utils/componentHelpers/albumTrackListHelpers'; interface AlbumTrackListProps { songs: SubsonicSong[]; diff --git a/src/components/AppShellQueueResizerSeam.tsx b/src/components/AppShellQueueResizerSeam.tsx index ba642c0c..62aeefbb 100644 --- a/src/components/AppShellQueueResizerSeam.tsx +++ b/src/components/AppShellQueueResizerSeam.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type { TFunction } from 'i18next'; import { PanelRight, PanelRightClose } from 'lucide-react'; -import { shouldSuppressQueueResizerMouseDown } from '../utils/appShellHelpers'; +import { shouldSuppressQueueResizerMouseDown } from '../utils/componentHelpers/appShellHelpers'; interface Props { isQueueVisible: boolean; diff --git a/src/components/AppUpdater.tsx b/src/components/AppUpdater.tsx index b0f111f0..c0705d4b 100644 --- a/src/components/AppUpdater.tsx +++ b/src/components/AppUpdater.tsx @@ -3,7 +3,7 @@ import { open } from '@tauri-apps/plugin-shell'; import { ArrowUpCircle, CheckCircle2, ChevronDown, Download, FolderOpen, RefreshCw, ShieldCheck, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { version as currentVersion } from '../../package.json'; -import { fmtBytes } from '../utils/appUpdaterHelpers'; +import { fmtBytes } from '../utils/componentHelpers/appUpdaterHelpers'; import { useAppUpdater } from '../hooks/useAppUpdater'; import Changelog from './appUpdater/Changelog'; diff --git a/src/components/BecauseYouLikeRail.tsx b/src/components/BecauseYouLikeRail.tsx index 93b2cd2f..d0c4527f 100644 --- a/src/components/BecauseYouLikeRail.tsx +++ b/src/components/BecauseYouLikeRail.tsx @@ -2,7 +2,7 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { getArtist, getArtistInfo } from '../api/subsonicArtists'; import { getAlbum } from '../api/subsonicLibrary'; import type { SubsonicAlbum } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -10,7 +10,7 @@ import { Play, ListPlus, Music } from 'lucide-react'; import CachedImage, { useCachedUrl } from './CachedImage'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; -import { playAlbum } from '../utils/playAlbum'; +import { playAlbum } from '../utils/playback/playAlbum'; import AlbumRow from './AlbumRow'; const ANCHOR_HISTORY_KEY_PREFIX = 'psysonic_because_anchor_history:'; diff --git a/src/components/ChangelogModal.tsx b/src/components/ChangelogModal.tsx index b53a3d76..96ad8296 100644 --- a/src/components/ChangelogModal.tsx +++ b/src/components/ChangelogModal.tsx @@ -5,7 +5,7 @@ import { Sparkles } from 'lucide-react'; import { version } from '../../package.json'; import changelogRaw from '../../CHANGELOG.md?raw'; import { useAuthStore } from '../store/authStore'; -import { findChangelogReleaseEntry } from '../utils/changelogReleaseMatch'; +import { findChangelogReleaseEntry } from '../utils/changelog/changelogReleaseMatch'; function renderInline(text: string): React.ReactNode[] { const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g); diff --git a/src/components/ConnectionIndicator.tsx b/src/components/ConnectionIndicator.tsx index e15395ea..f1b81730 100644 --- a/src/components/ConnectionIndicator.tsx +++ b/src/components/ConnectionIndicator.tsx @@ -6,9 +6,9 @@ import { useNavigate } from 'react-router-dom'; import { Check, ChevronDown } from 'lucide-react'; import { ConnectionStatus } from '../hooks/useConnectionStatus'; import { useAuthStore } from '../store/authStore'; -import { switchActiveServer } from '../utils/switchActiveServer'; -import { showToast } from '../utils/toast'; -import { serverListDisplayLabel } from '../utils/serverDisplayName'; +import { switchActiveServer } from '../utils/server/switchActiveServer'; +import { showToast } from '../utils/ui/toast'; +import { serverListDisplayLabel } from '../utils/server/serverDisplayName'; interface Props { status: ConnectionStatus; diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index bee8e19e..8b44c0de 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -5,14 +5,14 @@ import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; -import type { EntityShareKind } from '../utils/shareLink'; +import type { EntityShareKind } from '../utils/share/shareLink'; import { AddToPlaylistSubmenu } from './contextMenu/AddToPlaylistSubmenu'; import { copyShareLink as copyShareLinkAction, downloadAlbum as downloadAlbumAction, startInstantMix as startInstantMixAction, startRadio as startRadioAction, -} from '../utils/contextMenuActions'; +} from '../utils/componentHelpers/contextMenuActions'; import { useContextMenuKeyboardNav } from '../hooks/useContextMenuKeyboardNav'; import { useContextMenuRating } from '../hooks/useContextMenuRating'; import ContextMenuItems from './contextMenu/ContextMenuItems'; diff --git a/src/components/Equalizer.tsx b/src/components/Equalizer.tsx index 6c3c1e18..c9edb713 100644 --- a/src/components/Equalizer.tsx +++ b/src/components/Equalizer.tsx @@ -4,7 +4,7 @@ import { Save, Trash2, RotateCcw } from 'lucide-react'; import CustomSelect from './CustomSelect'; import { useEqStore, EQ_BANDS, BUILTIN_PRESETS } from '../store/eqStore'; import { useThemeStore } from '../store/themeStore'; -import { drawCurve } from '../utils/eqCurve'; +import { drawCurve } from '../utils/audio/eqCurve'; import VerticalFader from './equalizer/VerticalFader'; import AutoEqSection from './equalizer/AutoEqSection'; diff --git a/src/components/FpsOverlay.tsx b/src/components/FpsOverlay.tsx index 7bb36d61..a742ddca 100644 --- a/src/components/FpsOverlay.tsx +++ b/src/components/FpsOverlay.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; -import { usePerfProbeFlag } from '../utils/perfFlags'; +import { usePerfProbeFlag } from '../utils/perf/perfFlags'; const SAMPLE_MS = 500; diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index f69cef4f..e988c6c3 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -1,20 +1,20 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { getRandomAlbums, getAlbum } from '../api/subsonicLibrary'; import type { SubsonicAlbum } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus } from 'lucide-react'; import CachedImage, { useCachedUrl } from './CachedImage'; import { usePlayerStore } from '../store/playerStore'; import { useTranslation } from 'react-i18next'; -import { playAlbum } from '../utils/playAlbum'; +import { playAlbum } from '../utils/playback/playAlbum'; import { useIsMobile } from '../hooks/useIsMobile'; import { useWindowVisibility } from '../hooks/useWindowVisibility'; import { useAuthStore } from '../store/authStore'; import { useThemeStore } from '../store/themeStore'; -import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mixRatingFilter'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; const INTERVAL_MS = 10000; const HERO_ALBUM_COUNT = 8; diff --git a/src/components/LiveSearch.tsx b/src/components/LiveSearch.tsx index 117df282..45aa98a1 100644 --- a/src/components/LiveSearch.tsx +++ b/src/components/LiveSearch.tsx @@ -1,7 +1,7 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { search } from '../api/subsonicSearch'; import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Search, Disc3, Users, Music, TextSearch } from 'lucide-react'; @@ -9,7 +9,7 @@ import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import CachedImage, { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from './CachedImage'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; function debounce(fn: (q: string) => void, ms: number): (q: string) => void { let timer: ReturnType; diff --git a/src/components/LyricsPane.tsx b/src/components/LyricsPane.tsx index c11e95cc..e93136fa 100644 --- a/src/components/LyricsPane.tsx +++ b/src/components/LyricsPane.tsx @@ -7,7 +7,7 @@ import { useLyrics, type WordLyricsLine } from '../hooks/useLyrics'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import type { Track } from '../store/playerStoreTypes'; -import { EaseScroller, targetForFraction } from '../utils/easeScroll'; +import { EaseScroller, targetForFraction } from '../utils/ui/easeScroll'; import OverlayScrollArea from './OverlayScrollArea'; interface Props { diff --git a/src/components/MarqueeText.tsx b/src/components/MarqueeText.tsx index d199d7d3..4dd47717 100644 --- a/src/components/MarqueeText.tsx +++ b/src/components/MarqueeText.tsx @@ -1,5 +1,5 @@ import React, { useRef, useState, useEffect, useCallback } from 'react'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; interface Props { text: string; diff --git a/src/components/MiniPlayer.tsx b/src/components/MiniPlayer.tsx index 617f68e5..e2fe1b9a 100644 --- a/src/components/MiniPlayer.tsx +++ b/src/components/MiniPlayer.tsx @@ -10,7 +10,7 @@ import { COLLAPSED_SIZE, EXPANDED_SIZE, COLLAPSED_MIN, EXPANDED_MIN, EXPANDED_H_KEY, QUEUE_OPEN_KEY, readStoredExpandedHeight, readQueueOpen, initialSnapshot, -} from '../utils/miniPlayerHelpers'; +} from '../utils/componentHelpers/miniPlayerHelpers'; import { MiniTitlebar } from './miniPlayer/MiniTitlebar'; import { MiniMeta } from './miniPlayer/MiniMeta'; import { MiniControls } from './miniPlayer/MiniControls'; diff --git a/src/components/MobilePlayerView.tsx b/src/components/MobilePlayerView.tsx index 12fc836f..89cadf83 100644 --- a/src/components/MobilePlayerView.tsx +++ b/src/components/MobilePlayerView.tsx @@ -16,7 +16,7 @@ import LyricsPane from './LyricsPane'; import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import PlaybackDelayModal from './PlaybackDelayModal'; import PlaybackScheduleBadge from './PlaybackScheduleBadge'; -import { usePlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; +import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; // ── Color extraction ────────────────────────────────────────────────────────── // Samples a 16×16 canvas to find the most vibrant (highest-saturation, diff --git a/src/components/MobileSearchOverlay.tsx b/src/components/MobileSearchOverlay.tsx index 9f0ce15f..1696a9bf 100644 --- a/src/components/MobileSearchOverlay.tsx +++ b/src/components/MobileSearchOverlay.tsx @@ -1,7 +1,7 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { search } from '../api/subsonicSearch'; import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; @@ -10,7 +10,7 @@ import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import CachedImage, { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from './CachedImage'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; const STORAGE_KEY = 'psysonic_recent_searches'; const MAX_RECENT = 6; diff --git a/src/components/OrbitDiagnosticsPopover.tsx b/src/components/OrbitDiagnosticsPopover.tsx index caf666e1..add58f74 100644 --- a/src/components/OrbitDiagnosticsPopover.tsx +++ b/src/components/OrbitDiagnosticsPopover.tsx @@ -4,7 +4,7 @@ import { Copy, Trash2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useOrbitStore } from '../store/orbitStore'; import { usePlayerStore } from '../store/playerStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { computeOrbitDriftMs } from '../utils/orbit'; import { clearOrbitEvents, diff --git a/src/components/OrbitJoinModal.tsx b/src/components/OrbitJoinModal.tsx index c20a777f..58f3452a 100644 --- a/src/components/OrbitJoinModal.tsx +++ b/src/components/OrbitJoinModal.tsx @@ -9,9 +9,9 @@ import { readOrbitState, joinOrbitSession, } from '../utils/orbit'; -import { switchActiveServer } from '../utils/switchActiveServer'; +import { switchActiveServer } from '../utils/server/switchActiveServer'; import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; interface Props { onClose: () => void; diff --git a/src/components/OrbitSessionBar.tsx b/src/components/OrbitSessionBar.tsx index d4590bde..d2b3014f 100644 --- a/src/components/OrbitSessionBar.tsx +++ b/src/components/OrbitSessionBar.tsx @@ -1,5 +1,5 @@ import { getSong } from '../api/subsonicLibrary'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useEffect, useRef, useState } from 'react'; import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/OrbitSettingsPopover.tsx b/src/components/OrbitSettingsPopover.tsx index 2070052f..0613aa13 100644 --- a/src/components/OrbitSettingsPopover.tsx +++ b/src/components/OrbitSettingsPopover.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { useOrbitStore } from '../store/orbitStore'; import { updateOrbitSettings, triggerOrbitShuffleNow } from '../utils/orbit'; import { ORBIT_DEFAULT_SETTINGS, ORBIT_SHUFFLE_INTERVAL_PRESETS_MIN, type OrbitShuffleIntervalMin } from '../api/orbit'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; interface Props { anchorRef: React.RefObject; diff --git a/src/components/OverlayScrollArea.tsx b/src/components/OverlayScrollArea.tsx index 30ade1a5..ddc2aa9b 100644 --- a/src/components/OverlayScrollArea.tsx +++ b/src/components/OverlayScrollArea.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { computeOverlayScrollbarThumbMeta } from '../utils/overlayScrollbarMetrics'; -import { bindOverlayScrollbarThumbDrag } from '../utils/overlayScrollbarThumb'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { computeOverlayScrollbarThumbMeta } from '../utils/ui/overlayScrollbarMetrics'; +import { bindOverlayScrollbarThumbDrag } from '../utils/ui/overlayScrollbarThumb'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; export type OverlayScrollRailInset = 'none' | 'mini' | 'panel'; diff --git a/src/components/PasteClipboardHandler.tsx b/src/components/PasteClipboardHandler.tsx index 7bd4e7c3..466dd079 100644 --- a/src/components/PasteClipboardHandler.tsx +++ b/src/components/PasteClipboardHandler.tsx @@ -2,10 +2,10 @@ import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; -import { decodeSharePayloadFromText } from '../utils/shareLink'; -import { decodeServerMagicStringFromText } from '../utils/serverMagicString'; -import { applySharePastePayload } from '../utils/applySharePaste'; -import { showToast } from '../utils/toast'; +import { decodeSharePayloadFromText } from '../utils/share/shareLink'; +import { decodeServerMagicStringFromText } from '../utils/server/serverMagicString'; +import { applySharePastePayload } from '../utils/share/applySharePaste'; +import { showToast } from '../utils/ui/toast'; import { parseOrbitShareLink, joinOrbitSession, @@ -13,7 +13,7 @@ import { readOrbitState, OrbitJoinError, } from '../utils/orbit'; -import { switchActiveServer } from '../utils/switchActiveServer'; +import { switchActiveServer } from '../utils/server/switchActiveServer'; import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore'; import ConfirmModal from './ConfirmModal'; diff --git a/src/components/PlaybackDelayModal.tsx b/src/components/PlaybackDelayModal.tsx index a688b9d1..dbc609bc 100644 --- a/src/components/PlaybackDelayModal.tsx +++ b/src/components/PlaybackDelayModal.tsx @@ -6,7 +6,7 @@ import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import type { TFunction } from 'i18next'; -import { formatPlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; +import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; function formatClockTime(ts: number): string { const d = new Date(ts); diff --git a/src/components/PlaybackScheduleBadge.tsx b/src/components/PlaybackScheduleBadge.tsx index 7c9de1ee..f36aecb4 100644 --- a/src/components/PlaybackScheduleBadge.tsx +++ b/src/components/PlaybackScheduleBadge.tsx @@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; -import { formatPlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; +import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; import { useWindowVisibility } from '../hooks/useWindowVisibility'; export interface PlaybackScheduleBadgeProps { diff --git a/src/components/PlayerBar.tsx b/src/components/PlayerBar.tsx index 84260681..3ec6d546 100644 --- a/src/components/PlayerBar.tsx +++ b/src/components/PlayerBar.tsx @@ -25,10 +25,10 @@ import { useRadioMetadata } from '../hooks/useRadioMetadata'; import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import PlaybackDelayModal from './PlaybackDelayModal'; import PlaybackScheduleBadge from './PlaybackScheduleBadge'; -import { usePlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; +import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; import { usePreviewStore } from '../store/previewStore'; -import { usePerfProbeFlags } from '../utils/perfFlags'; -import { formatTime } from '../utils/playerBarHelpers'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; +import { formatTime } from '../utils/componentHelpers/playerBarHelpers'; import { PlaybackTime, RemainingTime } from './playerBar/PlaybackClock'; import { PlayerTrackInfo } from './playerBar/PlayerTrackInfo'; import { PlayerTransportControls } from './playerBar/PlayerTransportControls'; diff --git a/src/components/QueuePanel.tsx b/src/components/QueuePanel.tsx index c4fb38ae..ce605214 100644 --- a/src/components/QueuePanel.tsx +++ b/src/components/QueuePanel.tsx @@ -1,6 +1,6 @@ import { getPlaylist, updatePlaylist } from '../api/subsonicPlaylists'; import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import type { Track } from '../store/playerStoreTypes'; import { useState, useRef, useMemo } from 'react'; import { usePlayerStore } from '../store/playerStore'; @@ -13,9 +13,9 @@ import { useCachedUrl } from './CachedImage'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '../store/authStore'; -import { encodeSharePayload } from '../utils/shareLink'; -import { copyTextToClipboard } from '../utils/serverMagicString'; -import { showToast } from '../utils/toast'; +import { encodeSharePayload } from '../utils/share/shareLink'; +import { copyTextToClipboard } from '../utils/server/serverMagicString'; +import { showToast } from '../utils/ui/toast'; import { useThemeStore } from '../store/themeStore'; import { useLyricsStore } from '../store/lyricsStore'; import LyricsPane from './LyricsPane'; @@ -26,7 +26,7 @@ import { useQueueToolbarStore } from '../store/queueToolbarStore'; import { DurationMode, formatTime, -} from '../utils/queuePanelHelpers'; +} from '../utils/componentHelpers/queuePanelHelpers'; import { SavePlaylistModal } from './queuePanel/SavePlaylistModal'; import { LoadPlaylistModal } from './queuePanel/LoadPlaylistModal'; import { QueueHeader } from './queuePanel/QueueHeader'; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 0a978532..b3523107 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -16,9 +16,9 @@ import OverlayScrollArea from './OverlayScrollArea'; import { getLibraryItemsForReorder, getSystemItemsForReorder, -} from '../utils/sidebarNavReorder'; +} from '../utils/componentHelpers/sidebarNavReorder'; import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { useSidebarNewReleasesUnread } from '../hooks/useSidebarNewReleasesUnread'; import { useSidebarNavDnd } from '../hooks/useSidebarNavDnd'; import { useSidebarLibraryDropdown } from '../hooks/useSidebarLibraryDropdown'; diff --git a/src/components/SongCard.tsx b/src/components/SongCard.tsx index 91e10a0b..e0a96c2c 100644 --- a/src/components/SongCard.tsx +++ b/src/components/SongCard.tsx @@ -1,13 +1,13 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { memo, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus, Star } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { usePlayerStore } from '../store/playerStore'; import CachedImage from './CachedImage'; -import { enqueueAndPlay } from '../utils/playSong'; +import { enqueueAndPlay } from '../utils/playback/playSong'; import { useDragDrop } from '../contexts/DragDropContext'; import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior'; diff --git a/src/components/SongInfoModal.tsx b/src/components/SongInfoModal.tsx index be797f16..0ed4e65b 100644 --- a/src/components/SongInfoModal.tsx +++ b/src/components/SongInfoModal.tsx @@ -8,8 +8,8 @@ import { useShallow } from 'zustand/react/shallow'; import { ndGetSongPath } from '../api/navidromeAdmin'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; -import { copyTextToClipboard } from '../utils/serverMagicString'; -import { showToast } from '../utils/toast'; +import { copyTextToClipboard } from '../utils/server/serverMagicString'; +import { showToast } from '../utils/ui/toast'; function formatDuration(s: number): string { const m = Math.floor(s / 60); diff --git a/src/components/SongRail.tsx b/src/components/SongRail.tsx index ff0e5e1e..d29d06b4 100644 --- a/src/components/SongRail.tsx +++ b/src/components/SongRail.tsx @@ -2,7 +2,7 @@ import type { SubsonicSong } from '../api/subsonicTypes'; import React, { useRef, useState, useEffect, useMemo } from 'react'; import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react'; import SongCard from './SongCard'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { dedupeById } from '../utils/dedupeById'; interface Props { diff --git a/src/components/SongRow.tsx b/src/components/SongRow.tsx index 8a140b6b..f8c9ec55 100644 --- a/src/components/SongRow.tsx +++ b/src/components/SongRow.tsx @@ -1,11 +1,11 @@ import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { memo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { usePlayerStore } from '../store/playerStore'; -import { enqueueAndPlay } from '../utils/playSong'; +import { enqueueAndPlay } from '../utils/playback/playSong'; import { useDragDrop } from '../contexts/DragDropContext'; import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior'; diff --git a/src/components/StatsExportModal.tsx b/src/components/StatsExportModal.tsx index 8a1e02b6..a447c7b7 100644 --- a/src/components/StatsExportModal.tsx +++ b/src/components/StatsExportModal.tsx @@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next'; import { X } from 'lucide-react'; import { save } from '@tauri-apps/plugin-dialog'; import { writeFile } from '@tauri-apps/plugin-fs'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { exportAlbumCardBlob, renderAlbumCardCanvas, ExportFormat, ExportGridSize, -} from '../utils/exportAlbumCard'; +} from '../utils/export/exportAlbumCard'; interface Props { open: boolean; diff --git a/src/components/WaveformSeek.tsx b/src/components/WaveformSeek.tsx index 355906e2..3542a445 100644 --- a/src/components/WaveformSeek.tsx +++ b/src/components/WaveformSeek.tsx @@ -8,8 +8,8 @@ import { STATIC_REDRAW_FORCE_MS, STATIC_REDRAW_MIN_MS, fmt, invalidateColorCache, isBarQuantizedSeekStyle, makeAnimState, quantizeProgressByBars, -} from '../utils/waveformSeekHelpers'; -import { drawSeekbar } from '../utils/waveformSeekRenderers'; +} from '../utils/waveform/waveformSeekHelpers'; +import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers'; import { useWaveformHeights } from '../hooks/useWaveformHeights'; import { useWaveformInterpolation } from '../hooks/useWaveformInterpolation'; diff --git a/src/components/WaveformSeekPreview.tsx b/src/components/WaveformSeekPreview.tsx index 7cad9a5a..495af2f6 100644 --- a/src/components/WaveformSeekPreview.tsx +++ b/src/components/WaveformSeekPreview.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react'; import type { SeekbarStyle } from '../store/authStoreTypes'; -import { makeAnimState, makeHeights } from '../utils/waveformSeekHelpers'; -import { drawSeekbar } from '../utils/waveformSeekRenderers'; +import { makeAnimState, makeHeights } from '../utils/waveform/waveformSeekHelpers'; +import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers'; interface Props { style: SeekbarStyle; diff --git a/src/components/albumTrackList/AlbumTrackListMobile.tsx b/src/components/albumTrackList/AlbumTrackListMobile.tsx index 123e485d..c57eb44c 100644 --- a/src/components/albumTrackList/AlbumTrackListMobile.tsx +++ b/src/components/albumTrackList/AlbumTrackListMobile.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { AudioLines } from 'lucide-react'; import type { SubsonicSong } from '../../api/subsonicTypes'; import type { Track } from '../../store/playerStoreTypes'; -import { songToTrack } from '../../utils/songToTrack'; -import { formatDuration } from '../../utils/albumTrackListHelpers'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { formatDuration } from '../../utils/componentHelpers/albumTrackListHelpers'; interface Props { discNums: number[]; diff --git a/src/components/albumTrackList/TrackRow.tsx b/src/components/albumTrackList/TrackRow.tsx index 0c4b898a..9d4bcb7d 100644 --- a/src/components/albumTrackList/TrackRow.tsx +++ b/src/components/albumTrackList/TrackRow.tsx @@ -5,12 +5,12 @@ import { useNavigate } from 'react-router-dom'; import type { ColDef } from '../../utils/useTracklistColumns'; import type { SubsonicSong } from '../../api/subsonicTypes'; import type { Track } from '../../store/playerStoreTypes'; -import { songToTrack } from '../../utils/songToTrack'; +import { songToTrack } from '../../utils/playback/songToTrack'; import { useSelectionStore } from '../../store/selectionStore'; import { useThemeStore } from '../../store/themeStore'; import { usePreviewStore } from '../../store/previewStore'; import StarRating from '../StarRating'; -import { codecLabel, formatDuration, type ColKey } from '../../utils/albumTrackListHelpers'; +import { codecLabel, formatDuration, type ColKey } from '../../utils/componentHelpers/albumTrackListHelpers'; type ContextMenuFn = ( x: number, diff --git a/src/components/albumTrackList/TracklistColumnPicker.tsx b/src/components/albumTrackList/TracklistColumnPicker.tsx index c63293a2..dd7a0a04 100644 --- a/src/components/albumTrackList/TracklistColumnPicker.tsx +++ b/src/components/albumTrackList/TracklistColumnPicker.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Check, ChevronDown, RotateCcw } from 'lucide-react'; import type { TFunction } from 'i18next'; -import { COLUMNS } from '../../utils/albumTrackListHelpers'; +import { COLUMNS } from '../../utils/componentHelpers/albumTrackListHelpers'; interface Props { pickerRef: React.RefObject; diff --git a/src/components/albumTrackList/TracklistHeaderRow.tsx b/src/components/albumTrackList/TracklistHeaderRow.tsx index 0c19c829..fa744212 100644 --- a/src/components/albumTrackList/TracklistHeaderRow.tsx +++ b/src/components/albumTrackList/TracklistHeaderRow.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type { TFunction } from 'i18next'; import type { ColDef } from '../../utils/useTracklistColumns'; -import { CENTERED_COLS, isSortable, type ColKey, type SortKey } from '../../utils/albumTrackListHelpers'; +import { CENTERED_COLS, isSortable, type ColKey, type SortKey } from '../../utils/componentHelpers/albumTrackListHelpers'; interface Props { visibleCols: readonly ColDef[]; diff --git a/src/components/artistDetail/ArtistDetailHero.tsx b/src/components/artistDetail/ArtistDetailHero.tsx index ce61f529..b32a1cd5 100644 --- a/src/components/artistDetail/ArtistDetailHero.tsx +++ b/src/components/artistDetail/ArtistDetailHero.tsx @@ -10,7 +10,7 @@ import { useOfflineStore } from '../../store/offlineStore'; import { useOfflineJobStore } from '../../store/offlineJobStore'; import { useAuthStore } from '../../store/authStore'; import { useIsMobile } from '../../hooks/useIsMobile'; -import { extractCoverColors } from '../../utils/dynamicColors'; +import { extractCoverColors } from '../../utils/ui/dynamicColors'; import CachedImage from '../CachedImage'; import CoverLightbox from '../CoverLightbox'; import LastfmIcon from '../LastfmIcon'; diff --git a/src/components/artistDetail/ArtistDetailTopTracks.tsx b/src/components/artistDetail/ArtistDetailTopTracks.tsx index 06421a4b..ad5a4c50 100644 --- a/src/components/artistDetail/ArtistDetailTopTracks.tsx +++ b/src/components/artistDetail/ArtistDetailTopTracks.tsx @@ -5,8 +5,8 @@ import type { SubsonicSong } from '../../api/subsonicTypes'; import { usePlayerStore } from '../../store/playerStore'; import { usePreviewStore } from '../../store/previewStore'; import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior'; -import { songToTrack } from '../../utils/songToTrack'; -import { formatDuration } from '../../utils/artistDetailHelpers'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { formatDuration } from '../../utils/componentHelpers/artistDetailHelpers'; import ArtistSuggestionTrackCover from './ArtistSuggestionTrackCover'; interface Props { diff --git a/src/components/artists/ArtistAvatars.tsx b/src/components/artists/ArtistAvatars.tsx index 166ac759..db2d93d0 100644 --- a/src/components/artists/ArtistAvatars.tsx +++ b/src/components/artists/ArtistAvatars.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl'; import type { SubsonicArtist } from '../../api/subsonicTypes'; import CachedImage from '../CachedImage'; -import { nameColor, nameInitial } from '../../utils/artistsHelpers'; +import { nameColor, nameInitial } from '../../utils/componentHelpers/artistsHelpers'; interface AvatarProps { artist: SubsonicArtist; diff --git a/src/components/artists/ArtistsListView.tsx b/src/components/artists/ArtistsListView.tsx index 6999450c..892c17a2 100644 --- a/src/components/artists/ArtistsListView.tsx +++ b/src/components/artists/ArtistsListView.tsx @@ -4,7 +4,7 @@ import type { Virtualizer } from '@tanstack/react-virtual'; import type { TFunction } from 'i18next'; import type { SubsonicArtist } from '../../api/subsonicTypes'; import type { PlayerState } from '../../store/playerStoreTypes'; -import type { ArtistListFlatRow } from '../../utils/artistsHelpers'; +import type { ArtistListFlatRow } from '../../utils/componentHelpers/artistsHelpers'; import { ArtistRowAvatar } from './ArtistAvatars'; interface RowProps { diff --git a/src/components/contextMenu/AddToPlaylistSubmenu.tsx b/src/components/contextMenu/AddToPlaylistSubmenu.tsx index df447538..a26897a1 100644 --- a/src/components/contextMenu/AddToPlaylistSubmenu.tsx +++ b/src/components/contextMenu/AddToPlaylistSubmenu.tsx @@ -4,11 +4,11 @@ import { ListMusic, Plus } from 'lucide-react'; import { getPlaylist, updatePlaylist } from '../../api/subsonicPlaylists'; import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import { usePlaylistStore } from '../../store/playlistStore'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; import { confirmAddAllDuplicates, isSmartPlaylistName, -} from '../../utils/contextMenuHelpers'; +} from '../../utils/componentHelpers/contextMenuHelpers'; interface Props { songIds: string[]; diff --git a/src/components/contextMenu/AlbumContextItems.tsx b/src/components/contextMenu/AlbumContextItems.tsx index 49e9db4d..4d8995b0 100644 --- a/src/components/contextMenu/AlbumContextItems.tsx +++ b/src/components/contextMenu/AlbumContextItems.tsx @@ -5,7 +5,7 @@ import { getAlbum } from '../../api/subsonicLibrary'; import { star, unstar } from '../../api/subsonicStarRating'; import type { SubsonicAlbum } from '../../api/subsonicTypes'; import { useAuthStore } from '../../store/authStore'; -import { songToTrack } from '../../utils/songToTrack'; +import { songToTrack } from '../../utils/playback/songToTrack'; import StarRating from '../StarRating'; import { AlbumToPlaylistSubmenu } from './AlbumArtistToPlaylistSubmenu'; import { MultiAlbumToPlaylistSubmenu } from './MultiAlbumToPlaylistSubmenu'; diff --git a/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx b/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx index 095d5b72..448cbcde 100644 --- a/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx +++ b/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx @@ -4,11 +4,11 @@ import { ListMusic, Plus } from 'lucide-react'; import { getAlbum } from '../../api/subsonicLibrary'; import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import { usePlaylistStore } from '../../store/playlistStore'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; import { confirmAddAllDuplicates, isSmartPlaylistName, -} from '../../utils/contextMenuHelpers'; +} from '../../utils/componentHelpers/contextMenuHelpers'; interface Props { albumIds: string[]; diff --git a/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx b/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx index e70060d0..3342cb4f 100644 --- a/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx +++ b/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx @@ -6,11 +6,11 @@ import { getArtist } from '../../api/subsonicArtists'; import { getPlaylists } from '../../api/subsonicPlaylists'; import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import { usePlaylistStore } from '../../store/playlistStore'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; import { confirmAddAllDuplicates, isSmartPlaylistName, -} from '../../utils/contextMenuHelpers'; +} from '../../utils/componentHelpers/contextMenuHelpers'; interface Props { artistIds: string[]; diff --git a/src/components/contextMenu/PlaylistContextItems.tsx b/src/components/contextMenu/PlaylistContextItems.tsx index 6ce7f7b3..bc172779 100644 --- a/src/components/contextMenu/PlaylistContextItems.tsx +++ b/src/components/contextMenu/PlaylistContextItems.tsx @@ -46,7 +46,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
handleAction(async () => { - const { showToast } = await import('../../utils/toast'); + const { showToast } = await import('../../utils/ui/toast'); const { deletePlaylist } = await import('../../api/subsonicPlaylists'); const { removeId } = usePlaylistStore.getState(); try { @@ -89,7 +89,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) { )}
handleAction(async () => { - const { showToast } = await import('../../utils/toast'); + const { showToast } = await import('../../utils/ui/toast'); const { deletePlaylist } = await import('../../api/subsonicPlaylists'); const { removeId } = usePlaylistStore.getState(); const deletedIds: string[] = []; diff --git a/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx b/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx index 5d84f230..5877f71c 100644 --- a/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx +++ b/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 're import { useTranslation } from 'react-i18next'; import { ListMusic, Plus } from 'lucide-react'; import { usePlaylistStore } from '../../store/playlistStore'; -import { showToast } from '../../utils/toast'; -import { isSmartPlaylistName } from '../../utils/contextMenuHelpers'; +import { showToast } from '../../utils/ui/toast'; +import { isSmartPlaylistName } from '../../utils/componentHelpers/contextMenuHelpers'; interface SingleProps { playlist: { id: string; name: string }; diff --git a/src/components/contextMenu/SongContextItems.tsx b/src/components/contextMenu/SongContextItems.tsx index c8bb0937..dcac8382 100644 --- a/src/components/contextMenu/SongContextItems.tsx +++ b/src/components/contextMenu/SongContextItems.tsx @@ -7,8 +7,8 @@ import { lastfmLoveTrack, lastfmUnloveTrack } from '../../api/lastfm'; import type { Track } from '../../store/playerStoreTypes'; import { useAuthStore } from '../../store/authStore'; import { usePlaylistStore } from '../../store/playlistStore'; -import { songToTrack } from '../../utils/songToTrack'; -import { showToast } from '../../utils/toast'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { showToast } from '../../utils/ui/toast'; import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '../../utils/orbit'; import LastfmIcon from '../LastfmIcon'; import StarRating from '../StarRating'; @@ -166,7 +166,7 @@ export default function SongContextItems(props: ContextMenuItemsProps) { {playlistId && playlistSongIndex !== undefined && (
handleAction(async () => { const { getPlaylist, updatePlaylist } = await import('../../api/subsonicPlaylists'); - const { showToast } = await import('../../utils/toast'); + const { showToast } = await import('../../utils/ui/toast'); const touchPlaylist = usePlaylistStore.getState().touchPlaylist; try { const { songs } = await getPlaylist(playlistId); diff --git a/src/components/contextMenu/contextMenuItemTypes.ts b/src/components/contextMenu/contextMenuItemTypes.ts index 60442aca..88865b9f 100644 --- a/src/components/contextMenu/contextMenuItemTypes.ts +++ b/src/components/contextMenu/contextMenuItemTypes.ts @@ -1,7 +1,7 @@ import type React from 'react'; import type { SubsonicAlbum, SubsonicArtist } from '../../api/subsonicTypes'; import type { Track } from '../../store/playerStoreTypes'; -import type { EntityShareKind } from '../../utils/shareLink'; +import type { EntityShareKind } from '../../utils/share/shareLink'; export type RatingKind = 'song' | 'album' | 'artist'; diff --git a/src/components/deviceSync/DeviceSyncBrowserPanel.tsx b/src/components/deviceSync/DeviceSyncBrowserPanel.tsx index 38000ce5..b1003b39 100644 --- a/src/components/deviceSync/DeviceSyncBrowserPanel.tsx +++ b/src/components/deviceSync/DeviceSyncBrowserPanel.tsx @@ -8,7 +8,7 @@ import type { SubsonicAlbum, SubsonicArtist, SubsonicPlaylist, } from '../../api/subsonicTypes'; import type { DeviceSyncSource } from '../../store/deviceSyncStore'; -import type { SourceTab } from '../../utils/deviceSyncHelpers'; +import type { SourceTab } from '../../utils/deviceSync/deviceSyncHelpers'; import BrowserRow from './BrowserRow'; interface Props { diff --git a/src/components/deviceSync/DeviceSyncDevicePanel.tsx b/src/components/deviceSync/DeviceSyncDevicePanel.tsx index bb9d4476..d61e3545 100644 --- a/src/components/deviceSync/DeviceSyncDevicePanel.tsx +++ b/src/components/deviceSync/DeviceSyncDevicePanel.tsx @@ -7,7 +7,7 @@ import { } from 'lucide-react'; import { useDeviceSyncJobStore } from '../../store/deviceSyncJobStore'; import type { DeviceSyncSource } from '../../store/deviceSyncStore'; -import type { SyncStatus } from '../../utils/deviceSyncHelpers'; +import type { SyncStatus } from '../../utils/deviceSync/deviceSyncHelpers'; interface Props { sources: DeviceSyncSource[]; diff --git a/src/components/deviceSync/DeviceSyncHeader.tsx b/src/components/deviceSync/DeviceSyncHeader.tsx index 3767ee9f..089c072d 100644 --- a/src/components/deviceSync/DeviceSyncHeader.tsx +++ b/src/components/deviceSync/DeviceSyncHeader.tsx @@ -4,8 +4,8 @@ import { AlertCircle, FolderOpen, HardDriveUpload, RefreshCw, Usb, } from 'lucide-react'; import CustomSelect from '../CustomSelect'; -import type { RemovableDrive } from '../../utils/deviceSyncHelpers'; -import { formatBytes } from '../../utils/deviceSyncHelpers'; +import type { RemovableDrive } from '../../utils/deviceSync/deviceSyncHelpers'; +import { formatBytes } from '../../utils/deviceSync/deviceSyncHelpers'; import type { DeviceSyncSource } from '../../store/deviceSyncStore'; interface Props { diff --git a/src/components/deviceSync/DeviceSyncMigrationModal.tsx b/src/components/deviceSync/DeviceSyncMigrationModal.tsx index be0767af..178310ad 100644 --- a/src/components/deviceSync/DeviceSyncMigrationModal.tsx +++ b/src/components/deviceSync/DeviceSyncMigrationModal.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react'; import type { MigrationPair, MigrationPhase, MigrationResult, -} from '../../utils/runDeviceSyncMigration'; +} from '../../utils/deviceSync/runDeviceSyncMigration'; interface Props { migrationPhase: MigrationPhase; diff --git a/src/components/deviceSync/DeviceSyncPreSyncModal.tsx b/src/components/deviceSync/DeviceSyncPreSyncModal.tsx index ae317c38..c7ee30e3 100644 --- a/src/components/deviceSync/DeviceSyncPreSyncModal.tsx +++ b/src/components/deviceSync/DeviceSyncPreSyncModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { AlertCircle, Loader2 } from 'lucide-react'; -import type { SyncDelta } from '../../utils/runDeviceSyncExecution'; +import type { SyncDelta } from '../../utils/deviceSync/runDeviceSyncExecution'; interface Props { preSyncOpen: boolean; diff --git a/src/components/favorites/FavoritesSongsSectionHeader.tsx b/src/components/favorites/FavoritesSongsSectionHeader.tsx index 64eaa641..9d48094a 100644 --- a/src/components/favorites/FavoritesSongsSectionHeader.tsx +++ b/src/components/favorites/FavoritesSongsSectionHeader.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react'; import type { SubsonicSong } from '../../api/subsonicTypes'; import { usePlayerStore } from '../../store/playerStore'; -import { songToTrack } from '../../utils/songToTrack'; +import { songToTrack } from '../../utils/playback/songToTrack'; import GenreFilterBar from '../GenreFilterBar'; interface Props { diff --git a/src/components/favorites/FavoritesSongsTracklist.tsx b/src/components/favorites/FavoritesSongsTracklist.tsx index 49283b14..5234e174 100644 --- a/src/components/favorites/FavoritesSongsTracklist.tsx +++ b/src/components/favorites/FavoritesSongsTracklist.tsx @@ -12,7 +12,7 @@ import { usePreviewStore } from '../../store/previewStore'; import { useSelectionStore } from '../../store/selectionStore'; import { useDragDrop } from '../../contexts/DragDropContext'; import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior'; -import { songToTrack } from '../../utils/songToTrack'; +import { songToTrack } from '../../utils/playback/songToTrack'; import { AddToPlaylistSubmenu } from '../ContextMenu'; import StarRating from '../StarRating'; diff --git a/src/components/folderBrowser/FolderBrowserColumn.tsx b/src/components/folderBrowser/FolderBrowserColumn.tsx index 5d6a1d43..1c5b6a85 100644 --- a/src/components/folderBrowser/FolderBrowserColumn.tsx +++ b/src/components/folderBrowser/FolderBrowserColumn.tsx @@ -6,7 +6,7 @@ import type { Track } from '../../store/playerStoreTypes'; import { folderBrowserHasKeyModifiers, isFolderBrowserArrowKey, type Column, -} from '../../utils/folderBrowserHelpers'; +} from '../../utils/componentHelpers/folderBrowserHelpers'; interface Props { col: Column; diff --git a/src/components/fullscreenPlayer/FsLyricsApple.tsx b/src/components/fullscreenPlayer/FsLyricsApple.tsx index 5a8fbe7e..a877f172 100644 --- a/src/components/fullscreenPlayer/FsLyricsApple.tsx +++ b/src/components/fullscreenPlayer/FsLyricsApple.tsx @@ -6,7 +6,7 @@ import { useWordLyricsSync } from '../../hooks/useWordLyricsSync'; import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; import type { LrcLine } from '../../api/lrclib'; import type { Track } from '../../store/playerStoreTypes'; -import { EaseScroller, targetForFraction } from '../../utils/easeScroll'; +import { EaseScroller, targetForFraction } from '../../utils/ui/easeScroll'; // Apple Music-style fullscreen lyrics. // Full-screen scrollable list. Active line auto-scrolls to ~35% from top. diff --git a/src/components/fullscreenPlayer/FsPlayBtn.tsx b/src/components/fullscreenPlayer/FsPlayBtn.tsx index cbb25274..e62ea8db 100644 --- a/src/components/fullscreenPlayer/FsPlayBtn.tsx +++ b/src/components/fullscreenPlayer/FsPlayBtn.tsx @@ -3,7 +3,7 @@ import { Moon, Pause, Play, Sunrise } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { usePlayerStore } from '../../store/playerStore'; import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress'; -import { usePlaybackScheduleRemaining } from '../../utils/playbackScheduleFormat'; +import { usePlaybackScheduleRemaining } from '../../utils/format/playbackScheduleFormat'; import PlaybackDelayModal from '../PlaybackDelayModal'; import PlaybackScheduleBadge from '../PlaybackScheduleBadge'; diff --git a/src/components/fullscreenPlayer/FsSeekbar.tsx b/src/components/fullscreenPlayer/FsSeekbar.tsx index 4a765eda..20ebaf34 100644 --- a/src/components/fullscreenPlayer/FsSeekbar.tsx +++ b/src/components/fullscreenPlayer/FsSeekbar.tsx @@ -1,7 +1,7 @@ import React, { memo, useCallback, useEffect, useRef } from 'react'; import { usePlayerStore } from '../../store/playerStore'; import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; -import { formatTime } from '../../utils/fullscreenPlayerHelpers'; +import { formatTime } from '../../utils/componentHelpers/fullscreenPlayerHelpers'; // Full-width seekbar — imperative DOM updates, zero React re-renders on tick. export const FsSeekbar = memo(function FsSeekbar({ duration }: { duration: number }) { diff --git a/src/components/internetRadio/RadioDirectoryModal.tsx b/src/components/internetRadio/RadioDirectoryModal.tsx index a558b58b..fe89e7cf 100644 --- a/src/components/internetRadio/RadioDirectoryModal.tsx +++ b/src/components/internetRadio/RadioDirectoryModal.tsx @@ -9,7 +9,7 @@ import { import { type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE, } from '../../api/subsonicTypes'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; interface RadioDirectoryModalProps { onClose: () => void; diff --git a/src/components/miniPlayer/MiniControls.tsx b/src/components/miniPlayer/MiniControls.tsx index e581b066..cc5ca061 100644 --- a/src/components/miniPlayer/MiniControls.tsx +++ b/src/components/miniPlayer/MiniControls.tsx @@ -1,5 +1,5 @@ import { Pause, Play, SkipBack, SkipForward } from 'lucide-react'; -import { fmt } from '../../utils/miniPlayerHelpers'; +import { fmt } from '../../utils/componentHelpers/miniPlayerHelpers'; import type { MiniControlAction } from '../../utils/miniPlayerBridge'; interface Props { diff --git a/src/components/nowPlaying/AlbumCard.tsx b/src/components/nowPlaying/AlbumCard.tsx index 1fb00d2b..704d4fe1 100644 --- a/src/components/nowPlaying/AlbumCard.tsx +++ b/src/components/nowPlaying/AlbumCard.tsx @@ -2,7 +2,7 @@ import React, { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Disc3, ExternalLink, Star } from 'lucide-react'; import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; -import { formatTime, formatTotalDuration } from '../../utils/nowPlayingHelpers'; +import { formatTime, formatTotalDuration } from '../../utils/componentHelpers/nowPlayingHelpers'; interface AlbumCardProps { album: SubsonicAlbum | null; diff --git a/src/components/nowPlaying/ArtistCard.tsx b/src/components/nowPlaying/ArtistCard.tsx index a449ef40..59c5a6ec 100644 --- a/src/components/nowPlaying/ArtistCard.tsx +++ b/src/components/nowPlaying/ArtistCard.tsx @@ -2,7 +2,7 @@ import React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } fr import { useTranslation } from 'react-i18next'; import { ExternalLink } from 'lucide-react'; import type { SubsonicArtistInfo } from '../../api/subsonicTypes'; -import { isRealArtistImage, sanitizeHtml } from '../../utils/nowPlayingHelpers'; +import { isRealArtistImage, sanitizeHtml } from '../../utils/componentHelpers/nowPlayingHelpers'; import CachedImage from '../CachedImage'; interface ArtistCardProps { diff --git a/src/components/nowPlaying/CreditsCard.tsx b/src/components/nowPlaying/CreditsCard.tsx index 98c11982..3fb6b02a 100644 --- a/src/components/nowPlaying/CreditsCard.tsx +++ b/src/components/nowPlaying/CreditsCard.tsx @@ -1,6 +1,6 @@ import React, { memo } from 'react'; import { useTranslation } from 'react-i18next'; -import type { ContributorRow } from '../../utils/nowPlayingHelpers'; +import type { ContributorRow } from '../../utils/componentHelpers/nowPlayingHelpers'; interface CreditsCardProps { rows: ContributorRow[]; } diff --git a/src/components/nowPlaying/Hero.tsx b/src/components/nowPlaying/Hero.tsx index 54a6590f..a84e6deb 100644 --- a/src/components/nowPlaying/Hero.tsx +++ b/src/components/nowPlaying/Hero.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Headphones, Heart, MicVocal, Music, Star } from 'lucide-react'; import type { LastfmArtistStats, LastfmTrackInfo } from '../../api/lastfm'; import LastfmIcon from '../LastfmIcon'; -import { formatTime } from '../../utils/nowPlayingHelpers'; +import { formatTime } from '../../utils/componentHelpers/nowPlayingHelpers'; interface HeroProps { track: { title: string; artist: string; album: string; year?: number; diff --git a/src/components/nowPlaying/RadioView.tsx b/src/components/nowPlaying/RadioView.tsx index 3d0fb047..a4a3a7f1 100644 --- a/src/components/nowPlaying/RadioView.tsx +++ b/src/components/nowPlaying/RadioView.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react'; import type { useRadioMetadata } from '../../hooks/useRadioMetadata'; import { usePlayerStore } from '../../store/playerStore'; -import { formatTime } from '../../utils/nowPlayingHelpers'; +import { formatTime } from '../../utils/componentHelpers/nowPlayingHelpers'; type NonNullStoreField> = NonNullable[K]>; diff --git a/src/components/nowPlaying/TopSongsCard.tsx b/src/components/nowPlaying/TopSongsCard.tsx index da322bb4..3a7e9a01 100644 --- a/src/components/nowPlaying/TopSongsCard.tsx +++ b/src/components/nowPlaying/TopSongsCard.tsx @@ -2,7 +2,7 @@ import React, { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { ExternalLink, Play, TrendingUp } from 'lucide-react'; import type { SubsonicSong } from '../../api/subsonicTypes'; -import { formatTime } from '../../utils/nowPlayingHelpers'; +import { formatTime } from '../../utils/componentHelpers/nowPlayingHelpers'; interface TopSongsCardProps { artistName: string; diff --git a/src/components/nowPlaying/TourCard.tsx b/src/components/nowPlaying/TourCard.tsx index e8e9028f..abc76f10 100644 --- a/src/components/nowPlaying/TourCard.tsx +++ b/src/components/nowPlaying/TourCard.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Calendar, Info } from 'lucide-react'; import { open as shellOpen } from '@tauri-apps/plugin-shell'; import type { BandsintownEvent } from '../../api/bandsintown'; -import { isoToParts } from '../../utils/nowPlayingHelpers'; +import { isoToParts } from '../../utils/componentHelpers/nowPlayingHelpers'; interface TourCardProps { artistName: string; diff --git a/src/components/playerBar/PlaybackClock.tsx b/src/components/playerBar/PlaybackClock.tsx index 0793e974..e8d53a13 100644 --- a/src/components/playerBar/PlaybackClock.tsx +++ b/src/components/playerBar/PlaybackClock.tsx @@ -1,6 +1,6 @@ import { memo, useEffect, useRef } from 'react'; import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; -import { formatTime } from '../../utils/playerBarHelpers'; +import { formatTime } from '../../utils/componentHelpers/playerBarHelpers'; /** Renders the playback clock without ever causing PlayerBar to re-render. * Updates the DOM directly via an imperative store subscription. */ diff --git a/src/components/playerBar/PlayerSeekbarSection.tsx b/src/components/playerBar/PlayerSeekbarSection.tsx index 1856d720..e3822e2b 100644 --- a/src/components/playerBar/PlayerSeekbarSection.tsx +++ b/src/components/playerBar/PlayerSeekbarSection.tsx @@ -2,7 +2,7 @@ import { ArrowLeftRight } from 'lucide-react'; import type { TFunction } from 'i18next'; import type { RadioMetadata } from '../../hooks/useRadioMetadata'; import { useThemeStore } from '../../store/themeStore'; -import { formatTime } from '../../utils/playerBarHelpers'; +import { formatTime } from '../../utils/componentHelpers/playerBarHelpers'; import WaveformSeek from '../WaveformSeek'; import { PlaybackTime, RemainingTime } from './PlaybackClock'; diff --git a/src/components/playerBar/PlayerTransportControls.tsx b/src/components/playerBar/PlayerTransportControls.tsx index 208a3fbc..a9ffb803 100644 --- a/src/components/playerBar/PlayerTransportControls.tsx +++ b/src/components/playerBar/PlayerTransportControls.tsx @@ -6,7 +6,7 @@ import type { PlayerState } from '../../store/playerStoreTypes'; import { usePreviewStore } from '../../store/previewStore'; import PlaybackScheduleBadge from '../PlaybackScheduleBadge'; import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress'; -import { usePlaybackScheduleRemaining } from '../../utils/playbackScheduleFormat'; +import { usePlaybackScheduleRemaining } from '../../utils/format/playbackScheduleFormat'; type RepeatMode = PlayerState['repeatMode']; type PlayPauseBind = ReturnType['playPauseBind']; diff --git a/src/components/playlist/CsvImportReportModal.tsx b/src/components/playlist/CsvImportReportModal.tsx index 59a9c7d9..36c77617 100644 --- a/src/components/playlist/CsvImportReportModal.tsx +++ b/src/components/playlist/CsvImportReportModal.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Download, X } from 'lucide-react'; -import { showToast } from '../../utils/toast'; -import type { SpotifyCsvTrack } from '../../utils/spotifyCsvImport'; +import { showToast } from '../../utils/ui/toast'; +import type { SpotifyCsvTrack } from '../../utils/playlist/spotifyCsvImport'; interface CsvReportModalProps { report: { diff --git a/src/components/playlist/PlaylistHero.tsx b/src/components/playlist/PlaylistHero.tsx index 3b0bc30a..bec92c17 100644 --- a/src/components/playlist/PlaylistHero.tsx +++ b/src/components/playlist/PlaylistHero.tsx @@ -10,7 +10,7 @@ import type { ZipDownload } from '../../store/zipDownloadStore'; import { useThemeStore } from '../../store/themeStore'; import { displayPlaylistName, formatSize, isSmartPlaylistName, totalDurationLabel, -} from '../../utils/playlistDetailHelpers'; +} from '../../utils/componentHelpers/playlistDetailHelpers'; import CachedImage from '../CachedImage'; interface Props { diff --git a/src/components/playlist/PlaylistSongSearchPanel.tsx b/src/components/playlist/PlaylistSongSearchPanel.tsx index 53a66aab..4499b299 100644 --- a/src/components/playlist/PlaylistSongSearchPanel.tsx +++ b/src/components/playlist/PlaylistSongSearchPanel.tsx @@ -4,8 +4,8 @@ import { Check, ListPlus, X } from 'lucide-react'; import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl'; import type { SubsonicSong } from '../../api/subsonicTypes'; import { usePlayerStore } from '../../store/playerStore'; -import { songToTrack } from '../../utils/songToTrack'; -import { formatDuration } from '../../utils/playlistDetailHelpers'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { formatDuration } from '../../utils/componentHelpers/playlistDetailHelpers'; import CachedImage from '../CachedImage'; import { AddToPlaylistSubmenu } from '../ContextMenu'; diff --git a/src/components/playlist/PlaylistSuggestions.tsx b/src/components/playlist/PlaylistSuggestions.tsx index f0406342..b74bd827 100644 --- a/src/components/playlist/PlaylistSuggestions.tsx +++ b/src/components/playlist/PlaylistSuggestions.tsx @@ -7,8 +7,8 @@ import type { SubsonicSong } from '../../api/subsonicTypes'; import { usePlayerStore } from '../../store/playerStore'; import { usePreviewStore } from '../../store/previewStore'; import { useThemeStore } from '../../store/themeStore'; -import { songToTrack } from '../../utils/songToTrack'; -import { codecLabel, formatDuration } from '../../utils/playlistDetailHelpers'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { codecLabel, formatDuration } from '../../utils/componentHelpers/playlistDetailHelpers'; const PL_CENTERED = new Set(['favorite', 'rating', 'duration']); diff --git a/src/components/playlist/PlaylistTracklist.tsx b/src/components/playlist/PlaylistTracklist.tsx index 5b0a466a..fb3c206b 100644 --- a/src/components/playlist/PlaylistTracklist.tsx +++ b/src/components/playlist/PlaylistTracklist.tsx @@ -12,9 +12,9 @@ import { usePreviewStore } from '../../store/previewStore'; import { useThemeStore } from '../../store/themeStore'; import { useDragDrop } from '../../contexts/DragDropContext'; import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior'; -import { songToTrack } from '../../utils/songToTrack'; -import { codecLabel, formatDuration } from '../../utils/playlistDetailHelpers'; -import type { PlaylistSortKey, PlaylistSortDir } from '../../utils/playlistDisplayedSongs'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { codecLabel, formatDuration } from '../../utils/componentHelpers/playlistDetailHelpers'; +import type { PlaylistSortKey, PlaylistSortDir } from '../../utils/playlist/playlistDisplayedSongs'; import StarRating from '../StarRating'; import { AddToPlaylistSubmenu } from '../ContextMenu'; diff --git a/src/components/playlists/PlaylistCard.tsx b/src/components/playlists/PlaylistCard.tsx index 90f46c15..d37dc4d1 100644 --- a/src/components/playlists/PlaylistCard.tsx +++ b/src/components/playlists/PlaylistCard.tsx @@ -6,8 +6,8 @@ import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import { usePlayerStore } from '../../store/playerStore'; import { displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist, -} from '../../utils/playlistsSmart'; -import { formatHumanHoursMinutes } from '../../utils/formatHumanDuration'; +} from '../../utils/playlist/playlistsSmart'; +import { formatHumanHoursMinutes } from '../../utils/format/formatHumanDuration'; import { PlaylistCardMainCover, PlaylistSmartCoverCell } from './PlaylistCoverImages'; interface Props { diff --git a/src/components/playlists/PlaylistsHeader.tsx b/src/components/playlists/PlaylistsHeader.tsx index d24b0d07..6727fd03 100644 --- a/src/components/playlists/PlaylistsHeader.tsx +++ b/src/components/playlists/PlaylistsHeader.tsx @@ -4,7 +4,7 @@ import { CheckSquare2, Plus, Trash2 } from 'lucide-react'; import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import { defaultSmartFilters, type SmartFilters, -} from '../../utils/playlistsSmart'; +} from '../../utils/playlist/playlistsSmart'; interface Props { selectionMode: boolean; diff --git a/src/components/playlists/PlaylistsSmartEditor.tsx b/src/components/playlists/PlaylistsSmartEditor.tsx index e3a43d4b..bc43fe4f 100644 --- a/src/components/playlists/PlaylistsSmartEditor.tsx +++ b/src/components/playlists/PlaylistsSmartEditor.tsx @@ -5,7 +5,7 @@ import StarRating from '../StarRating'; import { LIMIT_MAX, YEAR_MAX, YEAR_MIN, clampYear, defaultSmartFilters, type SmartFilters, -} from '../../utils/playlistsSmart'; +} from '../../utils/playlist/playlistsSmart'; interface Props { smartFilters: SmartFilters; diff --git a/src/components/queuePanel/QueueCurrentTrack.tsx b/src/components/queuePanel/QueueCurrentTrack.tsx index a1f19dd0..15824876 100644 --- a/src/components/queuePanel/QueueCurrentTrack.tsx +++ b/src/components/queuePanel/QueueCurrentTrack.tsx @@ -3,13 +3,13 @@ import { ChevronDown, FolderOpen, HardDrive, Music, Waves } from 'lucide-react'; import type { TFunction } from 'i18next'; import type { Track } from '../../store/playerStoreTypes'; import type { LoudnessLufsPreset, NormalizationEngine } from '../../store/authStoreTypes'; -import type { PlaybackSourceKind } from '../../utils/resolvePlaybackUrl'; +import type { PlaybackSourceKind } from '../../utils/playback/resolvePlaybackUrl'; import { formatQueueReplayGainParts, renderStars, -} from '../../utils/queuePanelHelpers'; -import { loudnessGainPlaceholderUntilCacheDb } from '../../utils/loudnessPlaceholder'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider'; +} from '../../utils/componentHelpers/queuePanelHelpers'; +import { loudnessGainPlaceholderUntilCacheDb } from '../../utils/audio/loudnessPlaceholder'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; import { QueueLufsTargetMenu } from './QueueLufsTargetMenu'; interface Props { diff --git a/src/components/queuePanel/QueueHeader.tsx b/src/components/queuePanel/QueueHeader.tsx index 94795a20..1487bb99 100644 --- a/src/components/queuePanel/QueueHeader.tsx +++ b/src/components/queuePanel/QueueHeader.tsx @@ -3,7 +3,7 @@ import { ChevronDown, ListMusic } from 'lucide-react'; import type { TFunction } from 'i18next'; import { usePlayerStore } from '../../store/playerStore'; import type { Track } from '../../store/playerStoreTypes'; -import type { DurationMode } from '../../utils/queuePanelHelpers'; +import type { DurationMode } from '../../utils/componentHelpers/queuePanelHelpers'; interface Props { queue: Track[]; diff --git a/src/components/queuePanel/QueueList.tsx b/src/components/queuePanel/QueueList.tsx index ca69b029..c4e74350 100644 --- a/src/components/queuePanel/QueueList.tsx +++ b/src/components/queuePanel/QueueList.tsx @@ -5,7 +5,7 @@ import OverlayScrollArea from '../OverlayScrollArea'; import { usePlayerStore } from '../../store/playerStore'; import { useLuckyMixStore } from '../../store/luckyMixStore'; import type { Track, PlayerState } from '../../store/playerStoreTypes'; -import { formatTime } from '../../utils/queuePanelHelpers'; +import { formatTime } from '../../utils/componentHelpers/queuePanelHelpers'; type StartDrag = ( payload: { data: string; label: string }, diff --git a/src/components/randomMix/RandomMixTrackRow.tsx b/src/components/randomMix/RandomMixTrackRow.tsx index 25745a75..2d0330f8 100644 --- a/src/components/randomMix/RandomMixTrackRow.tsx +++ b/src/components/randomMix/RandomMixTrackRow.tsx @@ -5,7 +5,7 @@ import type { SubsonicSong } from '../../api/subsonicTypes'; import type { Track } from '../../store/playerStoreTypes'; import { usePreviewStore } from '../../store/previewStore'; import { useDragDrop } from '../../contexts/DragDropContext'; -import { formatRandomMixDuration } from '../../utils/randomMixHelpers'; +import { formatRandomMixDuration } from '../../utils/componentHelpers/randomMixHelpers'; interface Props { song: SubsonicSong; diff --git a/src/components/settings/AddServerForm.tsx b/src/components/settings/AddServerForm.tsx index 546e67a2..a1ed60e9 100644 --- a/src/components/settings/AddServerForm.tsx +++ b/src/components/settings/AddServerForm.tsx @@ -2,14 +2,14 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Eye, EyeOff } from 'lucide-react'; import type { ServerProfile } from '../../store/authStoreTypes'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; import { decodeServerMagicString, encodeServerMagicString, DECODED_PASSWORD_VISUAL_MASK, type ServerMagicPayload, -} from '../../utils/serverMagicString'; -import { shortHostFromServerUrl } from '../../utils/serverDisplayName'; +} from '../../utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '../../utils/server/serverDisplayName'; export function AddServerForm({ onSave, diff --git a/src/components/settings/AudioTab.tsx b/src/components/settings/AudioTab.tsx index 4c5989cd..a0410914 100644 --- a/src/components/settings/AudioTab.tsx +++ b/src/components/settings/AudioTab.tsx @@ -4,7 +4,7 @@ import { Music2, Sliders, Waves } from 'lucide-react'; import { useAuthStore } from '../../store/authStore'; import Equalizer from '../Equalizer'; import SettingsSubSection from '../SettingsSubSection'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; import { useAudioDevicesProbe } from '../../hooks/useAudioDevicesProbe'; import { AudioOutputDeviceSection } from './audio/AudioOutputDeviceSection'; import { NormalizationBlock } from './audio/NormalizationBlock'; diff --git a/src/components/settings/BackupSection.tsx b/src/components/settings/BackupSection.tsx index 4239907c..47d46609 100644 --- a/src/components/settings/BackupSection.tsx +++ b/src/components/settings/BackupSection.tsx @@ -1,8 +1,8 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Download, HardDrive, Upload } from 'lucide-react'; -import { exportBackup, importBackup } from '../../utils/backup'; -import { showToast } from '../../utils/toast'; +import { exportBackup, importBackup } from '../../utils/export/backup'; +import { showToast } from '../../utils/ui/toast'; export function BackupSection() { const { t } = useTranslation(); diff --git a/src/components/settings/ServersTab.tsx b/src/components/settings/ServersTab.tsx index e9cafed2..42cfc0e3 100644 --- a/src/components/settings/ServersTab.tsx +++ b/src/components/settings/ServersTab.tsx @@ -7,10 +7,10 @@ import { useAuthStore } from '../../store/authStore'; import type { ServerProfile } from '../../store/authStoreTypes'; import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../../api/subsonic'; import { useDragDrop } from '../../contexts/DragDropContext'; -import { type ServerMagicPayload } from '../../utils/serverMagicString'; -import { showAudiomuseNavidromeServerSetting } from '../../utils/subsonicServerIdentity'; -import { serverListDisplayLabel } from '../../utils/serverDisplayName'; -import { switchActiveServer } from '../../utils/switchActiveServer'; +import { type ServerMagicPayload } from '../../utils/server/serverMagicString'; +import { showAudiomuseNavidromeServerSetting } from '../../utils/server/subsonicServerIdentity'; +import { serverListDisplayLabel } from '../../utils/server/serverDisplayName'; +import { switchActiveServer } from '../../utils/server/switchActiveServer'; import { AddServerForm } from './AddServerForm'; import { ServerGripHandle } from './ServerGripHandle'; diff --git a/src/components/settings/SidebarCustomizer.tsx b/src/components/settings/SidebarCustomizer.tsx index 7c60f5d7..36ce0df4 100644 --- a/src/components/settings/SidebarCustomizer.tsx +++ b/src/components/settings/SidebarCustomizer.tsx @@ -6,7 +6,7 @@ import { useAuthStore } from '../../store/authStore'; import { useSidebarStore, SidebarItemConfig } from '../../store/sidebarStore'; import { useLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable'; import { ALL_NAV_ITEMS } from '../../config/navItems'; -import { applySidebarDropReorder } from '../../utils/sidebarNavReorder'; +import { applySidebarDropReorder } from '../../utils/componentHelpers/sidebarNavReorder'; type DropTarget = { idx: number; before: boolean; section: 'library' | 'system' } | null; diff --git a/src/components/settings/StorageTab.tsx b/src/components/settings/StorageTab.tsx index 079ae921..fa78e9e6 100644 --- a/src/components/settings/StorageTab.tsx +++ b/src/components/settings/StorageTab.tsx @@ -8,8 +8,8 @@ import { useHotCacheStore } from '../../store/hotCacheStore'; import { useOfflineStore } from '../../store/offlineStore'; import { usePlayerStore } from '../../store/playerStore'; import { clearImageCache, getImageCacheSize } from '../../utils/imageCache'; -import { formatBytes, snapHotCacheMb } from '../../utils/formatBytes'; -import { showToast } from '../../utils/toast'; +import { formatBytes, snapHotCacheMb } from '../../utils/format/formatBytes'; +import { showToast } from '../../utils/ui/toast'; import SettingsSubSection from '../SettingsSubSection'; export function StorageTab() { diff --git a/src/components/settings/SystemTab.tsx b/src/components/settings/SystemTab.tsx index 03f1f2bb..fbac5c07 100644 --- a/src/components/settings/SystemTab.tsx +++ b/src/components/settings/SystemTab.tsx @@ -9,7 +9,7 @@ import i18n from '../../i18n'; import { useAuthStore } from '../../store/authStore'; import type { LoggingMode } from '../../store/authStoreTypes'; import { IS_LINUX } from '../../utils/platform'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; import { AboutPsysonicBrandHeader } from '../AboutPsysonicLol'; import CustomSelect from '../CustomSelect'; import LicensesPanel from '../LicensesPanel'; diff --git a/src/components/settings/UserForm.tsx b/src/components/settings/UserForm.tsx index 6d0eff26..1b54a023 100644 --- a/src/components/settings/UserForm.tsx +++ b/src/components/settings/UserForm.tsx @@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Eye, EyeOff, Shield, Wand2 } from 'lucide-react'; import { ndUpdateUser, type NdLibrary, type NdUser } from '../../api/navidromeAdmin'; -import { showToast } from '../../utils/toast'; +import { showToast } from '../../utils/ui/toast'; import { copyTextToClipboard, encodeServerMagicString, -} from '../../utils/serverMagicString'; -import { shortHostFromServerUrl } from '../../utils/serverDisplayName'; +} from '../../utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '../../utils/server/serverDisplayName'; export interface UserFormState { userName: string; diff --git a/src/components/settings/audio/AudioOutputDeviceSection.tsx b/src/components/settings/audio/AudioOutputDeviceSection.tsx index eacc7771..29c358a6 100644 --- a/src/components/settings/audio/AudioOutputDeviceSection.tsx +++ b/src/components/settings/audio/AudioOutputDeviceSection.tsx @@ -6,7 +6,7 @@ import CustomSelect from '../../CustomSelect'; import SettingsSubSection from '../../SettingsSubSection'; import { useAuthStore } from '../../../store/authStore'; import { IS_MACOS } from '../../../utils/platform'; -import { buildAudioDeviceSelectOptions } from '../../../utils/audioDeviceLabels'; +import { buildAudioDeviceSelectOptions } from '../../../utils/audio/audioDeviceLabels'; interface Props { audioDevices: string[]; diff --git a/src/components/settings/userMgmt/MagicStringModal.tsx b/src/components/settings/userMgmt/MagicStringModal.tsx index 4f0f106f..3b7cbfb3 100644 --- a/src/components/settings/userMgmt/MagicStringModal.tsx +++ b/src/components/settings/userMgmt/MagicStringModal.tsx @@ -3,12 +3,12 @@ import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; import type { TFunction } from 'i18next'; import { ndUpdateUser, type NdUser } from '../../../api/navidromeAdmin'; -import { showToast } from '../../../utils/toast'; +import { showToast } from '../../../utils/ui/toast'; import { copyTextToClipboard, encodeServerMagicString, -} from '../../../utils/serverMagicString'; -import { shortHostFromServerUrl } from '../../../utils/serverDisplayName'; +} from '../../../utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '../../../utils/server/serverDisplayName'; interface Props { user: NdUser; diff --git a/src/components/settings/userMgmt/UserMgmtRow.tsx b/src/components/settings/userMgmt/UserMgmtRow.tsx index 03f980dd..b7e0dfa3 100644 --- a/src/components/settings/userMgmt/UserMgmtRow.tsx +++ b/src/components/settings/userMgmt/UserMgmtRow.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Shield, Trash2, User, Wand2 } from 'lucide-react'; import type { i18n as I18nType, TFunction } from 'i18next'; import type { NdLibrary, NdUser } from '../../../api/navidromeAdmin'; -import { formatLastSeen } from '../../../utils/userMgmtHelpers'; +import { formatLastSeen } from '../../../utils/componentHelpers/userMgmtHelpers'; interface Props { user: NdUser; diff --git a/src/components/sidebar/SidebarNavBody.tsx b/src/components/sidebar/SidebarNavBody.tsx index 251a59f9..59297316 100644 --- a/src/components/sidebar/SidebarNavBody.tsx +++ b/src/components/sidebar/SidebarNavBody.tsx @@ -5,7 +5,7 @@ import { AudioLines, ChevronRight, HardDriveDownload, PlayCircle, Settings, Spar import type { SidebarItemConfig } from '../../store/sidebarStore'; import { ALL_NAV_ITEMS } from '../../config/navItems'; import WhatsNewBanner from '../WhatsNewBanner'; -import { displayPlaylistName, isSmartPlaylistName } from '../../utils/sidebarHelpers'; +import { displayPlaylistName, isSmartPlaylistName } from '../../utils/componentHelpers/sidebarHelpers'; import SidebarLibraryPicker from './SidebarLibraryPicker'; import SidebarActiveJobs from './SidebarActiveJobs'; diff --git a/src/components/sidebar/SidebarPerfProbeModal.tsx b/src/components/sidebar/SidebarPerfProbeModal.tsx index 80ae2634..f4011a4c 100644 --- a/src/components/sidebar/SidebarPerfProbeModal.tsx +++ b/src/components/sidebar/SidebarPerfProbeModal.tsx @@ -1,7 +1,7 @@ import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; import SidebarPerfProbePhase2 from './SidebarPerfProbePhase2'; -import { resetPerfProbeFlags, setPerfProbeFlag, type PerfProbeFlags } from '../../utils/perfFlags'; +import { resetPerfProbeFlags, setPerfProbeFlag, type PerfProbeFlags } from '../../utils/perf/perfFlags'; interface PerfCpu { app: number; diff --git a/src/components/sidebar/SidebarPerfProbePhase2.tsx b/src/components/sidebar/SidebarPerfProbePhase2.tsx index a79d5988..ce122a91 100644 --- a/src/components/sidebar/SidebarPerfProbePhase2.tsx +++ b/src/components/sidebar/SidebarPerfProbePhase2.tsx @@ -1,4 +1,4 @@ -import { setPerfProbeFlag, type PerfProbeFlags } from '../../utils/perfFlags'; +import { setPerfProbeFlag, type PerfProbeFlags } from '../../utils/perf/perfFlags'; export default function SidebarPerfProbePhase2({ perfFlags }: { perfFlags: PerfProbeFlags }) { return ( diff --git a/src/config/shortcutActionRegistry.ts b/src/config/shortcutActionRegistry.ts index fea36548..936c71dc 100644 --- a/src/config/shortcutActionRegistry.ts +++ b/src/config/shortcutActionRegistry.ts @@ -1,13 +1,13 @@ import { star, unstar } from '../api/subsonicStarRating'; import { getSong } from '../api/subsonicLibrary'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import { invoke } from '@tauri-apps/api/core'; import { getCurrentWindow } from '@tauri-apps/api/window'; import i18n from '../i18n'; import { usePlayerStore } from '../store/playerStore'; import { usePreviewStore } from '../store/previewStore'; import { useLyricsStore } from '../store/lyricsStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import type { ActionContext, ShortcutSlot, ShortcutActionMeta } from './shortcutTypes'; let cliPremuteVolume: number | null = null; diff --git a/src/config/shortcutDispatch.ts b/src/config/shortcutDispatch.ts index 953a7f8d..17d7ba1d 100644 --- a/src/config/shortcutDispatch.ts +++ b/src/config/shortcutDispatch.ts @@ -1,8 +1,8 @@ import { setRating } from '../api/subsonicStarRating'; import i18n from '../i18n'; import { usePlayerStore } from '../store/playerStore'; -import { showToast } from '../utils/toast'; -import { playByOpaqueId } from '../utils/playByOpaqueId'; +import { showToast } from '../utils/ui/toast'; +import { playByOpaqueId } from '../utils/playback/playByOpaqueId'; import type { ActionContext, CliContext } from './shortcutTypes'; import { SHORTCUT_ACTION_REGISTRY, diff --git a/src/hooks/tauriBridge/useCliBridge.ts b/src/hooks/tauriBridge/useCliBridge.ts index fb81e0cf..03aad1e2 100644 --- a/src/hooks/tauriBridge/useCliBridge.ts +++ b/src/hooks/tauriBridge/useCliBridge.ts @@ -5,10 +5,10 @@ import type { NavigateFunction } from 'react-router-dom'; import { getSimilarSongs } from '../../api/subsonicArtists'; import { getMusicFolders } from '../../api/subsonicLibrary'; import { search as subsonicSearch } from '../../api/subsonicSearch'; -import { shuffleArray } from '../../utils/shuffleArray'; -import { songToTrack } from '../../utils/songToTrack'; -import { showToast } from '../../utils/toast'; -import { switchActiveServer } from '../../utils/switchActiveServer'; +import { shuffleArray } from '../../utils/playback/shuffleArray'; +import { songToTrack } from '../../utils/playback/songToTrack'; +import { showToast } from '../../utils/ui/toast'; +import { switchActiveServer } from '../../utils/server/switchActiveServer'; import i18n from '../../i18n'; import { usePlayerStore } from '../../store/playerStore'; import { useAuthStore } from '../../store/authStore'; diff --git a/src/hooks/useAlbumTrackListSelection.ts b/src/hooks/useAlbumTrackListSelection.ts index f5e370bc..769825f1 100644 --- a/src/hooks/useAlbumTrackListSelection.ts +++ b/src/hooks/useAlbumTrackListSelection.ts @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import type { SubsonicSong } from '../api/subsonicTypes'; import { useSelectionStore } from '../store/selectionStore'; import { useDragDrop } from '../contexts/DragDropContext'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; interface UseAlbumTrackListSelectionArgs { songs: SubsonicSong[]; diff --git a/src/hooks/useAppUpdater.ts b/src/hooks/useAppUpdater.ts index 46fd4f6a..a9d7094e 100644 --- a/src/hooks/useAppUpdater.ts +++ b/src/hooks/useAppUpdater.ts @@ -5,7 +5,7 @@ import { invoke } from '@tauri-apps/api/core'; import { useTranslation } from 'react-i18next'; import { version as currentVersion } from '../../package.json'; import { IS_LINUX, IS_MACOS } from '../utils/platform'; -import { SKIP_KEY, isNewer, pickAsset, type ReleaseData, type DlState } from '../utils/appUpdaterHelpers'; +import { SKIP_KEY, isNewer, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers'; /** All update-modal state, the GitHub release probe and the download/relaunch * handlers. The component owns only the early-return guard and the JSX. */ diff --git a/src/hooks/useArtistsFiltering.ts b/src/hooks/useArtistsFiltering.ts index 830c86af..ecebce3c 100644 --- a/src/hooks/useArtistsFiltering.ts +++ b/src/hooks/useArtistsFiltering.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import type { SubsonicArtist } from '../api/subsonicTypes'; import { usePlayerStore } from '../store/playerStore'; -import { ALL_SENTINEL, type ArtistListFlatRow } from '../utils/artistsHelpers'; +import { ALL_SENTINEL, type ArtistListFlatRow } from '../utils/componentHelpers/artistsHelpers'; interface UseArtistsFilteringArgs { artists: SubsonicArtist[]; diff --git a/src/hooks/useAudioDevicesProbe.ts b/src/hooks/useAudioDevicesProbe.ts index fcaaff64..2a0a2940 100644 --- a/src/hooks/useAudioDevicesProbe.ts +++ b/src/hooks/useAudioDevicesProbe.ts @@ -4,8 +4,8 @@ import { listen } from '@tauri-apps/api/event'; import type { TFunction } from 'i18next'; import { useAuthStore } from '../store/authStore'; import { IS_MACOS } from '../utils/platform'; -import { sortAudioDeviceIds } from '../utils/audioDeviceLabels'; -import { showToast } from '../utils/toast'; +import { sortAudioDeviceIds } from '../utils/audio/audioDeviceLabels'; +import { showToast } from '../utils/ui/toast'; interface UseAudioDevicesProbeResult { audioDevices: string[]; diff --git a/src/hooks/useAutoEq.ts b/src/hooks/useAutoEq.ts index 6d091ec6..420340cb 100644 --- a/src/hooks/useAutoEq.ts +++ b/src/hooks/useAutoEq.ts @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { invoke } from '@tauri-apps/api/core'; import { useEqStore } from '../store/eqStore'; -import { parseFixedBandEqString, type AutoEqVariant, type AutoEqResult } from '../utils/autoEqParse'; +import { parseFixedBandEqString, type AutoEqVariant, type AutoEqResult } from '../utils/audio/autoEqParse'; /** AutoEQ search/apply state for the Equalizer. Loads the entries index lazily * when the section opens, filters client-side, and applies fetched profiles via diff --git a/src/hooks/useConnectionStatus.ts b/src/hooks/useConnectionStatus.ts index 1886993b..4c9f99ff 100644 --- a/src/hooks/useConnectionStatus.ts +++ b/src/hooks/useConnectionStatus.ts @@ -1,8 +1,8 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useAuthStore } from '../store/authStore'; import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../api/subsonic'; -import { serverListDisplayLabel } from '../utils/serverDisplayName'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { serverListDisplayLabel } from '../utils/server/serverDisplayName'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; export type ConnectionStatus = 'connected' | 'disconnected' | 'checking'; diff --git a/src/hooks/useContextMenuRating.ts b/src/hooks/useContextMenuRating.ts index a688da75..1e370fe5 100644 --- a/src/hooks/useContextMenuRating.ts +++ b/src/hooks/useContextMenuRating.ts @@ -3,7 +3,7 @@ import { setRating } from '../api/subsonicStarRating'; import type { SubsonicAlbum, SubsonicArtist } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; import { useAuthStore } from '../store/authStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; type RatingKind = 'song' | 'album' | 'artist'; diff --git a/src/hooks/useDeviceSyncBrowser.ts b/src/hooks/useDeviceSyncBrowser.ts index 4435efe0..3e3c4721 100644 --- a/src/hooks/useDeviceSyncBrowser.ts +++ b/src/hooks/useDeviceSyncBrowser.ts @@ -6,7 +6,7 @@ import { search as searchSubsonic } from '../api/subsonicSearch'; import type { SubsonicAlbum, SubsonicArtist, SubsonicPlaylist, } from '../api/subsonicTypes'; -import type { SourceTab } from '../utils/deviceSyncHelpers'; +import type { SourceTab } from '../utils/deviceSync/deviceSyncHelpers'; export interface DeviceSyncBrowserResult { playlists: SubsonicPlaylist[]; diff --git a/src/hooks/useDeviceSyncDeviceScan.ts b/src/hooks/useDeviceSyncDeviceScan.ts index c987d42a..bbe576e2 100644 --- a/src/hooks/useDeviceSyncDeviceScan.ts +++ b/src/hooks/useDeviceSyncDeviceScan.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef } from 'react'; import { invoke } from '@tauri-apps/api/core'; import type { TFunction } from 'i18next'; import { useDeviceSyncStore, type DeviceSyncSource } from '../store/deviceSyncStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; export interface DeviceSyncDeviceScanResult { scanDevice: () => Promise; diff --git a/src/hooks/useDeviceSyncDrives.ts b/src/hooks/useDeviceSyncDrives.ts index 22744f70..f8db5773 100644 --- a/src/hooks/useDeviceSyncDrives.ts +++ b/src/hooks/useDeviceSyncDrives.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import type { RemovableDrive } from '../utils/deviceSyncHelpers'; +import type { RemovableDrive } from '../utils/deviceSync/deviceSyncHelpers'; export interface DeviceSyncDrivesResult { drives: RemovableDrive[]; diff --git a/src/hooks/useDeviceSyncJobEvents.ts b/src/hooks/useDeviceSyncJobEvents.ts index 4d23b154..e2acace2 100644 --- a/src/hooks/useDeviceSyncJobEvents.ts +++ b/src/hooks/useDeviceSyncJobEvents.ts @@ -4,9 +4,9 @@ import { listen } from '@tauri-apps/api/event'; import type { TFunction } from 'i18next'; import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore'; import { useDeviceSyncStore } from '../store/deviceSyncStore'; -import { showToast } from '../utils/toast'; -import { trackToSyncInfo } from '../utils/deviceSyncHelpers'; -import { fetchTracksForSource } from '../utils/fetchTracksForSource'; +import { showToast } from '../utils/ui/toast'; +import { trackToSyncInfo } from '../utils/deviceSync/deviceSyncHelpers'; +import { fetchTracksForSource } from '../utils/playback/fetchTracksForSource'; export function useDeviceSyncJobEvents( t: TFunction, diff --git a/src/hooks/useDeviceSyncSourceStatuses.ts b/src/hooks/useDeviceSyncSourceStatuses.ts index 3545009e..3d81efde 100644 --- a/src/hooks/useDeviceSyncSourceStatuses.ts +++ b/src/hooks/useDeviceSyncSourceStatuses.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { fetchTracksForSource } from '../utils/fetchTracksForSource'; -import { trackToSyncInfo, type SyncStatus } from '../utils/deviceSyncHelpers'; +import { fetchTracksForSource } from '../utils/playback/fetchTracksForSource'; +import { trackToSyncInfo, type SyncStatus } from '../utils/deviceSync/deviceSyncHelpers'; import type { DeviceSyncSource } from '../store/deviceSyncStore'; export interface DeviceSyncSourceStatusesResult { diff --git a/src/hooks/useFolderBrowserKeyboardNav.ts b/src/hooks/useFolderBrowserKeyboardNav.ts index f503669f..ec70c9ac 100644 --- a/src/hooks/useFolderBrowserKeyboardNav.ts +++ b/src/hooks/useFolderBrowserKeyboardNav.ts @@ -4,7 +4,7 @@ import { usePlayerStore } from '../store/playerStore'; import { entryToTrack, folderBrowserHasKeyModifiers, isFolderBrowserArrowKey, type Column, type NavPos, -} from '../utils/folderBrowserHelpers'; +} from '../utils/componentHelpers/folderBrowserHelpers'; interface Args { columns: Column[]; diff --git a/src/hooks/useFolderBrowserNowPlayingPath.ts b/src/hooks/useFolderBrowserNowPlayingPath.ts index 93b074b6..6a45784b 100644 --- a/src/hooks/useFolderBrowserNowPlayingPath.ts +++ b/src/hooks/useFolderBrowserNowPlayingPath.ts @@ -3,7 +3,7 @@ import { useLocation } from 'react-router-dom'; import { getMusicDirectory, getMusicIndexes } from '../api/subsonicLibrary'; import type { SubsonicDirectoryEntry } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; -import type { Column, NavPos } from '../utils/folderBrowserHelpers'; +import type { Column, NavPos } from '../utils/componentHelpers/folderBrowserHelpers'; let persistedPlayingPathIds: string[] = []; diff --git a/src/hooks/useFolderBrowserScrolling.ts b/src/hooks/useFolderBrowserScrolling.ts index 6f80b9c9..cd01de9a 100644 --- a/src/hooks/useFolderBrowserScrolling.ts +++ b/src/hooks/useFolderBrowserScrolling.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import type { Column, NavPos } from '../utils/folderBrowserHelpers'; +import type { Column, NavPos } from '../utils/componentHelpers/folderBrowserHelpers'; interface Args { columns: Column[]; diff --git a/src/hooks/useFsDynamicAccent.ts b/src/hooks/useFsDynamicAccent.ts index 34cdc705..b9f3e54e 100644 --- a/src/hooks/useFsDynamicAccent.ts +++ b/src/hooks/useFsDynamicAccent.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { extractCoverColors } from '../utils/dynamicColors'; +import { extractCoverColors } from '../utils/ui/dynamicColors'; // Module-level cache: artKey → accent color string. // Survives track changes so same-album songs reuse the extracted color instantly. diff --git a/src/hooks/useLyrics.ts b/src/hooks/useLyrics.ts index 7923c0ea..6bf5876c 100644 --- a/src/hooks/useLyrics.ts +++ b/src/hooks/useLyrics.ts @@ -10,7 +10,7 @@ import { fetchLyricsPlus, hasWordSync } from '../api/lyricsplus'; import { useAuthStore } from '../store/authStore'; import { useOfflineStore } from '../store/offlineStore'; import { useHotCacheStore } from '../store/hotCacheStore'; -import { getCachedLyrics, putCachedLyrics, lyricsCacheKey } from '../utils/lyricsPersistentCache'; +import { getCachedLyrics, putCachedLyrics, lyricsCacheKey } from '../utils/cache/lyricsPersistentCache'; export type LyricsSource = 'server' | 'lrclib' | 'netease' | 'embedded' | 'lyricsplus'; /** diff --git a/src/hooks/useMiniWindowSetup.ts b/src/hooks/useMiniWindowSetup.ts index 609edb22..62988478 100644 --- a/src/hooks/useMiniWindowSetup.ts +++ b/src/hooks/useMiniWindowSetup.ts @@ -4,7 +4,7 @@ import { useAuthStore } from '../store/authStore'; import { IS_LINUX } from '../utils/platform'; import { EXPANDED_SIZE, EXPANDED_MIN, readStoredExpandedHeight, -} from '../utils/miniPlayerHelpers'; +} from '../utils/componentHelpers/miniPlayerHelpers'; /** Three window-bound setup effects bundled together: * - Linux WebKitGTK smooth-scroll per-window (re-applies after auth hydrates diff --git a/src/hooks/useNowPlayingFetchers.ts b/src/hooks/useNowPlayingFetchers.ts index 87b79718..148dc457 100644 --- a/src/hooks/useNowPlayingFetchers.ts +++ b/src/hooks/useNowPlayingFetchers.ts @@ -7,7 +7,7 @@ import { lastfmGetArtistStats, lastfmGetTrackInfo, lastfmIsConfigured, type LastfmArtistStats, type LastfmTrackInfo, } from '../api/lastfm'; -import { makeCache } from '../utils/nowPlayingCache'; +import { makeCache } from '../utils/cache/nowPlayingCache'; // Module-level TTL caches (shared across mounts) const songMetaCache = makeCache(); diff --git a/src/hooks/useOrbitGuest.ts b/src/hooks/useOrbitGuest.ts index bf6e3cbb..d6b8159f 100644 --- a/src/hooks/useOrbitGuest.ts +++ b/src/hooks/useOrbitGuest.ts @@ -1,5 +1,5 @@ import { getSong } from '../api/subsonicLibrary'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useEffect, useRef } from 'react'; import { useOrbitStore } from '../store/orbitStore'; import { useAuthStore } from '../store/authStore'; diff --git a/src/hooks/useOrbitHost.ts b/src/hooks/useOrbitHost.ts index b8edfe3c..0f7c6aaf 100644 --- a/src/hooks/useOrbitHost.ts +++ b/src/hooks/useOrbitHost.ts @@ -1,5 +1,5 @@ import { getSong } from '../api/subsonicLibrary'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useEffect, useRef } from 'react'; import { useOrbitStore } from '../store/orbitStore'; import { usePlayerStore } from '../store/playerStore'; @@ -18,7 +18,7 @@ import { type OrbitState, type OrbitQueueItem, } from '../api/orbit'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import i18n from '../i18n'; import { pushOrbitEvent } from '../utils/orbitDiag'; diff --git a/src/hooks/useOrbitSongRowBehavior.ts b/src/hooks/useOrbitSongRowBehavior.ts index c439feb1..8ab6ac29 100644 --- a/src/hooks/useOrbitSongRowBehavior.ts +++ b/src/hooks/useOrbitSongRowBehavior.ts @@ -7,7 +7,7 @@ import { evaluateOrbitSuggestGate, OrbitSuggestBlockedError, } from '../utils/orbit'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; /** * Shared behaviour for song rows that in "normal mode" swallow a full list diff --git a/src/hooks/usePendingSmartPolling.ts b/src/hooks/usePendingSmartPolling.ts index 7b0492db..73e29372 100644 --- a/src/hooks/usePendingSmartPolling.ts +++ b/src/hooks/usePendingSmartPolling.ts @@ -3,7 +3,7 @@ import type React from 'react'; import { getPlaylist } from '../api/subsonicPlaylists'; import type { SubsonicPlaylist } from '../api/subsonicTypes'; import { usePlaylistStore } from '../store/playlistStore'; -import type { PendingSmartPlaylist } from '../utils/playlistsSmart'; +import type { PendingSmartPlaylist } from '../utils/playlist/playlistsSmart'; /** * Poll Navidrome every 10 s for each pending smart playlist until its diff --git a/src/hooks/usePlaylistBulkPlayCallbacks.ts b/src/hooks/usePlaylistBulkPlayCallbacks.ts index 0f72cae1..b6bad8c4 100644 --- a/src/hooks/usePlaylistBulkPlayCallbacks.ts +++ b/src/hooks/usePlaylistBulkPlayCallbacks.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import type { Track } from '../store/playerStoreTypes'; -import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from '../utils/playlistBulkPlayActions'; +import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from '../utils/playlist/playlistBulkPlayActions'; export interface PlaylistBulkPlayCallbacksDeps { songsLength: number; diff --git a/src/hooks/usePlaylistDerived.ts b/src/hooks/usePlaylistDerived.ts index 9aa09ed5..704af4be 100644 --- a/src/hooks/usePlaylistDerived.ts +++ b/src/hooks/usePlaylistDerived.ts @@ -2,8 +2,8 @@ import { useMemo } from 'react'; import type { SubsonicSong } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; import { usePlayerStore } from '../store/playerStore'; -import { songToTrack } from '../utils/songToTrack'; -import { getDisplayedSongs, type PlaylistSortDir, type PlaylistSortKey } from '../utils/playlistDisplayedSongs'; +import { songToTrack } from '../utils/playback/songToTrack'; +import { getDisplayedSongs, type PlaylistSortDir, type PlaylistSortKey } from '../utils/playlist/playlistDisplayedSongs'; export interface PlaylistDerivedOptions { filterText: string; diff --git a/src/hooks/usePlaylistDnDReorder.ts b/src/hooks/usePlaylistDnDReorder.ts index 5d389b9d..395a0468 100644 --- a/src/hooks/usePlaylistDnDReorder.ts +++ b/src/hooks/usePlaylistDnDReorder.ts @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import type { SubsonicSong } from '../api/subsonicTypes'; import { useDragDrop } from '../contexts/DragDropContext'; -import { runPlaylistReorderDrop } from '../utils/runPlaylistReorderDrop'; +import { runPlaylistReorderDrop } from '../utils/playlist/runPlaylistReorderDrop'; export interface PlaylistDnDReorderDeps { tracklistRef: React.RefObject; diff --git a/src/hooks/usePlaylistSongMutations.ts b/src/hooks/usePlaylistSongMutations.ts index ba865456..06030b03 100644 --- a/src/hooks/usePlaylistSongMutations.ts +++ b/src/hooks/usePlaylistSongMutations.ts @@ -1,7 +1,7 @@ import type React from 'react'; import type { TFunction } from 'i18next'; import type { SubsonicPlaylist, SubsonicSong } from '../api/subsonicTypes'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; export interface PlaylistSongMutationsDeps { songs: SubsonicSong[]; diff --git a/src/hooks/useRadioMetadata.ts b/src/hooks/useRadioMetadata.ts index 7d071a15..beea080b 100644 --- a/src/hooks/useRadioMetadata.ts +++ b/src/hooks/useRadioMetadata.ts @@ -1,7 +1,7 @@ import type { InternetRadioStation } from '../api/subsonicTypes'; import { useEffect, useRef, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { guessAzuraCastApiUrl, normaliseAzuraCastHomepageUrl, diff --git a/src/hooks/useSidebarNavDnd.ts b/src/hooks/useSidebarNavDnd.ts index 94da9760..07180efd 100644 --- a/src/hooks/useSidebarNavDnd.ts +++ b/src/hooks/useSidebarNavDnd.ts @@ -6,12 +6,12 @@ import { getSystemItemsForReorder, isSidebarNavItemUserHideable, type SidebarNavDropTarget, -} from '../utils/sidebarNavReorder'; +} from '../utils/componentHelpers/sidebarNavReorder'; import { SIDEBAR_NAV_LONG_PRESS_MOVE_CANCEL_PX, SIDEBAR_NAV_LONG_PRESS_MS, isPointerOutsideAsideSidebar, -} from '../utils/sidebarHelpers'; +} from '../utils/componentHelpers/sidebarHelpers'; interface NavDndState { section: 'library' | 'system'; diff --git a/src/hooks/useSidebarNewReleasesUnread.ts b/src/hooks/useSidebarNewReleasesUnread.ts index f7574dfe..f8b1505b 100644 --- a/src/hooks/useSidebarNewReleasesUnread.ts +++ b/src/hooks/useSidebarNewReleasesUnread.ts @@ -7,7 +7,7 @@ import { NEW_RELEASES_UNREAD_SAMPLE_SIZE, NEW_RELEASES_UNREAD_STORAGE_PREFIX, mergeSeenNewReleaseIdsCap, -} from '../utils/sidebarHelpers'; +} from '../utils/componentHelpers/sidebarHelpers'; interface Args { serverId: string; diff --git a/src/hooks/useSidebarPerfProbe.ts b/src/hooks/useSidebarPerfProbe.ts index 6765017a..8e2255c2 100644 --- a/src/hooks/useSidebarPerfProbe.ts +++ b/src/hooks/useSidebarPerfProbe.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { setPerfProbeTelemetryActive } from '../utils/perfTelemetry'; +import { setPerfProbeTelemetryActive } from '../utils/perf/perfTelemetry'; interface PerfCpu { app: number; diff --git a/src/hooks/useSmartCoverCollage.ts b/src/hooks/useSmartCoverCollage.ts index 270eeddc..ea5bee1f 100644 --- a/src/hooks/useSmartCoverCollage.ts +++ b/src/hooks/useSmartCoverCollage.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { filterSongsToActiveLibrary } from '../api/subsonicLibrary'; import { getPlaylist } from '../api/subsonicPlaylists'; import type { SubsonicPlaylist } from '../api/subsonicTypes'; -import { isSmartPlaylistName } from '../utils/playlistsSmart'; +import { isSmartPlaylistName } from '../utils/playlist/playlistsSmart'; /** * Build the 2×2 cover collage for each smart playlist. Pulls each smart diff --git a/src/hooks/useUserMgmtActions.ts b/src/hooks/useUserMgmtActions.ts index 02e5bd85..8244596b 100644 --- a/src/hooks/useUserMgmtActions.ts +++ b/src/hooks/useUserMgmtActions.ts @@ -8,12 +8,12 @@ import { type NdLibrary, type NdUser, } from '../api/navidromeAdmin'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { copyTextToClipboard, encodeServerMagicString, -} from '../utils/serverMagicString'; -import { shortHostFromServerUrl } from '../utils/serverDisplayName'; +} from '../utils/server/serverMagicString'; +import { shortHostFromServerUrl } from '../utils/server/serverDisplayName'; import type { UserFormState } from '../components/settings/UserForm'; interface UseUserMgmtActionsArgs { diff --git a/src/hooks/useWaveformHeights.ts b/src/hooks/useWaveformHeights.ts index 0c6e2d0a..c76989da 100644 --- a/src/hooks/useWaveformHeights.ts +++ b/src/hooks/useWaveformHeights.ts @@ -1,12 +1,12 @@ import React, { useEffect } from 'react'; import type { SeekbarStyle } from '../store/authStoreTypes'; -import type { AnimState } from '../utils/waveformSeekHelpers'; +import type { AnimState } from '../utils/waveform/waveformSeekHelpers'; import { ANIMATED_STYLES, BAR_COUNT, FLAT_WAVE_NORM, WAVE_MORPH_MS, binsToHeights, easeOutCubic, heightsNearlyEqual, makeFlatWaveHeights, makeHeights, -} from '../utils/waveformSeekHelpers'; -import { drawSeekbar } from '../utils/waveformSeekRenderers'; +} from '../utils/waveform/waveformSeekHelpers'; +import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers'; interface Args { trackId: string | undefined; diff --git a/src/hooks/useWaveformInterpolation.ts b/src/hooks/useWaveformInterpolation.ts index 6a938d9b..492b9b84 100644 --- a/src/hooks/useWaveformInterpolation.ts +++ b/src/hooks/useWaveformInterpolation.ts @@ -4,8 +4,8 @@ import { getPlaybackProgressSnapshot } from '../store/playbackProgress'; import { ANIMATED_STYLES, AnimState, INTERPOLATION_PAINT_MIN_MS, isBarQuantizedSeekStyle, quantizeProgressByBars, -} from '../utils/waveformSeekHelpers'; -import { drawSeekbar } from '../utils/waveformSeekRenderers'; +} from '../utils/waveform/waveformSeekHelpers'; +import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers'; interface Args { duration: number; diff --git a/src/hotCachePrefetch.ts b/src/hotCachePrefetch.ts index cb80a416..b5e27951 100644 --- a/src/hotCachePrefetch.ts +++ b/src/hotCachePrefetch.ts @@ -9,7 +9,7 @@ import { bumpHotCachePreviousTrackGrace, clearHotCachePreviousGrace, getDeferHotCachePrefetch, -} from './utils/hotCacheGate'; +} from './utils/cache/hotCacheGate'; import { PREFETCH_AHEAD, type PrefetchJob, diff --git a/src/pages/AlbumDetail.tsx b/src/pages/AlbumDetail.tsx index d8734caa..92c2b76a 100644 --- a/src/pages/AlbumDetail.tsx +++ b/src/pages/AlbumDetail.tsx @@ -2,7 +2,7 @@ import { buildCoverArtUrl, coverArtCacheKey, buildDownloadUrl } from '../api/sub import { setRating, star, unstar } from '../api/subsonicStarRating'; import { getArtistInfo } from '../api/subsonicArtists'; import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { invoke } from '@tauri-apps/api/core'; @@ -22,9 +22,9 @@ import AlbumTrackList from '../components/AlbumTrackList'; import { AlbumDetailToolbar } from '../components/albumDetail/AlbumDetailToolbar'; import { useCachedUrl } from '../components/CachedImage'; import { useTranslation } from 'react-i18next'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { useSelectionStore } from '../store/selectionStore'; -import { sanitizeFilename } from '../utils/albumDetailHelpers'; +import { sanitizeFilename } from '../utils/componentHelpers/albumDetailHelpers'; export default function AlbumDetail() { const { t } = useTranslation(); diff --git a/src/pages/Albums.tsx b/src/pages/Albums.tsx index 224283a6..2bb0b08b 100644 --- a/src/pages/Albums.tsx +++ b/src/pages/Albums.tsx @@ -2,7 +2,7 @@ import { buildDownloadUrl } from '../api/subsonicStreamUrl'; import { getAlbumsByGenre } from '../api/subsonicGenres'; import { getAlbumList, getAlbum } from '../api/subsonicLibrary'; import type { SubsonicAlbum } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useState, useEffect, useRef, useCallback, useMemo, useLayoutEffect } from 'react'; import AlbumCard from '../components/AlbumCard'; import GenreFilterBar from '../components/GenreFilterBar'; @@ -16,13 +16,13 @@ import { useDownloadModalStore } from '../store/downloadModalStore'; import { usePlayerStore } from '../store/playerStore'; import { invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { useZipDownloadStore } from '../store/zipDownloadStore'; import { CheckSquare2, Download, HardDriveDownload, ListMusic, Disc3, ListPlus } from 'lucide-react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; import { useElementClientHeightById } from '../hooks/useResizeClientHeight'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { useRangeSelection } from '../hooks/useRangeSelection'; const ALBUM_GRID_GAP_PX = 16; // matches --space-4 diff --git a/src/pages/ArtistDetail.tsx b/src/pages/ArtistDetail.tsx index 6c13612d..c78c2b02 100644 --- a/src/pages/ArtistDetail.tsx +++ b/src/pages/ArtistDetail.tsx @@ -3,7 +3,7 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { setRating, star, unstar } from '../api/subsonicStarRating'; import { getAlbum } from '../api/subsonicLibrary'; import type { SubsonicArtist, SubsonicAlbum, SubsonicSong, SubsonicArtistInfo } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useEffect, useState, useRef, Fragment, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import AlbumCard from '../components/AlbumCard'; @@ -22,21 +22,21 @@ import { useTranslation } from 'react-i18next'; import { lastfmIsConfigured } from '../api/lastfm'; import LastfmIcon from '../components/LastfmIcon'; import { invalidateCoverArt } from '../utils/imageCache'; -import { showToast } from '../utils/toast'; -import { copyEntityShareLink } from '../utils/copyEntityShareLink'; -import { extractCoverColors } from '../utils/dynamicColors'; +import { showToast } from '../utils/ui/toast'; +import { copyEntityShareLink } from '../utils/share/copyEntityShareLink'; +import { extractCoverColors } from '../utils/ui/dynamicColors'; import StarRating from '../components/StarRating'; import { useArtistLayoutStore, type ArtistSectionId } from '../store/artistLayoutStore'; -import { sanitizeHtml } from '../utils/artistDetailHelpers'; +import { sanitizeHtml } from '../utils/componentHelpers/artistDetailHelpers'; import { useArtistDetailData } from '../hooks/useArtistDetailData'; import { useArtistSimilarArtists } from '../hooks/useArtistSimilarArtists'; import { runArtistDetailPlayAll, runArtistDetailShuffle, runArtistDetailStartRadio, -} from '../utils/runArtistDetailPlay'; +} from '../utils/componentHelpers/runArtistDetailPlay'; import { runArtistEntityRating, runArtistToggleStar, runArtistShare, runArtistImageUpload, -} from '../utils/runArtistDetailActions'; +} from '../utils/componentHelpers/runArtistDetailActions'; import ArtistDetailHero from '../components/artistDetail/ArtistDetailHero'; import ArtistDetailTopTracks from '../components/artistDetail/ArtistDetailTopTracks'; import ArtistDetailSimilarArtists from '../components/artistDetail/ArtistDetailSimilarArtists'; diff --git a/src/pages/Artists.tsx b/src/pages/Artists.tsx index df3b2b68..88f1017a 100644 --- a/src/pages/Artists.tsx +++ b/src/pages/Artists.tsx @@ -10,14 +10,14 @@ import { useTranslation } from 'react-i18next'; import { useVirtualizer } from '@tanstack/react-virtual'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; import { useElementClientHeightById } from '../hooks/useResizeClientHeight'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { ALL_SENTINEL, ALPHABET, ARTIST_LIST_LAST_IN_LETTER_EST, ARTIST_LIST_LETTER_ROW_EST, ARTIST_LIST_ROW_EST, -} from '../utils/artistsHelpers'; +} from '../utils/componentHelpers/artistsHelpers'; import { useArtistsFiltering } from '../hooks/useArtistsFiltering'; import { useArtistsInfiniteScroll } from '../hooks/useArtistsInfiniteScroll'; import { ArtistsGridView } from '../components/artists/ArtistsGridView'; diff --git a/src/pages/ComposerDetail.tsx b/src/pages/ComposerDetail.tsx index 8d2c1d8e..a1f4dcfd 100644 --- a/src/pages/ComposerDetail.tsx +++ b/src/pages/ComposerDetail.tsx @@ -13,8 +13,8 @@ import { open } from '@tauri-apps/plugin-shell'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; -import { copyEntityShareLink } from '../utils/copyEntityShareLink'; -import { showToast } from '../utils/toast'; +import { copyEntityShareLink } from '../utils/share/copyEntityShareLink'; +import { showToast } from '../utils/ui/toast'; /** Strip dangerous tags/attributes from server-provided HTML. Mirrors the * ArtistDetail sanitiser — kept inline because it's a 10-liner not worth a diff --git a/src/pages/Composers.tsx b/src/pages/Composers.tsx index 7116d8d2..353d342b 100644 --- a/src/pages/Composers.tsx +++ b/src/pages/Composers.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import { useVirtualizer } from '@tanstack/react-virtual'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; import { useElementClientHeightById } from '../hooks/useResizeClientHeight'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; const ALL_SENTINEL = 'ALL'; const ALPHABET = [ALL_SENTINEL, '#', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')]; diff --git a/src/pages/DeviceSync.tsx b/src/pages/DeviceSync.tsx index 07efca09..96282f09 100644 --- a/src/pages/DeviceSync.tsx +++ b/src/pages/DeviceSync.tsx @@ -10,13 +10,13 @@ import CustomSelect from '../components/CustomSelect'; import { useTranslation } from 'react-i18next'; import { useDeviceSyncStore, DeviceSyncSource } from '../store/deviceSyncStore'; import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { IS_WINDOWS } from '../utils/platform'; import { formatBytes, type SourceTab, -} from '../utils/deviceSyncHelpers'; +} from '../utils/deviceSync/deviceSyncHelpers'; import { useDeviceSyncDrives } from '../hooks/useDeviceSyncDrives'; import { useDeviceSyncSourceStatuses } from '../hooks/useDeviceSyncSourceStatuses'; import { useDeviceSyncBrowser } from '../hooks/useDeviceSyncBrowser'; @@ -26,13 +26,13 @@ import { runDeviceSyncMigrationPreview, runDeviceSyncMigrationExecute, type MigrationPhase, type MigrationPair, type MigrationResult, -} from '../utils/runDeviceSyncMigration'; +} from '../utils/deviceSync/runDeviceSyncMigration'; import { runDeviceSyncSummaryPrompt, runDeviceSyncExecute, type SyncDelta, -} from '../utils/runDeviceSyncExecution'; -import { runDeviceSyncChooseFolder } from '../utils/runDeviceSyncChooseFolder'; +} from '../utils/deviceSync/runDeviceSyncExecution'; +import { runDeviceSyncChooseFolder } from '../utils/deviceSync/runDeviceSyncChooseFolder'; import DeviceSyncHeader from '../components/deviceSync/DeviceSyncHeader'; import DeviceSyncPreSyncModal from '../components/deviceSync/DeviceSyncPreSyncModal'; import DeviceSyncMigrationModal from '../components/deviceSync/DeviceSyncMigrationModal'; diff --git a/src/pages/Favorites.tsx b/src/pages/Favorites.tsx index dd569444..7d0826ff 100644 --- a/src/pages/Favorites.tsx +++ b/src/pages/Favorites.tsx @@ -1,6 +1,6 @@ import { setRating, unstar } from '../api/subsonicStarRating'; import type { SubsonicAlbum, SubsonicArtist, SubsonicSong, InternetRadioStation } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns'; import { TopFavoriteArtistsRow } from '../components/favorites/TopFavoriteArtists'; diff --git a/src/pages/FolderBrowser.tsx b/src/pages/FolderBrowser.tsx index dd620204..a8c75552 100644 --- a/src/pages/FolderBrowser.tsx +++ b/src/pages/FolderBrowser.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { entryToAlbumIfPresent, entryToTrack, type Column, type ColumnKind, type NavPos, -} from '../utils/folderBrowserHelpers'; +} from '../utils/componentHelpers/folderBrowserHelpers'; import FolderBrowserColumn from '../components/folderBrowser/FolderBrowserColumn'; import { useFolderBrowserNowPlayingPath } from '../hooks/useFolderBrowserNowPlayingPath'; import { useFolderBrowserScrolling } from '../hooks/useFolderBrowserScrolling'; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4c14c493..6a431c0a 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -12,9 +12,9 @@ import { NavLink, useNavigate } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; import { useHomeStore } from '../store/homeStore'; import { useAuthStore } from '../store/authStore'; -import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mixRatingFilter'; -import { usePerfProbeFlags } from '../utils/perfFlags'; -import { bumpPerfCounter } from '../utils/perfTelemetry'; +import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; +import { bumpPerfCounter } from '../utils/perf/perfTelemetry'; import { dedupeById } from '../utils/dedupeById'; /** Match Random Albums overshoot when mix filter uses album/artist axes so hero + discover row can still fill. */ diff --git a/src/pages/InternetRadio.tsx b/src/pages/InternetRadio.tsx index 54d7fc07..a60ff9fa 100644 --- a/src/pages/InternetRadio.tsx +++ b/src/pages/InternetRadio.tsx @@ -11,7 +11,7 @@ import { invalidateCoverArt } from '../utils/imageCache'; import CustomSelect from '../components/CustomSelect'; import { useTranslation } from 'react-i18next'; import { open } from '@tauri-apps/plugin-shell'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import RadioToolbar, { type RadioSortBy } from '../components/internetRadio/RadioToolbar'; import AlphabetFilterBar from '../components/internetRadio/AlphabetFilterBar'; import RadioCard from '../components/internetRadio/RadioCard'; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 0580fc11..173a4c42 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -11,8 +11,8 @@ import { DECODED_PASSWORD_VISUAL_MASK, encodeServerMagicString, type ServerMagicPayload, -} from '../utils/serverMagicString'; -import { shortHostFromServerUrl, serverListDisplayLabel } from '../utils/serverDisplayName'; +} from '../utils/server/serverMagicString'; +import { shortHostFromServerUrl, serverListDisplayLabel } from '../utils/server/serverDisplayName'; const PsysonicLogo = () => ( Psysonic diff --git a/src/pages/LosslessAlbums.tsx b/src/pages/LosslessAlbums.tsx index cbdf4fa6..ccc507cb 100644 --- a/src/pages/LosslessAlbums.tsx +++ b/src/pages/LosslessAlbums.tsx @@ -1,7 +1,7 @@ import { buildDownloadUrl } from '../api/subsonicStreamUrl'; import { getAlbum } from '../api/subsonicLibrary'; import type { SubsonicAlbum } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import AlbumCard from '../components/AlbumCard'; import { ndListLosslessAlbumsPage } from '../api/navidromeBrowse'; @@ -12,8 +12,8 @@ import { useDownloadModalStore } from '../store/downloadModalStore'; import { usePlayerStore } from '../store/playerStore'; import { useZipDownloadStore } from '../store/zipDownloadStore'; import { useRangeSelection } from '../hooks/useRangeSelection'; -import { usePerfProbeFlags } from '../utils/perfFlags'; -import { showToast } from '../utils/toast'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; +import { showToast } from '../utils/ui/toast'; import { invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; import { CheckSquare2, Download, HardDriveDownload, ListPlus } from 'lucide-react'; diff --git a/src/pages/LuckyMix.tsx b/src/pages/LuckyMix.tsx index 871d4c98..a362542b 100644 --- a/src/pages/LuckyMix.tsx +++ b/src/pages/LuckyMix.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; -import { buildAndPlayLuckyMix } from '../utils/luckyMix'; +import { buildAndPlayLuckyMix } from '../utils/mix/luckyMix'; export default function LuckyMixPage() { const navigate = useNavigate(); diff --git a/src/pages/MostPlayed.tsx b/src/pages/MostPlayed.tsx index ad6cbb92..1c3e0796 100644 --- a/src/pages/MostPlayed.tsx +++ b/src/pages/MostPlayed.tsx @@ -1,14 +1,14 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { getAlbumList, getAlbum } from '../api/subsonicLibrary'; import type { SubsonicAlbum } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp, UsersRound, Play, ListPlus } from 'lucide-react'; import { useAuthStore } from '../store/authStore'; import { usePlayerStore } from '../store/playerStore'; import CachedImage from '../components/CachedImage'; -import { playAlbum } from '../utils/playAlbum'; +import { playAlbum } from '../utils/playback/playAlbum'; import { useTranslation } from 'react-i18next'; const PAGE_SIZE = 50; diff --git a/src/pages/NewReleases.tsx b/src/pages/NewReleases.tsx index 17a37889..fadf8d5f 100644 --- a/src/pages/NewReleases.tsx +++ b/src/pages/NewReleases.tsx @@ -13,7 +13,7 @@ import { useDownloadModalStore } from '../store/downloadModalStore'; import { usePlayerStore } from '../store/playerStore'; import { invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { useZipDownloadStore } from '../store/zipDownloadStore'; import { useRangeSelection } from '../hooks/useRangeSelection'; diff --git a/src/pages/NowPlaying.tsx b/src/pages/NowPlaying.tsx index c19913cd..feeb5110 100644 --- a/src/pages/NowPlaying.tsx +++ b/src/pages/NowPlaying.tsx @@ -8,7 +8,7 @@ import { open as shellOpen } from '@tauri-apps/plugin-shell'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useLyricsStore } from '../store/lyricsStore'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useCachedUrl } from '../components/CachedImage'; import CachedImage from '../components/CachedImage'; import { useRadioMetadata } from '../hooks/useRadioMetadata'; @@ -25,7 +25,7 @@ import { formatTime, formatCompact, isoToParts, buildContributorRows, type ContributorRow, -} from '../utils/nowPlayingHelpers'; +} from '../utils/componentHelpers/nowPlayingHelpers'; import NpCardWrap from '../components/nowPlaying/NpCardWrap'; import NpColumnEl from '../components/nowPlaying/NpColumnEl'; import RadioView from '../components/nowPlaying/RadioView'; diff --git a/src/pages/PlaylistDetail.tsx b/src/pages/PlaylistDetail.tsx index f1545fec..9d43c4e5 100644 --- a/src/pages/PlaylistDetail.tsx +++ b/src/pages/PlaylistDetail.tsx @@ -26,9 +26,9 @@ import { isSmartPlaylistName, displayPlaylistName, codecLabel, -} from '../utils/playlistDetailHelpers'; -import type { SpotifyCsvTrack } from '../utils/spotifyCsvImport'; -import { runPlaylistCsvImport } from '../utils/runPlaylistCsvImport'; +} from '../utils/componentHelpers/playlistDetailHelpers'; +import type { SpotifyCsvTrack } from '../utils/playlist/spotifyCsvImport'; +import { runPlaylistCsvImport } from '../utils/playlist/runPlaylistCsvImport'; import PlaylistEditModal from '../components/playlist/PlaylistEditModal'; import CsvImportReportModal from '../components/playlist/CsvImportReportModal'; import PlaylistSongSearchPanel from '../components/playlist/PlaylistSongSearchPanel'; @@ -36,11 +36,11 @@ import PlaylistSuggestions from '../components/playlist/PlaylistSuggestions'; import PlaylistHero from '../components/playlist/PlaylistHero'; import PlaylistTracklist from '../components/playlist/PlaylistTracklist'; import PlaylistFilterToolbar from '../components/playlist/PlaylistFilterToolbar'; -import type { PlaylistSortKey, PlaylistSortDir } from '../utils/playlistDisplayedSongs'; -import { runPlaylistZipDownload } from '../utils/runPlaylistZipDownload'; -import { runPlaylistSaveMeta } from '../utils/runPlaylistSaveMeta'; -import { runPlaylistLoad } from '../utils/runPlaylistLoad'; -import { startPlaylistRowDrag } from '../utils/startPlaylistRowDrag'; +import type { PlaylistSortKey, PlaylistSortDir } from '../utils/playlist/playlistDisplayedSongs'; +import { runPlaylistZipDownload } from '../utils/playlist/runPlaylistZipDownload'; +import { runPlaylistSaveMeta } from '../utils/playlist/runPlaylistSaveMeta'; +import { runPlaylistLoad } from '../utils/playlist/runPlaylistLoad'; +import { startPlaylistRowDrag } from '../utils/playlist/startPlaylistRowDrag'; import { usePlaylistCovers } from '../hooks/usePlaylistCovers'; import { usePlaylistSelection } from '../hooks/usePlaylistSelection'; import { usePlaylistSuggestions } from '../hooks/usePlaylistSuggestions'; diff --git a/src/pages/Playlists.tsx b/src/pages/Playlists.tsx index 60b9c4b7..c0abfc39 100644 --- a/src/pages/Playlists.tsx +++ b/src/pages/Playlists.tsx @@ -2,7 +2,7 @@ import { getPlaylist } from '../api/subsonicPlaylists'; import { getGenres } from '../api/subsonicGenres'; import { filterSongsToActiveLibrary } from '../api/subsonicLibrary'; import type { SubsonicPlaylist, SubsonicGenre } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useEffect, useState, useRef, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { usePlayerStore } from '../store/playerStore'; @@ -11,19 +11,19 @@ import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import { useRangeSelection } from '../hooks/useRangeSelection'; -import { formatHumanHoursMinutes } from '../utils/formatHumanDuration'; +import { formatHumanHoursMinutes } from '../utils/format/formatHumanDuration'; import { defaultSmartFilters, isSmartPlaylistName, type SmartFilters, type PendingSmartPlaylist, -} from '../utils/playlistsSmart'; +} from '../utils/playlist/playlistsSmart'; import { useSmartCoverCollage } from '../hooks/useSmartCoverCollage'; import { usePlaylistsLibraryScopeCounts } from '../hooks/usePlaylistsLibraryScopeCounts'; import { usePendingSmartPolling } from '../hooks/usePendingSmartPolling'; -import { runPlaylistsOpenSmartEditor } from '../utils/runPlaylistsOpenSmartEditor'; -import { runPlaylistsSaveSmart } from '../utils/runPlaylistsSaveSmart'; +import { runPlaylistsOpenSmartEditor } from '../utils/playlist/runPlaylistsOpenSmartEditor'; +import { runPlaylistsSaveSmart } from '../utils/playlist/runPlaylistsSaveSmart'; import { runPlaylistDelete, runPlaylistDeleteSelected, runPlaylistMergeSelected, -} from '../utils/runPlaylistsActions'; +} from '../utils/playlist/runPlaylistsActions'; import PlaylistsSmartEditor from '../components/playlists/PlaylistsSmartEditor'; import PlaylistsHeader from '../components/playlists/PlaylistsHeader'; import PlaylistCard from '../components/playlists/PlaylistCard'; diff --git a/src/pages/RandomAlbums.tsx b/src/pages/RandomAlbums.tsx index a2ca67fe..151b5477 100644 --- a/src/pages/RandomAlbums.tsx +++ b/src/pages/RandomAlbums.tsx @@ -8,12 +8,12 @@ import AlbumCard from '../components/AlbumCard'; import GenreFilterBar from '../components/GenreFilterBar'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; -import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mixRatingFilter'; +import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter'; import { useOfflineStore } from '../store/offlineStore'; import { useDownloadModalStore } from '../store/downloadModalStore'; import { invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { useZipDownloadStore } from '../store/zipDownloadStore'; import { useRangeSelection } from '../hooks/useRangeSelection'; diff --git a/src/pages/RandomMix.tsx b/src/pages/RandomMix.tsx index 26acdc7b..6cf0cb0a 100644 --- a/src/pages/RandomMix.tsx +++ b/src/pages/RandomMix.tsx @@ -1,7 +1,7 @@ import { star, unstar } from '../api/subsonicStarRating'; import { getGenres } from '../api/subsonicGenres'; import type { SubsonicSong, SubsonicGenre } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useEffect, useMemo, useState } from 'react'; import { usePlayerStore } from '../store/playerStore'; import { usePreviewStore } from '../store/previewStore'; @@ -12,8 +12,8 @@ import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior'; import { fetchRandomMixSongsUntilFull, getMixMinRatingsConfigFromAuth, -} from '../utils/mixRatingFilter'; -import { AUDIOBOOK_GENRES, filterRandomMixSongs, formatRandomMixDuration } from '../utils/randomMixHelpers'; +} from '../utils/mix/mixRatingFilter'; +import { AUDIOBOOK_GENRES, filterRandomMixSongs, formatRandomMixDuration } from '../utils/componentHelpers/randomMixHelpers'; import RandomMixHeader from '../components/randomMix/RandomMixHeader'; import RandomMixFiltersPanel from '../components/randomMix/RandomMixFiltersPanel'; import RandomMixGenrePanel from '../components/randomMix/RandomMixGenrePanel'; diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index e9146f64..bddcc487 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -19,7 +19,7 @@ import { SystemTab } from '../components/settings/SystemTab'; import { SETTINGS_INDEX, type Tab, matchScore, resolveTab } from '../components/settings/settingsTabs'; import { UserManagementSection } from '../components/settings/UserManagementSection'; import { ndLogin } from '../api/navidromeAdmin'; -import { type ServerMagicPayload } from '../utils/serverMagicString'; +import { type ServerMagicPayload } from '../utils/server/serverMagicString'; export default function Settings() { diff --git a/src/pages/Statistics.tsx b/src/pages/Statistics.tsx index 1404352e..3b2a96d8 100644 --- a/src/pages/Statistics.tsx +++ b/src/pages/Statistics.tsx @@ -3,7 +3,7 @@ import { getAlbumList } from '../api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicGenre } from '../api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { Share2 } from 'lucide-react'; -import { formatHumanHoursMinutes } from '../utils/formatHumanDuration'; +import { formatHumanHoursMinutes } from '../utils/format/formatHumanDuration'; import AlbumRow from '../components/AlbumRow'; import StatsExportModal from '../components/StatsExportModal'; import { useTranslation } from 'react-i18next'; diff --git a/src/pages/Tracks.tsx b/src/pages/Tracks.tsx index e0687f09..d9a1ab9d 100644 --- a/src/pages/Tracks.tsx +++ b/src/pages/Tracks.tsx @@ -1,7 +1,7 @@ import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { getRandomSongs } from '../api/subsonicLibrary'; import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { songToTrack } from '../utils/playback/songToTrack'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react'; @@ -11,9 +11,9 @@ import { usePlayerStore } from '../store/playerStore'; import CachedImage from '../components/CachedImage'; import SongRail from '../components/SongRail'; import VirtualSongList from '../components/VirtualSongList'; -import { playSongNow } from '../utils/playSong'; +import { playSongNow } from '../utils/playback/playSong'; import { ndListSongs, ndInvalidateSongsCache } from '../api/navidromeBrowse'; -import { usePerfProbeFlags } from '../utils/perfFlags'; +import { usePerfProbeFlags } from '../utils/perf/perfFlags'; const RANDOM_RAIL_SIZE = 18; /** Over-fetch buffer so the client-side `userRating > 0` filter still leaves diff --git a/src/pages/WhatsNew.tsx b/src/pages/WhatsNew.tsx index 9bdda429..da215119 100644 --- a/src/pages/WhatsNew.tsx +++ b/src/pages/WhatsNew.tsx @@ -4,8 +4,8 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { version } from '../../package.json'; import changelogRaw from '../../CHANGELOG.md?raw'; -import { findChangelogReleaseEntry } from '../utils/changelogReleaseMatch'; -import { renderChangelogBody } from '../utils/changelogMarkdown'; +import { findChangelogReleaseEntry } from '../utils/changelog/changelogReleaseMatch'; +import { renderChangelogBody } from '../utils/changelog/changelogMarkdown'; export default function WhatsNew() { const { t } = useTranslation(); diff --git a/src/store/applyQueueHistorySnapshot.ts b/src/store/applyQueueHistorySnapshot.ts index f4fdaea9..a14fe222 100644 --- a/src/store/applyQueueHistorySnapshot.ts +++ b/src/store/applyQueueHistorySnapshot.ts @@ -1,6 +1,6 @@ import { reportNowPlaying } from '../api/subsonicScrobble'; import { invoke } from '@tauri-apps/api/core'; -import { getPlaybackSourceKind } from '../utils/resolvePlaybackUrl'; +import { getPlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl'; import { useAuthStore } from './authStore'; import { bumpPlayGeneration, @@ -10,7 +10,7 @@ import { import { clearPreloadingIds } from './gaplessPreloadState'; import { deriveNormalizationSnapshot } from './normalizationSnapshot'; import type { PlayerState } from './playerStoreTypes'; -import { sameQueueTrackId, shallowCloneQueueTracks } from '../utils/queueIdentity'; +import { sameQueueTrackId, shallowCloneQueueTracks } from '../utils/playback/queueIdentity'; import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore'; import { setPendingQueueListScrollTop, diff --git a/src/store/audioEventHandlers.ts b/src/store/audioEventHandlers.ts index 0e1ce1e4..91353de7 100644 --- a/src/store/audioEventHandlers.ts +++ b/src/store/audioEventHandlers.ts @@ -2,12 +2,12 @@ import { reportNowPlaying, scrobbleSong } from '../api/subsonicScrobble'; import type { Track } from './playerStoreTypes'; import { invoke } from '@tauri-apps/api/core'; import { lastfmGetTrackLoved, lastfmScrobble, lastfmUpdateNowPlaying } from '../api/lastfm'; -import { setDeferHotCachePrefetch } from '../utils/hotCacheGate'; -import { getPerfProbeFlags } from '../utils/perfFlags'; -import { bumpPerfCounter } from '../utils/perfTelemetry'; -import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/resolveReplayGainDb'; -import { showToast } from '../utils/toast'; +import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; +import { getPerfProbeFlags } from '../utils/perf/perfFlags'; +import { bumpPerfCounter } from '../utils/perf/perfTelemetry'; +import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; +import { showToast } from '../utils/ui/toast'; import { useAuthStore } from './authStore'; import { getPlayGeneration, setIsAudioPaused } from './engineState'; import { diff --git a/src/store/audioListenerSetup/audioEngineListeners.ts b/src/store/audioListenerSetup/audioEngineListeners.ts index 9881651e..0e9cac82 100644 --- a/src/store/audioListenerSetup/audioEngineListeners.ts +++ b/src/store/audioListenerSetup/audioEngineListeners.ts @@ -1,7 +1,7 @@ import { listen } from '@tauri-apps/api/event'; -import { streamUrlTrackId } from '../../utils/resolvePlaybackUrl'; -import { normalizationAlmostEqual } from '../../utils/normalizationCompare'; -import { normalizeAnalysisTrackId } from '../../utils/queueIdentity'; +import { streamUrlTrackId } from '../../utils/playback/resolvePlaybackUrl'; +import { normalizationAlmostEqual } from '../../utils/audio/normalizationCompare'; +import { normalizeAnalysisTrackId } from '../../utils/playback/queueIdentity'; import { handleAudioEnded, handleAudioError, diff --git a/src/store/audioListenerSetup/authSyncListener.ts b/src/store/audioListenerSetup/authSyncListener.ts index a651e957..fad46663 100644 --- a/src/store/audioListenerSetup/authSyncListener.ts +++ b/src/store/audioListenerSetup/authSyncListener.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; import { useAuthStore } from '../authStore'; import { onAnalysisStorageChanged } from '../analysisSync'; import { emitNormalizationDebug } from '../normalizationDebug'; diff --git a/src/store/audioListenerSetup/initialAudioSync.ts b/src/store/audioListenerSetup/initialAudioSync.ts index 456aa4bb..d1f39f3f 100644 --- a/src/store/audioListenerSetup/initialAudioSync.ts +++ b/src/store/audioListenerSetup/initialAudioSync.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; import { useAuthStore } from '../authStore'; import { emitNormalizationDebug } from '../normalizationDebug'; import { invokeAudioSetNormalizationDeduped } from '../normalizationIpcDedupe'; diff --git a/src/store/authAudioSettingsActions.ts b/src/store/authAudioSettingsActions.ts index e66ab2c8..b1e73faa 100644 --- a/src/store/authAudioSettingsActions.ts +++ b/src/store/authAudioSettingsActions.ts @@ -1,4 +1,4 @@ -import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/loudnessPreAnalysisSlider'; +import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/audio/loudnessPreAnalysisSlider'; import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from './authStoreDefaults'; import { usePlayerStore } from './playerStore'; import type { AuthState } from './authStoreTypes'; diff --git a/src/store/authPerServerCapabilityActions.ts b/src/store/authPerServerCapabilityActions.ts index 2ede58cb..9bdc81f0 100644 --- a/src/store/authPerServerCapabilityActions.ts +++ b/src/store/authPerServerCapabilityActions.ts @@ -1,4 +1,4 @@ -import { isNavidromeAudiomuseSoftwareEligible } from '../utils/subsonicServerIdentity'; +import { isNavidromeAudiomuseSoftwareEligible } from '../utils/server/subsonicServerIdentity'; import type { AuthState } from './authStoreTypes'; type SetState = ( diff --git a/src/store/authStoreHelpers.ts b/src/store/authStoreHelpers.ts index 4c16cbcf..2105f374 100644 --- a/src/store/authStoreHelpers.ts +++ b/src/store/authStoreHelpers.ts @@ -1,4 +1,4 @@ -import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/loudnessPreAnalysisSlider'; +import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/audio/loudnessPreAnalysisSlider'; import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB, LOUDNESS_LUFS_PRESETS, diff --git a/src/store/authStoreRehydrate.ts b/src/store/authStoreRehydrate.ts index d2aa872e..7ecb2b59 100644 --- a/src/store/authStoreRehydrate.ts +++ b/src/store/authStoreRehydrate.ts @@ -2,7 +2,7 @@ import { IS_LINUX } from '../utils/platform'; import { LOUDNESS_PRE_ANALYSIS_REF_TARGET_LUFS, clampStoredLoudnessPreAnalysisAttenuationRefDb, -} from '../utils/loudnessPreAnalysisSlider'; +} from '../utils/audio/loudnessPreAnalysisSlider'; import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from './authStoreDefaults'; import { clampMixFilterMinStars, diff --git a/src/store/authStoreTypes.ts b/src/store/authStoreTypes.ts index 47948c3a..35e6da7b 100644 --- a/src/store/authStoreTypes.ts +++ b/src/store/authStoreTypes.ts @@ -2,7 +2,7 @@ import type { EntityRatingSupportLevel } from '../api/subsonicTypes'; import type { InstantMixProbeResult, SubsonicServerIdentity, -} from '../utils/subsonicServerIdentity'; +} from '../utils/server/subsonicServerIdentity'; export interface ServerProfile { id: string; diff --git a/src/store/hotCacheStore.ts b/src/store/hotCacheStore.ts index 90fef9d9..1021babe 100644 --- a/src/store/hotCacheStore.ts +++ b/src/store/hotCacheStore.ts @@ -2,7 +2,7 @@ import type { Track } from './playerStoreTypes'; import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import { invoke } from '@tauri-apps/api/core'; -import { isHotCachePreviousTrackUnderGrace } from '../utils/hotCacheGate'; +import { isHotCachePreviousTrackUnderGrace } from '../utils/cache/hotCacheGate'; import { emitAnalysisStorageChanged } from './analysisSync'; import { useAuthStore } from './authStore'; diff --git a/src/store/loudnessRefresh.test.ts b/src/store/loudnessRefresh.test.ts index f9815ce9..a6b9e609 100644 --- a/src/store/loudnessRefresh.test.ts +++ b/src/store/loudnessRefresh.test.ts @@ -39,7 +39,7 @@ const hoisted = vi.hoisted(() => { vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); vi.mock('../api/subsonicStreamUrl', () => ({ buildStreamUrl: hoisted.buildStreamUrlMock })); -vi.mock('../utils/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock })); +vi.mock('../utils/server/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock })); vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); vi.mock('./playerStore', () => ({ usePlayerStore: { diff --git a/src/store/loudnessRefresh.ts b/src/store/loudnessRefresh.ts index 983b08fb..9d9d5803 100644 --- a/src/store/loudnessRefresh.ts +++ b/src/store/loudnessRefresh.ts @@ -1,6 +1,6 @@ import { buildStreamUrl } from '../api/subsonicStreamUrl'; import { invoke } from '@tauri-apps/api/core'; -import { redactSubsonicUrlForLog } from '../utils/redactSubsonicUrl'; +import { redactSubsonicUrlForLog } from '../utils/server/redactSubsonicUrl'; import { useAuthStore } from './authStore'; import { usePlayerStore } from './playerStore'; import { emitNormalizationDebug } from './normalizationDebug'; diff --git a/src/store/miscActions.ts b/src/store/miscActions.ts index a4085517..06ae791f 100644 --- a/src/store/miscActions.ts +++ b/src/store/miscActions.ts @@ -1,8 +1,8 @@ import { getPlayQueue } from '../api/subsonicPlayQueue'; import { invoke } from '@tauri-apps/api/core'; import i18n from '../i18n'; -import { songToTrack } from '../utils/songToTrack'; -import { showToast } from '../utils/toast'; +import { songToTrack } from '../utils/playback/songToTrack'; +import { showToast } from '../utils/ui/toast'; import { useAuthStore } from './authStore'; import { bumpPlayGeneration, diff --git a/src/store/nextAction.ts b/src/store/nextAction.ts index 52d5dbf4..8823d58b 100644 --- a/src/store/nextAction.ts +++ b/src/store/nextAction.ts @@ -1,7 +1,7 @@ import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists'; import { invoke } from '@tauri-apps/api/core'; -import { buildInfiniteQueueCandidates } from '../utils/buildInfiniteQueueCandidates'; -import { songToTrack } from '../utils/songToTrack'; +import { buildInfiniteQueueCandidates } from '../utils/playback/buildInfiniteQueueCandidates'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useAuthStore } from './authStore'; import { setIsAudioPaused } from './engineState'; import { diff --git a/src/store/normalizationIpcDedupe.test.ts b/src/store/normalizationIpcDedupe.test.ts index 11259aca..bdf608e6 100644 --- a/src/store/normalizationIpcDedupe.test.ts +++ b/src/store/normalizationIpcDedupe.test.ts @@ -17,7 +17,7 @@ const { authState, invokeMock } = vi.hoisted(() => ({ vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock })); vi.mock('./authStore', () => ({ useAuthStore: { getState: () => authState } })); -vi.mock('../utils/loudnessPreAnalysisSlider', () => ({ +vi.mock('../utils/audio/loudnessPreAnalysisSlider', () => ({ effectiveLoudnessPreAnalysisAttenuationDb: (attenuation: number) => attenuation, })); diff --git a/src/store/normalizationIpcDedupe.ts b/src/store/normalizationIpcDedupe.ts index cc76445c..230d2296 100644 --- a/src/store/normalizationIpcDedupe.ts +++ b/src/store/normalizationIpcDedupe.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from './authStore'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/audio/loudnessPreAnalysisSlider'; /** * Two IPC entry points to the Rust normalization pipeline that get hammered diff --git a/src/store/normalizationSnapshot.test.ts b/src/store/normalizationSnapshot.test.ts index 3e1dc35d..214e0d87 100644 --- a/src/store/normalizationSnapshot.test.ts +++ b/src/store/normalizationSnapshot.test.ts @@ -21,7 +21,7 @@ vi.mock('./authStore', () => ({ useAuthStore: { getState: () => authState }, })); -vi.mock('../utils/resolveReplayGainDb', () => ({ +vi.mock('../utils/audio/resolveReplayGainDb', () => ({ resolveReplayGainDb: vi.fn( (track: Track) => (track as Track & { _testGain?: number | null })._testGain ?? null, ), @@ -99,7 +99,7 @@ describe("engine='replaygain'", () => { }); it('passes neighbour-track context for album-mode resolution', async () => { - const { resolveReplayGainDb } = await import('../utils/resolveReplayGainDb'); + const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb'); const spy = vi.mocked(resolveReplayGainDb); spy.mockClear(); authState.normalizationEngine = 'replaygain'; @@ -111,7 +111,7 @@ describe("engine='replaygain'", () => { }); it('passes null for prev when queueIndex is 0', async () => { - const { resolveReplayGainDb } = await import('../utils/resolveReplayGainDb'); + const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb'); const spy = vi.mocked(resolveReplayGainDb); spy.mockClear(); authState.normalizationEngine = 'replaygain'; @@ -123,7 +123,7 @@ describe("engine='replaygain'", () => { }); it('passes null for next when queueIndex is the last slot', async () => { - const { resolveReplayGainDb } = await import('../utils/resolveReplayGainDb'); + const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb'); const spy = vi.mocked(resolveReplayGainDb); spy.mockClear(); authState.normalizationEngine = 'replaygain'; diff --git a/src/store/normalizationSnapshot.ts b/src/store/normalizationSnapshot.ts index 401cffbd..4fa07062 100644 --- a/src/store/normalizationSnapshot.ts +++ b/src/store/normalizationSnapshot.ts @@ -1,6 +1,6 @@ import type { PlayerState, Track } from './playerStoreTypes'; import { useAuthStore } from './authStore'; -import { resolveReplayGainDb } from '../utils/resolveReplayGainDb'; +import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; /** * Compute the normalization fields that should land in the next state commit * when the runtime switches tracks or rewrites the queue. Three branches: diff --git a/src/store/offlineStore.ts b/src/store/offlineStore.ts index 9a3d6389..f2bd9ad3 100644 --- a/src/store/offlineStore.ts +++ b/src/store/offlineStore.ts @@ -6,7 +6,7 @@ import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from './authStore'; -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { useOfflineJobStore, cancelledDownloads } from './offlineJobStore'; import { emitAnalysisStorageChanged } from './analysisSync'; diff --git a/src/store/playTrackAction.ts b/src/store/playTrackAction.ts index e694382c..6385b306 100644 --- a/src/store/playTrackAction.ts +++ b/src/store/playTrackAction.ts @@ -1,11 +1,11 @@ import { reportNowPlaying } from '../api/subsonicScrobble'; import { invoke } from '@tauri-apps/api/core'; import { lastfmGetTrackLoved, lastfmUpdateNowPlaying } from '../api/lastfm'; -import { setDeferHotCachePrefetch } from '../utils/hotCacheGate'; +import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; import { orbitBulkGuard } from '../utils/orbitBulkGuard'; -import { sameQueueTrackId } from '../utils/queueIdentity'; -import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/resolveReplayGainDb'; +import { sameQueueTrackId } from '../utils/playback/queueIdentity'; +import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; import { useAuthStore } from './authStore'; import { bumpPlayGeneration, diff --git a/src/store/playbackUrlRouting.test.ts b/src/store/playbackUrlRouting.test.ts index aebf2f5d..84a87df7 100644 --- a/src/store/playbackUrlRouting.test.ts +++ b/src/store/playbackUrlRouting.test.ts @@ -21,7 +21,7 @@ vi.mock('./offlineStore', () => ({ }, })); -vi.mock('../utils/resolvePlaybackUrl', () => ({ +vi.mock('../utils/playback/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: vi.fn((trackId: string, serverId: string) => { if (offlineStoreState.localUrlByKey.has(`${serverId}:${trackId}`)) { return `psysonic-local://${serverId}/${trackId}`; diff --git a/src/store/playbackUrlRouting.ts b/src/store/playbackUrlRouting.ts index d9faa615..ad818ac2 100644 --- a/src/store/playbackUrlRouting.ts +++ b/src/store/playbackUrlRouting.ts @@ -1,5 +1,5 @@ -import { resolvePlaybackUrl, type PlaybackSourceKind } from '../utils/resolvePlaybackUrl'; -import { sameQueueTrackId } from '../utils/queueIdentity'; +import { resolvePlaybackUrl, type PlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl'; +import { sameQueueTrackId } from '../utils/playback/queueIdentity'; import { useOfflineStore } from './offlineStore'; /** diff --git a/src/store/playerStoreTypes.ts b/src/store/playerStoreTypes.ts index 0777d146..685232c5 100644 --- a/src/store/playerStoreTypes.ts +++ b/src/store/playerStoreTypes.ts @@ -1,5 +1,5 @@ import type { InternetRadioStation } from '../api/subsonicTypes'; -import type { PlaybackSourceKind } from '../utils/resolvePlaybackUrl'; +import type { PlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl'; export interface Track { id: string; diff --git a/src/store/queueUndoAudioRestore.test.ts b/src/store/queueUndoAudioRestore.test.ts index 09086e0c..39e9f498 100644 --- a/src/store/queueUndoAudioRestore.test.ts +++ b/src/store/queueUndoAudioRestore.test.ts @@ -37,9 +37,9 @@ const hoisted = vi.hoisted(() => { }); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -vi.mock('../utils/hotCacheGate', () => ({ setDeferHotCachePrefetch: hoisted.setDeferHotCachePrefetchMock })); -vi.mock('../utils/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: hoisted.resolvePlaybackUrlMock })); -vi.mock('../utils/resolveReplayGainDb', () => ({ resolveReplayGainDb: hoisted.resolveReplayGainDbMock })); +vi.mock('../utils/cache/hotCacheGate', () => ({ setDeferHotCachePrefetch: hoisted.setDeferHotCachePrefetchMock })); +vi.mock('../utils/playback/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: hoisted.resolvePlaybackUrlMock })); +vi.mock('../utils/audio/resolveReplayGainDb', () => ({ resolveReplayGainDb: hoisted.resolveReplayGainDbMock })); vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); vi.mock('./engineState', () => ({ getPlayGeneration: hoisted.getPlayGeneration, diff --git a/src/store/queueUndoAudioRestore.ts b/src/store/queueUndoAudioRestore.ts index 4c70f8c2..db494f71 100644 --- a/src/store/queueUndoAudioRestore.ts +++ b/src/store/queueUndoAudioRestore.ts @@ -1,8 +1,8 @@ import type { Track } from './playerStoreTypes'; import { invoke } from '@tauri-apps/api/core'; -import { setDeferHotCachePrefetch } from '../utils/hotCacheGate'; -import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/resolveReplayGainDb'; +import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; +import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; import { useAuthStore } from './authStore'; import { getPlayGeneration, setIsAudioPaused } from './engineState'; import { touchHotCacheOnPlayback } from './hotCacheTouch'; diff --git a/src/store/radioPlayer.test.ts b/src/store/radioPlayer.test.ts index df11ebde..b00221ab 100644 --- a/src/store/radioPlayer.test.ts +++ b/src/store/radioPlayer.test.ts @@ -15,7 +15,7 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock('../utils/toast', () => ({ showToast: hoisted.showToastMock })); +vi.mock('../utils/ui/toast', () => ({ showToast: hoisted.showToastMock })); vi.mock('./playerStore', () => ({ usePlayerStore: { getState: hoisted.playerStateGet, diff --git a/src/store/radioPlayer.ts b/src/store/radioPlayer.ts index 1d664096..406f146e 100644 --- a/src/store/radioPlayer.ts +++ b/src/store/radioPlayer.ts @@ -1,4 +1,4 @@ -import { showToast } from '../utils/toast'; +import { showToast } from '../utils/ui/toast'; import { usePlayerStore } from './playerStore'; /** diff --git a/src/store/resumeAction.ts b/src/store/resumeAction.ts index cebe21e5..bff7711d 100644 --- a/src/store/resumeAction.ts +++ b/src/store/resumeAction.ts @@ -1,10 +1,10 @@ import { getSong } from '../api/subsonicLibrary'; import { invoke } from '@tauri-apps/api/core'; import { estimateLivePosition } from '../api/orbit'; -import { setDeferHotCachePrefetch } from '../utils/hotCacheGate'; -import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/resolveReplayGainDb'; -import { songToTrack } from '../utils/songToTrack'; +import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; +import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; +import { songToTrack } from '../utils/playback/songToTrack'; import { useAuthStore } from './authStore'; import { bumpPlayGeneration, diff --git a/src/store/seekAction.ts b/src/store/seekAction.ts index 91da1a5a..0e1c143c 100644 --- a/src/store/seekAction.ts +++ b/src/store/seekAction.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; -import { isRecoverableSeekError } from '../utils/seekErrors'; +import { isRecoverableSeekError } from '../utils/audio/seekErrors'; import { useAuthStore } from './authStore'; import { shouldRebindPlaybackToHotCache } from './playbackUrlRouting'; import type { PlayerState } from './playerStoreTypes'; diff --git a/src/store/seekFallbackState.test.ts b/src/store/seekFallbackState.test.ts index 4f3aa48d..e5216525 100644 --- a/src/store/seekFallbackState.test.ts +++ b/src/store/seekFallbackState.test.ts @@ -19,7 +19,7 @@ const hoisted = vi.hoisted(() => { }); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -vi.mock('../utils/seekErrors', () => ({ isRecoverableSeekError: hoisted.isRecoverableSeekErrorMock })); +vi.mock('../utils/audio/seekErrors', () => ({ isRecoverableSeekError: hoisted.isRecoverableSeekErrorMock })); vi.mock('./seekTargetState', () => ({ setSeekTarget: hoisted.setSeekTargetMock })); vi.mock('./playerStore', () => ({ usePlayerStore: { getState: hoisted.playerStateGet }, diff --git a/src/store/seekFallbackState.ts b/src/store/seekFallbackState.ts index 4bba0e6f..7014c150 100644 --- a/src/store/seekFallbackState.ts +++ b/src/store/seekFallbackState.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; -import { isRecoverableSeekError } from '../utils/seekErrors'; +import { isRecoverableSeekError } from '../utils/audio/seekErrors'; import { usePlayerStore } from './playerStore'; import { setSeekTarget } from './seekTargetState'; diff --git a/src/store/updateReplayGainAction.ts b/src/store/updateReplayGainAction.ts index a3004c38..05c63009 100644 --- a/src/store/updateReplayGainAction.ts +++ b/src/store/updateReplayGainAction.ts @@ -1,6 +1,6 @@ -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider'; -import { loudnessGainPlaceholderUntilCacheDb } from '../utils/loudnessPlaceholder'; -import { resolveReplayGainDb } from '../utils/resolveReplayGainDb'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/audio/loudnessPreAnalysisSlider'; +import { loudnessGainPlaceholderUntilCacheDb } from '../utils/audio/loudnessPlaceholder'; +import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; import { useAuthStore } from './authStore'; import { getCachedLoudnessGain, diff --git a/src/store/waveformRefresh.test.ts b/src/store/waveformRefresh.test.ts index f49d3981..5579d882 100644 --- a/src/store/waveformRefresh.test.ts +++ b/src/store/waveformRefresh.test.ts @@ -22,7 +22,7 @@ const hoisted = vi.hoisted(() => ({ })); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -vi.mock('../utils/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock })); +vi.mock('../utils/waveform/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock })); vi.mock('./playerStore', () => ({ usePlayerStore: { getState: () => hoisted.playerSnapshot, diff --git a/src/store/waveformRefresh.ts b/src/store/waveformRefresh.ts index 6a288697..8b2ee880 100644 --- a/src/store/waveformRefresh.ts +++ b/src/store/waveformRefresh.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; -import { coerceWaveformBins } from '../utils/waveformParse'; +import { coerceWaveformBins } from '../utils/waveform/waveformParse'; import { usePlayerStore } from './playerStore'; import { getWaveformRefreshGen } from './waveformRefreshGen'; diff --git a/src/utils/audioDeviceLabels.ts b/src/utils/audio/audioDeviceLabels.ts similarity index 100% rename from src/utils/audioDeviceLabels.ts rename to src/utils/audio/audioDeviceLabels.ts diff --git a/src/utils/autoEqParse.ts b/src/utils/audio/autoEqParse.ts similarity index 100% rename from src/utils/autoEqParse.ts rename to src/utils/audio/autoEqParse.ts diff --git a/src/utils/eqCurve.ts b/src/utils/audio/eqCurve.ts similarity index 98% rename from src/utils/eqCurve.ts rename to src/utils/audio/eqCurve.ts index f564b109..c3a72929 100644 --- a/src/utils/eqCurve.ts +++ b/src/utils/audio/eqCurve.ts @@ -1,4 +1,4 @@ -import { EQ_BANDS } from '../store/eqStore'; +import { EQ_BANDS } from '../../store/eqStore'; // ─── Frequency response canvas ──────────────────────────────────────────────── diff --git a/src/utils/loudnessPlaceholder.ts b/src/utils/audio/loudnessPlaceholder.ts similarity index 100% rename from src/utils/loudnessPlaceholder.ts rename to src/utils/audio/loudnessPlaceholder.ts diff --git a/src/utils/loudnessPreAnalysisSlider.ts b/src/utils/audio/loudnessPreAnalysisSlider.ts similarity index 100% rename from src/utils/loudnessPreAnalysisSlider.ts rename to src/utils/audio/loudnessPreAnalysisSlider.ts diff --git a/src/utils/normalizationCompare.test.ts b/src/utils/audio/normalizationCompare.test.ts similarity index 100% rename from src/utils/normalizationCompare.test.ts rename to src/utils/audio/normalizationCompare.test.ts diff --git a/src/utils/normalizationCompare.ts b/src/utils/audio/normalizationCompare.ts similarity index 100% rename from src/utils/normalizationCompare.ts rename to src/utils/audio/normalizationCompare.ts diff --git a/src/utils/resolveReplayGainDb.test.ts b/src/utils/audio/resolveReplayGainDb.test.ts similarity index 98% rename from src/utils/resolveReplayGainDb.test.ts rename to src/utils/audio/resolveReplayGainDb.test.ts index c444166b..5e5bf48a 100644 --- a/src/utils/resolveReplayGainDb.test.ts +++ b/src/utils/audio/resolveReplayGainDb.test.ts @@ -5,7 +5,7 @@ * Originally lived in `playerStore.ts`; extracted in M0 of the frontend * refactor (2026-05-12). */ -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; import { resolveReplayGainDb } from './resolveReplayGainDb'; describe('resolveReplayGainDb', () => { diff --git a/src/utils/resolveReplayGainDb.ts b/src/utils/audio/resolveReplayGainDb.ts similarity index 94% rename from src/utils/resolveReplayGainDb.ts rename to src/utils/audio/resolveReplayGainDb.ts index a03bdf0e..a743cbb7 100644 --- a/src/utils/resolveReplayGainDb.ts +++ b/src/utils/audio/resolveReplayGainDb.ts @@ -1,4 +1,4 @@ -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; /** * Resolve the ReplayGain dB value for a track based on the configured mode. * In 'auto' mode, picks album-gain when an adjacent queue neighbour shares the diff --git a/src/utils/seekErrors.test.ts b/src/utils/audio/seekErrors.test.ts similarity index 100% rename from src/utils/seekErrors.test.ts rename to src/utils/audio/seekErrors.test.ts diff --git a/src/utils/seekErrors.ts b/src/utils/audio/seekErrors.ts similarity index 100% rename from src/utils/seekErrors.ts rename to src/utils/audio/seekErrors.ts diff --git a/src/utils/hotCacheGate.ts b/src/utils/cache/hotCacheGate.ts similarity index 100% rename from src/utils/hotCacheGate.ts rename to src/utils/cache/hotCacheGate.ts diff --git a/src/utils/lyricsPersistentCache.ts b/src/utils/cache/lyricsPersistentCache.ts similarity index 98% rename from src/utils/lyricsPersistentCache.ts rename to src/utils/cache/lyricsPersistentCache.ts index 14de2e52..805fdd98 100644 --- a/src/utils/lyricsPersistentCache.ts +++ b/src/utils/cache/lyricsPersistentCache.ts @@ -12,7 +12,7 @@ * - notFound entries: 7 days. Lets the user / server admin add lyrics * later without an indefinite negative cache. */ -import type { CachedLyrics } from '../hooks/useLyrics'; +import type { CachedLyrics } from '../../hooks/useLyrics'; const DB_NAME = 'psysonic-lyrics-cache'; const STORE_NAME = 'lyrics'; diff --git a/src/utils/nowPlayingCache.ts b/src/utils/cache/nowPlayingCache.ts similarity index 100% rename from src/utils/nowPlayingCache.ts rename to src/utils/cache/nowPlayingCache.ts diff --git a/src/utils/changelogMarkdown.tsx b/src/utils/changelog/changelogMarkdown.tsx similarity index 100% rename from src/utils/changelogMarkdown.tsx rename to src/utils/changelog/changelogMarkdown.tsx diff --git a/src/utils/changelogReleaseMatch.ts b/src/utils/changelog/changelogReleaseMatch.ts similarity index 100% rename from src/utils/changelogReleaseMatch.ts rename to src/utils/changelog/changelogReleaseMatch.ts diff --git a/src/utils/albumDetailHelpers.ts b/src/utils/componentHelpers/albumDetailHelpers.ts similarity index 100% rename from src/utils/albumDetailHelpers.ts rename to src/utils/componentHelpers/albumDetailHelpers.ts diff --git a/src/utils/albumTrackListHelpers.ts b/src/utils/componentHelpers/albumTrackListHelpers.ts similarity index 97% rename from src/utils/albumTrackListHelpers.ts rename to src/utils/componentHelpers/albumTrackListHelpers.ts index bae08f93..26a6771d 100644 --- a/src/utils/albumTrackListHelpers.ts +++ b/src/utils/componentHelpers/albumTrackListHelpers.ts @@ -1,4 +1,4 @@ -import type { ColDef } from './useTracklistColumns'; +import type { ColDef } from '../useTracklistColumns'; export function formatDuration(seconds: number): string { const h = Math.floor(seconds / 3600); diff --git a/src/utils/appShellHelpers.ts b/src/utils/componentHelpers/appShellHelpers.ts similarity index 97% rename from src/utils/appShellHelpers.ts rename to src/utils/componentHelpers/appShellHelpers.ts index 956e6f5d..521e8c46 100644 --- a/src/utils/appShellHelpers.ts +++ b/src/utils/componentHelpers/appShellHelpers.ts @@ -1,4 +1,4 @@ -import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; +import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll'; export const SIDEBAR_COLLAPSED_STORAGE_KEY = 'psysonic_sidebar_collapsed'; diff --git a/src/utils/appUpdaterHelpers.ts b/src/utils/componentHelpers/appUpdaterHelpers.ts similarity index 96% rename from src/utils/appUpdaterHelpers.ts rename to src/utils/componentHelpers/appUpdaterHelpers.ts index 356393df..3098551d 100644 --- a/src/utils/appUpdaterHelpers.ts +++ b/src/utils/componentHelpers/appUpdaterHelpers.ts @@ -1,4 +1,4 @@ -import { IS_LINUX, IS_MACOS, IS_WINDOWS } from './platform'; +import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../platform'; export const SKIP_KEY = 'psysonic_skipped_update_version'; diff --git a/src/utils/artistDetailHelpers.ts b/src/utils/componentHelpers/artistDetailHelpers.ts similarity index 100% rename from src/utils/artistDetailHelpers.ts rename to src/utils/componentHelpers/artistDetailHelpers.ts diff --git a/src/utils/artistsHelpers.ts b/src/utils/componentHelpers/artistsHelpers.ts similarity index 95% rename from src/utils/artistsHelpers.ts rename to src/utils/componentHelpers/artistsHelpers.ts index 51b0a732..8bd0bebe 100644 --- a/src/utils/artistsHelpers.ts +++ b/src/utils/componentHelpers/artistsHelpers.ts @@ -1,4 +1,4 @@ -import type { SubsonicArtist } from '../api/subsonicTypes'; +import type { SubsonicArtist } from '../../api/subsonicTypes'; export const ALL_SENTINEL = 'ALL'; export const ALPHABET = [ALL_SENTINEL, '#', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')]; diff --git a/src/utils/contextMenuActions.ts b/src/utils/componentHelpers/contextMenuActions.ts similarity index 88% rename from src/utils/contextMenuActions.ts rename to src/utils/componentHelpers/contextMenuActions.ts index 23698d44..c70147ae 100644 --- a/src/utils/contextMenuActions.ts +++ b/src/utils/componentHelpers/contextMenuActions.ts @@ -1,17 +1,17 @@ import { join } from '@tauri-apps/api/path'; import { invoke } from '@tauri-apps/api/core'; -import { getSimilarSongs2, getSimilarSongs, getTopSongs } from '../api/subsonicArtists'; -import { buildDownloadUrl } from '../api/subsonicStreamUrl'; -import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; -import type { Track } from '../store/playerStoreTypes'; -import { useZipDownloadStore } from '../store/zipDownloadStore'; -import { useDownloadModalStore } from '../store/downloadModalStore'; -import type { EntityShareKind } from './shareLink'; -import { copyEntityShareLink } from './copyEntityShareLink'; +import { getSimilarSongs2, getSimilarSongs, getTopSongs } from '../../api/subsonicArtists'; +import { buildDownloadUrl } from '../../api/subsonicStreamUrl'; +import { useAuthStore } from '../../store/authStore'; +import { usePlayerStore } from '../../store/playerStore'; +import type { Track } from '../../store/playerStoreTypes'; +import { useZipDownloadStore } from '../../store/zipDownloadStore'; +import { useDownloadModalStore } from '../../store/downloadModalStore'; +import type { EntityShareKind } from '../share/shareLink'; +import { copyEntityShareLink } from '../share/copyEntityShareLink'; import { sanitizeFilename, shuffleArray } from './contextMenuHelpers'; -import { songToTrack } from './songToTrack'; -import { showToast } from './toast'; +import { songToTrack } from '../playback/songToTrack'; +import { showToast } from '../ui/toast'; export async function copyShareLink( kind: EntityShareKind, diff --git a/src/utils/contextMenuHelpers.ts b/src/utils/componentHelpers/contextMenuHelpers.ts similarity index 95% rename from src/utils/contextMenuHelpers.ts rename to src/utils/componentHelpers/contextMenuHelpers.ts index ce6c3591..1951cab9 100644 --- a/src/utils/contextMenuHelpers.ts +++ b/src/utils/componentHelpers/contextMenuHelpers.ts @@ -1,4 +1,4 @@ -import { useConfirmModalStore } from '../store/confirmModalStore'; +import { useConfirmModalStore } from '../../store/confirmModalStore'; /** Psysonic smart playlists (Navidrome); not valid targets for manual add-to-playlist. */ export const SMART_PLAYLIST_PREFIX = 'psy-smart-'; diff --git a/src/utils/folderBrowserHelpers.ts b/src/utils/componentHelpers/folderBrowserHelpers.ts similarity index 93% rename from src/utils/folderBrowserHelpers.ts rename to src/utils/componentHelpers/folderBrowserHelpers.ts index c249f234..76a37b57 100644 --- a/src/utils/folderBrowserHelpers.ts +++ b/src/utils/componentHelpers/folderBrowserHelpers.ts @@ -1,6 +1,6 @@ import type React from 'react'; -import type { SubsonicAlbum, SubsonicDirectoryEntry } from '../api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; +import type { SubsonicAlbum, SubsonicDirectoryEntry } from '../../api/subsonicTypes'; +import type { Track } from '../../store/playerStoreTypes'; export type ColumnKind = 'roots' | 'indexes' | 'directory'; export type NavPos = { colIndex: number; rowIndex: number }; diff --git a/src/utils/fullscreenPlayerHelpers.ts b/src/utils/componentHelpers/fullscreenPlayerHelpers.ts similarity index 100% rename from src/utils/fullscreenPlayerHelpers.ts rename to src/utils/componentHelpers/fullscreenPlayerHelpers.ts diff --git a/src/utils/miniPlayerHelpers.ts b/src/utils/componentHelpers/miniPlayerHelpers.ts similarity index 95% rename from src/utils/miniPlayerHelpers.ts rename to src/utils/componentHelpers/miniPlayerHelpers.ts index d7f6d3e1..e40a29e0 100644 --- a/src/utils/miniPlayerHelpers.ts +++ b/src/utils/componentHelpers/miniPlayerHelpers.ts @@ -1,5 +1,5 @@ -import { usePlayerStore } from '../store/playerStore'; -import type { MiniSyncPayload, MiniTrackInfo } from './miniPlayerBridge'; +import { usePlayerStore } from '../../store/playerStore'; +import type { MiniSyncPayload, MiniTrackInfo } from '../miniPlayerBridge'; export const COLLAPSED_SIZE = { w: 340, h: 260 }; export const EXPANDED_SIZE = { w: 340, h: 500 }; diff --git a/src/utils/nowPlayingHelpers.ts b/src/utils/componentHelpers/nowPlayingHelpers.ts similarity index 98% rename from src/utils/nowPlayingHelpers.ts rename to src/utils/componentHelpers/nowPlayingHelpers.ts index 6231acd9..c7cf8931 100644 --- a/src/utils/nowPlayingHelpers.ts +++ b/src/utils/componentHelpers/nowPlayingHelpers.ts @@ -1,4 +1,4 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; +import type { SubsonicSong } from '../../api/subsonicTypes'; export function formatTime(s: number): string { if (!s || isNaN(s)) return '0:00'; diff --git a/src/utils/playerBarHelpers.ts b/src/utils/componentHelpers/playerBarHelpers.ts similarity index 100% rename from src/utils/playerBarHelpers.ts rename to src/utils/componentHelpers/playerBarHelpers.ts diff --git a/src/utils/playlistDetailHelpers.ts b/src/utils/componentHelpers/playlistDetailHelpers.ts similarity index 90% rename from src/utils/playlistDetailHelpers.ts rename to src/utils/componentHelpers/playlistDetailHelpers.ts index 0aa89033..a64b350e 100644 --- a/src/utils/playlistDetailHelpers.ts +++ b/src/utils/componentHelpers/playlistDetailHelpers.ts @@ -1,5 +1,5 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; -import { formatHumanHoursMinutes } from './formatHumanDuration'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { formatHumanHoursMinutes } from '../format/formatHumanDuration'; export function sanitizeFilename(name: string): string { return name diff --git a/src/utils/queuePanelHelpers.tsx b/src/utils/componentHelpers/queuePanelHelpers.tsx similarity index 95% rename from src/utils/queuePanelHelpers.tsx rename to src/utils/componentHelpers/queuePanelHelpers.tsx index eb52bdc7..77061804 100644 --- a/src/utils/queuePanelHelpers.tsx +++ b/src/utils/componentHelpers/queuePanelHelpers.tsx @@ -1,6 +1,6 @@ import { Star } from 'lucide-react'; import type { TFunction } from 'i18next'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; export type DurationMode = 'total' | 'remaining' | 'eta'; diff --git a/src/utils/randomMixHelpers.ts b/src/utils/componentHelpers/randomMixHelpers.ts similarity index 94% rename from src/utils/randomMixHelpers.ts rename to src/utils/componentHelpers/randomMixHelpers.ts index 93af1d7a..3111a207 100644 --- a/src/utils/randomMixHelpers.ts +++ b/src/utils/componentHelpers/randomMixHelpers.ts @@ -1,5 +1,5 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; -import { passesMixMinRatings, type MixMinRatingsConfig } from './mixRatingFilter'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { passesMixMinRatings, type MixMinRatingsConfig } from '../mix/mixRatingFilter'; export const AUDIOBOOK_GENRES = [ 'hörbuch', 'hoerbuch', 'hörspiel', 'hoerspiel', diff --git a/src/utils/runArtistDetailActions.ts b/src/utils/componentHelpers/runArtistDetailActions.ts similarity index 89% rename from src/utils/runArtistDetailActions.ts rename to src/utils/componentHelpers/runArtistDetailActions.ts index 562edba6..eaa17c15 100644 --- a/src/utils/runArtistDetailActions.ts +++ b/src/utils/componentHelpers/runArtistDetailActions.ts @@ -1,12 +1,12 @@ import type React from 'react'; import type { TFunction } from 'i18next'; -import { uploadArtistImage } from '../api/subsonicPlaylists'; -import { setRating, star, unstar } from '../api/subsonicStarRating'; -import type { SubsonicArtist } from '../api/subsonicTypes'; -import { useAuthStore } from '../store/authStore'; -import { copyEntityShareLink } from './copyEntityShareLink'; -import { invalidateCoverArt } from './imageCache'; -import { showToast } from './toast'; +import { uploadArtistImage } from '../../api/subsonicPlaylists'; +import { setRating, star, unstar } from '../../api/subsonicStarRating'; +import type { SubsonicArtist } from '../../api/subsonicTypes'; +import { useAuthStore } from '../../store/authStore'; +import { copyEntityShareLink } from '../share/copyEntityShareLink'; +import { invalidateCoverArt } from '../imageCache'; +import { showToast } from '../ui/toast'; export interface RunArtistEntityRatingDeps { artist: SubsonicArtist | null; diff --git a/src/utils/runArtistDetailPlay.ts b/src/utils/componentHelpers/runArtistDetailPlay.ts similarity index 89% rename from src/utils/runArtistDetailPlay.ts rename to src/utils/componentHelpers/runArtistDetailPlay.ts index b131986d..2cd8e380 100644 --- a/src/utils/runArtistDetailPlay.ts +++ b/src/utils/componentHelpers/runArtistDetailPlay.ts @@ -1,9 +1,9 @@ import type { TFunction } from 'i18next'; -import { getAlbum } from '../api/subsonicLibrary'; -import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists'; -import type { SubsonicAlbum, SubsonicArtist } from '../api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; -import { songToTrack } from './songToTrack'; +import { getAlbum } from '../../api/subsonicLibrary'; +import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; +import type { SubsonicAlbum, SubsonicArtist } from '../../api/subsonicTypes'; +import type { Track } from '../../store/playerStoreTypes'; +import { songToTrack } from '../playback/songToTrack'; async function fetchAllTracks(albums: SubsonicAlbum[]): Promise { const results = await Promise.all(albums.map(a => getAlbum(a.id))); diff --git a/src/utils/sidebarHelpers.ts b/src/utils/componentHelpers/sidebarHelpers.ts similarity index 100% rename from src/utils/sidebarHelpers.ts rename to src/utils/componentHelpers/sidebarHelpers.ts diff --git a/src/utils/sidebarNavReorder.ts b/src/utils/componentHelpers/sidebarNavReorder.ts similarity index 94% rename from src/utils/sidebarNavReorder.ts rename to src/utils/componentHelpers/sidebarNavReorder.ts index 636cf447..c1e97d8e 100644 --- a/src/utils/sidebarNavReorder.ts +++ b/src/utils/componentHelpers/sidebarNavReorder.ts @@ -1,5 +1,5 @@ -import { ALL_NAV_ITEMS } from '../config/navItems'; -import type { SidebarItemConfig } from '../store/sidebarStore'; +import { ALL_NAV_ITEMS } from '../../config/navItems'; +import type { SidebarItemConfig } from '../../store/sidebarStore'; export type SidebarNavSection = 'library' | 'system'; diff --git a/src/utils/userMgmtHelpers.ts b/src/utils/componentHelpers/userMgmtHelpers.ts similarity index 100% rename from src/utils/userMgmtHelpers.ts rename to src/utils/componentHelpers/userMgmtHelpers.ts diff --git a/src/utils/coverArtRegisteredSizes.test.ts b/src/utils/cover/coverArtRegisteredSizes.test.ts similarity index 100% rename from src/utils/coverArtRegisteredSizes.test.ts rename to src/utils/cover/coverArtRegisteredSizes.test.ts diff --git a/src/utils/coverArtRegisteredSizes.ts b/src/utils/cover/coverArtRegisteredSizes.ts similarity index 100% rename from src/utils/coverArtRegisteredSizes.ts rename to src/utils/cover/coverArtRegisteredSizes.ts diff --git a/src/utils/coverBlobDownscale.ts b/src/utils/cover/coverBlobDownscale.ts similarity index 100% rename from src/utils/coverBlobDownscale.ts rename to src/utils/cover/coverBlobDownscale.ts diff --git a/src/utils/deviceSyncHelpers.ts b/src/utils/deviceSync/deviceSyncHelpers.ts similarity index 96% rename from src/utils/deviceSyncHelpers.ts rename to src/utils/deviceSync/deviceSyncHelpers.ts index 3d6ebf79..02522719 100644 --- a/src/utils/deviceSyncHelpers.ts +++ b/src/utils/deviceSync/deviceSyncHelpers.ts @@ -1,4 +1,4 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; +import type { SubsonicSong } from '../../api/subsonicTypes'; export type SourceTab = 'playlists' | 'albums' | 'artists'; diff --git a/src/utils/deviceSyncLegacyTemplate.ts b/src/utils/deviceSync/deviceSyncLegacyTemplate.ts similarity index 97% rename from src/utils/deviceSyncLegacyTemplate.ts rename to src/utils/deviceSync/deviceSyncLegacyTemplate.ts index 3037653e..125ab9cb 100644 --- a/src/utils/deviceSyncLegacyTemplate.ts +++ b/src/utils/deviceSync/deviceSyncLegacyTemplate.ts @@ -1,4 +1,4 @@ -import { IS_WINDOWS } from './platform'; +import { IS_WINDOWS } from '../platform'; // Same sanitize rules the Rust side uses (`sanitize_path_component`): strip // Windows-illegal chars and control chars, trim leading/trailing dots + spaces. diff --git a/src/utils/runDeviceSyncChooseFolder.ts b/src/utils/deviceSync/runDeviceSyncChooseFolder.ts similarity index 91% rename from src/utils/runDeviceSyncChooseFolder.ts rename to src/utils/deviceSync/runDeviceSyncChooseFolder.ts index f327c37a..33711a02 100644 --- a/src/utils/runDeviceSyncChooseFolder.ts +++ b/src/utils/deviceSync/runDeviceSyncChooseFolder.ts @@ -1,8 +1,8 @@ import { invoke } from '@tauri-apps/api/core'; import { open as openDialog } from '@tauri-apps/plugin-dialog'; import type { TFunction } from 'i18next'; -import { useDeviceSyncStore, type DeviceSyncSource } from '../store/deviceSyncStore'; -import { showToast } from './toast'; +import { useDeviceSyncStore, type DeviceSyncSource } from '../../store/deviceSyncStore'; +import { showToast } from '../ui/toast'; export interface RunDeviceSyncChooseFolderDeps { t: TFunction; diff --git a/src/utils/runDeviceSyncExecution.ts b/src/utils/deviceSync/runDeviceSyncExecution.ts similarity index 91% rename from src/utils/runDeviceSyncExecution.ts rename to src/utils/deviceSync/runDeviceSyncExecution.ts index 27228200..bd02c629 100644 --- a/src/utils/runDeviceSyncExecution.ts +++ b/src/utils/deviceSync/runDeviceSyncExecution.ts @@ -1,12 +1,12 @@ import type { TFunction } from 'i18next'; import { invoke } from '@tauri-apps/api/core'; -import { buildDownloadUrl } from '../api/subsonicStreamUrl'; -import type { SubsonicSong } from '../api/subsonicTypes'; -import { useDeviceSyncStore, type DeviceSyncSource } from '../store/deviceSyncStore'; -import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore'; -import { showToast } from './toast'; +import { buildDownloadUrl } from '../../api/subsonicStreamUrl'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { useDeviceSyncStore, type DeviceSyncSource } from '../../store/deviceSyncStore'; +import { useDeviceSyncJobStore } from '../../store/deviceSyncJobStore'; +import { showToast } from '../ui/toast'; import { trackToSyncInfo, uuid } from './deviceSyncHelpers'; -import { fetchTracksForSource } from './fetchTracksForSource'; +import { fetchTracksForSource } from '../playback/fetchTracksForSource'; export interface SyncDelta { addBytes: number; @@ -37,7 +37,7 @@ export async function runDeviceSyncSummaryPrompt(deps: RunDeviceSyncSummaryDeps) setPreSyncOpen(true); try { - const { getClient } = await import('../api/subsonicClient'); + const { getClient } = await import('../../api/subsonicClient'); const { baseUrl, params } = getClient(); const payload = await invoke('calculate_sync_payload', { sources, diff --git a/src/utils/runDeviceSyncMigration.ts b/src/utils/deviceSync/runDeviceSyncMigration.ts similarity index 96% rename from src/utils/runDeviceSyncMigration.ts rename to src/utils/deviceSync/runDeviceSyncMigration.ts index 25501073..866785cb 100644 --- a/src/utils/runDeviceSyncMigration.ts +++ b/src/utils/deviceSync/runDeviceSyncMigration.ts @@ -1,11 +1,11 @@ import type React from 'react'; import { invoke } from '@tauri-apps/api/core'; -import type { DeviceSyncSource } from '../store/deviceSyncStore'; -import type { SubsonicSong } from '../api/subsonicTypes'; +import type { DeviceSyncSource } from '../../store/deviceSyncStore'; +import type { SubsonicSong } from '../../api/subsonicTypes'; import { applyLegacyTemplate } from './deviceSyncLegacyTemplate'; import { trackToSyncInfo } from './deviceSyncHelpers'; -import { fetchTracksForSource } from './fetchTracksForSource'; -import { IS_WINDOWS } from './platform'; +import { fetchTracksForSource } from '../playback/fetchTracksForSource'; +import { IS_WINDOWS } from '../platform'; export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing'; diff --git a/src/utils/backup.ts b/src/utils/export/backup.ts similarity index 96% rename from src/utils/backup.ts rename to src/utils/export/backup.ts index e6331548..65ef31e7 100644 --- a/src/utils/backup.ts +++ b/src/utils/export/backup.ts @@ -1,6 +1,6 @@ import { save, open as openDialog } from '@tauri-apps/plugin-dialog'; import { writeFile, readTextFile } from '@tauri-apps/plugin-fs'; -import { version as appVersion } from '../../package.json'; +import { version as appVersion } from '../../../package.json'; const BACKUP_VERSION = 1; diff --git a/src/utils/exportAlbumCard.ts b/src/utils/export/exportAlbumCard.ts similarity index 98% rename from src/utils/exportAlbumCard.ts rename to src/utils/export/exportAlbumCard.ts index f03e6928..08acd251 100644 --- a/src/utils/exportAlbumCard.ts +++ b/src/utils/export/exportAlbumCard.ts @@ -1,9 +1,9 @@ -import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; -import type { SubsonicAlbum } from '../api/subsonicTypes'; +import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl'; +import type { SubsonicAlbum } from '../../api/subsonicTypes'; import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; -import { getCachedBlob } from './imageCache'; -import PsysonicLogo from '../components/PsysonicLogo'; +import { getCachedBlob } from '../imageCache'; +import PsysonicLogo from '../../components/PsysonicLogo'; export type ExportFormat = 'story' | 'square' | 'twitter'; export type ExportGridSize = 3 | 4 | 5; diff --git a/src/utils/exportNewAlbums.ts b/src/utils/export/exportNewAlbums.ts similarity index 97% rename from src/utils/exportNewAlbums.ts rename to src/utils/export/exportNewAlbums.ts index df0bf8ac..9b3d6923 100644 --- a/src/utils/exportNewAlbums.ts +++ b/src/utils/export/exportNewAlbums.ts @@ -1,9 +1,9 @@ -import { buildCoverArtUrl } from '../api/subsonicStreamUrl'; -import { getAlbumList } from '../api/subsonicLibrary'; -import type { SubsonicAlbum } from '../api/subsonicTypes'; +import { buildCoverArtUrl } from '../../api/subsonicStreamUrl'; +import { getAlbumList } from '../../api/subsonicLibrary'; +import type { SubsonicAlbum } from '../../api/subsonicTypes'; import { writeFile } from '@tauri-apps/plugin-fs'; import { downloadDir, join } from '@tauri-apps/api/path'; -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '../../store/authStore'; // Catppuccin Macchiato palette const M = { crust: '#181926', diff --git a/src/utils/formatBytes.ts b/src/utils/format/formatBytes.ts similarity index 100% rename from src/utils/formatBytes.ts rename to src/utils/format/formatBytes.ts diff --git a/src/utils/formatHumanDuration.ts b/src/utils/format/formatHumanDuration.ts similarity index 92% rename from src/utils/formatHumanDuration.ts rename to src/utils/format/formatHumanDuration.ts index 25655fda..59f8f0d9 100644 --- a/src/utils/formatHumanDuration.ts +++ b/src/utils/format/formatHumanDuration.ts @@ -1,4 +1,4 @@ -import i18n from '../i18n'; +import i18n from '../../i18n'; /** Totals / statistics: localized "N hours M minutes" (not track mm:ss). */ export function formatHumanHoursMinutes(seconds: number): string { diff --git a/src/utils/playbackScheduleFormat.ts b/src/utils/format/playbackScheduleFormat.ts similarity index 94% rename from src/utils/playbackScheduleFormat.ts rename to src/utils/format/playbackScheduleFormat.ts index 1335d114..a8c44b48 100644 --- a/src/utils/playbackScheduleFormat.ts +++ b/src/utils/format/playbackScheduleFormat.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { useShallow } from 'zustand/react/shallow'; -import { usePlayerStore } from '../store/playerStore'; -import { useWindowVisibility } from '../hooks/useWindowVisibility'; +import { usePlayerStore } from '../../store/playerStore'; +import { useWindowVisibility } from '../../hooks/useWindowVisibility'; /** Remaining time until wall-clock `deadlineMs` (m:ss or h:mm:ss). */ export function formatPlaybackScheduleRemaining(deadlineMs: number | null, nowMs: number): string { diff --git a/src/utils/imageCache.ts b/src/utils/imageCache.ts index 2e3a4e12..92d95a15 100644 --- a/src/utils/imageCache.ts +++ b/src/utils/imageCache.ts @@ -2,7 +2,7 @@ // `imageCache/`; this file keeps the orchestration entry points and re-exports // the public surface so existing call sites import from `imageCache` unchanged. import { useAuthStore } from '../store/authStore'; -import { COVER_ART_REGISTERED_SIZES } from './coverArtRegisteredSizes'; +import { COVER_ART_REGISTERED_SIZES } from './cover/coverArtRegisteredSizes'; import { STORE_NAME } from './imageCache/constants'; import { blobCache, inflightBlobGets, rememberBlob } from './imageCache/blobCache'; import { purgeUrlEntry, clearAllUrlEntries } from './imageCache/urlPool'; diff --git a/src/utils/imageCache/coverSiblings.ts b/src/utils/imageCache/coverSiblings.ts index 6066a29a..0fba7c63 100644 --- a/src/utils/imageCache/coverSiblings.ts +++ b/src/utils/imageCache/coverSiblings.ts @@ -1,5 +1,5 @@ -import { COVER_ART_REGISTERED_SIZES } from '../coverArtRegisteredSizes'; -import { downscaleCoverBlob } from '../coverBlobDownscale'; +import { COVER_ART_REGISTERED_SIZES } from '../cover/coverArtRegisteredSizes'; +import { downscaleCoverBlob } from '../cover/coverBlobDownscale'; import { blobCache, rememberBlob } from './blobCache'; import { purgeUrlEntry } from './urlPool'; import { mapBlobsFromIDB, putBlob } from './idbStore'; diff --git a/src/utils/luckyMix.ts b/src/utils/mix/luckyMix.ts similarity index 95% rename from src/utils/luckyMix.ts rename to src/utils/mix/luckyMix.ts index e99811ad..2279111c 100644 --- a/src/utils/luckyMix.ts +++ b/src/utils/mix/luckyMix.ts @@ -1,15 +1,15 @@ -import { getSimilarSongs } from '../api/subsonicArtists'; -import { filterSongsToActiveLibrary, getRandomSongs } from '../api/subsonicLibrary'; -import type { SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { getSimilarSongs } from '../../api/subsonicArtists'; +import { filterSongsToActiveLibrary, getRandomSongs } from '../../api/subsonicLibrary'; +import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; +import type { Track } from '../../store/playerStoreTypes'; +import { songToTrack } from '../playback/songToTrack'; import { invoke } from '@tauri-apps/api/core'; -import i18n from '../i18n'; -import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; -import { useLuckyMixStore } from '../store/luckyMixStore'; -import { isLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; -import { showToast } from './toast'; +import i18n from '../../i18n'; +import { useAuthStore } from '../../store/authStore'; +import { usePlayerStore } from '../../store/playerStore'; +import { useLuckyMixStore } from '../../store/luckyMixStore'; +import { isLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable'; +import { showToast } from '../ui/toast'; import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth, diff --git a/src/utils/luckyMixHelpers.ts b/src/utils/mix/luckyMixHelpers.ts similarity index 95% rename from src/utils/luckyMixHelpers.ts rename to src/utils/mix/luckyMixHelpers.ts index b1b4c079..8c6c192e 100644 --- a/src/utils/luckyMixHelpers.ts +++ b/src/utils/mix/luckyMixHelpers.ts @@ -1,6 +1,6 @@ -import { getTopSongs } from '../api/subsonicArtists'; -import { filterSongsToActiveLibrary, getAlbum, getAlbumList, getRandomSongs } from '../api/subsonicLibrary'; -import type { SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes'; +import { getTopSongs } from '../../api/subsonicArtists'; +import { filterSongsToActiveLibrary, getAlbum, getAlbumList, getRandomSongs } from '../../api/subsonicLibrary'; +import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; import { filterSongsForLuckyMixRatings, type MixMinRatingsConfig, diff --git a/src/utils/mixRatingFilter.ts b/src/utils/mix/mixRatingFilter.ts similarity index 98% rename from src/utils/mixRatingFilter.ts rename to src/utils/mix/mixRatingFilter.ts index e87b815d..53dbeafa 100644 --- a/src/utils/mixRatingFilter.ts +++ b/src/utils/mix/mixRatingFilter.ts @@ -1,7 +1,7 @@ -import { parseSubsonicEntityStarRating, prefetchAlbumUserRatings, prefetchArtistUserRatings } from '../api/subsonicRatings'; -import { getRandomSongs } from '../api/subsonicLibrary'; -import type { SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes'; -import { useAuthStore } from '../store/authStore'; +import { parseSubsonicEntityStarRating, prefetchAlbumUserRatings, prefetchArtistUserRatings } from '../../api/subsonicRatings'; +import { getRandomSongs } from '../../api/subsonicLibrary'; +import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; +import { useAuthStore } from '../../store/authStore'; /** Default target list size for Random Mix; per-call override via `fetchRandomMixSongsUntilFull(c, { targetSize })`. */ export const RANDOM_MIX_TARGET_SIZE = 50; diff --git a/src/utils/orbit/guest.ts b/src/utils/orbit/guest.ts index 1c674196..90951765 100644 --- a/src/utils/orbit/guest.ts +++ b/src/utils/orbit/guest.ts @@ -1,6 +1,6 @@ import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '../../api/subsonicPlaylists'; import { getSong } from '../../api/subsonicLibrary'; -import { songToTrack } from '../songToTrack'; +import { songToTrack } from '../playback/songToTrack'; import { useAuthStore } from '../../store/authStore'; import { useOrbitStore } from '../../store/orbitStore'; import { usePlayerStore } from '../../store/playerStore'; diff --git a/src/utils/orbit/host.ts b/src/utils/orbit/host.ts index c53244b6..0934fafe 100644 --- a/src/utils/orbit/host.ts +++ b/src/utils/orbit/host.ts @@ -1,6 +1,6 @@ import { createPlaylist, deletePlaylist } from '../../api/subsonicPlaylists'; import { getSong } from '../../api/subsonicLibrary'; -import { songToTrack } from '../songToTrack'; +import { songToTrack } from '../playback/songToTrack'; import { useAuthStore } from '../../store/authStore'; import { useOrbitStore } from '../../store/orbitStore'; import { usePlayerStore } from '../../store/playerStore'; diff --git a/src/utils/orbit/shareLink.ts b/src/utils/orbit/shareLink.ts index c0ca58fc..d8784ba5 100644 --- a/src/utils/orbit/shareLink.ts +++ b/src/utils/orbit/shareLink.ts @@ -1,4 +1,4 @@ -import { decodeOrbitSharePayloadFromText, encodeSharePayload } from '../shareLink'; +import { decodeOrbitSharePayloadFromText, encodeSharePayload } from '../share/shareLink'; export interface OrbitShareLink { /** Base URL of the Navidrome server (decoded). */ diff --git a/src/utils/perfFlags.ts b/src/utils/perf/perfFlags.ts similarity index 100% rename from src/utils/perfFlags.ts rename to src/utils/perf/perfFlags.ts diff --git a/src/utils/perfTelemetry.ts b/src/utils/perf/perfTelemetry.ts similarity index 100% rename from src/utils/perfTelemetry.ts rename to src/utils/perf/perfTelemetry.ts diff --git a/src/utils/buildInfiniteQueueCandidates.test.ts b/src/utils/playback/buildInfiniteQueueCandidates.test.ts similarity index 96% rename from src/utils/buildInfiniteQueueCandidates.test.ts rename to src/utils/playback/buildInfiniteQueueCandidates.test.ts index 2e819ce6..92da8c87 100644 --- a/src/utils/buildInfiniteQueueCandidates.test.ts +++ b/src/utils/playback/buildInfiniteQueueCandidates.test.ts @@ -6,21 +6,21 @@ * refactor (2026-05-12). This test pins the artist-first / random-fallback * order, the dedup contract against existingIds, and the autoAdded flag. */ -import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists'; -import { getRandomSongs } from '../api/subsonicLibrary'; -import type { Track } from '../store/playerStoreTypes'; +import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; +import { getRandomSongs } from '../../api/subsonicLibrary'; +import type { Track } from '../../store/playerStoreTypes'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -vi.mock('../api/subsonicArtists', () => ({ +vi.mock('../../api/subsonicArtists', () => ({ getSimilarSongs2: vi.fn(), getTopSongs: vi.fn(), })); -vi.mock('../api/subsonicLibrary', () => ({ +vi.mock('../../api/subsonicLibrary', () => ({ getRandomSongs: vi.fn(), })); -vi.mock('./mixRatingFilter', () => ({ +vi.mock('../mix/mixRatingFilter', () => ({ getMixMinRatingsConfigFromAuth: vi.fn(), enrichSongsForMixRatingFilter: vi.fn(), passesMixMinRatings: vi.fn(), @@ -30,7 +30,7 @@ import { buildInfiniteQueueCandidates } from './buildInfiniteQueueCandidates'; import { enrichSongsForMixRatingFilter, getMixMinRatingsConfigFromAuth, -} from './mixRatingFilter'; +} from '../mix/mixRatingFilter'; import { makeSubsonicSong } from '@/test/helpers/factories'; const seed = (overrides: Partial = {}): Track => ({ diff --git a/src/utils/buildInfiniteQueueCandidates.ts b/src/utils/playback/buildInfiniteQueueCandidates.ts similarity index 90% rename from src/utils/buildInfiniteQueueCandidates.ts rename to src/utils/playback/buildInfiniteQueueCandidates.ts index a8048b25..33ef62ff 100644 --- a/src/utils/buildInfiniteQueueCandidates.ts +++ b/src/utils/playback/buildInfiniteQueueCandidates.ts @@ -1,11 +1,11 @@ -import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists'; -import { getRandomSongs } from '../api/subsonicLibrary'; -import type { Track } from '../store/playerStoreTypes'; +import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; +import { getRandomSongs } from '../../api/subsonicLibrary'; +import type { Track } from '../../store/playerStoreTypes'; import { enrichSongsForMixRatingFilter, getMixMinRatingsConfigFromAuth, passesMixMinRatings, -} from './mixRatingFilter'; +} from '../mix/mixRatingFilter'; import { shuffleArray } from './shuffleArray'; import { songToTrack } from './songToTrack'; /** diff --git a/src/utils/fetchTracksForSource.ts b/src/utils/playback/fetchTracksForSource.ts similarity index 71% rename from src/utils/fetchTracksForSource.ts rename to src/utils/playback/fetchTracksForSource.ts index aaff8aab..c2657058 100644 --- a/src/utils/fetchTracksForSource.ts +++ b/src/utils/playback/fetchTracksForSource.ts @@ -1,8 +1,8 @@ -import { getArtist } from '../api/subsonicArtists'; -import { getAlbum } from '../api/subsonicLibrary'; -import { getPlaylist } from '../api/subsonicPlaylists'; -import type { SubsonicSong } from '../api/subsonicTypes'; -import type { DeviceSyncSource } from '../store/deviceSyncStore'; +import { getArtist } from '../../api/subsonicArtists'; +import { getAlbum } from '../../api/subsonicLibrary'; +import { getPlaylist } from '../../api/subsonicPlaylists'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import type { DeviceSyncSource } from '../../store/deviceSyncStore'; export async function fetchTracksForSource(source: DeviceSyncSource): Promise { if (source.type === 'playlist') { const { songs } = await getPlaylist(source.id); return songs; } diff --git a/src/utils/playAlbum.ts b/src/utils/playback/playAlbum.ts similarity index 91% rename from src/utils/playAlbum.ts rename to src/utils/playback/playAlbum.ts index 699d91cc..6c579632 100644 --- a/src/utils/playAlbum.ts +++ b/src/utils/playback/playAlbum.ts @@ -1,7 +1,7 @@ -import { getAlbum } from '../api/subsonicLibrary'; -import { usePlayerStore } from '../store/playerStore'; +import { getAlbum } from '../../api/subsonicLibrary'; +import { usePlayerStore } from '../../store/playerStore'; import { songToTrack } from './songToTrack'; -import { useOrbitStore } from '../store/orbitStore'; +import { useOrbitStore } from '../../store/orbitStore'; function fadeOut(setVolume: (v: number) => void, from: number, durationMs: number): Promise { return new Promise(resolve => { diff --git a/src/utils/playArtistShuffled.ts b/src/utils/playback/playArtistShuffled.ts similarity index 74% rename from src/utils/playArtistShuffled.ts rename to src/utils/playback/playArtistShuffled.ts index 68c5f5a6..28d5f8aa 100644 --- a/src/utils/playArtistShuffled.ts +++ b/src/utils/playback/playArtistShuffled.ts @@ -1,8 +1,8 @@ -import { getArtist } from '../api/subsonicArtists'; -import { getAlbum } from '../api/subsonicLibrary'; -import { songToTrack } from '../utils/songToTrack'; -import { shuffleArray } from '../utils/shuffleArray'; -import { usePlayerStore } from '../store/playerStore'; +import { getArtist } from '../../api/subsonicArtists'; +import { getAlbum } from '../../api/subsonicLibrary'; +import { songToTrack } from './songToTrack'; +import { shuffleArray } from './shuffleArray'; +import { usePlayerStore } from '../../store/playerStore'; /** * All tracks from the artist’s albums, shuffled — same idea as Artist page “shuffle play”. */ diff --git a/src/utils/playByOpaqueId.ts b/src/utils/playback/playByOpaqueId.ts similarity index 82% rename from src/utils/playByOpaqueId.ts rename to src/utils/playback/playByOpaqueId.ts index 688dd2f7..23b60549 100644 --- a/src/utils/playByOpaqueId.ts +++ b/src/utils/playback/playByOpaqueId.ts @@ -1,8 +1,8 @@ -import { getAlbum, getSong } from '../api/subsonicLibrary'; -import { songToTrack } from '../utils/songToTrack'; +import { getAlbum, getSong } from '../../api/subsonicLibrary'; +import { songToTrack } from './songToTrack'; import { playAlbum } from './playAlbum'; import { playArtistShuffled } from './playArtistShuffled'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '../../store/playerStore'; /** * `getSong` → `getAlbum` → `getArtist`: one opaque Subsonic id may refer to a track, * album, or artist depending on the server. diff --git a/src/utils/playSong.ts b/src/utils/playback/playSong.ts similarity index 91% rename from src/utils/playSong.ts rename to src/utils/playback/playSong.ts index 5bba1a23..8447931b 100644 --- a/src/utils/playSong.ts +++ b/src/utils/playback/playSong.ts @@ -1,6 +1,6 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; -import { usePlayerStore } from '../store/playerStore'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { songToTrack } from './songToTrack'; +import { usePlayerStore } from '../../store/playerStore'; function fadeOut(setVolume: (v: number) => void, from: number, durationMs: number): Promise { return new Promise(resolve => { const steps = 16; diff --git a/src/utils/queueIdentity.test.ts b/src/utils/playback/queueIdentity.test.ts similarity index 98% rename from src/utils/queueIdentity.test.ts rename to src/utils/playback/queueIdentity.test.ts index 0248916a..156c6dc8 100644 --- a/src/utils/queueIdentity.test.ts +++ b/src/utils/playback/queueIdentity.test.ts @@ -4,7 +4,7 @@ * routing through the HTTP source) and the no-op detection in * `queuesStructuralEqual` that prevents unnecessary store rewrites. */ -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; import { normalizeAnalysisTrackId, diff --git a/src/utils/queueIdentity.ts b/src/utils/playback/queueIdentity.ts similarity index 95% rename from src/utils/queueIdentity.ts rename to src/utils/playback/queueIdentity.ts index b7c35105..04780da6 100644 --- a/src/utils/queueIdentity.ts +++ b/src/utils/playback/queueIdentity.ts @@ -1,4 +1,4 @@ -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; /** * Strip the `stream:` prefix that some Rust events attach to track ids when * they're routed through the HTTP source. Both forms identify the same track, diff --git a/src/utils/resolvePlaybackUrl.test.ts b/src/utils/playback/resolvePlaybackUrl.test.ts similarity index 100% rename from src/utils/resolvePlaybackUrl.test.ts rename to src/utils/playback/resolvePlaybackUrl.test.ts diff --git a/src/utils/resolvePlaybackUrl.ts b/src/utils/playback/resolvePlaybackUrl.ts similarity index 91% rename from src/utils/resolvePlaybackUrl.ts rename to src/utils/playback/resolvePlaybackUrl.ts index 226ffe9c..0f7f1084 100644 --- a/src/utils/resolvePlaybackUrl.ts +++ b/src/utils/playback/resolvePlaybackUrl.ts @@ -1,6 +1,6 @@ -import { buildStreamUrl } from '../api/subsonicStreamUrl'; -import { useOfflineStore } from '../store/offlineStore'; -import { useHotCacheStore } from '../store/hotCacheStore'; +import { buildStreamUrl } from '../../api/subsonicStreamUrl'; +import { useOfflineStore } from '../../store/offlineStore'; +import { useHotCacheStore } from '../../store/hotCacheStore'; /** Same resolution order as {@link resolvePlaybackUrl} — for UI hints only. */ export type PlaybackSourceKind = 'offline' | 'hot' | 'stream'; diff --git a/src/utils/shuffleArray.test.ts b/src/utils/playback/shuffleArray.test.ts similarity index 100% rename from src/utils/shuffleArray.test.ts rename to src/utils/playback/shuffleArray.test.ts diff --git a/src/utils/shuffleArray.ts b/src/utils/playback/shuffleArray.ts similarity index 100% rename from src/utils/shuffleArray.ts rename to src/utils/playback/shuffleArray.ts diff --git a/src/utils/songToTrack.test.ts b/src/utils/playback/songToTrack.test.ts similarity index 98% rename from src/utils/songToTrack.test.ts rename to src/utils/playback/songToTrack.test.ts index 1aa278dd..612b9af6 100644 --- a/src/utils/songToTrack.test.ts +++ b/src/utils/playback/songToTrack.test.ts @@ -5,7 +5,7 @@ * playback queue. Originally lived in `playerStore.ts`; extracted in M0 of * the frontend refactor (2026-05-12). */ -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; import { songToTrack } from './songToTrack'; import { makeSubsonicSong } from '@/test/helpers/factories'; diff --git a/src/utils/songToTrack.ts b/src/utils/playback/songToTrack.ts similarity index 85% rename from src/utils/songToTrack.ts rename to src/utils/playback/songToTrack.ts index 6c7df16e..7b1a659f 100644 --- a/src/utils/songToTrack.ts +++ b/src/utils/playback/songToTrack.ts @@ -1,5 +1,5 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import type { Track } from '../../store/playerStoreTypes'; export function songToTrack(song: SubsonicSong): Track { return { id: song.id, diff --git a/src/utils/playlistBulkPlayActions.ts b/src/utils/playlist/playlistBulkPlayActions.ts similarity index 94% rename from src/utils/playlistBulkPlayActions.ts rename to src/utils/playlist/playlistBulkPlayActions.ts index 3e67a307..cb592e54 100644 --- a/src/utils/playlistBulkPlayActions.ts +++ b/src/utils/playlist/playlistBulkPlayActions.ts @@ -1,4 +1,4 @@ -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '../../store/playerStoreTypes'; export interface BulkPlayDeps { songsLength: number; diff --git a/src/utils/playlistDisplayedSongs.ts b/src/utils/playlist/playlistDisplayedSongs.ts similarity index 97% rename from src/utils/playlistDisplayedSongs.ts rename to src/utils/playlist/playlistDisplayedSongs.ts index 19812e2e..99581024 100644 --- a/src/utils/playlistDisplayedSongs.ts +++ b/src/utils/playlist/playlistDisplayedSongs.ts @@ -1,4 +1,4 @@ -import type { SubsonicSong } from '../api/subsonicTypes'; +import type { SubsonicSong } from '../../api/subsonicTypes'; export type PlaylistSortKey = 'natural' | 'title' | 'artist' | 'album' | 'favorite' | 'rating' | 'duration'; export type PlaylistSortDir = 'asc' | 'desc'; diff --git a/src/utils/playlistsSmart.ts b/src/utils/playlist/playlistsSmart.ts similarity index 100% rename from src/utils/playlistsSmart.ts rename to src/utils/playlist/playlistsSmart.ts diff --git a/src/utils/runPlaylistCsvImport.ts b/src/utils/playlist/runPlaylistCsvImport.ts similarity index 98% rename from src/utils/runPlaylistCsvImport.ts rename to src/utils/playlist/runPlaylistCsvImport.ts index 9cd44f8d..4b19281d 100644 --- a/src/utils/runPlaylistCsvImport.ts +++ b/src/utils/playlist/runPlaylistCsvImport.ts @@ -1,9 +1,9 @@ import type { TFunction } from 'i18next'; import { open as openDialog } from '@tauri-apps/plugin-dialog'; import { readTextFile } from '@tauri-apps/plugin-fs'; -import { search } from '../api/subsonicSearch'; -import type { SubsonicSong } from '../api/subsonicTypes'; -import { showToast } from './toast'; +import { search } from '../../api/subsonicSearch'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { showToast } from '../ui/toast'; import { parseSpotifyCsv, type SpotifyCsvTrack } from './spotifyCsvImport'; import { cleanTrackTitle, diff --git a/src/utils/runPlaylistLoad.ts b/src/utils/playlist/runPlaylistLoad.ts similarity index 86% rename from src/utils/runPlaylistLoad.ts rename to src/utils/playlist/runPlaylistLoad.ts index 9daf6e2e..d6ebdc43 100644 --- a/src/utils/runPlaylistLoad.ts +++ b/src/utils/playlist/runPlaylistLoad.ts @@ -1,7 +1,7 @@ import type React from 'react'; -import { getPlaylist } from '../api/subsonicPlaylists'; -import { filterSongsToActiveLibrary } from '../api/subsonicLibrary'; -import type { SubsonicPlaylist, SubsonicSong } from '../api/subsonicTypes'; +import { getPlaylist } from '../../api/subsonicPlaylists'; +import { filterSongsToActiveLibrary } from '../../api/subsonicLibrary'; +import type { SubsonicPlaylist, SubsonicSong } from '../../api/subsonicTypes'; export interface RunPlaylistLoadDeps { id: string; diff --git a/src/utils/runPlaylistReorderDrop.ts b/src/utils/playlist/runPlaylistReorderDrop.ts similarity index 96% rename from src/utils/runPlaylistReorderDrop.ts rename to src/utils/playlist/runPlaylistReorderDrop.ts index b3c628ac..b0f6a41b 100644 --- a/src/utils/runPlaylistReorderDrop.ts +++ b/src/utils/playlist/runPlaylistReorderDrop.ts @@ -1,5 +1,5 @@ import type React from 'react'; -import type { SubsonicSong } from '../api/subsonicTypes'; +import type { SubsonicSong } from '../../api/subsonicTypes'; export interface RunPlaylistReorderDropDeps { e: Event; diff --git a/src/utils/runPlaylistSaveMeta.ts b/src/utils/playlist/runPlaylistSaveMeta.ts similarity index 91% rename from src/utils/runPlaylistSaveMeta.ts rename to src/utils/playlist/runPlaylistSaveMeta.ts index 80601512..eaa2a564 100644 --- a/src/utils/runPlaylistSaveMeta.ts +++ b/src/utils/playlist/runPlaylistSaveMeta.ts @@ -1,7 +1,7 @@ import type { TFunction } from 'i18next'; -import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '../api/subsonicPlaylists'; -import type { SubsonicPlaylist } from '../api/subsonicTypes'; -import { showToast } from './toast'; +import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '../../api/subsonicPlaylists'; +import type { SubsonicPlaylist } from '../../api/subsonicTypes'; +import { showToast } from '../ui/toast'; export interface RunPlaylistSaveMetaDeps { id: string; diff --git a/src/utils/runPlaylistZipDownload.ts b/src/utils/playlist/runPlaylistZipDownload.ts similarity index 80% rename from src/utils/runPlaylistZipDownload.ts rename to src/utils/playlist/runPlaylistZipDownload.ts index 6f57c77a..e6ebd416 100644 --- a/src/utils/runPlaylistZipDownload.ts +++ b/src/utils/playlist/runPlaylistZipDownload.ts @@ -1,9 +1,9 @@ import { invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; -import { buildDownloadUrl } from '../api/subsonicStreamUrl'; -import type { SubsonicPlaylist } from '../api/subsonicTypes'; -import { useZipDownloadStore } from '../store/zipDownloadStore'; -import { sanitizeFilename } from './playlistDetailHelpers'; +import { buildDownloadUrl } from '../../api/subsonicStreamUrl'; +import type { SubsonicPlaylist } from '../../api/subsonicTypes'; +import { useZipDownloadStore } from '../../store/zipDownloadStore'; +import { sanitizeFilename } from '../componentHelpers/playlistDetailHelpers'; export interface RunPlaylistZipDownloadDeps { playlist: SubsonicPlaylist; diff --git a/src/utils/runPlaylistsActions.ts b/src/utils/playlist/runPlaylistsActions.ts similarity index 94% rename from src/utils/runPlaylistsActions.ts rename to src/utils/playlist/runPlaylistsActions.ts index 7e93f99f..71ac56c0 100644 --- a/src/utils/runPlaylistsActions.ts +++ b/src/utils/playlist/runPlaylistsActions.ts @@ -1,9 +1,9 @@ import type React from 'react'; import type { TFunction } from 'i18next'; -import { deletePlaylist, getPlaylist, updatePlaylist } from '../api/subsonicPlaylists'; -import type { SubsonicPlaylist } from '../api/subsonicTypes'; -import { usePlaylistStore } from '../store/playlistStore'; -import { showToast } from './toast'; +import { deletePlaylist, getPlaylist, updatePlaylist } from '../../api/subsonicPlaylists'; +import type { SubsonicPlaylist } from '../../api/subsonicTypes'; +import { usePlaylistStore } from '../../store/playlistStore'; +import { showToast } from '../ui/toast'; export interface RunPlaylistDeleteDeps { e: React.MouseEvent; diff --git a/src/utils/runPlaylistsOpenSmartEditor.ts b/src/utils/playlist/runPlaylistsOpenSmartEditor.ts similarity index 93% rename from src/utils/runPlaylistsOpenSmartEditor.ts rename to src/utils/playlist/runPlaylistsOpenSmartEditor.ts index 6c5dd4ad..6f01ae1c 100644 --- a/src/utils/runPlaylistsOpenSmartEditor.ts +++ b/src/utils/playlist/runPlaylistsOpenSmartEditor.ts @@ -1,12 +1,12 @@ import type React from 'react'; import type { TFunction } from 'i18next'; -import { ndGetSmartPlaylist, ndListSmartPlaylists } from '../api/navidromeSmart'; -import type { SubsonicPlaylist } from '../api/subsonicTypes'; +import { ndGetSmartPlaylist, ndListSmartPlaylists } from '../../api/navidromeSmart'; +import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import { defaultSmartFilters, displayPlaylistName, isSmartPlaylistName, parseSmartRulesToFilters, type SmartFilters, } from './playlistsSmart'; -import { showToast } from './toast'; +import { showToast } from '../ui/toast'; export interface RunPlaylistsOpenSmartEditorDeps { pl: SubsonicPlaylist; diff --git a/src/utils/runPlaylistsSaveSmart.ts b/src/utils/playlist/runPlaylistsSaveSmart.ts similarity index 94% rename from src/utils/runPlaylistsSaveSmart.ts rename to src/utils/playlist/runPlaylistsSaveSmart.ts index ceadbc30..69ce6476 100644 --- a/src/utils/runPlaylistsSaveSmart.ts +++ b/src/utils/playlist/runPlaylistsSaveSmart.ts @@ -1,13 +1,13 @@ import type React from 'react'; import type { TFunction } from 'i18next'; -import { ndCreateSmartPlaylist, ndUpdateSmartPlaylist } from '../api/navidromeSmart'; -import type { SubsonicPlaylist } from '../api/subsonicTypes'; -import { usePlaylistStore } from '../store/playlistStore'; +import { ndCreateSmartPlaylist, ndUpdateSmartPlaylist } from '../../api/navidromeSmart'; +import type { SubsonicPlaylist } from '../../api/subsonicTypes'; +import { usePlaylistStore } from '../../store/playlistStore'; import { buildSmartRulesPayload, defaultSmartFilters, SMART_PREFIX, type PendingSmartPlaylist, type SmartFilters, } from './playlistsSmart'; -import { showToast } from './toast'; +import { showToast } from '../ui/toast'; export interface RunPlaylistsSaveSmartDeps { isNavidromeServer: boolean; diff --git a/src/utils/spotifyCsvImport.ts b/src/utils/playlist/spotifyCsvImport.ts similarity index 100% rename from src/utils/spotifyCsvImport.ts rename to src/utils/playlist/spotifyCsvImport.ts diff --git a/src/utils/spotifyCsvMatch.ts b/src/utils/playlist/spotifyCsvMatch.ts similarity index 100% rename from src/utils/spotifyCsvMatch.ts rename to src/utils/playlist/spotifyCsvMatch.ts diff --git a/src/utils/startPlaylistRowDrag.ts b/src/utils/playlist/startPlaylistRowDrag.ts similarity index 94% rename from src/utils/startPlaylistRowDrag.ts rename to src/utils/playlist/startPlaylistRowDrag.ts index 3e27eb4c..b6d65896 100644 --- a/src/utils/startPlaylistRowDrag.ts +++ b/src/utils/playlist/startPlaylistRowDrag.ts @@ -1,6 +1,6 @@ import type React from 'react'; -import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from './songToTrack'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { songToTrack } from '../playback/songToTrack'; export interface StartPlaylistRowDragDeps { e: React.MouseEvent; diff --git a/src/utils/redactSubsonicUrl.ts b/src/utils/server/redactSubsonicUrl.ts similarity index 100% rename from src/utils/redactSubsonicUrl.ts rename to src/utils/server/redactSubsonicUrl.ts diff --git a/src/utils/serverDisplayName.test.ts b/src/utils/server/serverDisplayName.test.ts similarity index 95% rename from src/utils/serverDisplayName.test.ts rename to src/utils/server/serverDisplayName.test.ts index 6c529ad3..6ead9464 100644 --- a/src/utils/serverDisplayName.test.ts +++ b/src/utils/server/serverDisplayName.test.ts @@ -1,4 +1,4 @@ -import type { ServerProfile } from '../store/authStoreTypes'; +import type { ServerProfile } from '../../store/authStoreTypes'; import { describe, expect, it } from 'vitest'; import { serverListDisplayLabel, shortHostFromServerUrl } from './serverDisplayName'; diff --git a/src/utils/serverDisplayName.ts b/src/utils/server/serverDisplayName.ts similarity index 94% rename from src/utils/serverDisplayName.ts rename to src/utils/server/serverDisplayName.ts index 188a8b13..8009236c 100644 --- a/src/utils/serverDisplayName.ts +++ b/src/utils/server/serverDisplayName.ts @@ -1,4 +1,4 @@ -import type { ServerProfile } from '../store/authStoreTypes'; +import type { ServerProfile } from '../../store/authStoreTypes'; /** Host (+ port) from a server base URL, e.g. `https://music.one.com/foo` → `music.one.com`. */ export function shortHostFromServerUrl(urlRaw: string): string { const t = urlRaw.trim(); diff --git a/src/utils/serverMagicString.test.ts b/src/utils/server/serverMagicString.test.ts similarity index 100% rename from src/utils/serverMagicString.test.ts rename to src/utils/server/serverMagicString.test.ts diff --git a/src/utils/serverMagicString.ts b/src/utils/server/serverMagicString.ts similarity index 100% rename from src/utils/serverMagicString.ts rename to src/utils/server/serverMagicString.ts diff --git a/src/utils/subsonicServerIdentity.ts b/src/utils/server/subsonicServerIdentity.ts similarity index 100% rename from src/utils/subsonicServerIdentity.ts rename to src/utils/server/subsonicServerIdentity.ts diff --git a/src/utils/switchActiveServer.ts b/src/utils/server/switchActiveServer.ts similarity index 85% rename from src/utils/switchActiveServer.ts rename to src/utils/server/switchActiveServer.ts index f74fbb20..cac31afe 100644 --- a/src/utils/switchActiveServer.ts +++ b/src/utils/server/switchActiveServer.ts @@ -1,8 +1,8 @@ -import type { ServerProfile } from '../store/authStoreTypes'; -import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../api/subsonic'; -import { useAuthStore } from '../store/authStore'; -import { useOrbitStore } from '../store/orbitStore'; -import { endOrbitSession, leaveOrbitSession } from './orbit'; +import type { ServerProfile } from '../../store/authStoreTypes'; +import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../../api/subsonic'; +import { useAuthStore } from '../../store/authStore'; +import { useOrbitStore } from '../../store/orbitStore'; +import { endOrbitSession, leaveOrbitSession } from '../orbit'; export async function switchActiveServer(server: ServerProfile): Promise { try { diff --git a/src/utils/applySharePaste.ts b/src/utils/share/applySharePaste.ts similarity index 91% rename from src/utils/applySharePaste.ts rename to src/utils/share/applySharePaste.ts index 95bce45a..911b1d09 100644 --- a/src/utils/applySharePaste.ts +++ b/src/utils/share/applySharePaste.ts @@ -1,13 +1,13 @@ -import { getArtist } from '../api/subsonicArtists'; -import { getAlbum, getSong } from '../api/subsonicLibrary'; -import type { SubsonicSong } from '../api/subsonicTypes'; -import { songToTrack } from '../utils/songToTrack'; +import { getArtist } from '../../api/subsonicArtists'; +import { getAlbum, getSong } from '../../api/subsonicLibrary'; +import type { SubsonicSong } from '../../api/subsonicTypes'; +import { songToTrack } from '../playback/songToTrack'; import type { NavigateFunction } from 'react-router-dom'; import type { TFunction } from 'i18next'; -import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { useAuthStore } from '../../store/authStore'; +import { usePlayerStore } from '../../store/playerStore'; import { findServerIdForShareUrl, type EntitySharePayloadV1 } from './shareLink'; -import { showToast } from './toast'; +import { showToast } from '../ui/toast'; const RESOLVE_QUEUE_CHUNK = 12; diff --git a/src/utils/copyEntityShareLink.test.ts b/src/utils/share/copyEntityShareLink.test.ts similarity index 100% rename from src/utils/copyEntityShareLink.test.ts rename to src/utils/share/copyEntityShareLink.test.ts diff --git a/src/utils/copyEntityShareLink.ts b/src/utils/share/copyEntityShareLink.ts similarity index 78% rename from src/utils/copyEntityShareLink.ts rename to src/utils/share/copyEntityShareLink.ts index e6662504..8807ce00 100644 --- a/src/utils/copyEntityShareLink.ts +++ b/src/utils/share/copyEntityShareLink.ts @@ -1,6 +1,6 @@ -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '../../store/authStore'; import { encodeSharePayload, type EntityShareKind } from './shareLink'; -import { copyTextToClipboard } from './serverMagicString'; +import { copyTextToClipboard } from '../server/serverMagicString'; /** Copies a track / album / artist / composer share link (`psysonic2-`) to the clipboard. */ export async function copyEntityShareLink(kind: EntityShareKind, id: string): Promise { diff --git a/src/utils/shareLink.test.ts b/src/utils/share/shareLink.test.ts similarity index 99% rename from src/utils/shareLink.test.ts rename to src/utils/share/shareLink.test.ts index 153f27aa..a5c3c2cd 100644 --- a/src/utils/shareLink.test.ts +++ b/src/utils/share/shareLink.test.ts @@ -7,7 +7,7 @@ import { findServerIdForShareUrl, normalizeShareServerUrl, } from './shareLink'; -import { decodeServerMagicString, encodeServerMagicString, SERVER_MAGIC_STRING_PREFIX } from './serverMagicString'; +import { decodeServerMagicString, encodeServerMagicString, SERVER_MAGIC_STRING_PREFIX } from '../server/serverMagicString'; import { makeServer } from '@/test/helpers/factories'; describe('shareLink vs serverMagicString', () => { diff --git a/src/utils/shareLink.ts b/src/utils/share/shareLink.ts similarity index 98% rename from src/utils/shareLink.ts rename to src/utils/share/shareLink.ts index 7b2d938a..942cb699 100644 --- a/src/utils/shareLink.ts +++ b/src/utils/share/shareLink.ts @@ -1,4 +1,4 @@ -import type { ServerProfile } from '../store/authStoreTypes'; +import type { ServerProfile } from '../../store/authStoreTypes'; /** Library share (track / album / artist / queue). Same naming family as `psysonic1-` server invites. */ export const PSYSONIC_SHARE_PREFIX = 'psysonic2-'; diff --git a/src/utils/dragGhost.ts b/src/utils/ui/dragGhost.ts similarity index 100% rename from src/utils/dragGhost.ts rename to src/utils/ui/dragGhost.ts diff --git a/src/utils/dynamicColors.dom.test.ts b/src/utils/ui/dynamicColors.dom.test.ts similarity index 100% rename from src/utils/dynamicColors.dom.test.ts rename to src/utils/ui/dynamicColors.dom.test.ts diff --git a/src/utils/dynamicColors.test.ts b/src/utils/ui/dynamicColors.test.ts similarity index 100% rename from src/utils/dynamicColors.test.ts rename to src/utils/ui/dynamicColors.test.ts diff --git a/src/utils/dynamicColors.ts b/src/utils/ui/dynamicColors.ts similarity index 100% rename from src/utils/dynamicColors.ts rename to src/utils/ui/dynamicColors.ts diff --git a/src/utils/easeScroll.ts b/src/utils/ui/easeScroll.ts similarity index 100% rename from src/utils/easeScroll.ts rename to src/utils/ui/easeScroll.ts diff --git a/src/utils/overlayScrollbarMetrics.ts b/src/utils/ui/overlayScrollbarMetrics.ts similarity index 100% rename from src/utils/overlayScrollbarMetrics.ts rename to src/utils/ui/overlayScrollbarMetrics.ts diff --git a/src/utils/overlayScrollbarThumb.ts b/src/utils/ui/overlayScrollbarThumb.ts similarity index 100% rename from src/utils/overlayScrollbarThumb.ts rename to src/utils/ui/overlayScrollbarThumb.ts diff --git a/src/utils/toast.ts b/src/utils/ui/toast.ts similarity index 100% rename from src/utils/toast.ts rename to src/utils/ui/toast.ts diff --git a/src/utils/waveformParse.test.ts b/src/utils/waveform/waveformParse.test.ts similarity index 100% rename from src/utils/waveformParse.test.ts rename to src/utils/waveform/waveformParse.test.ts diff --git a/src/utils/waveformParse.ts b/src/utils/waveform/waveformParse.ts similarity index 100% rename from src/utils/waveformParse.ts rename to src/utils/waveform/waveformParse.ts diff --git a/src/utils/waveformSeekHelpers.ts b/src/utils/waveform/waveformSeekHelpers.ts similarity index 98% rename from src/utils/waveformSeekHelpers.ts rename to src/utils/waveform/waveformSeekHelpers.ts index cdd68319..86efd7b2 100644 --- a/src/utils/waveformSeekHelpers.ts +++ b/src/utils/waveform/waveformSeekHelpers.ts @@ -1,4 +1,4 @@ -import type { SeekbarStyle } from '../store/authStoreTypes'; +import type { SeekbarStyle } from '../../store/authStoreTypes'; export function fmt(s: number): string { if (!s || isNaN(s)) return '0:00'; diff --git a/src/utils/waveformSeekRenderers.ts b/src/utils/waveform/waveformSeekRenderers.ts similarity index 93% rename from src/utils/waveformSeekRenderers.ts rename to src/utils/waveform/waveformSeekRenderers.ts index 021f08db..019fed39 100644 --- a/src/utils/waveformSeekRenderers.ts +++ b/src/utils/waveform/waveformSeekRenderers.ts @@ -1,5 +1,5 @@ -import type { SeekbarStyle } from '../store/authStoreTypes'; -import { bumpPerfCounter } from './perfTelemetry'; +import type { SeekbarStyle } from '../../store/authStoreTypes'; +import { bumpPerfCounter } from '../perf/perfTelemetry'; import { AnimState, makeAnimState } from './waveformSeekHelpers'; import { drawBar, drawLineDot, drawNeon, drawSegmented, drawThick, drawWaveform, diff --git a/src/utils/waveformSeekRenderersAnimated.ts b/src/utils/waveform/waveformSeekRenderersAnimated.ts similarity index 100% rename from src/utils/waveformSeekRenderersAnimated.ts rename to src/utils/waveform/waveformSeekRenderersAnimated.ts diff --git a/src/utils/waveformSeekRenderersStatic.ts b/src/utils/waveform/waveformSeekRenderersStatic.ts similarity index 100% rename from src/utils/waveformSeekRenderersStatic.ts rename to src/utils/waveform/waveformSeekRenderersStatic.ts