refactor(utils): group utils/ files into topic folders (Phase L, part 1) (#689)

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.
This commit is contained in:
Frank Stellmacher
2026-05-14 14:27:44 +02:00
committed by GitHub
parent 2409a1fec8
commit 7a7a9f5e6b
324 changed files with 551 additions and 551 deletions
+11 -11
View File
@@ -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
+1 -1
View File
@@ -5,7 +5,7 @@ import {
isNavidromeAudiomuseSoftwareEligible,
type InstantMixProbeResult,
type SubsonicServerIdentity,
} from '../utils/subsonicServerIdentity';
} from '../utils/server/subsonicServerIdentity';
import {
SUBSONIC_CLIENT,
api,
+1 -1
View File
@@ -1,4 +1,4 @@
import type { SubsonicServerIdentity } from '../utils/subsonicServerIdentity';
import type { SubsonicServerIdentity } from '../utils/server/subsonicServerIdentity';
export interface SubsonicAlbum {
id: string;
+2 -2
View File
@@ -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 +
+2 -2
View File
@@ -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)` : '';
+1 -1
View File
@@ -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', () => ({
+1 -1
View File
@@ -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';
/**
+2 -2
View File
@@ -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';
+2 -2
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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[];
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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:';
+1 -1
View File
@@ -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);
+3 -3
View File
@@ -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;
+2 -2
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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;
+4 -4
View File
@@ -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;
+2 -2
View File
@@ -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<typeof setTimeout>;
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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<HTMLElement | null>;
+3 -3
View File
@@ -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';
+5 -5
View File
@@ -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';
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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 {
+3 -3
View File
@@ -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';
+5 -5
View File
@@ -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';
+2 -2
View File
@@ -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';
+2 -2
View File
@@ -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';
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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';
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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';
+2 -2
View File
@@ -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;
@@ -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[];
+2 -2
View File
@@ -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,
@@ -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<HTMLDivElement | null>;
@@ -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[];
@@ -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';
@@ -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 {
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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 {
@@ -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[];
@@ -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';
@@ -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[];
@@ -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[];
@@ -46,7 +46,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
</div>
<div className="context-menu-divider" />
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => 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) {
)}
</div>
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => 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[] = [];
@@ -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 };
@@ -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 && (
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => 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);
@@ -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';
@@ -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 {
@@ -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[];
@@ -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 {
@@ -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;
@@ -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;
@@ -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 {
@@ -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';
@@ -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;
@@ -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.
@@ -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';
@@ -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 }) {
@@ -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;
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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[]; }
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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<K extends keyof ReturnType<typeof usePlayerStore.getState>> =
NonNullable<ReturnType<typeof usePlayerStore.getState>[K]>;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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. */
@@ -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';
@@ -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<typeof usePlaybackDelayPress>['playPauseBind'];
@@ -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: {
+1 -1
View File
@@ -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 {
@@ -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';
@@ -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']);
@@ -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';
+2 -2
View File
@@ -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 {
+1 -1
View File
@@ -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;
@@ -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;
@@ -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 {
+1 -1
View File
@@ -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[];
+1 -1
View File
@@ -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 },
@@ -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;
+3 -3
View File
@@ -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,
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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();
+4 -4
View File
@@ -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';
@@ -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;

Some files were not shown because too many files have changed in this diff Show More