mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(lib): consolidate generic infra into src/lib
Move domain-agnostic, feature-free helpers out of the flat utils/ and store/ roots into src/lib/ (plan M4, §3 lib/ layer): - lib/format/: formatBytes, formatClockTime, formatDuration, formatHumanDuration, relativeTime (pure format/date/byte/clock helpers) - lib/i18n.ts: i18next bootstrap (global app infra) - lib/util/: sanitizeHtml, platform, dedupeById, safeStorage (pure helpers + the zustand storage adapter) playbackScheduleFormat stays in utils/format (runtime usePlayerStore dep = audio-core coupling, not generic). formatClockTime keeps a type-only @/store/authStoreTypes import (erased, no runtime inversion — accepted per the deviceSync precedent). Pure move via deep @/lib/* specifiers (no barrel → no mock-collapse surface). tsc 0, lint 0/0, full suite 319 files / 2353 tests green. Tooling note: lib_move.py regex extended to also rewrite side-effect imports (import './i18n') and vi.importActual paths — both were silent gaps.
This commit is contained in:
@@ -54,7 +54,7 @@ import { useNowPlayingPrewarm } from '@/features/nowPlaying';
|
||||
import { useOfflineAutoNav } from '@/features/offline';
|
||||
import { useOfflineLibraryFilterSuspend } from '@/features/offline';
|
||||
import { AppShellQueueResizerSeam } from '../components/AppShellQueueResizerSeam';
|
||||
import { IS_LINUX, IS_MACOS } from '../utils/platform';
|
||||
import { IS_LINUX, IS_MACOS } from '@/lib/util/platform';
|
||||
import { useConnectionStatus } from '../hooks/useConnectionStatus';
|
||||
import { useIdlePlayQueuePull } from '../hooks/useIdlePlayQueuePull';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import { useLibraryCoverBackfill } from '../hooks/useLibraryCoverBackfill';
|
||||
import { useCoverRevalidateScheduler } from '../cover/useCoverRevalidateScheduler';
|
||||
import { runCoverIdbUpgradeMigration } from '../utils/migrations/coverIdbUpgradeMigration';
|
||||
import { useMigrationOrchestrator } from '../hooks/useMigrationOrchestrator';
|
||||
import { IS_WINDOWS } from '../utils/platform';
|
||||
import { IS_WINDOWS } from '@/lib/util/platform';
|
||||
import TauriEventBridge from './TauriEventBridge';
|
||||
import AppShell from './AppShell';
|
||||
import ErrorBoundary from '../components/ErrorBoundary';
|
||||
|
||||
@@ -25,7 +25,7 @@ vi.mock('../store/keybindingsStore', () => ({
|
||||
vi.mock('../utils/perf/perfFlags', () => ({
|
||||
usePerfProbeFlags: () => ({ disableTooltipPortal: true }),
|
||||
}));
|
||||
vi.mock('../i18n', () => ({
|
||||
vi.mock('@/lib/i18n', () => ({
|
||||
default: { changeLanguage: vi.fn() },
|
||||
}));
|
||||
vi.mock('@/features/miniPlayer', () => ({ default: () => <div data-testid="mini-player" /> }));
|
||||
@@ -38,7 +38,7 @@ vi.mock('../contexts/DragDropContext', () => ({
|
||||
|
||||
import { render, cleanup } from '@testing-library/react';
|
||||
import MiniPlayerApp from './MiniPlayerApp';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
beforeEach(() => {
|
||||
themeRehydrate.mockClear();
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useThemeStore } from '../store/themeStore';
|
||||
import { useFontStore } from '../store/fontStore';
|
||||
import { useKeybindingsStore } from '../store/keybindingsStore';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
/**
|
||||
* Mini-player webview tree. Rendered in the secondary Tauri window labelled
|
||||
|
||||
@@ -3,7 +3,7 @@ import { open } from '@tauri-apps/plugin-shell';
|
||||
import { ArrowUpCircle, CheckCircle2, ChevronDown, Download, FolderOpen, RefreshCw, ShieldCheck } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { version as currentVersion } from '../../package.json';
|
||||
import { formatBytes } from '../utils/format/formatBytes';
|
||||
import { formatBytes } from '@/lib/format/formatBytes';
|
||||
import { useAppUpdater } from '../hooks/useAppUpdater';
|
||||
import Modal from './Modal';
|
||||
import Changelog from './appUpdater/Changelog';
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useAuthStore } from '../store/authStore';
|
||||
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
|
||||
import { useLongPressAction } from '../hooks/useLongPressAction';
|
||||
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
|
||||
import { formatHumanHoursMinutes } from '../utils/format/formatHumanDuration';
|
||||
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import { albumArtistDisplayName } from '@/features/album';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useCachedUrl } from '@/ui/CachedImage';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { formatTrackTime } from '../utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { resolveQueueTrack } from '../utils/library/queueTrackView';
|
||||
import {
|
||||
getQueueResolverVersion,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
|
||||
import { formatClockTime } from '../utils/format/formatClockTime';
|
||||
import { formatClockTime } from '@/lib/format/formatClockTime';
|
||||
import {
|
||||
isValidPlaybackSchedulePreviewTimestamp,
|
||||
parsePlaybackDelayCustomMinutes,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useLibraryIndexStore } from '../store/libraryIndexStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { copyTextToClipboard } from '../utils/server/serverMagicString';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { formatTrackTime } from '../utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { formatLastSeen } from '../utils/componentHelpers/userMgmtHelpers';
|
||||
import { libraryIsReady } from '../utils/library/libraryReady';
|
||||
import {
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
resolveQueueBpm,
|
||||
type ParsedTrackEnrichment,
|
||||
} from '../utils/library/trackEnrichment';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
function formatSize(bytes?: number): string | null {
|
||||
if (!bytes) return null;
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useRef, useState, useEffect, useMemo } from 'react';
|
||||
import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react';
|
||||
import SongCard from './SongCard';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { usePlayerStore } from '../store/playerStore';
|
||||
import { enqueueAndPlay } from '../utils/playback/playSong';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { formatTrackTime } from '../utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
|
||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { Minus, Square, X } from 'lucide-react';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { IS_MACOS } from '../utils/platform';
|
||||
import { IS_MACOS } from '@/lib/util/platform';
|
||||
|
||||
export default function TitleBar() {
|
||||
const win = getCurrentWindow();
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
formatPlaybarClock,
|
||||
formatPlaybarToggleClock,
|
||||
formatTrackTime,
|
||||
} from '../../utils/format/formatDuration';
|
||||
} from '@/lib/format/formatDuration';
|
||||
|
||||
/** Renders the playback clock without ever causing PlayerBar to re-render.
|
||||
* Updates the DOM directly via an imperative store subscription. */
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ArrowLeftRight } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { RadioMetadata } from '@/features/radio';
|
||||
import { useThemeStore } from '../../store/themeStore';
|
||||
import { formatTrackTime, playbarMinuteFieldWidth } from '../../utils/format/formatDuration';
|
||||
import { formatTrackTime, playbarMinuteFieldWidth } from '@/lib/format/formatDuration';
|
||||
import { WaveformSeek } from '@/features/waveform';
|
||||
import { PlaybackTime, ToggleClock } from './PlaybackClock';
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import { useAuthStore } from '../../store/authStore';
|
||||
import type { QueueItemRef } from '../../store/playerStoreTypes';
|
||||
import type { QueueDisplayMode } from '../../store/authStoreTypes';
|
||||
import type { DurationMode } from '../../utils/componentHelpers/queuePanelHelpers';
|
||||
import { formatLongDuration } from '../../utils/format/formatDuration';
|
||||
import { formatClockTime } from '../../utils/format/formatClockTime';
|
||||
import { formatLongDuration } from '@/lib/format/formatDuration';
|
||||
import { formatClockTime } from '@/lib/format/formatClockTime';
|
||||
import { resolveQueueTrack } from '../../utils/library/queueTrackView';
|
||||
import {
|
||||
getQueueResolverVersion,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useLuckyMixStore } from '../../store/luckyMixStore';
|
||||
import type { QueueItemRef, PlayerState } from '../../store/playerStoreTypes';
|
||||
import type { QueueDisplayMode } from '../../store/authStoreTypes';
|
||||
import { formatTrackTime } from '../../utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { resolveQueueTrack } from '../../utils/library/queueTrackView';
|
||||
import {
|
||||
getQueueResolverVersion,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import { useLyricsStore } from '../store/lyricsStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { queueSongRating } from '../store/pendingStarSync';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { playByOpaqueId } from '../utils/playback/playByOpaqueId';
|
||||
|
||||
@@ -14,9 +14,9 @@ import StarRating from '@/components/StarRating';
|
||||
import { copyEntityShareLink } from '@/utils/share/copyEntityShareLink';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency';
|
||||
import { formatLongDuration } from '@/utils/format/formatDuration';
|
||||
import { formatMb } from '@/utils/format/formatBytes';
|
||||
import { sanitizeHtml } from '@/utils/sanitizeHtml';
|
||||
import { formatLongDuration } from '@/lib/format/formatDuration';
|
||||
import { formatMb } from '@/lib/format/formatBytes';
|
||||
import { sanitizeHtml } from '@/lib/util/sanitizeHtml';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AudioLines } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { formatLongDuration } from '@/utils/format/formatDuration';
|
||||
import { formatLongDuration } from '@/lib/format/formatDuration';
|
||||
|
||||
interface Props {
|
||||
discNums: number[];
|
||||
|
||||
@@ -11,9 +11,9 @@ import { useThemeStore } from '@/store/themeStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
|
||||
import StarRating from '@/components/StarRating';
|
||||
import { codecLabel, type ColKey } from '@/features/album/utils/albumTrackListHelpers';
|
||||
import { formatLongDuration } from '@/utils/format/formatDuration';
|
||||
import { formatLongDuration } from '@/lib/format/formatDuration';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
coverTrafficGridPaginationDepth,
|
||||
} from '@/cover/coverTraffic';
|
||||
import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/utils/library/albumCompilation';
|
||||
import type { AlbumCompFilter } from '@/utils/library/albumCompilation';
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
|
||||
import {
|
||||
fetchGenreAlbumPage,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getAlbumsByGenre } from '@/lib/api/subsonicGenres';
|
||||
import { getAlbumList } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { shuffleArray } from '@/utils/playback/shuffleArray';
|
||||
import React, { useEffect, useLayoutEffect, useState, useCallback, useRef } from 'react';
|
||||
import { RefreshCw, Download, HardDriveDownload } from 'lucide-react';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { usePlayerStore } from '@/store/playerStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import ArtistTopTrackCover from '@/features/artist/components/ArtistTopTrackCover';
|
||||
import { topSongAlbumForCover } from '@/features/artist/components/topSongAlbumForCover';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getArtists } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import {
|
||||
fetchLocalArtistCatalogChunk,
|
||||
fetchNetworkStarredArtists,
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertCircle, Loader2 } from 'lucide-react';
|
||||
import type { SyncDelta } from '@/features/deviceSync/utils/runDeviceSyncExecution';
|
||||
import { formatMb } from '@/utils/format/formatBytes';
|
||||
import { formatMb } from '@/lib/format/formatBytes';
|
||||
|
||||
interface Props {
|
||||
preSyncOpen: boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IS_WINDOWS } from '@/utils/platform';
|
||||
import { IS_WINDOWS } from '@/lib/util/platform';
|
||||
|
||||
// Same sanitize rules the Rust side uses (`sanitize_path_component`): strip
|
||||
// Windows-illegal chars and control chars, trim leading/trailing dots + spaces.
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { applyLegacyTemplate } from '@/features/deviceSync/utils/deviceSyncLegacyTemplate';
|
||||
import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers';
|
||||
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
|
||||
import { IS_WINDOWS } from '@/utils/platform';
|
||||
import { IS_WINDOWS } from '@/lib/util/platform';
|
||||
|
||||
export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing';
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '@/i18n';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import StarRating from '@/components/StarRating';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { formatClockTime } from '@/utils/format/formatClockTime';
|
||||
import { formatClockTime } from '@/lib/format/formatClockTime';
|
||||
|
||||
/**
|
||||
* Standalone wall-clock for the fullscreen player. Owns its own state so the
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
getQueueResolverVersion,
|
||||
subscribeQueueResolver,
|
||||
} from '@/utils/library/queueTrackResolver';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
|
||||
/**
|
||||
* Centered "current / total" readout for the control bar. Updates the current
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Pause, Play, SkipBack, SkipForward } from 'lucide-react';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import type { MiniControlAction } from '@/features/miniPlayer/utils/miniPlayerBridge';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Maximize2, Pin, PinOff, X } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { IS_LINUX } from '@/utils/platform';
|
||||
import { IS_LINUX } from '@/lib/util/platform';
|
||||
|
||||
interface Props {
|
||||
trackTitle: string | undefined;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { IS_LINUX } from '@/utils/platform';
|
||||
import { IS_LINUX } from '@/lib/util/platform';
|
||||
import {
|
||||
EXPANDED_SIZE, EXPANDED_MIN, readStoredExpandedHeight,
|
||||
} from '@/features/miniPlayer/utils/miniPlayerHelpers';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Disc3, ExternalLink, Star } from 'lucide-react';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { formatTotalDuration } from '@/utils/componentHelpers/nowPlayingHelpers';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
|
||||
interface AlbumCardProps {
|
||||
album: SubsonicAlbum | null;
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { CoverArtRef } from '@/cover/types';
|
||||
import type { ArtistStats, TrackStats } from '@/music-network';
|
||||
import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { useEnrichmentPrimaryLabel } from '@/hooks/useEnrichmentPrimaryLabel';
|
||||
import { useEnrichmentPrimaryIcon } from '@/hooks/useEnrichmentPrimaryIcon';
|
||||
import { renderPresetIcon } from '@/components/settings/musicNetwork/presetIcon';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react';
|
||||
import type { useRadioMetadata } from '@/features/radio';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
|
||||
type NonNullStoreField<K extends keyof ReturnType<typeof usePlayerStore.getState>> =
|
||||
NonNullable<ReturnType<typeof usePlayerStore.getState>[K]>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
|
||||
interface TopSongsCardProps {
|
||||
artistName: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { HardDriveDownload, Heart } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatBytes } from '@/utils/format/formatBytes';
|
||||
import { formatBytes } from '@/lib/format/formatBytes';
|
||||
|
||||
const OPEN_DELAY_MS = 450;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { HardDriveDownload, Check, X } from 'lucide-react';
|
||||
import { useZipDownloadStore } from '@/features/offline/store/zipDownloadStore';
|
||||
import { formatBytes } from '@/utils/format/formatBytes';
|
||||
import { formatBytes } from '@/lib/format/formatBytes';
|
||||
|
||||
function ZipDownloadItem({ id }: { id: string }) {
|
||||
const dismiss = useZipDownloadStore(s => s.dismiss);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getStarredForServer } from '@/lib/api/subsonicStarRating';
|
||||
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { cancelledDownloads, useOfflineJobStore } from '@/features/offline/store/offlineJobStore';
|
||||
import { useFavoritesOfflineSyncStore } from '@/features/offline/store/favoritesOfflineSyncStore';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
albumToAlbum,
|
||||
trackToSong,
|
||||
} from '@/utils/library/advancedSearchLocal';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||
import {
|
||||
|
||||
@@ -19,7 +19,7 @@ import OrbitSettingsPopover from '@/features/orbit/components/OrbitSettingsPopov
|
||||
import OrbitSharePopover from '@/features/orbit/components/OrbitSharePopover';
|
||||
import OrbitDiagnosticsPopover from '@/features/orbit/components/OrbitDiagnosticsPopover';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
|
||||
/**
|
||||
* Orbit — top-strip session indicator.
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
resetPendingResendState,
|
||||
} from '@/features/orbit/utils/orbit';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { estimateLivePosition, type OrbitState } from '@/features/orbit/api/orbit';
|
||||
import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag';
|
||||
import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat';
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
type OrbitQueueItem,
|
||||
} from '@/features/orbit/api/orbit';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag';
|
||||
import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { useConfirmModalStore } from '@/store/confirmModalStore';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
/**
|
||||
* Ask the user before dropping many tracks into the shared Orbit queue.
|
||||
|
||||
@@ -7,7 +7,7 @@ import { usePlayerStore } from '@/store/playerStore';
|
||||
import {
|
||||
displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist,
|
||||
} from '@/features/playlist/utils/playlistsSmart';
|
||||
import { formatHumanHoursMinutes } from '@/utils/format/formatHumanDuration';
|
||||
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
|
||||
import { useDragSource } from '@/contexts/DragDropContext';
|
||||
import { PlaylistCardMainCover, PlaylistSmartCoverCell } from '@/features/playlist/components/PlaylistCoverImages';
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
import i18n from '@/i18n';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import StarRating from '@/components/StarRating';
|
||||
import { PlaylistArtistCell } from '@/features/playlist/components/PlaylistArtistCell';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Check, ListPlus, X } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes';
|
||||
import { AddToPlaylistSubmenu } from '@/components/ContextMenu';
|
||||
|
||||
@@ -14,8 +14,8 @@ import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { getQueueTracksView } from '@/utils/library/queueTrackView';
|
||||
import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import i18n from '@/i18n';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
const PL_CENTERED = new Set(['favorite', 'rating', 'duration', 'playCount', 'bpm']);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Music, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||
import { formatTrackTime } from '@/utils/format/formatDuration';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import type { ShareQueuePreviewState } from '@/features/search/hooks/useShareQueuePreview';
|
||||
import { sharePayloadTotal, type QueueableShareSearchPayload } from '@/utils/share/shareSearch';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import type { SeekbarStyle, WindowButtonStyle } from '@/store/authStoreTypes';
|
||||
import { useFontStore, FontId } from '@/store/fontStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { IS_LINUX, IS_WINDOWS } from '@/utils/platform';
|
||||
import { IS_LINUX, IS_WINDOWS } from '@/lib/util/platform';
|
||||
import SettingsSubSection from '@/features/settings/components/SettingsSubSection';
|
||||
import { SettingsGroup } from '@/features/settings/components/SettingsGroup';
|
||||
import { SettingsToggle } from '@/features/settings/components/SettingsToggle';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SettingsGroup } from '@/features/settings/components/SettingsGroup';
|
||||
import { SettingsToggle } from '@/features/settings/components/SettingsToggle';
|
||||
import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider';
|
||||
import { useAudioDevicesProbe } from '@/hooks/useAudioDevicesProbe';
|
||||
import { IS_MACOS } from '@/utils/platform';
|
||||
import { IS_MACOS } from '@/lib/util/platform';
|
||||
import { AudioOutputDeviceSection } from '@/features/settings/components/audio/AudioOutputDeviceSection';
|
||||
import { NormalizationBlock } from '@/features/settings/components/audio/NormalizationBlock';
|
||||
import { PlaybackRateBlock } from '@/features/settings/components/audio/PlaybackRateBlock';
|
||||
|
||||
@@ -17,7 +17,7 @@ import { clearDiskSrcCacheForServer } from '@/cover/diskSrcCache';
|
||||
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName';
|
||||
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { formatBytes } from '@/utils/format/formatBytes';
|
||||
import { formatBytes } from '@/lib/format/formatBytes';
|
||||
import { clearImageCache, getImageCacheSize } from '@/utils/imageCache';
|
||||
import { wakeLibraryCoverBackfill } from '@/utils/library/coverBackfillWake';
|
||||
import {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Download, FolderOpen, Trash2, X } from 'lucide-react';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { countHotCacheTracks } from '@/store/hotCacheStore';
|
||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||
import { formatBytes, snapHotCacheMb } from '@/utils/format/formatBytes';
|
||||
import { formatBytes, snapHotCacheMb } from '@/lib/format/formatBytes';
|
||||
import SettingsSubSection from '@/features/settings/components/SettingsSubSection';
|
||||
import { SettingsGroup } from '@/features/settings/components/SettingsGroup';
|
||||
import { SettingsToggle } from '@/features/settings/components/SettingsToggle';
|
||||
|
||||
@@ -6,10 +6,10 @@ import { save as saveDialog } from '@tauri-apps/plugin-dialog';
|
||||
import { open as openUrl } from '@tauri-apps/plugin-shell';
|
||||
import { AppWindow, ChevronDown, Download, ExternalLink, Globe, HardDrive, Info, Scale, Sliders, Users } from 'lucide-react';
|
||||
import { version as appVersion } from '@/../package.json';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import type { ClockFormat, LinuxWaylandTextRenderProfile, LoggingMode } from '@/store/authStoreTypes';
|
||||
import { IS_LINUX } from '@/utils/platform';
|
||||
import { IS_LINUX } from '@/lib/util/platform';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { AboutPsysonicBrandHeader } from '@/components/AboutPsysonicLol';
|
||||
import CustomSelect from '@/ui/CustomSelect';
|
||||
|
||||
@@ -6,7 +6,7 @@ import CoverLightbox from '@/components/CoverLightbox';
|
||||
import { useThemeAnimationRisk } from '@/hooks/useThemeAnimationRisk';
|
||||
import { AnimatedThemeBadge } from '@/features/settings/components/AnimatedThemeBadge';
|
||||
import CustomSelect from '@/ui/CustomSelect';
|
||||
import { formatRelativeTime } from '@/utils/format/relativeTime';
|
||||
import { formatRelativeTime } from '@/lib/format/relativeTime';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useInstalledThemesStore, type InstalledTheme } from '@/store/installedThemesStore';
|
||||
import {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
AudioLines, HardDrive, Info, Keyboard, LayoutGrid, Music2, Paintbrush, Palette, Search, Server, Sparkles, Users, X,
|
||||
} from 'lucide-react';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { IS_MACOS } from '@/utils/platform';
|
||||
import { IS_MACOS } from '@/lib/util/platform';
|
||||
import { AppearanceTab } from '@/features/settings/components/AppearanceTab';
|
||||
import { ThemesTab } from '@/features/settings/components/ThemesTab';
|
||||
import { AudioTab } from '@/features/settings/components/AudioTab';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { usePlayerStatsRecordingEnabled } from '@/features/stats/hooks/usePlayer
|
||||
import PlayerStatsHeatmap from '@/features/stats/components/PlayerStatsHeatmap';
|
||||
import PlayerStatsIndexRequiredNotice from '@/features/stats/components/PlayerStatsIndexRequiredNotice';
|
||||
import PlayerStatsRecentDays from '@/features/stats/components/PlayerStatsRecentDays';
|
||||
import { formatPlayerStatsListeningTotal } from '@/utils/format/formatHumanDuration';
|
||||
import { formatPlayerStatsListeningTotal } from '@/lib/format/formatHumanDuration';
|
||||
|
||||
const currentCalendarYear = () => new Date().getFullYear();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { PlaySessionDayDetail } from '@/lib/api/library';
|
||||
import { formatPlayerStatsListenedSec } from '@/utils/format/formatHumanDuration';
|
||||
import { formatPlayerStatsListenedSec } from '@/lib/format/formatHumanDuration';
|
||||
|
||||
type Props = {
|
||||
detail: PlaySessionDayDetail;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type PlaySessionDayDetail,
|
||||
type PlaySessionRecentDay,
|
||||
} from '@/lib/api/library';
|
||||
import { formatPlayerStatsListeningTotal } from '@/utils/format/formatHumanDuration';
|
||||
import { formatPlayerStatsListeningTotal } from '@/lib/format/formatHumanDuration';
|
||||
import {
|
||||
formatPlayerStatsDayLabel,
|
||||
PLAYER_STATS_RECENT_DAYS_LIMIT,
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SubsonicAlbum, SubsonicGenre } from '@/lib/api/subsonicTypes';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Share2 } from 'lucide-react';
|
||||
import { formatHumanHoursMinutes } from '@/utils/format/formatHumanDuration';
|
||||
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
|
||||
import { AlbumRow } from '@/features/album';
|
||||
import StatsExportModal from '@/features/stats/components/StatsExportModal';
|
||||
import PlayerStatisticsPanel from '@/features/stats/components/PlayerStatisticsPanel';
|
||||
|
||||
@@ -10,7 +10,7 @@ import { shuffleArray } from '../../utils/playback/shuffleArray';
|
||||
import { songToTrack } from '../../utils/playback/songToTrack';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
import { switchActiveServer } from '../../utils/server/switchActiveServer';
|
||||
import i18n from '../../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { executeCliPlayerCommand } from '../../config/shortcutActions';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { dirname } from '@tauri-apps/api/path';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { version as currentVersion } from '../../package.json';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '@/lib/util/platform';
|
||||
import { SKIP_KEY, isNewer, isWithinModerationWindow, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers';
|
||||
|
||||
/** All update-modal state, the GitHub release probe and the download/relaunch
|
||||
|
||||
@@ -3,7 +3,7 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { IS_MACOS } from '../utils/platform';
|
||||
import { IS_MACOS } from '@/lib/util/platform';
|
||||
import { sortAudioDeviceIds } from '../utils/audio/audioDeviceLabels';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import type { LinuxWaylandTextRenderProfile } from '../store/authStoreTypes';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '@/lib/util/platform';
|
||||
|
||||
/**
|
||||
* One-shot platform + window-shell configuration. Reads tiling-WM state,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { IS_LINUX } from '../utils/platform';
|
||||
import { IS_LINUX } from '@/lib/util/platform';
|
||||
|
||||
/**
|
||||
* Whether animated themes may raise CPU load on this setup — Linux with the
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* tests pin only the `hour12` mapping, which is what the setting controls.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatClockTime } from './formatClockTime';
|
||||
import { formatClockTime } from '@/lib/format/formatClockTime';
|
||||
|
||||
const SAMPLE_TS = Date.UTC(2026, 0, 1, 19, 17, 0); // 19:17 UTC, deterministic
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ClockFormat } from '../../store/authStoreTypes';
|
||||
import type { ClockFormat } from '@/store/authStoreTypes';
|
||||
|
||||
/**
|
||||
* Localized wall-clock `HH:MM` for a timestamp (sleep-timer / queue-ETA labels).
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
formatPlaybarToggleClock,
|
||||
playbarMinuteFieldWidth,
|
||||
PLAYBAR_CLOCK_PAD,
|
||||
} from './formatDuration';
|
||||
} from '@/lib/format/formatDuration';
|
||||
|
||||
describe('formatTrackTime', () => {
|
||||
it('formats m:ss with zero-padded seconds', () => {
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// Echo the i18n key + params so the rounding/branching can be asserted
|
||||
// without depending on a locale's exact "N hours M minutes" template.
|
||||
vi.mock('../../i18n', () => ({
|
||||
vi.mock('@/lib/i18n', () => ({
|
||||
default: {
|
||||
t: (key: string, params: Record<string, unknown>) => `${key}|${JSON.stringify(params)}`,
|
||||
resolvedLanguage: 'en',
|
||||
@@ -10,7 +10,7 @@ vi.mock('../../i18n', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
import { formatHumanHoursMinutes, formatPlayerStatsListeningTotal, formatPlayerStatsListenedSec } from './formatHumanDuration';
|
||||
import { formatHumanHoursMinutes, formatPlayerStatsListeningTotal, formatPlayerStatsListenedSec } from '@/lib/format/formatHumanDuration';
|
||||
|
||||
describe('formatHumanHoursMinutes', () => {
|
||||
it('rounds to the nearest minute instead of truncating', () => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import i18n from '../../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
/**
|
||||
* Totals / statistics: localized "N hours M minutes" (not track mm:ss).
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { formatRelativeTime } from './relativeTime';
|
||||
import { formatRelativeTime } from '@/lib/format/relativeTime';
|
||||
|
||||
describe('formatRelativeTime', () => {
|
||||
const SECOND = 1000;
|
||||
@@ -1,17 +1,17 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import { deTranslation } from './locales/de';
|
||||
import { enTranslation } from './locales/en';
|
||||
import { frTranslation } from './locales/fr';
|
||||
import { zhTranslation } from './locales/zh';
|
||||
import { nbTranslation } from './locales/nb';
|
||||
import { ruTranslation } from './locales/ru';
|
||||
import { nlTranslation } from './locales/nl';
|
||||
import { esTranslation } from './locales/es';
|
||||
import { roTranslation } from './locales/ro';
|
||||
import { jaTranslation } from './locales/ja';
|
||||
import { huTranslation } from './locales/hu';
|
||||
import { plTranslation } from './locales/pl';
|
||||
import { deTranslation } from '@/locales/de';
|
||||
import { enTranslation } from '@/locales/en';
|
||||
import { frTranslation } from '@/locales/fr';
|
||||
import { zhTranslation } from '@/locales/zh';
|
||||
import { nbTranslation } from '@/locales/nb';
|
||||
import { ruTranslation } from '@/locales/ru';
|
||||
import { nlTranslation } from '@/locales/nl';
|
||||
import { esTranslation } from '@/locales/es';
|
||||
import { roTranslation } from '@/locales/ro';
|
||||
import { jaTranslation } from '@/locales/ja';
|
||||
import { huTranslation } from '@/locales/hu';
|
||||
import { plTranslation } from '@/locales/pl';
|
||||
|
||||
const savedLanguage = localStorage.getItem('psysonic_language') || 'en';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { createSafeJSONStorage } from './safeStorage';
|
||||
import { createSafeJSONStorage } from '@/lib/util/safeStorage';
|
||||
|
||||
describe('createSafeJSONStorage', () => {
|
||||
afterEach(() => {
|
||||
+1
-1
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { runPreReactBootstrap } from './app/bootstrap';
|
||||
import { scheduleStartupSplashDismiss } from './app/startupSplash';
|
||||
import './i18n';
|
||||
import '@/lib/i18n';
|
||||
import './styles/themes/index.css';
|
||||
import './styles/layout/index.css';
|
||||
import './styles/components/index.css';
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useAuthStore } from '../store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { copyEntityShareLink } from '../utils/share/copyEntityShareLink';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { sanitizeHtml } from '../utils/sanitizeHtml';
|
||||
import { sanitizeHtml } from '@/lib/util/sanitizeHtml';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers } from '../cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import { useAuthStore } from '../store/authStore';
|
||||
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { bumpPerfCounter } from '../utils/perf/perfTelemetry';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { shuffleArray } from '../utils/playback/shuffleArray';
|
||||
import { useLibraryCoverPrefetch } from '../cover/useLibraryCoverPrefetch';
|
||||
import { primeAlbumCoversForDisplay, warmHomeMainstageCovers } from '../cover/warmDiskPeek';
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import {
|
||||
} from '../utils/server/serverHttpHeaders';
|
||||
import { syncServerHttpContextForProfile } from '../utils/server/syncServerHttpContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import CustomSelect from '@/ui/CustomSelect';
|
||||
import {
|
||||
decodeServerMagicString,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getAlbumsByGenre } from '@/lib/api/subsonicGenres';
|
||||
import { getAlbumList } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { dedupeById } from '../utils/dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { useEffect, useLayoutEffect, useState, useCallback, useRef, useMemo } from 'react';
|
||||
import { Download, HardDriveDownload } from 'lucide-react';
|
||||
import SelectionToggleButton from '../components/SelectionToggleButton';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IS_LINUX } from '../utils/platform';
|
||||
import { IS_LINUX } from '@/lib/util/platform';
|
||||
import { sanitizeHiResCrossfadeResampleHz } from '../utils/audio/hiResCrossfadeResample';
|
||||
import {
|
||||
sanitizeAutodjOverlapCapMode,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { applyServerPlayQueue } from './applyServerPlayQueue';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import i18n from '../i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { showToast } from '../utils/ui/toast';
|
||||
import { useAuthStore } from './authStore';
|
||||
import {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { readInitialNetworkLovedCache, persistNetworkLovedCache } from './networkLovedCacheStorage';
|
||||
import { readInitialPlayerPrefs, persistPlayerPrefs } from './playerPrefsStorage';
|
||||
import { createHydrationGatedStorage, createSafeJSONStorage } from './safeStorage';
|
||||
import { createHydrationGatedStorage, createSafeJSONStorage } from '@/lib/util/safeStorage';
|
||||
import { emitPlaybackProgress } from './playbackProgress';
|
||||
import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes';
|
||||
import { toQueueItemRefs } from '../utils/library/queueItemRef';
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { ReactElement, ReactNode } from 'react';
|
||||
import { render, type RenderOptions, type RenderResult } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from '@/i18n';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
interface WrapperOptions {
|
||||
route?: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../platform';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '@/lib/util/platform';
|
||||
|
||||
export const SKIP_KEY = 'psysonic_skipped_update_version';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { sanitizeHtml as sanitizeHtmlBase } from '../sanitizeHtml';
|
||||
import { sanitizeHtml as sanitizeHtmlBase } from '@/lib/util/sanitizeHtml';
|
||||
|
||||
export function formatTime(s: number): string {
|
||||
if (!s || isNaN(s)) return '0:00';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { formatHumanHoursMinutes } from '../format/formatHumanDuration';
|
||||
import { formatMb } from '../format/formatBytes';
|
||||
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
|
||||
import { formatMb } from '@/lib/format/formatBytes';
|
||||
|
||||
export function sanitizeFilename(name: string): string {
|
||||
return name
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatRelativeTime } from '../format/relativeTime';
|
||||
import { formatRelativeTime } from '@/lib/format/relativeTime';
|
||||
|
||||
/**
|
||||
* Render a relative time string like "3 hours ago" / "in 2 weeks" with
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { dedupeById } from '../dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { isOfflineBrowseActive } from '@/features/offline';
|
||||
import { loadOfflineAlbumCatalogChunk } from '@/features/offline';
|
||||
import type { AlbumBrowseQuery } from './albumBrowseTypes';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { libraryAdvancedSearch, libraryListAlbumsByGenre } from '@/lib/api/library';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { libraryScopeForServer } from '@/lib/api/subsonicClient';
|
||||
import { dedupeById } from '../dedupeById';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { albumToAlbum } from './advancedSearchLocal';
|
||||
import { sharedServerFilters } from './albumBrowseFilters';
|
||||
import { albumSortClauses, sortSubsonicAlbums } from './albumBrowseSort';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user