From 209dd614427a09cb97e270bce9ba944ef4e6f078 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Tue, 30 Jun 2026 08:07:04 +0200 Subject: [PATCH] refactor(lib): consolidate generic infra into src/lib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move domain-agnostic, feature-free helpers out of the flat utils/ and store/ roots into src/lib/ (plan M4, §3 lib/ layer): - lib/format/: formatBytes, formatClockTime, formatDuration, formatHumanDuration, relativeTime (pure format/date/byte/clock helpers) - lib/i18n.ts: i18next bootstrap (global app infra) - lib/util/: sanitizeHtml, platform, dedupeById, safeStorage (pure helpers + the zustand storage adapter) playbackScheduleFormat stays in utils/format (runtime usePlayerStore dep = audio-core coupling, not generic). formatClockTime keeps a type-only @/store/authStoreTypes import (erased, no runtime inversion — accepted per the deviceSync precedent). Pure move via deep @/lib/* specifiers (no barrel → no mock-collapse surface). tsc 0, lint 0/0, full suite 319 files / 2353 tests green. Tooling note: lib_move.py regex extended to also rewrite side-effect imports (import './i18n') and vi.importActual paths — both were silent gaps. --- src/app/AppShell.tsx | 2 +- src/app/MainApp.tsx | 2 +- src/app/MiniPlayerApp.test.tsx | 4 ++-- src/app/MiniPlayerApp.tsx | 2 +- src/components/AppUpdater.tsx | 2 +- src/components/BecauseYouLikeRail.tsx | 2 +- src/components/MobilePlayerView.tsx | 2 +- src/components/PlaybackDelayModal.tsx | 2 +- src/components/SongInfoModal.tsx | 4 ++-- src/components/SongRail.tsx | 2 +- src/components/SongRow.tsx | 2 +- src/components/TitleBar.tsx | 2 +- src/components/playerBar/PlaybackClock.tsx | 2 +- .../playerBar/PlayerSeekbarSection.tsx | 2 +- src/components/queuePanel/QueueHeader.tsx | 4 ++-- src/components/queuePanel/QueueList.tsx | 2 +- src/config/shortcutActionRegistry.ts | 2 +- src/config/shortcutDispatch.ts | 2 +- src/features/album/components/AlbumHeader.tsx | 6 ++--- src/features/album/components/AlbumRow.tsx | 2 +- .../album/components/AlbumTrackListMobile.tsx | 2 +- src/features/album/components/TrackRow.tsx | 4 ++-- .../album/hooks/useAlbumBrowseData.ts | 2 +- .../album/hooks/useGenreAlbumBrowse.ts | 2 +- src/features/album/pages/RandomAlbums.tsx | 2 +- .../components/ArtistDetailTopTracks.tsx | 2 +- .../artist/hooks/useArtistsBrowseCatalog.ts | 2 +- .../components/DeviceSyncPreSyncModal.tsx | 2 +- .../utils/deviceSyncLegacyTemplate.ts | 2 +- .../utils/runDeviceSyncMigration.ts | 2 +- .../favorites/components/FavoriteSongRow.tsx | 4 ++-- .../fullscreenPlayer/components/FsClock.tsx | 2 +- .../components/FsQueueModal.tsx | 2 +- .../components/FsTimeReadout.tsx | 2 +- .../miniPlayer/components/MiniControls.tsx | 2 +- .../miniPlayer/components/MiniTitlebar.tsx | 2 +- .../miniPlayer/hooks/useMiniWindowSetup.ts | 2 +- .../nowPlaying/components/AlbumCard.tsx | 2 +- src/features/nowPlaying/components/Hero.tsx | 2 +- .../nowPlaying/components/RadioView.tsx | 2 +- .../nowPlaying/components/TopSongsCard.tsx | 2 +- .../components/OfflineLibraryDiskStat.tsx | 2 +- .../offline/components/ZipDownloadOverlay.tsx | 2 +- .../offline/utils/favoritesOfflineSync.ts | 2 +- .../offline/utils/offlineStarredLoad.ts | 2 +- .../orbit/components/OrbitSessionBar.tsx | 2 +- src/features/orbit/hooks/useOrbitGuest.ts | 2 +- src/features/orbit/hooks/useOrbitHost.ts | 2 +- src/features/orbit/utils/orbitBulkGuard.ts | 2 +- .../playlist/components/PlaylistCard.tsx | 2 +- .../playlist/components/PlaylistRow.tsx | 4 ++-- .../components/PlaylistSongSearchPanel.tsx | 2 +- .../components/PlaylistSuggestions.tsx | 4 ++-- .../components/ShareQueuePreviewModal.tsx | 2 +- .../settings/components/AppearanceTab.tsx | 2 +- src/features/settings/components/AudioTab.tsx | 2 +- .../components/CoverCacheStrategySection.tsx | 2 +- .../settings/components/StorageTab.tsx | 2 +- .../settings/components/SystemTab.tsx | 4 ++-- .../settings/components/ThemeStoreSection.tsx | 2 +- src/features/settings/pages/Settings.tsx | 2 +- .../components/PlayerStatisticsPanel.tsx | 2 +- .../stats/components/PlayerStatsDayTracks.tsx | 2 +- .../components/PlayerStatsRecentDays.tsx | 2 +- src/features/stats/pages/Statistics.tsx | 2 +- src/hooks/tauriBridge/useCliBridge.ts | 2 +- src/hooks/useAppUpdater.ts | 2 +- src/hooks/useAudioDevicesProbe.ts | 2 +- src/hooks/usePlatformShellSetup.ts | 2 +- src/hooks/useThemeAnimationRisk.ts | 2 +- src/{utils => lib}/format/formatBytes.ts | 0 .../format/formatClockTime.test.ts | 2 +- src/{utils => lib}/format/formatClockTime.ts | 2 +- .../format/formatDuration.test.ts | 2 +- src/{utils => lib}/format/formatDuration.ts | 0 .../format/formatHumanDuration.test.ts | 4 ++-- .../format/formatHumanDuration.ts | 2 +- .../format/relativeTime.test.ts | 2 +- src/{utils => lib}/format/relativeTime.ts | 0 src/{ => lib}/i18n.ts | 24 +++++++++---------- src/{utils => lib/util}/dedupeById.ts | 0 src/{utils => lib/util}/platform.ts | 0 src/{store => lib/util}/safeStorage.test.ts | 2 +- src/{store => lib/util}/safeStorage.ts | 0 src/{utils => lib/util}/sanitizeHtml.ts | 0 src/main.tsx | 2 +- src/pages/ComposerDetail.tsx | 2 +- src/pages/Home.tsx | 2 +- src/pages/Login.tsx | 2 +- src/pages/NewReleases.tsx | 2 +- src/store/authStoreRehydrate.ts | 2 +- src/store/miscActions.ts | 2 +- src/store/playerStore.ts | 2 +- src/test/helpers/renderWithProviders.tsx | 2 +- .../componentHelpers/appUpdaterHelpers.ts | 2 +- .../componentHelpers/nowPlayingHelpers.ts | 2 +- .../componentHelpers/playlistDetailHelpers.ts | 4 ++-- src/utils/componentHelpers/userMgmtHelpers.ts | 2 +- src/utils/library/albumBrowseCatalogChunk.ts | 2 +- src/utils/library/albumBrowseLocal.ts | 2 +- src/utils/library/albumBrowseNetwork.ts | 2 +- src/utils/mix/luckyMix.ts | 2 +- src/utils/perf/perfLiveCpuSnapshot.ts | 2 +- 103 files changed, 119 insertions(+), 119 deletions(-) rename src/{utils => lib}/format/formatBytes.ts (100%) rename src/{utils => lib}/format/formatClockTime.test.ts (95%) rename src/{utils => lib}/format/formatClockTime.ts (91%) rename src/{utils => lib}/format/formatDuration.test.ts (98%) rename src/{utils => lib}/format/formatDuration.ts (100%) rename src/{utils => lib}/format/formatHumanDuration.test.ts (96%) rename src/{utils => lib}/format/formatHumanDuration.ts (98%) rename src/{utils => lib}/format/relativeTime.test.ts (93%) rename src/{utils => lib}/format/relativeTime.ts (100%) rename src/{ => lib}/i18n.ts (63%) rename src/{utils => lib/util}/dedupeById.ts (100%) rename src/{utils => lib/util}/platform.ts (100%) rename src/{store => lib/util}/safeStorage.test.ts (96%) rename src/{store => lib/util}/safeStorage.ts (100%) rename src/{utils => lib/util}/sanitizeHtml.ts (100%) diff --git a/src/app/AppShell.tsx b/src/app/AppShell.tsx index 57a67314..66b0df52 100644 --- a/src/app/AppShell.tsx +++ b/src/app/AppShell.tsx @@ -54,7 +54,7 @@ import { useNowPlayingPrewarm } from '@/features/nowPlaying'; import { useOfflineAutoNav } from '@/features/offline'; import { useOfflineLibraryFilterSuspend } from '@/features/offline'; import { AppShellQueueResizerSeam } from '../components/AppShellQueueResizerSeam'; -import { IS_LINUX, IS_MACOS } from '../utils/platform'; +import { IS_LINUX, IS_MACOS } from '@/lib/util/platform'; import { useConnectionStatus } from '../hooks/useConnectionStatus'; import { useIdlePlayQueuePull } from '../hooks/useIdlePlayQueuePull'; import { useAuthStore } from '../store/authStore'; diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index a960f51f..78532e7c 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -29,7 +29,7 @@ import { useLibraryCoverBackfill } from '../hooks/useLibraryCoverBackfill'; import { useCoverRevalidateScheduler } from '../cover/useCoverRevalidateScheduler'; import { runCoverIdbUpgradeMigration } from '../utils/migrations/coverIdbUpgradeMigration'; import { useMigrationOrchestrator } from '../hooks/useMigrationOrchestrator'; -import { IS_WINDOWS } from '../utils/platform'; +import { IS_WINDOWS } from '@/lib/util/platform'; import TauriEventBridge from './TauriEventBridge'; import AppShell from './AppShell'; import ErrorBoundary from '../components/ErrorBoundary'; diff --git a/src/app/MiniPlayerApp.test.tsx b/src/app/MiniPlayerApp.test.tsx index 2996cd8a..2747f70b 100644 --- a/src/app/MiniPlayerApp.test.tsx +++ b/src/app/MiniPlayerApp.test.tsx @@ -25,7 +25,7 @@ vi.mock('../store/keybindingsStore', () => ({ vi.mock('../utils/perf/perfFlags', () => ({ usePerfProbeFlags: () => ({ disableTooltipPortal: true }), })); -vi.mock('../i18n', () => ({ +vi.mock('@/lib/i18n', () => ({ default: { changeLanguage: vi.fn() }, })); vi.mock('@/features/miniPlayer', () => ({ default: () =>
})); @@ -38,7 +38,7 @@ vi.mock('../contexts/DragDropContext', () => ({ import { render, cleanup } from '@testing-library/react'; import MiniPlayerApp from './MiniPlayerApp'; -import i18n from '../i18n'; +import i18n from '@/lib/i18n'; beforeEach(() => { themeRehydrate.mockClear(); diff --git a/src/app/MiniPlayerApp.tsx b/src/app/MiniPlayerApp.tsx index ff8b2031..8e4d3944 100644 --- a/src/app/MiniPlayerApp.tsx +++ b/src/app/MiniPlayerApp.tsx @@ -8,7 +8,7 @@ import { useThemeStore } from '../store/themeStore'; import { useFontStore } from '../store/fontStore'; import { useKeybindingsStore } from '../store/keybindingsStore'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; -import i18n from '../i18n'; +import i18n from '@/lib/i18n'; /** * Mini-player webview tree. Rendered in the secondary Tauri window labelled diff --git a/src/components/AppUpdater.tsx b/src/components/AppUpdater.tsx index 3beb7157..0b107a5b 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 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { version as currentVersion } from '../../package.json'; -import { formatBytes } from '../utils/format/formatBytes'; +import { formatBytes } from '@/lib/format/formatBytes'; import { useAppUpdater } from '../hooks/useAppUpdater'; import Modal from './Modal'; import Changelog from './appUpdater/Changelog'; diff --git a/src/components/BecauseYouLikeRail.tsx b/src/components/BecauseYouLikeRail.tsx index 4813dcf9..45c167f0 100644 --- a/src/components/BecauseYouLikeRail.tsx +++ b/src/components/BecauseYouLikeRail.tsx @@ -23,7 +23,7 @@ import { useAuthStore } from '../store/authStore'; import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum'; import { useLongPressAction } from '../hooks/useLongPressAction'; import { LongPressWaveOverlay } from './LongPressWaveOverlay'; -import { formatHumanHoursMinutes } from '../utils/format/formatHumanDuration'; +import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration'; import { AlbumRow } from '@/features/album'; import { albumArtistDisplayName } from '@/features/album'; diff --git a/src/components/MobilePlayerView.tsx b/src/components/MobilePlayerView.tsx index 74bc942d..d6ec4e70 100644 --- a/src/components/MobilePlayerView.tsx +++ b/src/components/MobilePlayerView.tsx @@ -16,7 +16,7 @@ import { import { usePlayerStore } from '../store/playerStore'; import { useCachedUrl } from '@/ui/CachedImage'; import { OpenArtistRefInline } from '@/features/artist'; -import { formatTrackTime } from '../utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import { resolveQueueTrack } from '../utils/library/queueTrackView'; import { getQueueResolverVersion, diff --git a/src/components/PlaybackDelayModal.tsx b/src/components/PlaybackDelayModal.tsx index 4a63ffb6..57f89524 100644 --- a/src/components/PlaybackDelayModal.tsx +++ b/src/components/PlaybackDelayModal.tsx @@ -8,7 +8,7 @@ import { useShallow } from 'zustand/react/shallow'; import type { TFunction } from 'i18next'; import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; -import { formatClockTime } from '../utils/format/formatClockTime'; +import { formatClockTime } from '@/lib/format/formatClockTime'; import { isValidPlaybackSchedulePreviewTimestamp, parsePlaybackDelayCustomMinutes, diff --git a/src/components/SongInfoModal.tsx b/src/components/SongInfoModal.tsx index 5d8b3624..f51dfb23 100644 --- a/src/components/SongInfoModal.tsx +++ b/src/components/SongInfoModal.tsx @@ -12,7 +12,7 @@ import { useLibraryIndexStore } from '../store/libraryIndexStore'; import { useTranslation } from 'react-i18next'; import { copyTextToClipboard } from '../utils/server/serverMagicString'; import { showToast } from '../utils/ui/toast'; -import { formatTrackTime } from '../utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import { formatLastSeen } from '../utils/componentHelpers/userMgmtHelpers'; import { libraryIsReady } from '../utils/library/libraryReady'; import { @@ -21,7 +21,7 @@ import { resolveQueueBpm, type ParsedTrackEnrichment, } from '../utils/library/trackEnrichment'; -import i18n from '../i18n'; +import i18n from '@/lib/i18n'; function formatSize(bytes?: number): string | null { if (!bytes) return null; diff --git a/src/components/SongRail.tsx b/src/components/SongRail.tsx index f74ef929..21f991a1 100644 --- a/src/components/SongRail.tsx +++ b/src/components/SongRail.tsx @@ -3,7 +3,7 @@ import React, { useRef, useState, useEffect, useMemo } from 'react'; import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react'; import SongCard from './SongCard'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; -import { dedupeById } from '../utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; interface Props { title: string; diff --git a/src/components/SongRow.tsx b/src/components/SongRow.tsx index 45919f8d..0d4bb1b1 100644 --- a/src/components/SongRow.tsx +++ b/src/components/SongRow.tsx @@ -9,7 +9,7 @@ import { usePlayerStore } from '../store/playerStore'; import { enqueueAndPlay } from '../utils/playback/playSong'; import { useDragDrop } from '../contexts/DragDropContext'; import { useOrbitSongRowBehavior } from '@/features/orbit'; -import { formatTrackTime } from '../utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index bbde192a..1a3d33de 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -3,7 +3,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window'; import { Minus, Square, X } from 'lucide-react'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; -import { IS_MACOS } from '../utils/platform'; +import { IS_MACOS } from '@/lib/util/platform'; export default function TitleBar() { const win = getCurrentWindow(); diff --git a/src/components/playerBar/PlaybackClock.tsx b/src/components/playerBar/PlaybackClock.tsx index c7f9e2ac..e3f698d1 100644 --- a/src/components/playerBar/PlaybackClock.tsx +++ b/src/components/playerBar/PlaybackClock.tsx @@ -4,7 +4,7 @@ import { formatPlaybarClock, formatPlaybarToggleClock, formatTrackTime, -} from '../../utils/format/formatDuration'; +} from '@/lib/format/formatDuration'; /** 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 9fbbc6d3..ee723481 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 '@/features/radio'; import { useThemeStore } from '../../store/themeStore'; -import { formatTrackTime, playbarMinuteFieldWidth } from '../../utils/format/formatDuration'; +import { formatTrackTime, playbarMinuteFieldWidth } from '@/lib/format/formatDuration'; import { WaveformSeek } from '@/features/waveform'; import { PlaybackTime, ToggleClock } from './PlaybackClock'; diff --git a/src/components/queuePanel/QueueHeader.tsx b/src/components/queuePanel/QueueHeader.tsx index 831d204d..7f33ea13 100644 --- a/src/components/queuePanel/QueueHeader.tsx +++ b/src/components/queuePanel/QueueHeader.tsx @@ -7,8 +7,8 @@ import { useAuthStore } from '../../store/authStore'; import type { QueueItemRef } from '../../store/playerStoreTypes'; import type { QueueDisplayMode } from '../../store/authStoreTypes'; import type { DurationMode } from '../../utils/componentHelpers/queuePanelHelpers'; -import { formatLongDuration } from '../../utils/format/formatDuration'; -import { formatClockTime } from '../../utils/format/formatClockTime'; +import { formatLongDuration } from '@/lib/format/formatDuration'; +import { formatClockTime } from '@/lib/format/formatClockTime'; import { resolveQueueTrack } from '../../utils/library/queueTrackView'; import { getQueueResolverVersion, diff --git a/src/components/queuePanel/QueueList.tsx b/src/components/queuePanel/QueueList.tsx index 8a0eb01f..d8242a50 100644 --- a/src/components/queuePanel/QueueList.tsx +++ b/src/components/queuePanel/QueueList.tsx @@ -7,7 +7,7 @@ import { usePlayerStore } from '../../store/playerStore'; import { useLuckyMixStore } from '../../store/luckyMixStore'; import type { QueueItemRef, PlayerState } from '../../store/playerStoreTypes'; import type { QueueDisplayMode } from '../../store/authStoreTypes'; -import { formatTrackTime } from '../../utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import { resolveQueueTrack } from '../../utils/library/queueTrackView'; import { getQueueResolverVersion, diff --git a/src/config/shortcutActionRegistry.ts b/src/config/shortcutActionRegistry.ts index cf999f52..a997cb34 100644 --- a/src/config/shortcutActionRegistry.ts +++ b/src/config/shortcutActionRegistry.ts @@ -3,7 +3,7 @@ import { getSong } from '@/lib/api/subsonicLibrary'; 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 i18n from '@/lib/i18n'; import { usePlayerStore } from '../store/playerStore'; import { usePreviewStore } from '../store/previewStore'; import { useLyricsStore } from '../store/lyricsStore'; diff --git a/src/config/shortcutDispatch.ts b/src/config/shortcutDispatch.ts index aee97109..a3061d94 100644 --- a/src/config/shortcutDispatch.ts +++ b/src/config/shortcutDispatch.ts @@ -1,5 +1,5 @@ import { queueSongRating } from '../store/pendingStarSync'; -import i18n from '../i18n'; +import i18n from '@/lib/i18n'; import { usePlayerStore } from '../store/playerStore'; import { showToast } from '../utils/ui/toast'; import { playByOpaqueId } from '../utils/playback/playByOpaqueId'; diff --git a/src/features/album/components/AlbumHeader.tsx b/src/features/album/components/AlbumHeader.tsx index 91256bbe..64d069f9 100644 --- a/src/features/album/components/AlbumHeader.tsx +++ b/src/features/album/components/AlbumHeader.tsx @@ -14,9 +14,9 @@ import StarRating from '@/components/StarRating'; import { copyEntityShareLink } from '@/utils/share/copyEntityShareLink'; import { showToast } from '@/utils/ui/toast'; import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency'; -import { formatLongDuration } from '@/utils/format/formatDuration'; -import { formatMb } from '@/utils/format/formatBytes'; -import { sanitizeHtml } from '@/utils/sanitizeHtml'; +import { formatLongDuration } from '@/lib/format/formatDuration'; +import { formatMb } from '@/lib/format/formatBytes'; +import { sanitizeHtml } from '@/lib/util/sanitizeHtml'; import { OpenArtistRefInline } from '@/features/artist'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline'; diff --git a/src/features/album/components/AlbumRow.tsx b/src/features/album/components/AlbumRow.tsx index 77727796..65e34ece 100644 --- a/src/features/album/components/AlbumRow.tsx +++ b/src/features/album/components/AlbumRow.tsx @@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react'; import { NavLink, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { usePerfProbeFlags } from '@/utils/perf/perfFlags'; -import { dedupeById } from '@/utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; interface Props { title: string; diff --git a/src/features/album/components/AlbumTrackListMobile.tsx b/src/features/album/components/AlbumTrackListMobile.tsx index b76df24d..be529dde 100644 --- a/src/features/album/components/AlbumTrackListMobile.tsx +++ b/src/features/album/components/AlbumTrackListMobile.tsx @@ -3,7 +3,7 @@ import { AudioLines } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import type { Track } from '@/store/playerStoreTypes'; import { songToTrack } from '@/utils/playback/songToTrack'; -import { formatLongDuration } from '@/utils/format/formatDuration'; +import { formatLongDuration } from '@/lib/format/formatDuration'; interface Props { discNums: number[]; diff --git a/src/features/album/components/TrackRow.tsx b/src/features/album/components/TrackRow.tsx index 99cd55e0..bd953939 100644 --- a/src/features/album/components/TrackRow.tsx +++ b/src/features/album/components/TrackRow.tsx @@ -11,9 +11,9 @@ import { useThemeStore } from '@/store/themeStore'; import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; import StarRating from '@/components/StarRating'; import { codecLabel, type ColKey } from '@/features/album/utils/albumTrackListHelpers'; -import { formatLongDuration } from '@/utils/format/formatDuration'; +import { formatLongDuration } from '@/lib/format/formatDuration'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline'; import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; diff --git a/src/features/album/hooks/useAlbumBrowseData.ts b/src/features/album/hooks/useAlbumBrowseData.ts index 8a00d468..19099b7d 100644 --- a/src/features/album/hooks/useAlbumBrowseData.ts +++ b/src/features/album/hooks/useAlbumBrowseData.ts @@ -12,7 +12,7 @@ import { coverTrafficGridPaginationDepth, } from '@/cover/coverTraffic'; import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue'; -import { dedupeById } from '@/utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/utils/library/albumCompilation'; import type { AlbumCompFilter } from '@/utils/library/albumCompilation'; import { diff --git a/src/features/album/hooks/useGenreAlbumBrowse.ts b/src/features/album/hooks/useGenreAlbumBrowse.ts index d305b0ae..8f6bbe7c 100644 --- a/src/features/album/hooks/useGenreAlbumBrowse.ts +++ b/src/features/album/hooks/useGenreAlbumBrowse.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { dedupeById } from '@/utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort'; import { fetchGenreAlbumPage, diff --git a/src/features/album/pages/RandomAlbums.tsx b/src/features/album/pages/RandomAlbums.tsx index f7fef95a..0a829222 100644 --- a/src/features/album/pages/RandomAlbums.tsx +++ b/src/features/album/pages/RandomAlbums.tsx @@ -3,7 +3,7 @@ import { getAlbumsByGenre } from '@/lib/api/subsonicGenres'; import { getAlbumList } from '@/lib/api/subsonicLibrary'; import { resolveAlbum } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { dedupeById } from '@/utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { shuffleArray } from '@/utils/playback/shuffleArray'; import React, { useEffect, useLayoutEffect, useState, useCallback, useRef } from 'react'; import { RefreshCw, Download, HardDriveDownload } from 'lucide-react'; diff --git a/src/features/artist/components/ArtistDetailTopTracks.tsx b/src/features/artist/components/ArtistDetailTopTracks.tsx index cdd6e8ac..1cc4b221 100644 --- a/src/features/artist/components/ArtistDetailTopTracks.tsx +++ b/src/features/artist/components/ArtistDetailTopTracks.tsx @@ -6,7 +6,7 @@ import { usePlayerStore } from '@/store/playerStore'; import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; import { useOrbitSongRowBehavior } from '@/features/orbit'; import { songToTrack } from '@/utils/playback/songToTrack'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import ArtistTopTrackCover from '@/features/artist/components/ArtistTopTrackCover'; import { topSongAlbumForCover } from '@/features/artist/components/topSongAlbumForCover'; diff --git a/src/features/artist/hooks/useArtistsBrowseCatalog.ts b/src/features/artist/hooks/useArtistsBrowseCatalog.ts index 267174e8..c24d1344 100644 --- a/src/features/artist/hooks/useArtistsBrowseCatalog.ts +++ b/src/features/artist/hooks/useArtistsBrowseCatalog.ts @@ -1,7 +1,7 @@ import { getArtists } from '@/lib/api/subsonicArtists'; import type { SubsonicArtist } from '@/lib/api/subsonicTypes'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { dedupeById } from '@/utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { fetchLocalArtistCatalogChunk, fetchNetworkStarredArtists, diff --git a/src/features/deviceSync/components/DeviceSyncPreSyncModal.tsx b/src/features/deviceSync/components/DeviceSyncPreSyncModal.tsx index 6ffd5fd0..fbbd4f6c 100644 --- a/src/features/deviceSync/components/DeviceSyncPreSyncModal.tsx +++ b/src/features/deviceSync/components/DeviceSyncPreSyncModal.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { AlertCircle, Loader2 } from 'lucide-react'; import type { SyncDelta } from '@/features/deviceSync/utils/runDeviceSyncExecution'; -import { formatMb } from '@/utils/format/formatBytes'; +import { formatMb } from '@/lib/format/formatBytes'; interface Props { preSyncOpen: boolean; diff --git a/src/features/deviceSync/utils/deviceSyncLegacyTemplate.ts b/src/features/deviceSync/utils/deviceSyncLegacyTemplate.ts index 23bfebf5..715537ec 100644 --- a/src/features/deviceSync/utils/deviceSyncLegacyTemplate.ts +++ b/src/features/deviceSync/utils/deviceSyncLegacyTemplate.ts @@ -1,4 +1,4 @@ -import { IS_WINDOWS } from '@/utils/platform'; +import { IS_WINDOWS } from '@/lib/util/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/features/deviceSync/utils/runDeviceSyncMigration.ts b/src/features/deviceSync/utils/runDeviceSyncMigration.ts index 7652445e..103fec45 100644 --- a/src/features/deviceSync/utils/runDeviceSyncMigration.ts +++ b/src/features/deviceSync/utils/runDeviceSyncMigration.ts @@ -5,7 +5,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { applyLegacyTemplate } from '@/features/deviceSync/utils/deviceSyncLegacyTemplate'; import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers'; import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource'; -import { IS_WINDOWS } from '@/utils/platform'; +import { IS_WINDOWS } from '@/lib/util/platform'; export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing'; diff --git a/src/features/favorites/components/FavoriteSongRow.tsx b/src/features/favorites/components/FavoriteSongRow.tsx index 8ed192aa..0a534901 100644 --- a/src/features/favorites/components/FavoriteSongRow.tsx +++ b/src/features/favorites/components/FavoriteSongRow.tsx @@ -5,8 +5,8 @@ import type { ColDef } from '@/utils/useTracklistColumns'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; -import i18n from '@/i18n'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import i18n from '@/lib/i18n'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import StarRating from '@/components/StarRating'; import { OpenArtistRefInline } from '@/features/artist'; import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; diff --git a/src/features/fullscreenPlayer/components/FsClock.tsx b/src/features/fullscreenPlayer/components/FsClock.tsx index 83f9ec6c..14478e4d 100644 --- a/src/features/fullscreenPlayer/components/FsClock.tsx +++ b/src/features/fullscreenPlayer/components/FsClock.tsx @@ -1,7 +1,7 @@ import { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '@/store/authStore'; -import { formatClockTime } from '@/utils/format/formatClockTime'; +import { formatClockTime } from '@/lib/format/formatClockTime'; /** * Standalone wall-clock for the fullscreen player. Owns its own state so the diff --git a/src/features/fullscreenPlayer/components/FsQueueModal.tsx b/src/features/fullscreenPlayer/components/FsQueueModal.tsx index c3edfe66..95fcca40 100644 --- a/src/features/fullscreenPlayer/components/FsQueueModal.tsx +++ b/src/features/fullscreenPlayer/components/FsQueueModal.tsx @@ -8,7 +8,7 @@ import { getQueueResolverVersion, subscribeQueueResolver, } from '@/utils/library/queueTrackResolver'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; interface Props { onClose: () => void; diff --git a/src/features/fullscreenPlayer/components/FsTimeReadout.tsx b/src/features/fullscreenPlayer/components/FsTimeReadout.tsx index 4f9d8af1..14c39091 100644 --- a/src/features/fullscreenPlayer/components/FsTimeReadout.tsx +++ b/src/features/fullscreenPlayer/components/FsTimeReadout.tsx @@ -1,6 +1,6 @@ import { memo, useEffect, useRef } from 'react'; import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; /** * Centered "current / total" readout for the control bar. Updates the current diff --git a/src/features/miniPlayer/components/MiniControls.tsx b/src/features/miniPlayer/components/MiniControls.tsx index 5fa75c9e..562bf6d6 100644 --- a/src/features/miniPlayer/components/MiniControls.tsx +++ b/src/features/miniPlayer/components/MiniControls.tsx @@ -1,5 +1,5 @@ import { Pause, Play, SkipBack, SkipForward } from 'lucide-react'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import type { MiniControlAction } from '@/features/miniPlayer/utils/miniPlayerBridge'; interface Props { diff --git a/src/features/miniPlayer/components/MiniTitlebar.tsx b/src/features/miniPlayer/components/MiniTitlebar.tsx index a80b991d..8dce6f9d 100644 --- a/src/features/miniPlayer/components/MiniTitlebar.tsx +++ b/src/features/miniPlayer/components/MiniTitlebar.tsx @@ -1,6 +1,6 @@ import { Maximize2, Pin, PinOff, X } from 'lucide-react'; import type { TFunction } from 'i18next'; -import { IS_LINUX } from '@/utils/platform'; +import { IS_LINUX } from '@/lib/util/platform'; interface Props { trackTitle: string | undefined; diff --git a/src/features/miniPlayer/hooks/useMiniWindowSetup.ts b/src/features/miniPlayer/hooks/useMiniWindowSetup.ts index 2b3b6bb3..14a32797 100644 --- a/src/features/miniPlayer/hooks/useMiniWindowSetup.ts +++ b/src/features/miniPlayer/hooks/useMiniWindowSetup.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '@/store/authStore'; -import { IS_LINUX } from '@/utils/platform'; +import { IS_LINUX } from '@/lib/util/platform'; import { EXPANDED_SIZE, EXPANDED_MIN, readStoredExpandedHeight, } from '@/features/miniPlayer/utils/miniPlayerHelpers'; diff --git a/src/features/nowPlaying/components/AlbumCard.tsx b/src/features/nowPlaying/components/AlbumCard.tsx index f0b60279..6136ae37 100644 --- a/src/features/nowPlaying/components/AlbumCard.tsx +++ b/src/features/nowPlaying/components/AlbumCard.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Disc3, ExternalLink, Star } from 'lucide-react'; import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; import { formatTotalDuration } from '@/utils/componentHelpers/nowPlayingHelpers'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; interface AlbumCardProps { album: SubsonicAlbum | null; diff --git a/src/features/nowPlaying/components/Hero.tsx b/src/features/nowPlaying/components/Hero.tsx index 36995327..963c2b08 100644 --- a/src/features/nowPlaying/components/Hero.tsx +++ b/src/features/nowPlaying/components/Hero.tsx @@ -6,7 +6,7 @@ import type { CoverArtRef } from '@/cover/types'; import type { ArtistStats, TrackStats } from '@/music-network'; import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes'; import { OpenArtistRefInline } from '@/features/artist'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import { useEnrichmentPrimaryLabel } from '@/hooks/useEnrichmentPrimaryLabel'; import { useEnrichmentPrimaryIcon } from '@/hooks/useEnrichmentPrimaryIcon'; import { renderPresetIcon } from '@/components/settings/musicNetwork/presetIcon'; diff --git a/src/features/nowPlaying/components/RadioView.tsx b/src/features/nowPlaying/components/RadioView.tsx index 84f06bfd..ee859f85 100644 --- a/src/features/nowPlaying/components/RadioView.tsx +++ b/src/features/nowPlaying/components/RadioView.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react'; import type { useRadioMetadata } from '@/features/radio'; import { usePlayerStore } from '@/store/playerStore'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; type NonNullStoreField> = NonNullable[K]>; diff --git a/src/features/nowPlaying/components/TopSongsCard.tsx b/src/features/nowPlaying/components/TopSongsCard.tsx index fbb45bfd..abbda127 100644 --- a/src/features/nowPlaying/components/TopSongsCard.tsx +++ b/src/features/nowPlaying/components/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 '@/lib/api/subsonicTypes'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; interface TopSongsCardProps { artistName: string; diff --git a/src/features/offline/components/OfflineLibraryDiskStat.tsx b/src/features/offline/components/OfflineLibraryDiskStat.tsx index 25dc3d60..50ecb439 100644 --- a/src/features/offline/components/OfflineLibraryDiskStat.tsx +++ b/src/features/offline/components/OfflineLibraryDiskStat.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { HardDriveDownload, Heart } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { formatBytes } from '@/utils/format/formatBytes'; +import { formatBytes } from '@/lib/format/formatBytes'; const OPEN_DELAY_MS = 450; diff --git a/src/features/offline/components/ZipDownloadOverlay.tsx b/src/features/offline/components/ZipDownloadOverlay.tsx index 8613d45d..c03586fd 100644 --- a/src/features/offline/components/ZipDownloadOverlay.tsx +++ b/src/features/offline/components/ZipDownloadOverlay.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { createPortal } from 'react-dom'; import { HardDriveDownload, Check, X } from 'lucide-react'; import { useZipDownloadStore } from '@/features/offline/store/zipDownloadStore'; -import { formatBytes } from '@/utils/format/formatBytes'; +import { formatBytes } from '@/lib/format/formatBytes'; function ZipDownloadItem({ id }: { id: string }) { const dismiss = useZipDownloadStore(s => s.dismiss); diff --git a/src/features/offline/utils/favoritesOfflineSync.ts b/src/features/offline/utils/favoritesOfflineSync.ts index ca494d91..1d86c619 100644 --- a/src/features/offline/utils/favoritesOfflineSync.ts +++ b/src/features/offline/utils/favoritesOfflineSync.ts @@ -6,7 +6,7 @@ import { getStarredForServer } from '@/lib/api/subsonicStarRating'; import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { invoke } from '@tauri-apps/api/core'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; import { useAuthStore } from '@/store/authStore'; import { cancelledDownloads, useOfflineJobStore } from '@/features/offline/store/offlineJobStore'; import { useFavoritesOfflineSyncStore } from '@/features/offline/store/favoritesOfflineSyncStore'; diff --git a/src/features/offline/utils/offlineStarredLoad.ts b/src/features/offline/utils/offlineStarredLoad.ts index dedcaa00..7abb10a4 100644 --- a/src/features/offline/utils/offlineStarredLoad.ts +++ b/src/features/offline/utils/offlineStarredLoad.ts @@ -11,7 +11,7 @@ import { albumToAlbum, trackToSong, } from '@/utils/library/advancedSearchLocal'; -import { dedupeById } from '@/utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode'; import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse'; import { diff --git a/src/features/orbit/components/OrbitSessionBar.tsx b/src/features/orbit/components/OrbitSessionBar.tsx index c138866f..834a60c8 100644 --- a/src/features/orbit/components/OrbitSessionBar.tsx +++ b/src/features/orbit/components/OrbitSessionBar.tsx @@ -19,7 +19,7 @@ import OrbitSettingsPopover from '@/features/orbit/components/OrbitSettingsPopov import OrbitSharePopover from '@/features/orbit/components/OrbitSharePopover'; import OrbitDiagnosticsPopover from '@/features/orbit/components/OrbitDiagnosticsPopover'; import ConfirmModal from '@/components/ConfirmModal'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; /** * Orbit — top-strip session indicator. diff --git a/src/features/orbit/hooks/useOrbitGuest.ts b/src/features/orbit/hooks/useOrbitGuest.ts index 61531106..5667d494 100644 --- a/src/features/orbit/hooks/useOrbitGuest.ts +++ b/src/features/orbit/hooks/useOrbitGuest.ts @@ -15,7 +15,7 @@ import { resetPendingResendState, } from '@/features/orbit/utils/orbit'; import { showToast } from '@/utils/ui/toast'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; import { estimateLivePosition, type OrbitState } from '@/features/orbit/api/orbit'; import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag'; import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat'; diff --git a/src/features/orbit/hooks/useOrbitHost.ts b/src/features/orbit/hooks/useOrbitHost.ts index f4bb4195..d404ae5a 100644 --- a/src/features/orbit/hooks/useOrbitHost.ts +++ b/src/features/orbit/hooks/useOrbitHost.ts @@ -20,7 +20,7 @@ import { type OrbitQueueItem, } from '@/features/orbit/api/orbit'; import { showToast } from '@/utils/ui/toast'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag'; import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat'; diff --git a/src/features/orbit/utils/orbitBulkGuard.ts b/src/features/orbit/utils/orbitBulkGuard.ts index fa5517e6..4774762c 100644 --- a/src/features/orbit/utils/orbitBulkGuard.ts +++ b/src/features/orbit/utils/orbitBulkGuard.ts @@ -1,6 +1,6 @@ import { useOrbitStore } from '@/features/orbit/store/orbitStore'; import { useConfirmModalStore } from '@/store/confirmModalStore'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; /** * Ask the user before dropping many tracks into the shared Orbit queue. diff --git a/src/features/playlist/components/PlaylistCard.tsx b/src/features/playlist/components/PlaylistCard.tsx index 8475c22a..4cbec304 100644 --- a/src/features/playlist/components/PlaylistCard.tsx +++ b/src/features/playlist/components/PlaylistCard.tsx @@ -7,7 +7,7 @@ import { usePlayerStore } from '@/store/playerStore'; import { displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist, } from '@/features/playlist/utils/playlistsSmart'; -import { formatHumanHoursMinutes } from '@/utils/format/formatHumanDuration'; +import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration'; import { useDragSource } from '@/contexts/DragDropContext'; import { PlaylistCardMainCover, PlaylistSmartCoverCell } from '@/features/playlist/components/PlaylistCoverImages'; diff --git a/src/features/playlist/components/PlaylistRow.tsx b/src/features/playlist/components/PlaylistRow.tsx index c96bb48b..1403eacd 100644 --- a/src/features/playlist/components/PlaylistRow.tsx +++ b/src/features/playlist/components/PlaylistRow.tsx @@ -5,8 +5,8 @@ import type { ColDef } from '@/utils/useTracklistColumns'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; -import i18n from '@/i18n'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import i18n from '@/lib/i18n'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import StarRating from '@/components/StarRating'; import { PlaylistArtistCell } from '@/features/playlist/components/PlaylistArtistCell'; diff --git a/src/features/playlist/components/PlaylistSongSearchPanel.tsx b/src/features/playlist/components/PlaylistSongSearchPanel.tsx index 33533c3d..35295f8a 100644 --- a/src/features/playlist/components/PlaylistSongSearchPanel.tsx +++ b/src/features/playlist/components/PlaylistSongSearchPanel.tsx @@ -4,7 +4,7 @@ import { Check, ListPlus, X } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { usePlayerStore } from '@/store/playerStore'; import { songToTrack } from '@/utils/playback/songToTrack'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage'; import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes'; import { AddToPlaylistSubmenu } from '@/components/ContextMenu'; diff --git a/src/features/playlist/components/PlaylistSuggestions.tsx b/src/features/playlist/components/PlaylistSuggestions.tsx index 4e26f4c9..3e473842 100644 --- a/src/features/playlist/components/PlaylistSuggestions.tsx +++ b/src/features/playlist/components/PlaylistSuggestions.tsx @@ -14,8 +14,8 @@ import { songToTrack } from '@/utils/playback/songToTrack'; import { getQueueTracksView } from '@/utils/library/queueTrackView'; import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; -import { formatTrackTime } from '@/utils/format/formatDuration'; -import i18n from '@/i18n'; +import { formatTrackTime } from '@/lib/format/formatDuration'; +import i18n from '@/lib/i18n'; const PL_CENTERED = new Set(['favorite', 'rating', 'duration', 'playCount', 'bpm']); diff --git a/src/features/search/components/ShareQueuePreviewModal.tsx b/src/features/search/components/ShareQueuePreviewModal.tsx index ab2f2019..5c5ccccf 100644 --- a/src/features/search/components/ShareQueuePreviewModal.tsx +++ b/src/features/search/components/ShareQueuePreviewModal.tsx @@ -4,7 +4,7 @@ import { Music, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import type { ServerProfile } from '@/store/authStoreTypes'; -import { formatTrackTime } from '@/utils/format/formatDuration'; +import { formatTrackTime } from '@/lib/format/formatDuration'; import type { ShareQueuePreviewState } from '@/features/search/hooks/useShareQueuePreview'; import { sharePayloadTotal, type QueueableShareSearchPayload } from '@/utils/share/shareSearch'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; diff --git a/src/features/settings/components/AppearanceTab.tsx b/src/features/settings/components/AppearanceTab.tsx index 40e60668..d701bd03 100644 --- a/src/features/settings/components/AppearanceTab.tsx +++ b/src/features/settings/components/AppearanceTab.tsx @@ -10,7 +10,7 @@ import { import type { SeekbarStyle, WindowButtonStyle } from '@/store/authStoreTypes'; import { useFontStore, FontId } from '@/store/fontStore'; import { useThemeStore } from '@/store/themeStore'; -import { IS_LINUX, IS_WINDOWS } from '@/utils/platform'; +import { IS_LINUX, IS_WINDOWS } from '@/lib/util/platform'; import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; diff --git a/src/features/settings/components/AudioTab.tsx b/src/features/settings/components/AudioTab.tsx index 78dbe8b9..22fb310f 100644 --- a/src/features/settings/components/AudioTab.tsx +++ b/src/features/settings/components/AudioTab.tsx @@ -8,7 +8,7 @@ import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; import { useAudioDevicesProbe } from '@/hooks/useAudioDevicesProbe'; -import { IS_MACOS } from '@/utils/platform'; +import { IS_MACOS } from '@/lib/util/platform'; import { AudioOutputDeviceSection } from '@/features/settings/components/audio/AudioOutputDeviceSection'; import { NormalizationBlock } from '@/features/settings/components/audio/NormalizationBlock'; import { PlaybackRateBlock } from '@/features/settings/components/audio/PlaybackRateBlock'; diff --git a/src/features/settings/components/CoverCacheStrategySection.tsx b/src/features/settings/components/CoverCacheStrategySection.tsx index bea3adab..b136d832 100644 --- a/src/features/settings/components/CoverCacheStrategySection.tsx +++ b/src/features/settings/components/CoverCacheStrategySection.tsx @@ -17,7 +17,7 @@ import { clearDiskSrcCacheForServer } from '@/cover/diskSrcCache'; import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { showToast } from '@/utils/ui/toast'; -import { formatBytes } from '@/utils/format/formatBytes'; +import { formatBytes } from '@/lib/format/formatBytes'; import { clearImageCache, getImageCacheSize } from '@/utils/imageCache'; import { wakeLibraryCoverBackfill } from '@/utils/library/coverBackfillWake'; import { diff --git a/src/features/settings/components/StorageTab.tsx b/src/features/settings/components/StorageTab.tsx index dea83f6a..5d8bef34 100644 --- a/src/features/settings/components/StorageTab.tsx +++ b/src/features/settings/components/StorageTab.tsx @@ -6,7 +6,7 @@ import { Download, FolderOpen, Trash2, X } from 'lucide-react'; import { useAuthStore } from '@/store/authStore'; import { countHotCacheTracks } from '@/store/hotCacheStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; -import { formatBytes, snapHotCacheMb } from '@/utils/format/formatBytes'; +import { formatBytes, snapHotCacheMb } from '@/lib/format/formatBytes'; import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; diff --git a/src/features/settings/components/SystemTab.tsx b/src/features/settings/components/SystemTab.tsx index 7d2738cd..50f925c1 100644 --- a/src/features/settings/components/SystemTab.tsx +++ b/src/features/settings/components/SystemTab.tsx @@ -6,10 +6,10 @@ import { save as saveDialog } from '@tauri-apps/plugin-dialog'; import { open as openUrl } from '@tauri-apps/plugin-shell'; import { AppWindow, ChevronDown, Download, ExternalLink, Globe, HardDrive, Info, Scale, Sliders, Users } from 'lucide-react'; import { version as appVersion } from '@/../package.json'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; import { useAuthStore } from '@/store/authStore'; import type { ClockFormat, LinuxWaylandTextRenderProfile, LoggingMode } from '@/store/authStoreTypes'; -import { IS_LINUX } from '@/utils/platform'; +import { IS_LINUX } from '@/lib/util/platform'; import { showToast } from '@/utils/ui/toast'; import { AboutPsysonicBrandHeader } from '@/components/AboutPsysonicLol'; import CustomSelect from '@/ui/CustomSelect'; diff --git a/src/features/settings/components/ThemeStoreSection.tsx b/src/features/settings/components/ThemeStoreSection.tsx index 7b57995f..8aa55cbf 100644 --- a/src/features/settings/components/ThemeStoreSection.tsx +++ b/src/features/settings/components/ThemeStoreSection.tsx @@ -6,7 +6,7 @@ import CoverLightbox from '@/components/CoverLightbox'; import { useThemeAnimationRisk } from '@/hooks/useThemeAnimationRisk'; import { AnimatedThemeBadge } from '@/features/settings/components/AnimatedThemeBadge'; import CustomSelect from '@/ui/CustomSelect'; -import { formatRelativeTime } from '@/utils/format/relativeTime'; +import { formatRelativeTime } from '@/lib/format/relativeTime'; import { useThemeStore } from '@/store/themeStore'; import { useInstalledThemesStore, type InstalledTheme } from '@/store/installedThemesStore'; import { diff --git a/src/features/settings/pages/Settings.tsx b/src/features/settings/pages/Settings.tsx index 781376fe..17e7a2c8 100644 --- a/src/features/settings/pages/Settings.tsx +++ b/src/features/settings/pages/Settings.tsx @@ -5,7 +5,7 @@ import { AudioLines, HardDrive, Info, Keyboard, LayoutGrid, Music2, Paintbrush, Palette, Search, Server, Sparkles, Users, X, } from 'lucide-react'; import { useAuthStore } from '@/store/authStore'; -import { IS_MACOS } from '@/utils/platform'; +import { IS_MACOS } from '@/lib/util/platform'; import { AppearanceTab } from '@/features/settings/components/AppearanceTab'; import { ThemesTab } from '@/features/settings/components/ThemesTab'; import { AudioTab } from '@/features/settings/components/AudioTab'; diff --git a/src/features/stats/components/PlayerStatisticsPanel.tsx b/src/features/stats/components/PlayerStatisticsPanel.tsx index f793f4cc..cba4d315 100644 --- a/src/features/stats/components/PlayerStatisticsPanel.tsx +++ b/src/features/stats/components/PlayerStatisticsPanel.tsx @@ -13,7 +13,7 @@ import { usePlayerStatsRecordingEnabled } from '@/features/stats/hooks/usePlayer import PlayerStatsHeatmap from '@/features/stats/components/PlayerStatsHeatmap'; import PlayerStatsIndexRequiredNotice from '@/features/stats/components/PlayerStatsIndexRequiredNotice'; import PlayerStatsRecentDays from '@/features/stats/components/PlayerStatsRecentDays'; -import { formatPlayerStatsListeningTotal } from '@/utils/format/formatHumanDuration'; +import { formatPlayerStatsListeningTotal } from '@/lib/format/formatHumanDuration'; const currentCalendarYear = () => new Date().getFullYear(); diff --git a/src/features/stats/components/PlayerStatsDayTracks.tsx b/src/features/stats/components/PlayerStatsDayTracks.tsx index e8b2a879..36d7145e 100644 --- a/src/features/stats/components/PlayerStatsDayTracks.tsx +++ b/src/features/stats/components/PlayerStatsDayTracks.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import type { PlaySessionDayDetail } from '@/lib/api/library'; -import { formatPlayerStatsListenedSec } from '@/utils/format/formatHumanDuration'; +import { formatPlayerStatsListenedSec } from '@/lib/format/formatHumanDuration'; type Props = { detail: PlaySessionDayDetail; diff --git a/src/features/stats/components/PlayerStatsRecentDays.tsx b/src/features/stats/components/PlayerStatsRecentDays.tsx index 550ec774..3fd01cac 100644 --- a/src/features/stats/components/PlayerStatsRecentDays.tsx +++ b/src/features/stats/components/PlayerStatsRecentDays.tsx @@ -7,7 +7,7 @@ import { type PlaySessionDayDetail, type PlaySessionRecentDay, } from '@/lib/api/library'; -import { formatPlayerStatsListeningTotal } from '@/utils/format/formatHumanDuration'; +import { formatPlayerStatsListeningTotal } from '@/lib/format/formatHumanDuration'; import { formatPlayerStatsDayLabel, PLAYER_STATS_RECENT_DAYS_LIMIT, diff --git a/src/features/stats/pages/Statistics.tsx b/src/features/stats/pages/Statistics.tsx index f5931bd8..a3bc3ca5 100644 --- a/src/features/stats/pages/Statistics.tsx +++ b/src/features/stats/pages/Statistics.tsx @@ -4,7 +4,7 @@ import type { SubsonicAlbum, SubsonicGenre } from '@/lib/api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Share2 } from 'lucide-react'; -import { formatHumanHoursMinutes } from '@/utils/format/formatHumanDuration'; +import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration'; import { AlbumRow } from '@/features/album'; import StatsExportModal from '@/features/stats/components/StatsExportModal'; import PlayerStatisticsPanel from '@/features/stats/components/PlayerStatisticsPanel'; diff --git a/src/hooks/tauriBridge/useCliBridge.ts b/src/hooks/tauriBridge/useCliBridge.ts index e26411b4..449dafa8 100644 --- a/src/hooks/tauriBridge/useCliBridge.ts +++ b/src/hooks/tauriBridge/useCliBridge.ts @@ -10,7 +10,7 @@ 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 i18n from '@/lib/i18n'; import { usePlayerStore } from '../../store/playerStore'; import { useAuthStore } from '../../store/authStore'; import { executeCliPlayerCommand } from '../../config/shortcutActions'; diff --git a/src/hooks/useAppUpdater.ts b/src/hooks/useAppUpdater.ts index 87f8a1d7..cbea8005 100644 --- a/src/hooks/useAppUpdater.ts +++ b/src/hooks/useAppUpdater.ts @@ -4,7 +4,7 @@ import { dirname } from '@tauri-apps/api/path'; import { invoke } from '@tauri-apps/api/core'; import { useTranslation } from 'react-i18next'; import { version as currentVersion } from '../../package.json'; -import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform'; +import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '@/lib/util/platform'; import { SKIP_KEY, isNewer, isWithinModerationWindow, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers'; /** All update-modal state, the GitHub release probe and the download/relaunch diff --git a/src/hooks/useAudioDevicesProbe.ts b/src/hooks/useAudioDevicesProbe.ts index f601c790..8f6c2bf1 100644 --- a/src/hooks/useAudioDevicesProbe.ts +++ b/src/hooks/useAudioDevicesProbe.ts @@ -3,7 +3,7 @@ import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; import type { TFunction } from 'i18next'; import { useAuthStore } from '../store/authStore'; -import { IS_MACOS } from '../utils/platform'; +import { IS_MACOS } from '@/lib/util/platform'; import { sortAudioDeviceIds } from '../utils/audio/audioDeviceLabels'; import { showToast } from '../utils/ui/toast'; diff --git a/src/hooks/usePlatformShellSetup.ts b/src/hooks/usePlatformShellSetup.ts index 80801809..e70bed91 100644 --- a/src/hooks/usePlatformShellSetup.ts +++ b/src/hooks/usePlatformShellSetup.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '../store/authStore'; import type { LinuxWaylandTextRenderProfile } from '../store/authStoreTypes'; -import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform'; +import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '@/lib/util/platform'; /** * One-shot platform + window-shell configuration. Reads tiling-WM state, diff --git a/src/hooks/useThemeAnimationRisk.ts b/src/hooks/useThemeAnimationRisk.ts index ba11f65d..7f124aef 100644 --- a/src/hooks/useThemeAnimationRisk.ts +++ b/src/hooks/useThemeAnimationRisk.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { IS_LINUX } from '../utils/platform'; +import { IS_LINUX } from '@/lib/util/platform'; /** * Whether animated themes may raise CPU load on this setup — Linux with the diff --git a/src/utils/format/formatBytes.ts b/src/lib/format/formatBytes.ts similarity index 100% rename from src/utils/format/formatBytes.ts rename to src/lib/format/formatBytes.ts diff --git a/src/utils/format/formatClockTime.test.ts b/src/lib/format/formatClockTime.test.ts similarity index 95% rename from src/utils/format/formatClockTime.test.ts rename to src/lib/format/formatClockTime.test.ts index baa41167..a5567e02 100644 --- a/src/utils/format/formatClockTime.test.ts +++ b/src/lib/format/formatClockTime.test.ts @@ -5,7 +5,7 @@ * tests pin only the `hour12` mapping, which is what the setting controls. */ import { describe, expect, it } from 'vitest'; -import { formatClockTime } from './formatClockTime'; +import { formatClockTime } from '@/lib/format/formatClockTime'; const SAMPLE_TS = Date.UTC(2026, 0, 1, 19, 17, 0); // 19:17 UTC, deterministic diff --git a/src/utils/format/formatClockTime.ts b/src/lib/format/formatClockTime.ts similarity index 91% rename from src/utils/format/formatClockTime.ts rename to src/lib/format/formatClockTime.ts index 41eb9672..72c892d2 100644 --- a/src/utils/format/formatClockTime.ts +++ b/src/lib/format/formatClockTime.ts @@ -1,4 +1,4 @@ -import type { ClockFormat } from '../../store/authStoreTypes'; +import type { ClockFormat } from '@/store/authStoreTypes'; /** * Localized wall-clock `HH:MM` for a timestamp (sleep-timer / queue-ETA labels). diff --git a/src/utils/format/formatDuration.test.ts b/src/lib/format/formatDuration.test.ts similarity index 98% rename from src/utils/format/formatDuration.test.ts rename to src/lib/format/formatDuration.test.ts index 2c009bd5..21b1702b 100644 --- a/src/utils/format/formatDuration.test.ts +++ b/src/lib/format/formatDuration.test.ts @@ -6,7 +6,7 @@ import { formatPlaybarToggleClock, playbarMinuteFieldWidth, PLAYBAR_CLOCK_PAD, -} from './formatDuration'; +} from '@/lib/format/formatDuration'; describe('formatTrackTime', () => { it('formats m:ss with zero-padded seconds', () => { diff --git a/src/utils/format/formatDuration.ts b/src/lib/format/formatDuration.ts similarity index 100% rename from src/utils/format/formatDuration.ts rename to src/lib/format/formatDuration.ts diff --git a/src/utils/format/formatHumanDuration.test.ts b/src/lib/format/formatHumanDuration.test.ts similarity index 96% rename from src/utils/format/formatHumanDuration.test.ts rename to src/lib/format/formatHumanDuration.test.ts index 8d98b8d2..579c06ab 100644 --- a/src/utils/format/formatHumanDuration.test.ts +++ b/src/lib/format/formatHumanDuration.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from 'vitest'; // Echo the i18n key + params so the rounding/branching can be asserted // without depending on a locale's exact "N hours M minutes" template. -vi.mock('../../i18n', () => ({ +vi.mock('@/lib/i18n', () => ({ default: { t: (key: string, params: Record) => `${key}|${JSON.stringify(params)}`, resolvedLanguage: 'en', @@ -10,7 +10,7 @@ vi.mock('../../i18n', () => ({ }, })); -import { formatHumanHoursMinutes, formatPlayerStatsListeningTotal, formatPlayerStatsListenedSec } from './formatHumanDuration'; +import { formatHumanHoursMinutes, formatPlayerStatsListeningTotal, formatPlayerStatsListenedSec } from '@/lib/format/formatHumanDuration'; describe('formatHumanHoursMinutes', () => { it('rounds to the nearest minute instead of truncating', () => { diff --git a/src/utils/format/formatHumanDuration.ts b/src/lib/format/formatHumanDuration.ts similarity index 98% rename from src/utils/format/formatHumanDuration.ts rename to src/lib/format/formatHumanDuration.ts index d3321dec..7d6e3103 100644 --- a/src/utils/format/formatHumanDuration.ts +++ b/src/lib/format/formatHumanDuration.ts @@ -1,4 +1,4 @@ -import i18n from '../../i18n'; +import i18n from '@/lib/i18n'; /** * Totals / statistics: localized "N hours M minutes" (not track mm:ss). diff --git a/src/utils/format/relativeTime.test.ts b/src/lib/format/relativeTime.test.ts similarity index 93% rename from src/utils/format/relativeTime.test.ts rename to src/lib/format/relativeTime.test.ts index 335707e1..47264410 100644 --- a/src/utils/format/relativeTime.test.ts +++ b/src/lib/format/relativeTime.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { formatRelativeTime } from './relativeTime'; +import { formatRelativeTime } from '@/lib/format/relativeTime'; describe('formatRelativeTime', () => { const SECOND = 1000; diff --git a/src/utils/format/relativeTime.ts b/src/lib/format/relativeTime.ts similarity index 100% rename from src/utils/format/relativeTime.ts rename to src/lib/format/relativeTime.ts diff --git a/src/i18n.ts b/src/lib/i18n.ts similarity index 63% rename from src/i18n.ts rename to src/lib/i18n.ts index 808844d0..93ca03bf 100644 --- a/src/i18n.ts +++ b/src/lib/i18n.ts @@ -1,17 +1,17 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -import { deTranslation } from './locales/de'; -import { enTranslation } from './locales/en'; -import { frTranslation } from './locales/fr'; -import { zhTranslation } from './locales/zh'; -import { nbTranslation } from './locales/nb'; -import { ruTranslation } from './locales/ru'; -import { nlTranslation } from './locales/nl'; -import { esTranslation } from './locales/es'; -import { roTranslation } from './locales/ro'; -import { jaTranslation } from './locales/ja'; -import { huTranslation } from './locales/hu'; -import { plTranslation } from './locales/pl'; +import { deTranslation } from '@/locales/de'; +import { enTranslation } from '@/locales/en'; +import { frTranslation } from '@/locales/fr'; +import { zhTranslation } from '@/locales/zh'; +import { nbTranslation } from '@/locales/nb'; +import { ruTranslation } from '@/locales/ru'; +import { nlTranslation } from '@/locales/nl'; +import { esTranslation } from '@/locales/es'; +import { roTranslation } from '@/locales/ro'; +import { jaTranslation } from '@/locales/ja'; +import { huTranslation } from '@/locales/hu'; +import { plTranslation } from '@/locales/pl'; const savedLanguage = localStorage.getItem('psysonic_language') || 'en'; diff --git a/src/utils/dedupeById.ts b/src/lib/util/dedupeById.ts similarity index 100% rename from src/utils/dedupeById.ts rename to src/lib/util/dedupeById.ts diff --git a/src/utils/platform.ts b/src/lib/util/platform.ts similarity index 100% rename from src/utils/platform.ts rename to src/lib/util/platform.ts diff --git a/src/store/safeStorage.test.ts b/src/lib/util/safeStorage.test.ts similarity index 96% rename from src/store/safeStorage.test.ts rename to src/lib/util/safeStorage.test.ts index 19ac5443..5c863043 100644 --- a/src/store/safeStorage.test.ts +++ b/src/lib/util/safeStorage.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; -import { createSafeJSONStorage } from './safeStorage'; +import { createSafeJSONStorage } from '@/lib/util/safeStorage'; describe('createSafeJSONStorage', () => { afterEach(() => { diff --git a/src/store/safeStorage.ts b/src/lib/util/safeStorage.ts similarity index 100% rename from src/store/safeStorage.ts rename to src/lib/util/safeStorage.ts diff --git a/src/utils/sanitizeHtml.ts b/src/lib/util/sanitizeHtml.ts similarity index 100% rename from src/utils/sanitizeHtml.ts rename to src/lib/util/sanitizeHtml.ts diff --git a/src/main.tsx b/src/main.tsx index b1889cfd..6087111d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import { runPreReactBootstrap } from './app/bootstrap'; import { scheduleStartupSplashDismiss } from './app/startupSplash'; -import './i18n'; +import '@/lib/i18n'; import './styles/themes/index.css'; import './styles/layout/index.css'; import './styles/components/index.css'; diff --git a/src/pages/ComposerDetail.tsx b/src/pages/ComposerDetail.tsx index 71a2f7d7..65ca2c3f 100644 --- a/src/pages/ComposerDetail.tsx +++ b/src/pages/ComposerDetail.tsx @@ -16,7 +16,7 @@ import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import { copyEntityShareLink } from '../utils/share/copyEntityShareLink'; import { showToast } from '../utils/ui/toast'; -import { sanitizeHtml } from '../utils/sanitizeHtml'; +import { sanitizeHtml } from '@/lib/util/sanitizeHtml'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { albumGridWarmCovers } from '../cover/layoutSizes'; import { VirtualCardGrid } from '../components/VirtualCardGrid'; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 7354923e..3c97deb0 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -16,7 +16,7 @@ import { useAuthStore } from '../store/authStore'; import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { bumpPerfCounter } from '../utils/perf/perfTelemetry'; -import { dedupeById } from '../utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { shuffleArray } from '../utils/playback/shuffleArray'; import { useLibraryCoverPrefetch } from '../cover/useLibraryCoverPrefetch'; import { primeAlbumCoversForDisplay, warmHomeMainstageCovers } from '../cover/warmDiskPeek'; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 72ef970b..a6649d29 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -12,7 +12,7 @@ import { } from '../utils/server/serverHttpHeaders'; import { syncServerHttpContextForProfile } from '../utils/server/syncServerHttpContext'; import { useTranslation } from 'react-i18next'; -import i18n from '../i18n'; +import i18n from '@/lib/i18n'; import CustomSelect from '@/ui/CustomSelect'; import { decodeServerMagicString, diff --git a/src/pages/NewReleases.tsx b/src/pages/NewReleases.tsx index aa43ec31..a1575341 100644 --- a/src/pages/NewReleases.tsx +++ b/src/pages/NewReleases.tsx @@ -3,7 +3,7 @@ import { getAlbumsByGenre } from '@/lib/api/subsonicGenres'; import { getAlbumList } from '@/lib/api/subsonicLibrary'; import { resolveAlbum } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { dedupeById } from '../utils/dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { useEffect, useLayoutEffect, useState, useCallback, useRef, useMemo } from 'react'; import { Download, HardDriveDownload } from 'lucide-react'; import SelectionToggleButton from '../components/SelectionToggleButton'; diff --git a/src/store/authStoreRehydrate.ts b/src/store/authStoreRehydrate.ts index 8152957f..f80428f4 100644 --- a/src/store/authStoreRehydrate.ts +++ b/src/store/authStoreRehydrate.ts @@ -1,4 +1,4 @@ -import { IS_LINUX } from '../utils/platform'; +import { IS_LINUX } from '@/lib/util/platform'; import { sanitizeHiResCrossfadeResampleHz } from '../utils/audio/hiResCrossfadeResample'; import { sanitizeAutodjOverlapCapMode, diff --git a/src/store/miscActions.ts b/src/store/miscActions.ts index 26e2da86..be4ea353 100644 --- a/src/store/miscActions.ts +++ b/src/store/miscActions.ts @@ -1,6 +1,6 @@ import { applyServerPlayQueue } from './applyServerPlayQueue'; import { invoke } from '@tauri-apps/api/core'; -import i18n from '../i18n'; +import i18n from '@/lib/i18n'; import { showToast } from '../utils/ui/toast'; import { useAuthStore } from './authStore'; import { diff --git a/src/store/playerStore.ts b/src/store/playerStore.ts index 6ea716a6..a4700e64 100644 --- a/src/store/playerStore.ts +++ b/src/store/playerStore.ts @@ -2,7 +2,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { readInitialNetworkLovedCache, persistNetworkLovedCache } from './networkLovedCacheStorage'; import { readInitialPlayerPrefs, persistPlayerPrefs } from './playerPrefsStorage'; -import { createHydrationGatedStorage, createSafeJSONStorage } from './safeStorage'; +import { createHydrationGatedStorage, createSafeJSONStorage } from '@/lib/util/safeStorage'; import { emitPlaybackProgress } from './playbackProgress'; import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes'; import { toQueueItemRefs } from '../utils/library/queueItemRef'; diff --git a/src/test/helpers/renderWithProviders.tsx b/src/test/helpers/renderWithProviders.tsx index 3b8a54c6..770ec39e 100644 --- a/src/test/helpers/renderWithProviders.tsx +++ b/src/test/helpers/renderWithProviders.tsx @@ -16,7 +16,7 @@ import type { ReactElement, ReactNode } from 'react'; import { render, type RenderOptions, type RenderResult } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; -import i18n from '@/i18n'; +import i18n from '@/lib/i18n'; interface WrapperOptions { route?: string; diff --git a/src/utils/componentHelpers/appUpdaterHelpers.ts b/src/utils/componentHelpers/appUpdaterHelpers.ts index 490d428c..b1589212 100644 --- a/src/utils/componentHelpers/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 '@/lib/util/platform'; export const SKIP_KEY = 'psysonic_skipped_update_version'; diff --git a/src/utils/componentHelpers/nowPlayingHelpers.ts b/src/utils/componentHelpers/nowPlayingHelpers.ts index 46e1311d..62a91833 100644 --- a/src/utils/componentHelpers/nowPlayingHelpers.ts +++ b/src/utils/componentHelpers/nowPlayingHelpers.ts @@ -1,5 +1,5 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { sanitizeHtml as sanitizeHtmlBase } from '../sanitizeHtml'; +import { sanitizeHtml as sanitizeHtmlBase } from '@/lib/util/sanitizeHtml'; export function formatTime(s: number): string { if (!s || isNaN(s)) return '0:00'; diff --git a/src/utils/componentHelpers/playlistDetailHelpers.ts b/src/utils/componentHelpers/playlistDetailHelpers.ts index 76cdb546..6c075a0b 100644 --- a/src/utils/componentHelpers/playlistDetailHelpers.ts +++ b/src/utils/componentHelpers/playlistDetailHelpers.ts @@ -1,6 +1,6 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { formatHumanHoursMinutes } from '../format/formatHumanDuration'; -import { formatMb } from '../format/formatBytes'; +import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration'; +import { formatMb } from '@/lib/format/formatBytes'; export function sanitizeFilename(name: string): string { return name diff --git a/src/utils/componentHelpers/userMgmtHelpers.ts b/src/utils/componentHelpers/userMgmtHelpers.ts index 6cba9bb8..b73db12f 100644 --- a/src/utils/componentHelpers/userMgmtHelpers.ts +++ b/src/utils/componentHelpers/userMgmtHelpers.ts @@ -1,4 +1,4 @@ -import { formatRelativeTime } from '../format/relativeTime'; +import { formatRelativeTime } from '@/lib/format/relativeTime'; /** * Render a relative time string like "3 hours ago" / "in 2 weeks" with diff --git a/src/utils/library/albumBrowseCatalogChunk.ts b/src/utils/library/albumBrowseCatalogChunk.ts index 6105c394..1f2a1b95 100644 --- a/src/utils/library/albumBrowseCatalogChunk.ts +++ b/src/utils/library/albumBrowseCatalogChunk.ts @@ -1,5 +1,5 @@ import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { dedupeById } from '../dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { isOfflineBrowseActive } from '@/features/offline'; import { loadOfflineAlbumCatalogChunk } from '@/features/offline'; import type { AlbumBrowseQuery } from './albumBrowseTypes'; diff --git a/src/utils/library/albumBrowseLocal.ts b/src/utils/library/albumBrowseLocal.ts index 6d77713e..5a3eef53 100644 --- a/src/utils/library/albumBrowseLocal.ts +++ b/src/utils/library/albumBrowseLocal.ts @@ -1,7 +1,7 @@ import { libraryAdvancedSearch, libraryListAlbumsByGenre } from '@/lib/api/library'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; import { libraryScopeForServer } from '@/lib/api/subsonicClient'; -import { dedupeById } from '../dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { albumToAlbum } from './advancedSearchLocal'; import { sharedServerFilters } from './albumBrowseFilters'; import { albumSortClauses, sortSubsonicAlbums } from './albumBrowseSort'; diff --git a/src/utils/library/albumBrowseNetwork.ts b/src/utils/library/albumBrowseNetwork.ts index 8870dee5..debab8d2 100644 --- a/src/utils/library/albumBrowseNetwork.ts +++ b/src/utils/library/albumBrowseNetwork.ts @@ -1,7 +1,7 @@ import { getAlbumList } from '@/lib/api/subsonicLibrary'; import { getAlbumsByGenre } from '@/lib/api/subsonicGenres'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { dedupeById } from '../dedupeById'; +import { dedupeById } from '@/lib/util/dedupeById'; import { filterAlbumsByCompilation, filterAlbumsByYearBounds, diff --git a/src/utils/mix/luckyMix.ts b/src/utils/mix/luckyMix.ts index 7c7e801e..49341690 100644 --- a/src/utils/mix/luckyMix.ts +++ b/src/utils/mix/luckyMix.ts @@ -4,7 +4,7 @@ import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; import type { QueueItemRef } from '../../store/playerStoreTypes'; import { songToTrack } from '../playback/songToTrack'; import { invoke } from '@tauri-apps/api/core'; -import i18n from '../../i18n'; +import i18n from '@/lib/i18n'; import { useAuthStore } from '../../store/authStore'; import { pushQueueUndoFromGetter } from '../../store/queueUndo'; import { usePlayerStore } from '../../store/playerStore'; diff --git a/src/utils/perf/perfLiveCpuSnapshot.ts b/src/utils/perf/perfLiveCpuSnapshot.ts index b5ae62b7..5b3dc220 100644 --- a/src/utils/perf/perfLiveCpuSnapshot.ts +++ b/src/utils/perf/perfLiveCpuSnapshot.ts @@ -1,4 +1,4 @@ -import { IS_LINUX, IS_MACOS } from '../platform'; +import { IS_LINUX, IS_MACOS } from '@/lib/util/platform'; /** Matches Rust `performance_cpu_snapshot` (Linux `/proc`, macOS sysinfo). */ export function perfLiveCpuSnapshotSupported(): boolean {