From 7a7a9f5e6b1363ee66c66fb405da11d074d3106e Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Thu, 14 May 2026 14:27:44 +0200 Subject: [PATCH] refactor(utils): group utils/ files into topic folders (Phase L, part 1) (#689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 111 of 122 top-level src/utils/ files move into 16 topic folders (audio, cache, cover, share, server, playback, playlist, deviceSync, waveform, mix, format, export, changelog, ui, perf, componentHelpers). True singletons with no cluster stay at the utils/ root. Pure file-move: a path-aware codemod rewrote 539 relative-import specifiers across 275 files; no logic touched. The hot-path coverage gate list (.github/frontend-hot-path-files.txt) is updated to the new paths for the 11 gated utils files — a mechanical consequence of the move, not a CI change. tsc is green. --- .github/frontend-hot-path-files.txt | 22 +++++++++---------- src/api/subsonic.ts | 2 +- src/api/subsonicTypes.ts | 2 +- src/app/AppShell.tsx | 4 ++-- src/app/MainApp.tsx | 4 ++-- src/app/MiniPlayerApp.test.tsx | 2 +- src/app/MiniPlayerApp.tsx | 2 +- src/components/AlbumCard.tsx | 4 ++-- src/components/AlbumHeader.tsx | 4 ++-- src/components/AlbumRow.tsx | 2 +- src/components/AlbumTrackList.tsx | 4 ++-- src/components/AppShellQueueResizerSeam.tsx | 2 +- src/components/AppUpdater.tsx | 2 +- src/components/BecauseYouLikeRail.tsx | 4 ++-- src/components/ChangelogModal.tsx | 2 +- src/components/ConnectionIndicator.tsx | 6 ++--- src/components/ContextMenu.tsx | 4 ++-- src/components/Equalizer.tsx | 2 +- src/components/FpsOverlay.tsx | 2 +- src/components/Hero.tsx | 8 +++---- src/components/LiveSearch.tsx | 4 ++-- src/components/LyricsPane.tsx | 2 +- src/components/MarqueeText.tsx | 2 +- src/components/MiniPlayer.tsx | 2 +- src/components/MobilePlayerView.tsx | 2 +- src/components/MobileSearchOverlay.tsx | 4 ++-- src/components/OrbitDiagnosticsPopover.tsx | 2 +- src/components/OrbitJoinModal.tsx | 4 ++-- src/components/OrbitSessionBar.tsx | 2 +- src/components/OrbitSettingsPopover.tsx | 2 +- src/components/OverlayScrollArea.tsx | 6 ++--- src/components/PasteClipboardHandler.tsx | 10 ++++----- src/components/PlaybackDelayModal.tsx | 2 +- src/components/PlaybackScheduleBadge.tsx | 2 +- src/components/PlayerBar.tsx | 6 ++--- src/components/QueuePanel.tsx | 10 ++++----- src/components/Sidebar.tsx | 4 ++-- src/components/SongCard.tsx | 4 ++-- src/components/SongInfoModal.tsx | 4 ++-- src/components/SongRail.tsx | 2 +- src/components/SongRow.tsx | 4 ++-- src/components/StatsExportModal.tsx | 4 ++-- src/components/WaveformSeek.tsx | 4 ++-- src/components/WaveformSeekPreview.tsx | 4 ++-- .../albumTrackList/AlbumTrackListMobile.tsx | 4 ++-- src/components/albumTrackList/TrackRow.tsx | 4 ++-- .../albumTrackList/TracklistColumnPicker.tsx | 2 +- .../albumTrackList/TracklistHeaderRow.tsx | 2 +- .../artistDetail/ArtistDetailHero.tsx | 2 +- .../artistDetail/ArtistDetailTopTracks.tsx | 4 ++-- src/components/artists/ArtistAvatars.tsx | 2 +- src/components/artists/ArtistsListView.tsx | 2 +- .../contextMenu/AddToPlaylistSubmenu.tsx | 4 ++-- .../contextMenu/AlbumContextItems.tsx | 2 +- .../MultiAlbumToPlaylistSubmenu.tsx | 4 ++-- .../MultiArtistToPlaylistSubmenu.tsx | 4 ++-- .../contextMenu/PlaylistContextItems.tsx | 4 ++-- .../PlaylistToPlaylistSubmenus.tsx | 4 ++-- .../contextMenu/SongContextItems.tsx | 6 ++--- .../contextMenu/contextMenuItemTypes.ts | 2 +- .../deviceSync/DeviceSyncBrowserPanel.tsx | 2 +- .../deviceSync/DeviceSyncDevicePanel.tsx | 2 +- .../deviceSync/DeviceSyncHeader.tsx | 4 ++-- .../deviceSync/DeviceSyncMigrationModal.tsx | 2 +- .../deviceSync/DeviceSyncPreSyncModal.tsx | 2 +- .../favorites/FavoritesSongsSectionHeader.tsx | 2 +- .../favorites/FavoritesSongsTracklist.tsx | 2 +- .../folderBrowser/FolderBrowserColumn.tsx | 2 +- .../fullscreenPlayer/FsLyricsApple.tsx | 2 +- src/components/fullscreenPlayer/FsPlayBtn.tsx | 2 +- src/components/fullscreenPlayer/FsSeekbar.tsx | 2 +- .../internetRadio/RadioDirectoryModal.tsx | 2 +- src/components/miniPlayer/MiniControls.tsx | 2 +- src/components/nowPlaying/AlbumCard.tsx | 2 +- src/components/nowPlaying/ArtistCard.tsx | 2 +- src/components/nowPlaying/CreditsCard.tsx | 2 +- src/components/nowPlaying/Hero.tsx | 2 +- src/components/nowPlaying/RadioView.tsx | 2 +- src/components/nowPlaying/TopSongsCard.tsx | 2 +- src/components/nowPlaying/TourCard.tsx | 2 +- src/components/playerBar/PlaybackClock.tsx | 2 +- .../playerBar/PlayerSeekbarSection.tsx | 2 +- .../playerBar/PlayerTransportControls.tsx | 2 +- .../playlist/CsvImportReportModal.tsx | 4 ++-- src/components/playlist/PlaylistHero.tsx | 2 +- .../playlist/PlaylistSongSearchPanel.tsx | 4 ++-- .../playlist/PlaylistSuggestions.tsx | 4 ++-- src/components/playlist/PlaylistTracklist.tsx | 6 ++--- src/components/playlists/PlaylistCard.tsx | 4 ++-- src/components/playlists/PlaylistsHeader.tsx | 2 +- .../playlists/PlaylistsSmartEditor.tsx | 2 +- .../queuePanel/QueueCurrentTrack.tsx | 8 +++---- src/components/queuePanel/QueueHeader.tsx | 2 +- src/components/queuePanel/QueueList.tsx | 2 +- .../randomMix/RandomMixTrackRow.tsx | 2 +- src/components/settings/AddServerForm.tsx | 6 ++--- src/components/settings/AudioTab.tsx | 2 +- src/components/settings/BackupSection.tsx | 4 ++-- src/components/settings/ServersTab.tsx | 8 +++---- src/components/settings/SidebarCustomizer.tsx | 2 +- src/components/settings/StorageTab.tsx | 4 ++-- src/components/settings/SystemTab.tsx | 2 +- src/components/settings/UserForm.tsx | 6 ++--- .../audio/AudioOutputDeviceSection.tsx | 2 +- .../settings/userMgmt/MagicStringModal.tsx | 6 ++--- .../settings/userMgmt/UserMgmtRow.tsx | 2 +- src/components/sidebar/SidebarNavBody.tsx | 2 +- .../sidebar/SidebarPerfProbeModal.tsx | 2 +- .../sidebar/SidebarPerfProbePhase2.tsx | 2 +- src/config/shortcutActionRegistry.ts | 4 ++-- src/config/shortcutDispatch.ts | 4 ++-- src/hooks/tauriBridge/useCliBridge.ts | 8 +++---- src/hooks/useAlbumTrackListSelection.ts | 2 +- src/hooks/useAppUpdater.ts | 2 +- src/hooks/useArtistsFiltering.ts | 2 +- src/hooks/useAudioDevicesProbe.ts | 4 ++-- src/hooks/useAutoEq.ts | 2 +- src/hooks/useConnectionStatus.ts | 4 ++-- src/hooks/useContextMenuRating.ts | 2 +- src/hooks/useDeviceSyncBrowser.ts | 2 +- src/hooks/useDeviceSyncDeviceScan.ts | 2 +- src/hooks/useDeviceSyncDrives.ts | 2 +- src/hooks/useDeviceSyncJobEvents.ts | 6 ++--- src/hooks/useDeviceSyncSourceStatuses.ts | 4 ++-- src/hooks/useFolderBrowserKeyboardNav.ts | 2 +- src/hooks/useFolderBrowserNowPlayingPath.ts | 2 +- src/hooks/useFolderBrowserScrolling.ts | 2 +- src/hooks/useFsDynamicAccent.ts | 2 +- src/hooks/useLyrics.ts | 2 +- src/hooks/useMiniWindowSetup.ts | 2 +- src/hooks/useNowPlayingFetchers.ts | 2 +- src/hooks/useOrbitGuest.ts | 2 +- src/hooks/useOrbitHost.ts | 4 ++-- src/hooks/useOrbitSongRowBehavior.ts | 2 +- src/hooks/usePendingSmartPolling.ts | 2 +- src/hooks/usePlaylistBulkPlayCallbacks.ts | 2 +- src/hooks/usePlaylistDerived.ts | 4 ++-- src/hooks/usePlaylistDnDReorder.ts | 2 +- src/hooks/usePlaylistSongMutations.ts | 2 +- src/hooks/useRadioMetadata.ts | 2 +- src/hooks/useSidebarNavDnd.ts | 4 ++-- src/hooks/useSidebarNewReleasesUnread.ts | 2 +- src/hooks/useSidebarPerfProbe.ts | 2 +- src/hooks/useSmartCoverCollage.ts | 2 +- src/hooks/useUserMgmtActions.ts | 6 ++--- src/hooks/useWaveformHeights.ts | 6 ++--- src/hooks/useWaveformInterpolation.ts | 4 ++-- src/hotCachePrefetch.ts | 2 +- src/pages/AlbumDetail.tsx | 6 ++--- src/pages/Albums.tsx | 6 ++--- src/pages/ArtistDetail.tsx | 14 ++++++------ src/pages/Artists.tsx | 4 ++-- src/pages/ComposerDetail.tsx | 4 ++-- src/pages/Composers.tsx | 2 +- src/pages/DeviceSync.tsx | 10 ++++----- src/pages/Favorites.tsx | 2 +- src/pages/FolderBrowser.tsx | 2 +- src/pages/Home.tsx | 6 ++--- src/pages/InternetRadio.tsx | 2 +- src/pages/Login.tsx | 4 ++-- src/pages/LosslessAlbums.tsx | 6 ++--- src/pages/LuckyMix.tsx | 2 +- src/pages/MostPlayed.tsx | 4 ++-- src/pages/NewReleases.tsx | 2 +- src/pages/NowPlaying.tsx | 4 ++-- src/pages/PlaylistDetail.tsx | 16 +++++++------- src/pages/Playlists.tsx | 12 +++++----- src/pages/RandomAlbums.tsx | 4 ++-- src/pages/RandomMix.tsx | 6 ++--- src/pages/Settings.tsx | 2 +- src/pages/Statistics.tsx | 2 +- src/pages/Tracks.tsx | 6 ++--- src/pages/WhatsNew.tsx | 4 ++-- src/store/applyQueueHistorySnapshot.ts | 4 ++-- src/store/audioEventHandlers.ts | 12 +++++----- .../audioEngineListeners.ts | 6 ++--- .../audioListenerSetup/authSyncListener.ts | 2 +- .../audioListenerSetup/initialAudioSync.ts | 2 +- src/store/authAudioSettingsActions.ts | 2 +- src/store/authPerServerCapabilityActions.ts | 2 +- src/store/authStoreHelpers.ts | 2 +- src/store/authStoreRehydrate.ts | 2 +- src/store/authStoreTypes.ts | 2 +- src/store/hotCacheStore.ts | 2 +- src/store/loudnessRefresh.test.ts | 2 +- src/store/loudnessRefresh.ts | 2 +- src/store/miscActions.ts | 4 ++-- src/store/nextAction.ts | 4 ++-- src/store/normalizationIpcDedupe.test.ts | 2 +- src/store/normalizationIpcDedupe.ts | 2 +- src/store/normalizationSnapshot.test.ts | 8 +++---- src/store/normalizationSnapshot.ts | 2 +- src/store/offlineStore.ts | 2 +- src/store/playTrackAction.ts | 8 +++---- src/store/playbackUrlRouting.test.ts | 2 +- src/store/playbackUrlRouting.ts | 4 ++-- src/store/playerStoreTypes.ts | 2 +- src/store/queueUndoAudioRestore.test.ts | 6 ++--- src/store/queueUndoAudioRestore.ts | 6 ++--- src/store/radioPlayer.test.ts | 2 +- src/store/radioPlayer.ts | 2 +- src/store/resumeAction.ts | 8 +++---- src/store/seekAction.ts | 2 +- src/store/seekFallbackState.test.ts | 2 +- src/store/seekFallbackState.ts | 2 +- src/store/updateReplayGainAction.ts | 6 ++--- src/store/waveformRefresh.test.ts | 2 +- src/store/waveformRefresh.ts | 2 +- src/utils/{ => audio}/audioDeviceLabels.ts | 0 src/utils/{ => audio}/autoEqParse.ts | 0 src/utils/{ => audio}/eqCurve.ts | 2 +- src/utils/{ => audio}/loudnessPlaceholder.ts | 0 .../{ => audio}/loudnessPreAnalysisSlider.ts | 0 .../{ => audio}/normalizationCompare.test.ts | 0 src/utils/{ => audio}/normalizationCompare.ts | 0 .../{ => audio}/resolveReplayGainDb.test.ts | 2 +- src/utils/{ => audio}/resolveReplayGainDb.ts | 2 +- src/utils/{ => audio}/seekErrors.test.ts | 0 src/utils/{ => audio}/seekErrors.ts | 0 src/utils/{ => cache}/hotCacheGate.ts | 0 .../{ => cache}/lyricsPersistentCache.ts | 2 +- src/utils/{ => cache}/nowPlayingCache.ts | 0 .../{ => changelog}/changelogMarkdown.tsx | 0 .../{ => changelog}/changelogReleaseMatch.ts | 0 .../albumDetailHelpers.ts | 0 .../albumTrackListHelpers.ts | 2 +- .../{ => componentHelpers}/appShellHelpers.ts | 2 +- .../appUpdaterHelpers.ts | 2 +- .../artistDetailHelpers.ts | 0 .../{ => componentHelpers}/artistsHelpers.ts | 2 +- .../contextMenuActions.ts | 22 +++++++++---------- .../contextMenuHelpers.ts | 2 +- .../folderBrowserHelpers.ts | 4 ++-- .../fullscreenPlayerHelpers.ts | 0 .../miniPlayerHelpers.ts | 4 ++-- .../nowPlayingHelpers.ts | 2 +- .../playerBarHelpers.ts | 0 .../playlistDetailHelpers.ts | 4 ++-- .../queuePanelHelpers.tsx | 2 +- .../randomMixHelpers.ts | 4 ++-- .../runArtistDetailActions.ts | 14 ++++++------ .../runArtistDetailPlay.ts | 10 ++++----- .../{ => componentHelpers}/sidebarHelpers.ts | 0 .../sidebarNavReorder.ts | 4 ++-- .../{ => componentHelpers}/userMgmtHelpers.ts | 0 .../coverArtRegisteredSizes.test.ts | 0 .../{ => cover}/coverArtRegisteredSizes.ts | 0 src/utils/{ => cover}/coverBlobDownscale.ts | 0 .../{ => deviceSync}/deviceSyncHelpers.ts | 2 +- .../deviceSyncLegacyTemplate.ts | 2 +- .../runDeviceSyncChooseFolder.ts | 4 ++-- .../runDeviceSyncExecution.ts | 14 ++++++------ .../runDeviceSyncMigration.ts | 8 +++---- src/utils/{ => export}/backup.ts | 2 +- src/utils/{ => export}/exportAlbumCard.ts | 8 +++---- src/utils/{ => export}/exportNewAlbums.ts | 8 +++---- src/utils/{ => format}/formatBytes.ts | 0 src/utils/{ => format}/formatHumanDuration.ts | 2 +- .../{ => format}/playbackScheduleFormat.ts | 4 ++-- src/utils/imageCache.ts | 2 +- src/utils/imageCache/coverSiblings.ts | 4 ++-- src/utils/{ => mix}/luckyMix.ts | 22 +++++++++---------- src/utils/{ => mix}/luckyMixHelpers.ts | 6 ++--- src/utils/{ => mix}/mixRatingFilter.ts | 8 +++---- src/utils/orbit/guest.ts | 2 +- src/utils/orbit/host.ts | 2 +- src/utils/orbit/shareLink.ts | 2 +- src/utils/{ => perf}/perfFlags.ts | 0 src/utils/{ => perf}/perfTelemetry.ts | 0 .../buildInfiniteQueueCandidates.test.ts | 14 ++++++------ .../buildInfiniteQueueCandidates.ts | 8 +++---- .../{ => playback}/fetchTracksForSource.ts | 10 ++++----- src/utils/{ => playback}/playAlbum.ts | 6 ++--- .../{ => playback}/playArtistShuffled.ts | 10 ++++----- src/utils/{ => playback}/playByOpaqueId.ts | 6 ++--- src/utils/{ => playback}/playSong.ts | 6 ++--- .../{ => playback}/queueIdentity.test.ts | 2 +- src/utils/{ => playback}/queueIdentity.ts | 2 +- .../{ => playback}/resolvePlaybackUrl.test.ts | 0 .../{ => playback}/resolvePlaybackUrl.ts | 6 ++--- src/utils/{ => playback}/shuffleArray.test.ts | 0 src/utils/{ => playback}/shuffleArray.ts | 0 src/utils/{ => playback}/songToTrack.test.ts | 2 +- src/utils/{ => playback}/songToTrack.ts | 4 ++-- .../{ => playlist}/playlistBulkPlayActions.ts | 2 +- .../{ => playlist}/playlistDisplayedSongs.ts | 2 +- src/utils/{ => playlist}/playlistsSmart.ts | 0 .../{ => playlist}/runPlaylistCsvImport.ts | 6 ++--- src/utils/{ => playlist}/runPlaylistLoad.ts | 6 ++--- .../{ => playlist}/runPlaylistReorderDrop.ts | 2 +- .../{ => playlist}/runPlaylistSaveMeta.ts | 6 ++--- .../{ => playlist}/runPlaylistZipDownload.ts | 8 +++---- .../{ => playlist}/runPlaylistsActions.ts | 8 +++---- .../runPlaylistsOpenSmartEditor.ts | 6 ++--- .../{ => playlist}/runPlaylistsSaveSmart.ts | 8 +++---- src/utils/{ => playlist}/spotifyCsvImport.ts | 0 src/utils/{ => playlist}/spotifyCsvMatch.ts | 0 .../{ => playlist}/startPlaylistRowDrag.ts | 4 ++-- src/utils/{ => server}/redactSubsonicUrl.ts | 0 .../{ => server}/serverDisplayName.test.ts | 2 +- src/utils/{ => server}/serverDisplayName.ts | 2 +- .../{ => server}/serverMagicString.test.ts | 0 src/utils/{ => server}/serverMagicString.ts | 0 .../{ => server}/subsonicServerIdentity.ts | 0 src/utils/{ => server}/switchActiveServer.ts | 10 ++++----- src/utils/{ => share}/applySharePaste.ts | 14 ++++++------ .../{ => share}/copyEntityShareLink.test.ts | 0 src/utils/{ => share}/copyEntityShareLink.ts | 4 ++-- src/utils/{ => share}/shareLink.test.ts | 2 +- src/utils/{ => share}/shareLink.ts | 2 +- src/utils/{ => ui}/dragGhost.ts | 0 src/utils/{ => ui}/dynamicColors.dom.test.ts | 0 src/utils/{ => ui}/dynamicColors.test.ts | 0 src/utils/{ => ui}/dynamicColors.ts | 0 src/utils/{ => ui}/easeScroll.ts | 0 src/utils/{ => ui}/overlayScrollbarMetrics.ts | 0 src/utils/{ => ui}/overlayScrollbarThumb.ts | 0 src/utils/{ => ui}/toast.ts | 0 .../{ => waveform}/waveformParse.test.ts | 0 src/utils/{ => waveform}/waveformParse.ts | 0 .../{ => waveform}/waveformSeekHelpers.ts | 2 +- .../{ => waveform}/waveformSeekRenderers.ts | 4 ++-- .../waveformSeekRenderersAnimated.ts | 0 .../waveformSeekRenderersStatic.ts | 0 324 files changed, 551 insertions(+), 551 deletions(-) rename src/utils/{ => audio}/audioDeviceLabels.ts (100%) rename src/utils/{ => audio}/autoEqParse.ts (100%) rename src/utils/{ => audio}/eqCurve.ts (98%) rename src/utils/{ => audio}/loudnessPlaceholder.ts (100%) rename src/utils/{ => audio}/loudnessPreAnalysisSlider.ts (100%) rename src/utils/{ => audio}/normalizationCompare.test.ts (100%) rename src/utils/{ => audio}/normalizationCompare.ts (100%) rename src/utils/{ => audio}/resolveReplayGainDb.test.ts (98%) rename src/utils/{ => audio}/resolveReplayGainDb.ts (94%) rename src/utils/{ => audio}/seekErrors.test.ts (100%) rename src/utils/{ => audio}/seekErrors.ts (100%) rename src/utils/{ => cache}/hotCacheGate.ts (100%) rename src/utils/{ => cache}/lyricsPersistentCache.ts (98%) rename src/utils/{ => cache}/nowPlayingCache.ts (100%) rename src/utils/{ => changelog}/changelogMarkdown.tsx (100%) rename src/utils/{ => changelog}/changelogReleaseMatch.ts (100%) rename src/utils/{ => componentHelpers}/albumDetailHelpers.ts (100%) rename src/utils/{ => componentHelpers}/albumTrackListHelpers.ts (97%) rename src/utils/{ => componentHelpers}/appShellHelpers.ts (97%) rename src/utils/{ => componentHelpers}/appUpdaterHelpers.ts (96%) rename src/utils/{ => componentHelpers}/artistDetailHelpers.ts (100%) rename src/utils/{ => componentHelpers}/artistsHelpers.ts (95%) rename src/utils/{ => componentHelpers}/contextMenuActions.ts (88%) rename src/utils/{ => componentHelpers}/contextMenuHelpers.ts (95%) rename src/utils/{ => componentHelpers}/folderBrowserHelpers.ts (93%) rename src/utils/{ => componentHelpers}/fullscreenPlayerHelpers.ts (100%) rename src/utils/{ => componentHelpers}/miniPlayerHelpers.ts (95%) rename src/utils/{ => componentHelpers}/nowPlayingHelpers.ts (98%) rename src/utils/{ => componentHelpers}/playerBarHelpers.ts (100%) rename src/utils/{ => componentHelpers}/playlistDetailHelpers.ts (90%) rename src/utils/{ => componentHelpers}/queuePanelHelpers.tsx (95%) rename src/utils/{ => componentHelpers}/randomMixHelpers.ts (94%) rename src/utils/{ => componentHelpers}/runArtistDetailActions.ts (89%) rename src/utils/{ => componentHelpers}/runArtistDetailPlay.ts (89%) rename src/utils/{ => componentHelpers}/sidebarHelpers.ts (100%) rename src/utils/{ => componentHelpers}/sidebarNavReorder.ts (94%) rename src/utils/{ => componentHelpers}/userMgmtHelpers.ts (100%) rename src/utils/{ => cover}/coverArtRegisteredSizes.test.ts (100%) rename src/utils/{ => cover}/coverArtRegisteredSizes.ts (100%) rename src/utils/{ => cover}/coverBlobDownscale.ts (100%) rename src/utils/{ => deviceSync}/deviceSyncHelpers.ts (96%) rename src/utils/{ => deviceSync}/deviceSyncLegacyTemplate.ts (97%) rename src/utils/{ => deviceSync}/runDeviceSyncChooseFolder.ts (91%) rename src/utils/{ => deviceSync}/runDeviceSyncExecution.ts (91%) rename src/utils/{ => deviceSync}/runDeviceSyncMigration.ts (96%) rename src/utils/{ => export}/backup.ts (96%) rename src/utils/{ => export}/exportAlbumCard.ts (98%) rename src/utils/{ => export}/exportNewAlbums.ts (97%) rename src/utils/{ => format}/formatBytes.ts (100%) rename src/utils/{ => format}/formatHumanDuration.ts (92%) rename src/utils/{ => format}/playbackScheduleFormat.ts (94%) rename src/utils/{ => mix}/luckyMix.ts (95%) rename src/utils/{ => mix}/luckyMixHelpers.ts (95%) rename src/utils/{ => mix}/mixRatingFilter.ts (98%) rename src/utils/{ => perf}/perfFlags.ts (100%) rename src/utils/{ => perf}/perfTelemetry.ts (100%) rename src/utils/{ => playback}/buildInfiniteQueueCandidates.test.ts (96%) rename src/utils/{ => playback}/buildInfiniteQueueCandidates.ts (90%) rename src/utils/{ => playback}/fetchTracksForSource.ts (71%) rename src/utils/{ => playback}/playAlbum.ts (91%) rename src/utils/{ => playback}/playArtistShuffled.ts (74%) rename src/utils/{ => playback}/playByOpaqueId.ts (82%) rename src/utils/{ => playback}/playSong.ts (91%) rename src/utils/{ => playback}/queueIdentity.test.ts (98%) rename src/utils/{ => playback}/queueIdentity.ts (95%) rename src/utils/{ => playback}/resolvePlaybackUrl.test.ts (100%) rename src/utils/{ => playback}/resolvePlaybackUrl.ts (91%) rename src/utils/{ => playback}/shuffleArray.test.ts (100%) rename src/utils/{ => playback}/shuffleArray.ts (100%) rename src/utils/{ => playback}/songToTrack.test.ts (98%) rename src/utils/{ => playback}/songToTrack.ts (85%) rename src/utils/{ => playlist}/playlistBulkPlayActions.ts (94%) rename src/utils/{ => playlist}/playlistDisplayedSongs.ts (97%) rename src/utils/{ => playlist}/playlistsSmart.ts (100%) rename src/utils/{ => playlist}/runPlaylistCsvImport.ts (98%) rename src/utils/{ => playlist}/runPlaylistLoad.ts (86%) rename src/utils/{ => playlist}/runPlaylistReorderDrop.ts (96%) rename src/utils/{ => playlist}/runPlaylistSaveMeta.ts (91%) rename src/utils/{ => playlist}/runPlaylistZipDownload.ts (80%) rename src/utils/{ => playlist}/runPlaylistsActions.ts (94%) rename src/utils/{ => playlist}/runPlaylistsOpenSmartEditor.ts (93%) rename src/utils/{ => playlist}/runPlaylistsSaveSmart.ts (94%) rename src/utils/{ => playlist}/spotifyCsvImport.ts (100%) rename src/utils/{ => playlist}/spotifyCsvMatch.ts (100%) rename src/utils/{ => playlist}/startPlaylistRowDrag.ts (94%) rename src/utils/{ => server}/redactSubsonicUrl.ts (100%) rename src/utils/{ => server}/serverDisplayName.test.ts (95%) rename src/utils/{ => server}/serverDisplayName.ts (94%) rename src/utils/{ => server}/serverMagicString.test.ts (100%) rename src/utils/{ => server}/serverMagicString.ts (100%) rename src/utils/{ => server}/subsonicServerIdentity.ts (100%) rename src/utils/{ => server}/switchActiveServer.ts (85%) rename src/utils/{ => share}/applySharePaste.ts (91%) rename src/utils/{ => share}/copyEntityShareLink.test.ts (100%) rename src/utils/{ => share}/copyEntityShareLink.ts (78%) rename src/utils/{ => share}/shareLink.test.ts (99%) rename src/utils/{ => share}/shareLink.ts (98%) rename src/utils/{ => ui}/dragGhost.ts (100%) rename src/utils/{ => ui}/dynamicColors.dom.test.ts (100%) rename src/utils/{ => ui}/dynamicColors.test.ts (100%) rename src/utils/{ => ui}/dynamicColors.ts (100%) rename src/utils/{ => ui}/easeScroll.ts (100%) rename src/utils/{ => ui}/overlayScrollbarMetrics.ts (100%) rename src/utils/{ => ui}/overlayScrollbarThumb.ts (100%) rename src/utils/{ => ui}/toast.ts (100%) rename src/utils/{ => waveform}/waveformParse.test.ts (100%) rename src/utils/{ => waveform}/waveformParse.ts (100%) rename src/utils/{ => waveform}/waveformSeekHelpers.ts (98%) rename src/utils/{ => waveform}/waveformSeekRenderers.ts (93%) rename src/utils/{ => waveform}/waveformSeekRenderersAnimated.ts (100%) rename src/utils/{ => waveform}/waveformSeekRenderersStatic.ts (100%) 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