mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
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:
committed by
GitHub
parent
2409a1fec8
commit
7a7a9f5e6b
@@ -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
@@ -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,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;
|
||||||
|
|||||||
@@ -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
@@ -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)` : '';
|
||||||
|
|||||||
@@ -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', () => ({
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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,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:';
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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';
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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[]; }
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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]>;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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: {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user