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) # - 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) ──────────────────────────── # ── utils (already at or above threshold) ────────────────────────────
src/utils/coverArtRegisteredSizes.ts src/utils/cover/coverArtRegisteredSizes.ts
src/utils/serverDisplayName.ts src/utils/server/serverDisplayName.ts
src/utils/serverMagicString.ts src/utils/server/serverMagicString.ts
src/utils/shareLink.ts src/utils/share/shareLink.ts
src/utils/dynamicColors.ts src/utils/ui/dynamicColors.ts
src/utils/resolvePlaybackUrl.ts src/utils/playback/resolvePlaybackUrl.ts
src/utils/copyEntityShareLink.ts src/utils/share/copyEntityShareLink.ts
# ── M0: pure helpers extracted from playerStore.ts (2026-05-12) ────── # ── M0: pure helpers extracted from playerStore.ts (2026-05-12) ──────
src/utils/shuffleArray.ts src/utils/playback/shuffleArray.ts
src/utils/resolveReplayGainDb.ts src/utils/audio/resolveReplayGainDb.ts
src/utils/songToTrack.ts src/utils/playback/songToTrack.ts
src/utils/buildInfiniteQueueCandidates.ts src/utils/playback/buildInfiniteQueueCandidates.ts
# ── Phase B.1: pre-React bootstrap + window-kind detector (2026-05-12) ── # ── Phase B.1: pre-React bootstrap + window-kind detector (2026-05-12) ──
src/app/windowKind.ts src/app/windowKind.ts
+1 -1
View File
@@ -5,7 +5,7 @@ import {
isNavidromeAudiomuseSoftwareEligible, isNavidromeAudiomuseSoftwareEligible,
type InstantMixProbeResult, type InstantMixProbeResult,
type SubsonicServerIdentity, type SubsonicServerIdentity,
} from '../utils/subsonicServerIdentity'; } from '../utils/server/subsonicServerIdentity';
import { import {
SUBSONIC_CLIENT, SUBSONIC_CLIENT,
api, 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 { export interface SubsonicAlbum {
id: string; id: string;
+2 -2
View File
@@ -50,11 +50,11 @@ import { usePlayerStore } from '../store/playerStore';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { useFontStore } from '../store/fontStore'; import { useFontStore } from '../store/fontStore';
import { useEqStore } from '../store/eqStore'; import { useEqStore } from '../store/eqStore';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { import {
persistSidebarCollapsed, persistSidebarCollapsed,
readInitialSidebarCollapsed, readInitialSidebarCollapsed,
} from '../utils/appShellHelpers'; } from '../utils/componentHelpers/appShellHelpers';
/** /**
* The main webview's persistent layout: titlebar (Linux only) + sidebar + * 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 { lazy, Suspense, useEffect, useState } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { WindowVisibilityProvider } from '../hooks/useWindowVisibility'; import { WindowVisibilityProvider } from '../hooks/useWindowVisibility';
import { DragDropProvider } from '../contexts/DragDropContext'; import { DragDropProvider } from '../contexts/DragDropContext';
import PasteClipboardHandler from '../components/PasteClipboardHandler'; import PasteClipboardHandler from '../components/PasteClipboardHandler';
@@ -69,7 +69,7 @@ export default function MainApp() {
const handleExport = async (since: number) => { const handleExport = async (since: number) => {
setExportPickerOpen(false); setExportPickerOpen(false);
try { try {
const { exportNewAlbumsImage } = await import('../utils/exportNewAlbums'); const { exportNewAlbumsImage } = await import('../utils/export/exportNewAlbums');
const result = await exportNewAlbumsImage(since); const result = await exportNewAlbumsImage(since);
if (result) { if (result) {
const files = result.paths.length > 1 ? ` (${result.paths.length} Dateien)` : ''; 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', () => ({ vi.mock('../store/keybindingsStore', () => ({
useKeybindingsStore: { persist: { rehydrate: keybindingsRehydrate } }, useKeybindingsStore: { persist: { rehydrate: keybindingsRehydrate } },
})); }));
vi.mock('../utils/perfFlags', () => ({ vi.mock('../utils/perf/perfFlags', () => ({
usePerfProbeFlags: () => ({ disableTooltipPortal: true }), usePerfProbeFlags: () => ({ disableTooltipPortal: true }),
})); }));
vi.mock('../i18n', () => ({ vi.mock('../i18n', () => ({
+1 -1
View File
@@ -7,7 +7,7 @@ import FpsOverlay from '../components/FpsOverlay';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { useFontStore } from '../store/fontStore'; import { useFontStore } from '../store/fontStore';
import { useKeybindingsStore } from '../store/keybindingsStore'; import { useKeybindingsStore } from '../store/keybindingsStore';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import i18n from '../i18n'; import i18n from '../i18n';
/** /**
+2 -2
View File
@@ -1,7 +1,7 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getAlbum } from '../api/subsonicLibrary'; import { getAlbum } from '../api/subsonicLibrary';
import type { SubsonicAlbum } from '../api/subsonicTypes'; import type { SubsonicAlbum } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack'; import { songToTrack } from '../utils/playback/songToTrack';
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react'; import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react';
@@ -10,7 +10,7 @@ import { usePlayerStore } from '../store/playerStore';
import { useOfflineStore } from '../store/offlineStore'; import { useOfflineStore } from '../store/offlineStore';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import CachedImage from './CachedImage'; import CachedImage from './CachedImage';
import { playAlbum } from '../utils/playAlbum'; import { playAlbum } from '../utils/playback/playAlbum';
import { useDragDrop } from '../contexts/DragDropContext'; import { useDragDrop } from '../contexts/DragDropContext';
import { isAlbumRecentlyAdded } from '../utils/albumRecency'; import { isAlbumRecentlyAdded } from '../utils/albumRecency';
+2 -2
View File
@@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next';
import { useIsMobile } from '../hooks/useIsMobile'; import { useIsMobile } from '../hooks/useIsMobile';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import StarRating from './StarRating'; import StarRating from './StarRating';
import { copyEntityShareLink } from '../utils/copyEntityShareLink'; import { copyEntityShareLink } from '../utils/share/copyEntityShareLink';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { isAlbumRecentlyAdded } from '../utils/albumRecency'; import { isAlbumRecentlyAdded } from '../utils/albumRecency';
function formatDuration(seconds: number): string { 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 { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react';
import { NavLink, useNavigate } from 'react-router-dom'; import { NavLink, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { dedupeById } from '../utils/dedupeById'; import { dedupeById } from '../utils/dedupeById';
interface Props { interface Props {
+2 -2
View File
@@ -9,14 +9,14 @@ import { useSelectionStore } from '../store/selectionStore';
import { import {
COLUMNS, COLUMNS,
type SortKey, type SortKey,
} from '../utils/albumTrackListHelpers'; } from '../utils/componentHelpers/albumTrackListHelpers';
import { useAlbumTrackListSelection } from '../hooks/useAlbumTrackListSelection'; import { useAlbumTrackListSelection } from '../hooks/useAlbumTrackListSelection';
import { TrackRow } from './albumTrackList/TrackRow'; import { TrackRow } from './albumTrackList/TrackRow';
import { AlbumTrackListMobile } from './albumTrackList/AlbumTrackListMobile'; import { AlbumTrackListMobile } from './albumTrackList/AlbumTrackListMobile';
import { TracklistColumnPicker } from './albumTrackList/TracklistColumnPicker'; import { TracklistColumnPicker } from './albumTrackList/TracklistColumnPicker';
import { TracklistHeaderRow } from './albumTrackList/TracklistHeaderRow'; import { TracklistHeaderRow } from './albumTrackList/TracklistHeaderRow';
export type { SortKey } from '../utils/albumTrackListHelpers'; export type { SortKey } from '../utils/componentHelpers/albumTrackListHelpers';
interface AlbumTrackListProps { interface AlbumTrackListProps {
songs: SubsonicSong[]; songs: SubsonicSong[];
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { PanelRight, PanelRightClose } from 'lucide-react'; import { PanelRight, PanelRightClose } from 'lucide-react';
import { shouldSuppressQueueResizerMouseDown } from '../utils/appShellHelpers'; import { shouldSuppressQueueResizerMouseDown } from '../utils/componentHelpers/appShellHelpers';
interface Props { interface Props {
isQueueVisible: boolean; 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 { ArrowUpCircle, CheckCircle2, ChevronDown, Download, FolderOpen, RefreshCw, ShieldCheck, X } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { version as currentVersion } from '../../package.json'; import { version as currentVersion } from '../../package.json';
import { fmtBytes } from '../utils/appUpdaterHelpers'; import { fmtBytes } from '../utils/componentHelpers/appUpdaterHelpers';
import { useAppUpdater } from '../hooks/useAppUpdater'; import { useAppUpdater } from '../hooks/useAppUpdater';
import Changelog from './appUpdater/Changelog'; 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 { getArtist, getArtistInfo } from '../api/subsonicArtists';
import { getAlbum } from '../api/subsonicLibrary'; import { getAlbum } from '../api/subsonicLibrary';
import type { SubsonicAlbum } from '../api/subsonicTypes'; 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 React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -10,7 +10,7 @@ import { Play, ListPlus, Music } from 'lucide-react';
import CachedImage, { useCachedUrl } from './CachedImage'; import CachedImage, { useCachedUrl } from './CachedImage';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { playAlbum } from '../utils/playAlbum'; import { playAlbum } from '../utils/playback/playAlbum';
import AlbumRow from './AlbumRow'; import AlbumRow from './AlbumRow';
const ANCHOR_HISTORY_KEY_PREFIX = 'psysonic_because_anchor_history:'; 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 { version } from '../../package.json';
import changelogRaw from '../../CHANGELOG.md?raw'; import changelogRaw from '../../CHANGELOG.md?raw';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { findChangelogReleaseEntry } from '../utils/changelogReleaseMatch'; import { findChangelogReleaseEntry } from '../utils/changelog/changelogReleaseMatch';
function renderInline(text: string): React.ReactNode[] { function renderInline(text: string): React.ReactNode[] {
const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g); 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 { Check, ChevronDown } from 'lucide-react';
import { ConnectionStatus } from '../hooks/useConnectionStatus'; import { ConnectionStatus } from '../hooks/useConnectionStatus';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { switchActiveServer } from '../utils/switchActiveServer'; import { switchActiveServer } from '../utils/server/switchActiveServer';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { serverListDisplayLabel } from '../utils/serverDisplayName'; import { serverListDisplayLabel } from '../utils/server/serverDisplayName';
interface Props { interface Props {
status: ConnectionStatus; status: ConnectionStatus;
+2 -2
View File
@@ -5,14 +5,14 @@ import { usePlayerStore } from '../store/playerStore';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { EntityShareKind } from '../utils/shareLink'; import type { EntityShareKind } from '../utils/share/shareLink';
import { AddToPlaylistSubmenu } from './contextMenu/AddToPlaylistSubmenu'; import { AddToPlaylistSubmenu } from './contextMenu/AddToPlaylistSubmenu';
import { import {
copyShareLink as copyShareLinkAction, copyShareLink as copyShareLinkAction,
downloadAlbum as downloadAlbumAction, downloadAlbum as downloadAlbumAction,
startInstantMix as startInstantMixAction, startInstantMix as startInstantMixAction,
startRadio as startRadioAction, startRadio as startRadioAction,
} from '../utils/contextMenuActions'; } from '../utils/componentHelpers/contextMenuActions';
import { useContextMenuKeyboardNav } from '../hooks/useContextMenuKeyboardNav'; import { useContextMenuKeyboardNav } from '../hooks/useContextMenuKeyboardNav';
import { useContextMenuRating } from '../hooks/useContextMenuRating'; import { useContextMenuRating } from '../hooks/useContextMenuRating';
import ContextMenuItems from './contextMenu/ContextMenuItems'; 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 CustomSelect from './CustomSelect';
import { useEqStore, EQ_BANDS, BUILTIN_PRESETS } from '../store/eqStore'; import { useEqStore, EQ_BANDS, BUILTIN_PRESETS } from '../store/eqStore';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { drawCurve } from '../utils/eqCurve'; import { drawCurve } from '../utils/audio/eqCurve';
import VerticalFader from './equalizer/VerticalFader'; import VerticalFader from './equalizer/VerticalFader';
import AutoEqSection from './equalizer/AutoEqSection'; import AutoEqSection from './equalizer/AutoEqSection';
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { usePerfProbeFlag } from '../utils/perfFlags'; import { usePerfProbeFlag } from '../utils/perf/perfFlags';
const SAMPLE_MS = 500; const SAMPLE_MS = 500;
+4 -4
View File
@@ -1,20 +1,20 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { getRandomAlbums, getAlbum } from '../api/subsonicLibrary'; import { getRandomAlbums, getAlbum } from '../api/subsonicLibrary';
import type { SubsonicAlbum } from '../api/subsonicTypes'; 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 React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Play, ListPlus } from 'lucide-react'; import { Play, ListPlus } from 'lucide-react';
import CachedImage, { useCachedUrl } from './CachedImage'; import CachedImage, { useCachedUrl } from './CachedImage';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { playAlbum } from '../utils/playAlbum'; import { playAlbum } from '../utils/playback/playAlbum';
import { useIsMobile } from '../hooks/useIsMobile'; import { useIsMobile } from '../hooks/useIsMobile';
import { useWindowVisibility } from '../hooks/useWindowVisibility'; import { useWindowVisibility } from '../hooks/useWindowVisibility';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mixRatingFilter'; import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
const INTERVAL_MS = 10000; const INTERVAL_MS = 10000;
const HERO_ALBUM_COUNT = 8; const HERO_ALBUM_COUNT = 8;
+2 -2
View File
@@ -1,7 +1,7 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { search } from '../api/subsonicSearch'; import { search } from '../api/subsonicSearch';
import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes'; 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 React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Search, Disc3, Users, Music, TextSearch } from 'lucide-react'; import { Search, Disc3, Users, Music, TextSearch } from 'lucide-react';
@@ -9,7 +9,7 @@ import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import CachedImage, { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from './CachedImage'; 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 { function debounce(fn: (q: string) => void, ms: number): (q: string) => void {
let timer: ReturnType<typeof setTimeout>; 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 { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Track } from '../store/playerStoreTypes'; import type { Track } from '../store/playerStoreTypes';
import { EaseScroller, targetForFraction } from '../utils/easeScroll'; import { EaseScroller, targetForFraction } from '../utils/ui/easeScroll';
import OverlayScrollArea from './OverlayScrollArea'; import OverlayScrollArea from './OverlayScrollArea';
interface Props { interface Props {
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useRef, useState, useEffect, useCallback } from 'react'; import React, { useRef, useState, useEffect, useCallback } from 'react';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
interface Props { interface Props {
text: string; text: string;
+1 -1
View File
@@ -10,7 +10,7 @@ import {
COLLAPSED_SIZE, EXPANDED_SIZE, COLLAPSED_MIN, EXPANDED_MIN, COLLAPSED_SIZE, EXPANDED_SIZE, COLLAPSED_MIN, EXPANDED_MIN,
EXPANDED_H_KEY, QUEUE_OPEN_KEY, EXPANDED_H_KEY, QUEUE_OPEN_KEY,
readStoredExpandedHeight, readQueueOpen, initialSnapshot, readStoredExpandedHeight, readQueueOpen, initialSnapshot,
} from '../utils/miniPlayerHelpers'; } from '../utils/componentHelpers/miniPlayerHelpers';
import { MiniTitlebar } from './miniPlayer/MiniTitlebar'; import { MiniTitlebar } from './miniPlayer/MiniTitlebar';
import { MiniMeta } from './miniPlayer/MiniMeta'; import { MiniMeta } from './miniPlayer/MiniMeta';
import { MiniControls } from './miniPlayer/MiniControls'; import { MiniControls } from './miniPlayer/MiniControls';
+1 -1
View File
@@ -16,7 +16,7 @@ import LyricsPane from './LyricsPane';
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
import PlaybackDelayModal from './PlaybackDelayModal'; import PlaybackDelayModal from './PlaybackDelayModal';
import PlaybackScheduleBadge from './PlaybackScheduleBadge'; import PlaybackScheduleBadge from './PlaybackScheduleBadge';
import { usePlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
// ── Color extraction ────────────────────────────────────────────────────────── // ── Color extraction ──────────────────────────────────────────────────────────
// Samples a 16×16 canvas to find the most vibrant (highest-saturation, // 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 { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { search } from '../api/subsonicSearch'; import { search } from '../api/subsonicSearch';
import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes'; 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 React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -10,7 +10,7 @@ import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import CachedImage, { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from './CachedImage'; 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 STORAGE_KEY = 'psysonic_recent_searches';
const MAX_RECENT = 6; const MAX_RECENT = 6;
+1 -1
View File
@@ -4,7 +4,7 @@ import { Copy, Trash2 } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '../store/orbitStore'; import { useOrbitStore } from '../store/orbitStore';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { computeOrbitDriftMs } from '../utils/orbit'; import { computeOrbitDriftMs } from '../utils/orbit';
import { import {
clearOrbitEvents, clearOrbitEvents,
+2 -2
View File
@@ -9,9 +9,9 @@ import {
readOrbitState, readOrbitState,
joinOrbitSession, joinOrbitSession,
} from '../utils/orbit'; } from '../utils/orbit';
import { switchActiveServer } from '../utils/switchActiveServer'; import { switchActiveServer } from '../utils/server/switchActiveServer';
import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore'; import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
interface Props { interface Props {
onClose: () => void; onClose: () => void;
+1 -1
View File
@@ -1,5 +1,5 @@
import { getSong } from '../api/subsonicLibrary'; import { getSong } from '../api/subsonicLibrary';
import { songToTrack } from '../utils/songToTrack'; import { songToTrack } from '../utils/playback/songToTrack';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react'; import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '../store/orbitStore'; import { useOrbitStore } from '../store/orbitStore';
import { updateOrbitSettings, triggerOrbitShuffleNow } from '../utils/orbit'; import { updateOrbitSettings, triggerOrbitShuffleNow } from '../utils/orbit';
import { ORBIT_DEFAULT_SETTINGS, ORBIT_SHUFFLE_INTERVAL_PRESETS_MIN, type OrbitShuffleIntervalMin } from '../api/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 { interface Props {
anchorRef: React.RefObject<HTMLElement | null>; anchorRef: React.RefObject<HTMLElement | null>;
+3 -3
View File
@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { computeOverlayScrollbarThumbMeta } from '../utils/overlayScrollbarMetrics'; import { computeOverlayScrollbarThumbMeta } from '../utils/ui/overlayScrollbarMetrics';
import { bindOverlayScrollbarThumbDrag } from '../utils/overlayScrollbarThumb'; import { bindOverlayScrollbarThumbDrag } from '../utils/ui/overlayScrollbarThumb';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
export type OverlayScrollRailInset = 'none' | 'mini' | 'panel'; 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 { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { decodeSharePayloadFromText } from '../utils/shareLink'; import { decodeSharePayloadFromText } from '../utils/share/shareLink';
import { decodeServerMagicStringFromText } from '../utils/serverMagicString'; import { decodeServerMagicStringFromText } from '../utils/server/serverMagicString';
import { applySharePastePayload } from '../utils/applySharePaste'; import { applySharePastePayload } from '../utils/share/applySharePaste';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { import {
parseOrbitShareLink, parseOrbitShareLink,
joinOrbitSession, joinOrbitSession,
@@ -13,7 +13,7 @@ import {
readOrbitState, readOrbitState,
OrbitJoinError, OrbitJoinError,
} from '../utils/orbit'; } from '../utils/orbit';
import { switchActiveServer } from '../utils/switchActiveServer'; import { switchActiveServer } from '../utils/server/switchActiveServer';
import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore'; import { useOrbitAccountPickerStore } from '../store/orbitAccountPickerStore';
import ConfirmModal from './ConfirmModal'; import ConfirmModal from './ConfirmModal';
+1 -1
View File
@@ -6,7 +6,7 @@ import { usePlayerStore } from '../store/playerStore';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { formatPlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
function formatClockTime(ts: number): string { function formatClockTime(ts: number): string {
const d = new Date(ts); const d = new Date(ts);
+1 -1
View File
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { formatPlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
import { useWindowVisibility } from '../hooks/useWindowVisibility'; import { useWindowVisibility } from '../hooks/useWindowVisibility';
export interface PlaybackScheduleBadgeProps { export interface PlaybackScheduleBadgeProps {
+3 -3
View File
@@ -25,10 +25,10 @@ import { useRadioMetadata } from '../hooks/useRadioMetadata';
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
import PlaybackDelayModal from './PlaybackDelayModal'; import PlaybackDelayModal from './PlaybackDelayModal';
import PlaybackScheduleBadge from './PlaybackScheduleBadge'; import PlaybackScheduleBadge from './PlaybackScheduleBadge';
import { usePlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
import { usePreviewStore } from '../store/previewStore'; import { usePreviewStore } from '../store/previewStore';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { formatTime } from '../utils/playerBarHelpers'; import { formatTime } from '../utils/componentHelpers/playerBarHelpers';
import { PlaybackTime, RemainingTime } from './playerBar/PlaybackClock'; import { PlaybackTime, RemainingTime } from './playerBar/PlaybackClock';
import { PlayerTrackInfo } from './playerBar/PlayerTrackInfo'; import { PlayerTrackInfo } from './playerBar/PlayerTrackInfo';
import { PlayerTransportControls } from './playerBar/PlayerTransportControls'; import { PlayerTransportControls } from './playerBar/PlayerTransportControls';
+5 -5
View File
@@ -1,6 +1,6 @@
import { getPlaylist, updatePlaylist } from '../api/subsonicPlaylists'; import { getPlaylist, updatePlaylist } from '../api/subsonicPlaylists';
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { songToTrack } from '../utils/songToTrack'; import { songToTrack } from '../utils/playback/songToTrack';
import type { Track } from '../store/playerStoreTypes'; import type { Track } from '../store/playerStoreTypes';
import { useState, useRef, useMemo } from 'react'; import { useState, useRef, useMemo } from 'react';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
@@ -13,9 +13,9 @@ import { useCachedUrl } from './CachedImage';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { encodeSharePayload } from '../utils/shareLink'; import { encodeSharePayload } from '../utils/share/shareLink';
import { copyTextToClipboard } from '../utils/serverMagicString'; import { copyTextToClipboard } from '../utils/server/serverMagicString';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { useLyricsStore } from '../store/lyricsStore'; import { useLyricsStore } from '../store/lyricsStore';
import LyricsPane from './LyricsPane'; import LyricsPane from './LyricsPane';
@@ -26,7 +26,7 @@ import { useQueueToolbarStore } from '../store/queueToolbarStore';
import { import {
DurationMode, DurationMode,
formatTime, formatTime,
} from '../utils/queuePanelHelpers'; } from '../utils/componentHelpers/queuePanelHelpers';
import { SavePlaylistModal } from './queuePanel/SavePlaylistModal'; import { SavePlaylistModal } from './queuePanel/SavePlaylistModal';
import { LoadPlaylistModal } from './queuePanel/LoadPlaylistModal'; import { LoadPlaylistModal } from './queuePanel/LoadPlaylistModal';
import { QueueHeader } from './queuePanel/QueueHeader'; import { QueueHeader } from './queuePanel/QueueHeader';
+2 -2
View File
@@ -16,9 +16,9 @@ import OverlayScrollArea from './OverlayScrollArea';
import { import {
getLibraryItemsForReorder, getLibraryItemsForReorder,
getSystemItemsForReorder, getSystemItemsForReorder,
} from '../utils/sidebarNavReorder'; } from '../utils/componentHelpers/sidebarNavReorder';
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { useSidebarNewReleasesUnread } from '../hooks/useSidebarNewReleasesUnread'; import { useSidebarNewReleasesUnread } from '../hooks/useSidebarNewReleasesUnread';
import { useSidebarNavDnd } from '../hooks/useSidebarNavDnd'; import { useSidebarNavDnd } from '../hooks/useSidebarNavDnd';
import { useSidebarLibraryDropdown } from '../hooks/useSidebarLibraryDropdown'; import { useSidebarLibraryDropdown } from '../hooks/useSidebarLibraryDropdown';
+2 -2
View File
@@ -1,13 +1,13 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import type { SubsonicSong } from '../api/subsonicTypes'; import type { SubsonicSong } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack'; import { songToTrack } from '../utils/playback/songToTrack';
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Play, ListPlus, Star } from 'lucide-react'; import { Play, ListPlus, Star } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
import CachedImage from './CachedImage'; import CachedImage from './CachedImage';
import { enqueueAndPlay } from '../utils/playSong'; import { enqueueAndPlay } from '../utils/playback/playSong';
import { useDragDrop } from '../contexts/DragDropContext'; import { useDragDrop } from '../contexts/DragDropContext';
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior'; 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 { ndGetSongPath } from '../api/navidromeAdmin';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { copyTextToClipboard } from '../utils/serverMagicString'; import { copyTextToClipboard } from '../utils/server/serverMagicString';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
function formatDuration(s: number): string { function formatDuration(s: number): string {
const m = Math.floor(s / 60); 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 React, { useRef, useState, useEffect, useMemo } from 'react';
import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react'; import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react';
import SongCard from './SongCard'; import SongCard from './SongCard';
import { usePerfProbeFlags } from '../utils/perfFlags'; import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { dedupeById } from '../utils/dedupeById'; import { dedupeById } from '../utils/dedupeById';
interface Props { interface Props {
+2 -2
View File
@@ -1,11 +1,11 @@
import type { SubsonicSong } from '../api/subsonicTypes'; import type { SubsonicSong } from '../api/subsonicTypes';
import { songToTrack } from '../utils/songToTrack'; import { songToTrack } from '../utils/playback/songToTrack';
import React, { memo } from 'react'; import React, { memo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Play, ListPlus } from 'lucide-react'; import { Play, ListPlus } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore'; import { usePlayerStore } from '../store/playerStore';
import { enqueueAndPlay } from '../utils/playSong'; import { enqueueAndPlay } from '../utils/playback/playSong';
import { useDragDrop } from '../contexts/DragDropContext'; import { useDragDrop } from '../contexts/DragDropContext';
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior'; import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
+2 -2
View File
@@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { save } from '@tauri-apps/plugin-dialog'; import { save } from '@tauri-apps/plugin-dialog';
import { writeFile } from '@tauri-apps/plugin-fs'; import { writeFile } from '@tauri-apps/plugin-fs';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/ui/toast';
import { import {
exportAlbumCardBlob, exportAlbumCardBlob,
renderAlbumCardCanvas, renderAlbumCardCanvas,
ExportFormat, ExportFormat,
ExportGridSize, ExportGridSize,
} from '../utils/exportAlbumCard'; } from '../utils/export/exportAlbumCard';
interface Props { interface Props {
open: boolean; open: boolean;
+2 -2
View File
@@ -8,8 +8,8 @@ import {
STATIC_REDRAW_FORCE_MS, STATIC_REDRAW_MIN_MS, STATIC_REDRAW_FORCE_MS, STATIC_REDRAW_MIN_MS,
fmt, invalidateColorCache, isBarQuantizedSeekStyle, makeAnimState, fmt, invalidateColorCache, isBarQuantizedSeekStyle, makeAnimState,
quantizeProgressByBars, quantizeProgressByBars,
} from '../utils/waveformSeekHelpers'; } from '../utils/waveform/waveformSeekHelpers';
import { drawSeekbar } from '../utils/waveformSeekRenderers'; import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers';
import { useWaveformHeights } from '../hooks/useWaveformHeights'; import { useWaveformHeights } from '../hooks/useWaveformHeights';
import { useWaveformInterpolation } from '../hooks/useWaveformInterpolation'; import { useWaveformInterpolation } from '../hooks/useWaveformInterpolation';
+2 -2
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import type { SeekbarStyle } from '../store/authStoreTypes'; import type { SeekbarStyle } from '../store/authStoreTypes';
import { makeAnimState, makeHeights } from '../utils/waveformSeekHelpers'; import { makeAnimState, makeHeights } from '../utils/waveform/waveformSeekHelpers';
import { drawSeekbar } from '../utils/waveformSeekRenderers'; import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers';
interface Props { interface Props {
style: SeekbarStyle; style: SeekbarStyle;
@@ -2,8 +2,8 @@ import React from 'react';
import { AudioLines } from 'lucide-react'; import { AudioLines } from 'lucide-react';
import type { SubsonicSong } from '../../api/subsonicTypes'; import type { SubsonicSong } from '../../api/subsonicTypes';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { formatDuration } from '../../utils/albumTrackListHelpers'; import { formatDuration } from '../../utils/componentHelpers/albumTrackListHelpers';
interface Props { interface Props {
discNums: number[]; discNums: number[];
+2 -2
View File
@@ -5,12 +5,12 @@ import { useNavigate } from 'react-router-dom';
import type { ColDef } from '../../utils/useTracklistColumns'; import type { ColDef } from '../../utils/useTracklistColumns';
import type { SubsonicSong } from '../../api/subsonicTypes'; import type { SubsonicSong } from '../../api/subsonicTypes';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { useSelectionStore } from '../../store/selectionStore'; import { useSelectionStore } from '../../store/selectionStore';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { usePreviewStore } from '../../store/previewStore'; import { usePreviewStore } from '../../store/previewStore';
import StarRating from '../StarRating'; import StarRating from '../StarRating';
import { codecLabel, formatDuration, type ColKey } from '../../utils/albumTrackListHelpers'; import { codecLabel, formatDuration, type ColKey } from '../../utils/componentHelpers/albumTrackListHelpers';
type ContextMenuFn = ( type ContextMenuFn = (
x: number, x: number,
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Check, ChevronDown, RotateCcw } from 'lucide-react'; import { Check, ChevronDown, RotateCcw } from 'lucide-react';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { COLUMNS } from '../../utils/albumTrackListHelpers'; import { COLUMNS } from '../../utils/componentHelpers/albumTrackListHelpers';
interface Props { interface Props {
pickerRef: React.RefObject<HTMLDivElement | null>; pickerRef: React.RefObject<HTMLDivElement | null>;
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import type { ColDef } from '../../utils/useTracklistColumns'; 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 { interface Props {
visibleCols: readonly ColDef[]; visibleCols: readonly ColDef[];
@@ -10,7 +10,7 @@ import { useOfflineStore } from '../../store/offlineStore';
import { useOfflineJobStore } from '../../store/offlineJobStore'; import { useOfflineJobStore } from '../../store/offlineJobStore';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import { useIsMobile } from '../../hooks/useIsMobile'; import { useIsMobile } from '../../hooks/useIsMobile';
import { extractCoverColors } from '../../utils/dynamicColors'; import { extractCoverColors } from '../../utils/ui/dynamicColors';
import CachedImage from '../CachedImage'; import CachedImage from '../CachedImage';
import CoverLightbox from '../CoverLightbox'; import CoverLightbox from '../CoverLightbox';
import LastfmIcon from '../LastfmIcon'; import LastfmIcon from '../LastfmIcon';
@@ -5,8 +5,8 @@ import type { SubsonicSong } from '../../api/subsonicTypes';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { usePreviewStore } from '../../store/previewStore'; import { usePreviewStore } from '../../store/previewStore';
import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior'; import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { formatDuration } from '../../utils/artistDetailHelpers'; import { formatDuration } from '../../utils/componentHelpers/artistDetailHelpers';
import ArtistSuggestionTrackCover from './ArtistSuggestionTrackCover'; import ArtistSuggestionTrackCover from './ArtistSuggestionTrackCover';
interface Props { interface Props {
+1 -1
View File
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl';
import type { SubsonicArtist } from '../../api/subsonicTypes'; import type { SubsonicArtist } from '../../api/subsonicTypes';
import CachedImage from '../CachedImage'; import CachedImage from '../CachedImage';
import { nameColor, nameInitial } from '../../utils/artistsHelpers'; import { nameColor, nameInitial } from '../../utils/componentHelpers/artistsHelpers';
interface AvatarProps { interface AvatarProps {
artist: SubsonicArtist; artist: SubsonicArtist;
+1 -1
View File
@@ -4,7 +4,7 @@ import type { Virtualizer } from '@tanstack/react-virtual';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import type { SubsonicArtist } from '../../api/subsonicTypes'; import type { SubsonicArtist } from '../../api/subsonicTypes';
import type { PlayerState } from '../../store/playerStoreTypes'; import type { PlayerState } from '../../store/playerStoreTypes';
import type { ArtistListFlatRow } from '../../utils/artistsHelpers'; import type { ArtistListFlatRow } from '../../utils/componentHelpers/artistsHelpers';
import { ArtistRowAvatar } from './ArtistAvatars'; import { ArtistRowAvatar } from './ArtistAvatars';
interface RowProps { interface RowProps {
@@ -4,11 +4,11 @@ import { ListMusic, Plus } from 'lucide-react';
import { getPlaylist, updatePlaylist } from '../../api/subsonicPlaylists'; import { getPlaylist, updatePlaylist } from '../../api/subsonicPlaylists';
import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import type { SubsonicPlaylist } from '../../api/subsonicTypes';
import { usePlaylistStore } from '../../store/playlistStore'; import { usePlaylistStore } from '../../store/playlistStore';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import { import {
confirmAddAllDuplicates, confirmAddAllDuplicates,
isSmartPlaylistName, isSmartPlaylistName,
} from '../../utils/contextMenuHelpers'; } from '../../utils/componentHelpers/contextMenuHelpers';
interface Props { interface Props {
songIds: string[]; songIds: string[];
@@ -5,7 +5,7 @@ import { getAlbum } from '../../api/subsonicLibrary';
import { star, unstar } from '../../api/subsonicStarRating'; import { star, unstar } from '../../api/subsonicStarRating';
import type { SubsonicAlbum } from '../../api/subsonicTypes'; import type { SubsonicAlbum } from '../../api/subsonicTypes';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import StarRating from '../StarRating'; import StarRating from '../StarRating';
import { AlbumToPlaylistSubmenu } from './AlbumArtistToPlaylistSubmenu'; import { AlbumToPlaylistSubmenu } from './AlbumArtistToPlaylistSubmenu';
import { MultiAlbumToPlaylistSubmenu } from './MultiAlbumToPlaylistSubmenu'; import { MultiAlbumToPlaylistSubmenu } from './MultiAlbumToPlaylistSubmenu';
@@ -4,11 +4,11 @@ import { ListMusic, Plus } from 'lucide-react';
import { getAlbum } from '../../api/subsonicLibrary'; import { getAlbum } from '../../api/subsonicLibrary';
import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import type { SubsonicPlaylist } from '../../api/subsonicTypes';
import { usePlaylistStore } from '../../store/playlistStore'; import { usePlaylistStore } from '../../store/playlistStore';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import { import {
confirmAddAllDuplicates, confirmAddAllDuplicates,
isSmartPlaylistName, isSmartPlaylistName,
} from '../../utils/contextMenuHelpers'; } from '../../utils/componentHelpers/contextMenuHelpers';
interface Props { interface Props {
albumIds: string[]; albumIds: string[];
@@ -6,11 +6,11 @@ import { getArtist } from '../../api/subsonicArtists';
import { getPlaylists } from '../../api/subsonicPlaylists'; import { getPlaylists } from '../../api/subsonicPlaylists';
import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import type { SubsonicPlaylist } from '../../api/subsonicTypes';
import { usePlaylistStore } from '../../store/playlistStore'; import { usePlaylistStore } from '../../store/playlistStore';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import { import {
confirmAddAllDuplicates, confirmAddAllDuplicates,
isSmartPlaylistName, isSmartPlaylistName,
} from '../../utils/contextMenuHelpers'; } from '../../utils/componentHelpers/contextMenuHelpers';
interface Props { interface Props {
artistIds: string[]; artistIds: string[];
@@ -46,7 +46,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
</div> </div>
<div className="context-menu-divider" /> <div className="context-menu-divider" />
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => { <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 { deletePlaylist } = await import('../../api/subsonicPlaylists');
const { removeId } = usePlaylistStore.getState(); const { removeId } = usePlaylistStore.getState();
try { try {
@@ -89,7 +89,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
)} )}
</div> </div>
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => { <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 { deletePlaylist } = await import('../../api/subsonicPlaylists');
const { removeId } = usePlaylistStore.getState(); const { removeId } = usePlaylistStore.getState();
const deletedIds: string[] = []; const deletedIds: string[] = [];
@@ -2,8 +2,8 @@ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 're
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ListMusic, Plus } from 'lucide-react'; import { ListMusic, Plus } from 'lucide-react';
import { usePlaylistStore } from '../../store/playlistStore'; import { usePlaylistStore } from '../../store/playlistStore';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import { isSmartPlaylistName } from '../../utils/contextMenuHelpers'; import { isSmartPlaylistName } from '../../utils/componentHelpers/contextMenuHelpers';
interface SingleProps { interface SingleProps {
playlist: { id: string; name: string }; playlist: { id: string; name: string };
@@ -7,8 +7,8 @@ import { lastfmLoveTrack, lastfmUnloveTrack } from '../../api/lastfm';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import { usePlaylistStore } from '../../store/playlistStore'; import { usePlaylistStore } from '../../store/playlistStore';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '../../utils/orbit'; import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '../../utils/orbit';
import LastfmIcon from '../LastfmIcon'; import LastfmIcon from '../LastfmIcon';
import StarRating from '../StarRating'; import StarRating from '../StarRating';
@@ -166,7 +166,7 @@ export default function SongContextItems(props: ContextMenuItemsProps) {
{playlistId && playlistSongIndex !== undefined && ( {playlistId && playlistSongIndex !== undefined && (
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => { <div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => {
const { getPlaylist, updatePlaylist } = await import('../../api/subsonicPlaylists'); 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; const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try { try {
const { songs } = await getPlaylist(playlistId); const { songs } = await getPlaylist(playlistId);
@@ -1,7 +1,7 @@
import type React from 'react'; import type React from 'react';
import type { SubsonicAlbum, SubsonicArtist } from '../../api/subsonicTypes'; import type { SubsonicAlbum, SubsonicArtist } from '../../api/subsonicTypes';
import type { Track } from '../../store/playerStoreTypes'; 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'; export type RatingKind = 'song' | 'album' | 'artist';
@@ -8,7 +8,7 @@ import type {
SubsonicAlbum, SubsonicArtist, SubsonicPlaylist, SubsonicAlbum, SubsonicArtist, SubsonicPlaylist,
} from '../../api/subsonicTypes'; } from '../../api/subsonicTypes';
import type { DeviceSyncSource } from '../../store/deviceSyncStore'; import type { DeviceSyncSource } from '../../store/deviceSyncStore';
import type { SourceTab } from '../../utils/deviceSyncHelpers'; import type { SourceTab } from '../../utils/deviceSync/deviceSyncHelpers';
import BrowserRow from './BrowserRow'; import BrowserRow from './BrowserRow';
interface Props { interface Props {
@@ -7,7 +7,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useDeviceSyncJobStore } from '../../store/deviceSyncJobStore'; import { useDeviceSyncJobStore } from '../../store/deviceSyncJobStore';
import type { DeviceSyncSource } from '../../store/deviceSyncStore'; import type { DeviceSyncSource } from '../../store/deviceSyncStore';
import type { SyncStatus } from '../../utils/deviceSyncHelpers'; import type { SyncStatus } from '../../utils/deviceSync/deviceSyncHelpers';
interface Props { interface Props {
sources: DeviceSyncSource[]; sources: DeviceSyncSource[];
@@ -4,8 +4,8 @@ import {
AlertCircle, FolderOpen, HardDriveUpload, RefreshCw, Usb, AlertCircle, FolderOpen, HardDriveUpload, RefreshCw, Usb,
} from 'lucide-react'; } from 'lucide-react';
import CustomSelect from '../CustomSelect'; import CustomSelect from '../CustomSelect';
import type { RemovableDrive } from '../../utils/deviceSyncHelpers'; import type { RemovableDrive } from '../../utils/deviceSync/deviceSyncHelpers';
import { formatBytes } from '../../utils/deviceSyncHelpers'; import { formatBytes } from '../../utils/deviceSync/deviceSyncHelpers';
import type { DeviceSyncSource } from '../../store/deviceSyncStore'; import type { DeviceSyncSource } from '../../store/deviceSyncStore';
interface Props { interface Props {
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react'; import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
import type { import type {
MigrationPair, MigrationPhase, MigrationResult, MigrationPair, MigrationPhase, MigrationResult,
} from '../../utils/runDeviceSyncMigration'; } from '../../utils/deviceSync/runDeviceSyncMigration';
interface Props { interface Props {
migrationPhase: MigrationPhase; migrationPhase: MigrationPhase;
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AlertCircle, Loader2 } from 'lucide-react'; import { AlertCircle, Loader2 } from 'lucide-react';
import type { SyncDelta } from '../../utils/runDeviceSyncExecution'; import type { SyncDelta } from '../../utils/deviceSync/runDeviceSyncExecution';
interface Props { interface Props {
preSyncOpen: boolean; preSyncOpen: boolean;
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react'; import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react';
import type { SubsonicSong } from '../../api/subsonicTypes'; import type { SubsonicSong } from '../../api/subsonicTypes';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import GenreFilterBar from '../GenreFilterBar'; import GenreFilterBar from '../GenreFilterBar';
interface Props { interface Props {
@@ -12,7 +12,7 @@ import { usePreviewStore } from '../../store/previewStore';
import { useSelectionStore } from '../../store/selectionStore'; import { useSelectionStore } from '../../store/selectionStore';
import { useDragDrop } from '../../contexts/DragDropContext'; import { useDragDrop } from '../../contexts/DragDropContext';
import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior'; import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { AddToPlaylistSubmenu } from '../ContextMenu'; import { AddToPlaylistSubmenu } from '../ContextMenu';
import StarRating from '../StarRating'; import StarRating from '../StarRating';
@@ -6,7 +6,7 @@ import type { Track } from '../../store/playerStoreTypes';
import { import {
folderBrowserHasKeyModifiers, isFolderBrowserArrowKey, folderBrowserHasKeyModifiers, isFolderBrowserArrowKey,
type Column, type Column,
} from '../../utils/folderBrowserHelpers'; } from '../../utils/componentHelpers/folderBrowserHelpers';
interface Props { interface Props {
col: Column; col: Column;
@@ -6,7 +6,7 @@ import { useWordLyricsSync } from '../../hooks/useWordLyricsSync';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress';
import type { LrcLine } from '../../api/lrclib'; import type { LrcLine } from '../../api/lrclib';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import { EaseScroller, targetForFraction } from '../../utils/easeScroll'; import { EaseScroller, targetForFraction } from '../../utils/ui/easeScroll';
// Apple Music-style fullscreen lyrics. // Apple Music-style fullscreen lyrics.
// Full-screen scrollable list. Active line auto-scrolls to ~35% from top. // 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 { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress'; import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress';
import { usePlaybackScheduleRemaining } from '../../utils/playbackScheduleFormat'; import { usePlaybackScheduleRemaining } from '../../utils/format/playbackScheduleFormat';
import PlaybackDelayModal from '../PlaybackDelayModal'; import PlaybackDelayModal from '../PlaybackDelayModal';
import PlaybackScheduleBadge from '../PlaybackScheduleBadge'; import PlaybackScheduleBadge from '../PlaybackScheduleBadge';
@@ -1,7 +1,7 @@
import React, { memo, useCallback, useEffect, useRef } from 'react'; import React, { memo, useCallback, useEffect, useRef } from 'react';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; 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. // Full-width seekbar — imperative DOM updates, zero React re-renders on tick.
export const FsSeekbar = memo(function FsSeekbar({ duration }: { duration: number }) { export const FsSeekbar = memo(function FsSeekbar({ duration }: { duration: number }) {
@@ -9,7 +9,7 @@ import {
import { import {
type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE, type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE,
} from '../../api/subsonicTypes'; } from '../../api/subsonicTypes';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
interface RadioDirectoryModalProps { interface RadioDirectoryModalProps {
onClose: () => void; onClose: () => void;
+1 -1
View File
@@ -1,5 +1,5 @@
import { Pause, Play, SkipBack, SkipForward } from 'lucide-react'; 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'; import type { MiniControlAction } from '../../utils/miniPlayerBridge';
interface Props { interface Props {
+1 -1
View File
@@ -2,7 +2,7 @@ import React, { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Disc3, ExternalLink, Star } from 'lucide-react'; import { Disc3, ExternalLink, Star } from 'lucide-react';
import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes'; import type { SubsonicAlbum, SubsonicSong } from '../../api/subsonicTypes';
import { formatTime, formatTotalDuration } from '../../utils/nowPlayingHelpers'; import { formatTime, formatTotalDuration } from '../../utils/componentHelpers/nowPlayingHelpers';
interface AlbumCardProps { interface AlbumCardProps {
album: SubsonicAlbum | null; 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 { useTranslation } from 'react-i18next';
import { ExternalLink } from 'lucide-react'; import { ExternalLink } from 'lucide-react';
import type { SubsonicArtistInfo } from '../../api/subsonicTypes'; import type { SubsonicArtistInfo } from '../../api/subsonicTypes';
import { isRealArtistImage, sanitizeHtml } from '../../utils/nowPlayingHelpers'; import { isRealArtistImage, sanitizeHtml } from '../../utils/componentHelpers/nowPlayingHelpers';
import CachedImage from '../CachedImage'; import CachedImage from '../CachedImage';
interface ArtistCardProps { interface ArtistCardProps {
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { ContributorRow } from '../../utils/nowPlayingHelpers'; import type { ContributorRow } from '../../utils/componentHelpers/nowPlayingHelpers';
interface CreditsCardProps { rows: ContributorRow[]; } 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 { Headphones, Heart, MicVocal, Music, Star } from 'lucide-react';
import type { LastfmArtistStats, LastfmTrackInfo } from '../../api/lastfm'; import type { LastfmArtistStats, LastfmTrackInfo } from '../../api/lastfm';
import LastfmIcon from '../LastfmIcon'; import LastfmIcon from '../LastfmIcon';
import { formatTime } from '../../utils/nowPlayingHelpers'; import { formatTime } from '../../utils/componentHelpers/nowPlayingHelpers';
interface HeroProps { interface HeroProps {
track: { title: string; artist: string; album: string; year?: number; 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 { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react';
import type { useRadioMetadata } from '../../hooks/useRadioMetadata'; import type { useRadioMetadata } from '../../hooks/useRadioMetadata';
import { usePlayerStore } from '../../store/playerStore'; 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>> = type NonNullStoreField<K extends keyof ReturnType<typeof usePlayerStore.getState>> =
NonNullable<ReturnType<typeof usePlayerStore.getState>[K]>; 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 { useTranslation } from 'react-i18next';
import { ExternalLink, Play, TrendingUp } from 'lucide-react'; import { ExternalLink, Play, TrendingUp } from 'lucide-react';
import type { SubsonicSong } from '../../api/subsonicTypes'; import type { SubsonicSong } from '../../api/subsonicTypes';
import { formatTime } from '../../utils/nowPlayingHelpers'; import { formatTime } from '../../utils/componentHelpers/nowPlayingHelpers';
interface TopSongsCardProps { interface TopSongsCardProps {
artistName: string; artistName: string;
+1 -1
View File
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Calendar, Info } from 'lucide-react'; import { Calendar, Info } from 'lucide-react';
import { open as shellOpen } from '@tauri-apps/plugin-shell'; import { open as shellOpen } from '@tauri-apps/plugin-shell';
import type { BandsintownEvent } from '../../api/bandsintown'; import type { BandsintownEvent } from '../../api/bandsintown';
import { isoToParts } from '../../utils/nowPlayingHelpers'; import { isoToParts } from '../../utils/componentHelpers/nowPlayingHelpers';
interface TourCardProps { interface TourCardProps {
artistName: string; artistName: string;
+1 -1
View File
@@ -1,6 +1,6 @@
import { memo, useEffect, useRef } from 'react'; import { memo, useEffect, useRef } from 'react';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; 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. /** Renders the playback clock without ever causing PlayerBar to re-render.
* Updates the DOM directly via an imperative store subscription. */ * 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 { TFunction } from 'i18next';
import type { RadioMetadata } from '../../hooks/useRadioMetadata'; import type { RadioMetadata } from '../../hooks/useRadioMetadata';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { formatTime } from '../../utils/playerBarHelpers'; import { formatTime } from '../../utils/componentHelpers/playerBarHelpers';
import WaveformSeek from '../WaveformSeek'; import WaveformSeek from '../WaveformSeek';
import { PlaybackTime, RemainingTime } from './PlaybackClock'; import { PlaybackTime, RemainingTime } from './PlaybackClock';
@@ -6,7 +6,7 @@ import type { PlayerState } from '../../store/playerStoreTypes';
import { usePreviewStore } from '../../store/previewStore'; import { usePreviewStore } from '../../store/previewStore';
import PlaybackScheduleBadge from '../PlaybackScheduleBadge'; import PlaybackScheduleBadge from '../PlaybackScheduleBadge';
import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress'; import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress';
import { usePlaybackScheduleRemaining } from '../../utils/playbackScheduleFormat'; import { usePlaybackScheduleRemaining } from '../../utils/format/playbackScheduleFormat';
type RepeatMode = PlayerState['repeatMode']; type RepeatMode = PlayerState['repeatMode'];
type PlayPauseBind = ReturnType<typeof usePlaybackDelayPress>['playPauseBind']; type PlayPauseBind = ReturnType<typeof usePlaybackDelayPress>['playPauseBind'];
@@ -2,8 +2,8 @@ import React from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Download, X } from 'lucide-react'; import { Download, X } from 'lucide-react';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import type { SpotifyCsvTrack } from '../../utils/spotifyCsvImport'; import type { SpotifyCsvTrack } from '../../utils/playlist/spotifyCsvImport';
interface CsvReportModalProps { interface CsvReportModalProps {
report: { report: {
+1 -1
View File
@@ -10,7 +10,7 @@ import type { ZipDownload } from '../../store/zipDownloadStore';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { import {
displayPlaylistName, formatSize, isSmartPlaylistName, totalDurationLabel, displayPlaylistName, formatSize, isSmartPlaylistName, totalDurationLabel,
} from '../../utils/playlistDetailHelpers'; } from '../../utils/componentHelpers/playlistDetailHelpers';
import CachedImage from '../CachedImage'; import CachedImage from '../CachedImage';
interface Props { interface Props {
@@ -4,8 +4,8 @@ import { Check, ListPlus, X } from 'lucide-react';
import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl'; import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl';
import type { SubsonicSong } from '../../api/subsonicTypes'; import type { SubsonicSong } from '../../api/subsonicTypes';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { formatDuration } from '../../utils/playlistDetailHelpers'; import { formatDuration } from '../../utils/componentHelpers/playlistDetailHelpers';
import CachedImage from '../CachedImage'; import CachedImage from '../CachedImage';
import { AddToPlaylistSubmenu } from '../ContextMenu'; import { AddToPlaylistSubmenu } from '../ContextMenu';
@@ -7,8 +7,8 @@ import type { SubsonicSong } from '../../api/subsonicTypes';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { usePreviewStore } from '../../store/previewStore'; import { usePreviewStore } from '../../store/previewStore';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { codecLabel, formatDuration } from '../../utils/playlistDetailHelpers'; import { codecLabel, formatDuration } from '../../utils/componentHelpers/playlistDetailHelpers';
const PL_CENTERED = new Set(['favorite', 'rating', 'duration']); const PL_CENTERED = new Set(['favorite', 'rating', 'duration']);
@@ -12,9 +12,9 @@ import { usePreviewStore } from '../../store/previewStore';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { useDragDrop } from '../../contexts/DragDropContext'; import { useDragDrop } from '../../contexts/DragDropContext';
import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior'; import { useOrbitSongRowBehavior } from '../../hooks/useOrbitSongRowBehavior';
import { songToTrack } from '../../utils/songToTrack'; import { songToTrack } from '../../utils/playback/songToTrack';
import { codecLabel, formatDuration } from '../../utils/playlistDetailHelpers'; import { codecLabel, formatDuration } from '../../utils/componentHelpers/playlistDetailHelpers';
import type { PlaylistSortKey, PlaylistSortDir } from '../../utils/playlistDisplayedSongs'; import type { PlaylistSortKey, PlaylistSortDir } from '../../utils/playlist/playlistDisplayedSongs';
import StarRating from '../StarRating'; import StarRating from '../StarRating';
import { AddToPlaylistSubmenu } from '../ContextMenu'; import { AddToPlaylistSubmenu } from '../ContextMenu';
+2 -2
View File
@@ -6,8 +6,8 @@ import type { SubsonicPlaylist } from '../../api/subsonicTypes';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { import {
displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist, displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist,
} from '../../utils/playlistsSmart'; } from '../../utils/playlist/playlistsSmart';
import { formatHumanHoursMinutes } from '../../utils/formatHumanDuration'; import { formatHumanHoursMinutes } from '../../utils/format/formatHumanDuration';
import { PlaylistCardMainCover, PlaylistSmartCoverCell } from './PlaylistCoverImages'; import { PlaylistCardMainCover, PlaylistSmartCoverCell } from './PlaylistCoverImages';
interface Props { interface Props {
+1 -1
View File
@@ -4,7 +4,7 @@ import { CheckSquare2, Plus, Trash2 } from 'lucide-react';
import type { SubsonicPlaylist } from '../../api/subsonicTypes'; import type { SubsonicPlaylist } from '../../api/subsonicTypes';
import { import {
defaultSmartFilters, type SmartFilters, defaultSmartFilters, type SmartFilters,
} from '../../utils/playlistsSmart'; } from '../../utils/playlist/playlistsSmart';
interface Props { interface Props {
selectionMode: boolean; selectionMode: boolean;
@@ -5,7 +5,7 @@ import StarRating from '../StarRating';
import { import {
LIMIT_MAX, YEAR_MAX, YEAR_MIN, clampYear, defaultSmartFilters, LIMIT_MAX, YEAR_MAX, YEAR_MIN, clampYear, defaultSmartFilters,
type SmartFilters, type SmartFilters,
} from '../../utils/playlistsSmart'; } from '../../utils/playlist/playlistsSmart';
interface Props { interface Props {
smartFilters: SmartFilters; smartFilters: SmartFilters;
@@ -3,13 +3,13 @@ import { ChevronDown, FolderOpen, HardDrive, Music, Waves } from 'lucide-react';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import type { LoudnessLufsPreset, NormalizationEngine } from '../../store/authStoreTypes'; import type { LoudnessLufsPreset, NormalizationEngine } from '../../store/authStoreTypes';
import type { PlaybackSourceKind } from '../../utils/resolvePlaybackUrl'; import type { PlaybackSourceKind } from '../../utils/playback/resolvePlaybackUrl';
import { import {
formatQueueReplayGainParts, formatQueueReplayGainParts,
renderStars, renderStars,
} from '../../utils/queuePanelHelpers'; } from '../../utils/componentHelpers/queuePanelHelpers';
import { loudnessGainPlaceholderUntilCacheDb } from '../../utils/loudnessPlaceholder'; import { loudnessGainPlaceholderUntilCacheDb } from '../../utils/audio/loudnessPlaceholder';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider'; import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider';
import { QueueLufsTargetMenu } from './QueueLufsTargetMenu'; import { QueueLufsTargetMenu } from './QueueLufsTargetMenu';
interface Props { interface Props {
+1 -1
View File
@@ -3,7 +3,7 @@ import { ChevronDown, ListMusic } from 'lucide-react';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import type { DurationMode } from '../../utils/queuePanelHelpers'; import type { DurationMode } from '../../utils/componentHelpers/queuePanelHelpers';
interface Props { interface Props {
queue: Track[]; queue: Track[];
+1 -1
View File
@@ -5,7 +5,7 @@ import OverlayScrollArea from '../OverlayScrollArea';
import { usePlayerStore } from '../../store/playerStore'; import { usePlayerStore } from '../../store/playerStore';
import { useLuckyMixStore } from '../../store/luckyMixStore'; import { useLuckyMixStore } from '../../store/luckyMixStore';
import type { Track, PlayerState } from '../../store/playerStoreTypes'; import type { Track, PlayerState } from '../../store/playerStoreTypes';
import { formatTime } from '../../utils/queuePanelHelpers'; import { formatTime } from '../../utils/componentHelpers/queuePanelHelpers';
type StartDrag = ( type StartDrag = (
payload: { data: string; label: string }, payload: { data: string; label: string },
@@ -5,7 +5,7 @@ import type { SubsonicSong } from '../../api/subsonicTypes';
import type { Track } from '../../store/playerStoreTypes'; import type { Track } from '../../store/playerStoreTypes';
import { usePreviewStore } from '../../store/previewStore'; import { usePreviewStore } from '../../store/previewStore';
import { useDragDrop } from '../../contexts/DragDropContext'; import { useDragDrop } from '../../contexts/DragDropContext';
import { formatRandomMixDuration } from '../../utils/randomMixHelpers'; import { formatRandomMixDuration } from '../../utils/componentHelpers/randomMixHelpers';
interface Props { interface Props {
song: SubsonicSong; song: SubsonicSong;
+3 -3
View File
@@ -2,14 +2,14 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Eye, EyeOff } from 'lucide-react'; import { Eye, EyeOff } from 'lucide-react';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '../../store/authStoreTypes';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
import { import {
decodeServerMagicString, decodeServerMagicString,
encodeServerMagicString, encodeServerMagicString,
DECODED_PASSWORD_VISUAL_MASK, DECODED_PASSWORD_VISUAL_MASK,
type ServerMagicPayload, type ServerMagicPayload,
} from '../../utils/serverMagicString'; } from '../../utils/server/serverMagicString';
import { shortHostFromServerUrl } from '../../utils/serverDisplayName'; import { shortHostFromServerUrl } from '../../utils/server/serverDisplayName';
export function AddServerForm({ export function AddServerForm({
onSave, onSave,
+1 -1
View File
@@ -4,7 +4,7 @@ import { Music2, Sliders, Waves } from 'lucide-react';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import Equalizer from '../Equalizer'; import Equalizer from '../Equalizer';
import SettingsSubSection from '../SettingsSubSection'; import SettingsSubSection from '../SettingsSubSection';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider'; import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider';
import { useAudioDevicesProbe } from '../../hooks/useAudioDevicesProbe'; import { useAudioDevicesProbe } from '../../hooks/useAudioDevicesProbe';
import { AudioOutputDeviceSection } from './audio/AudioOutputDeviceSection'; import { AudioOutputDeviceSection } from './audio/AudioOutputDeviceSection';
import { NormalizationBlock } from './audio/NormalizationBlock'; import { NormalizationBlock } from './audio/NormalizationBlock';
+2 -2
View File
@@ -1,8 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Download, HardDrive, Upload } from 'lucide-react'; import { Download, HardDrive, Upload } from 'lucide-react';
import { exportBackup, importBackup } from '../../utils/backup'; import { exportBackup, importBackup } from '../../utils/export/backup';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/ui/toast';
export function BackupSection() { export function BackupSection() {
const { t } = useTranslation(); const { t } = useTranslation();
+4 -4
View File
@@ -7,10 +7,10 @@ import { useAuthStore } from '../../store/authStore';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '../../store/authStoreTypes';
import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../../api/subsonic'; import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../../api/subsonic';
import { useDragDrop } from '../../contexts/DragDropContext'; import { useDragDrop } from '../../contexts/DragDropContext';
import { type ServerMagicPayload } from '../../utils/serverMagicString'; import { type ServerMagicPayload } from '../../utils/server/serverMagicString';
import { showAudiomuseNavidromeServerSetting } from '../../utils/subsonicServerIdentity'; import { showAudiomuseNavidromeServerSetting } from '../../utils/server/subsonicServerIdentity';
import { serverListDisplayLabel } from '../../utils/serverDisplayName'; import { serverListDisplayLabel } from '../../utils/server/serverDisplayName';
import { switchActiveServer } from '../../utils/switchActiveServer'; import { switchActiveServer } from '../../utils/server/switchActiveServer';
import { AddServerForm } from './AddServerForm'; import { AddServerForm } from './AddServerForm';
import { ServerGripHandle } from './ServerGripHandle'; import { ServerGripHandle } from './ServerGripHandle';
@@ -6,7 +6,7 @@ import { useAuthStore } from '../../store/authStore';
import { useSidebarStore, SidebarItemConfig } from '../../store/sidebarStore'; import { useSidebarStore, SidebarItemConfig } from '../../store/sidebarStore';
import { useLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable'; import { useLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable';
import { ALL_NAV_ITEMS } from '../../config/navItems'; 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; 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