diff --git a/src/api/navidromeBrowse.ts b/src/api/navidromeBrowse.ts index 3d1a3a34..aecbe360 100644 --- a/src/api/navidromeBrowse.ts +++ b/src/api/navidromeBrowse.ts @@ -1,8 +1,7 @@ +import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from './subsonicTypes'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '../store/authStore'; import { ndLogin } from './navidromeAdmin'; -import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from './subsonic'; - /** Server-keyed Bearer token cache. Cheap to keep — Navidrome tokens are long-lived. */ let cachedToken: { serverUrl: string; token: string } | null = null; diff --git a/src/api/subsonic.contract.test.ts b/src/api/subsonic.contract.test.ts index a0f649d7..9d289161 100644 --- a/src/api/subsonic.contract.test.ts +++ b/src/api/subsonic.contract.test.ts @@ -11,15 +11,8 @@ * mocking and are not in this PR. */ import { beforeEach, describe, expect, it } from 'vitest'; -import { - buildCoverArtUrl, - buildDownloadUrl, - buildStreamUrl, - coverArtCacheKey, - getClient, - libraryFilterParams, - parseSubsonicEntityStarRating, -} from './subsonic'; +import { buildCoverArtUrl, buildDownloadUrl, buildStreamUrl, coverArtCacheKey, parseSubsonicEntityStarRating } from './subsonic'; +import { getClient, libraryFilterParams } from './subsonicClient'; import { useAuthStore } from '@/store/authStore'; import { resetAuthStore } from '@/test/helpers/storeReset'; diff --git a/src/api/subsonic.ts b/src/api/subsonic.ts index 0701cff0..94084255 100644 --- a/src/api/subsonic.ts +++ b/src/api/subsonic.ts @@ -2,236 +2,48 @@ import axios from 'axios'; import md5 from 'md5'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '../store/authStore'; -import { version } from '../../package.json'; import { isNavidromeAudiomuseSoftwareEligible, type InstantMixProbeResult, type SubsonicServerIdentity, } from '../utils/subsonicServerIdentity'; +import { + SUBSONIC_CLIENT, + api, + getAuthParams, + getClient, + libraryFilterParams, + secureRandomSalt, +} from './subsonicClient'; +import type { + AlbumInfo, + EntityRatingSupportLevel, + InternetRadioStation, + PingWithCredentialsResult, + RadioBrowserStation, + RandomSongsFilters, + SearchResults, + StarredResults, + StatisticsFormatSample, + StatisticsLibraryAggregates, + StatisticsOverviewData, + SubsonicAlbum, + SubsonicArtist, + SubsonicArtistInfo, + SubsonicDirectory, + SubsonicDirectoryEntry, + SubsonicGenre, + SubsonicMusicFolder, + SubsonicNowPlaying, + SubsonicOpenArtistRef, + SubsonicPlaylist, + SubsonicSong, + SubsonicStructuredLyrics, +} from './subsonicTypes'; -const SUBSONIC_CLIENT = `psysonic/${version}`; - -// ─── Secure random salt ──────────────────────────────────────── -function secureRandomSalt(): string { - const buf = new Uint8Array(8); - crypto.getRandomValues(buf); - return Array.from(buf, b => b.toString(16).padStart(2, '0')).join(''); -} - -// ─── Token Auth ─────────────────────────────────────────────── -function getAuthParams(username: string, password: string) { - const salt = secureRandomSalt(); - const token = md5(password + salt); - return { u: username, t: token, s: salt, v: '1.16.1', c: SUBSONIC_CLIENT, f: 'json' }; -} - -export function getClient() { - const { getBaseUrl, getActiveServer } = useAuthStore.getState(); - const server = getActiveServer(); - const baseUrl = getBaseUrl(); - if (!baseUrl) throw new Error('No server configured'); - const params = getAuthParams(server?.username ?? '', server?.password ?? ''); - return { baseUrl: `${baseUrl}/rest`, params }; -} - -async function api(endpoint: string, extra: Record = {}, timeout = 15000): Promise { - const { baseUrl, params } = getClient(); - const resp = await axios.get(`${baseUrl}/${endpoint}`, { - params: { ...params, ...extra }, - paramsSerializer: { indexes: null }, - timeout, - }); - const data = resp.data?.['subsonic-response']; - if (!data) throw new Error('Invalid response from server (possibly not a Subsonic server)'); - if (data.status !== 'ok') throw new Error(data.error?.message ?? 'Subsonic API error'); - return data as T; -} - -/** Optional `musicFolderId` when the user narrowed browsing to one Subsonic library (see `getMusicFolders`). */ -export function libraryFilterParams(): Record { - const { activeServerId, musicLibraryFilterByServer } = useAuthStore.getState(); - if (!activeServerId) return {}; - const f = musicLibraryFilterByServer[activeServerId]; - if (f === undefined || f === 'all') return {}; - return { musicFolderId: f }; -} - -// ─── Types ──────────────────────────────────────────────────── -export interface SubsonicAlbum { - id: string; - name: string; - artist: string; - artistId: string; - coverArt?: string; - songCount: number; - duration: number; - playCount?: number; - year?: number; - genre?: string; - starred?: string; - recordLabel?: string; - created?: string; - /** Present on some servers (e.g. OpenSubsonic) for album-level rating. */ - userRating?: number; - /** OpenSubsonic: true when the album is tagged as a compilation. */ - isCompilation?: boolean; - /** OpenSubsonic: release types from MusicBrainz tags (e.g. "Album", "EP", "Single", "Compilation", "Live"). */ - releaseTypes?: string[]; -} /** OpenSubsonic `artists` / `albumArtists` entries on a child song (may include `userRating`). */ -export interface SubsonicOpenArtistRef { - id?: string; - name?: string; - userRating?: number; - /** Navidrome / alternate OpenSubsonic payloads (same meaning as `userRating`). */ - rating?: number; -} - -export interface SubsonicSong { - id: string; - title: string; - artist: string; - album: string; - albumId: string; - artistId?: string; - duration: number; - track?: number; - discNumber?: number; - coverArt?: string; - year?: number; - userRating?: number; - /** Some OpenSubsonic responses attach parent ratings on child songs. */ - albumUserRating?: number; - artistUserRating?: number; - artists?: SubsonicOpenArtistRef[]; - albumArtists?: SubsonicOpenArtistRef[]; - // Audio technical info - bitRate?: number; - suffix?: string; - contentType?: string; - size?: number; - samplingRate?: number; - bitDepth?: number; - channelCount?: number; - starred?: string; - genre?: string; - path?: string; - albumArtist?: string; - /** ISRC code when available (e.g., Navidrome) */ - isrc?: string; - replayGain?: { - trackGain?: number; - albumGain?: number; - trackPeak?: number; - albumPeak?: number; - }; - /** OpenSubsonic: structured composer credit (string for back-compat). */ - displayComposer?: string; - /** OpenSubsonic: structured contributors list — Navidrome ≥ 0.55. */ - contributors?: Array<{ - role: string; - subRole?: string; - artist: { id?: string; name: string }; - }>; -} - -export interface InternetRadioStation { - id: string; - name: string; - streamUrl: string; - homepageUrl?: string; - coverArt?: string; // Navidrome v0.61.0+ -} - -export interface RadioBrowserStation { - stationuuid: string; - name: string; - url: string; - favicon: string; - tags: string; -} - -export interface SubsonicPlaylist { - id: string; - name: string; - songCount: number; - duration: number; - created: string; - changed: string; - owner?: string; - public?: boolean; - comment?: string; - coverArt?: string; -} - -export interface SubsonicNowPlaying extends SubsonicSong { - username: string; - minutesAgo: number; - playerId: number; - playerName: string; -} - -export interface SubsonicArtist { - id: string; - name: string; - albumCount?: number; - coverArt?: string; - starred?: string; - /** Present on some servers (e.g. OpenSubsonic) for artist-level rating. */ - userRating?: number; -} - -export interface SubsonicGenre { - value: string; - songCount: number; - albumCount: number; -} - -export interface SubsonicMusicFolder { - id: string; - name: string; -} - -export interface SubsonicArtistInfo { - biography?: string; - musicBrainzId?: string; - lastFmUrl?: string; - smallImageUrl?: string; - mediumImageUrl?: string; - largeImageUrl?: string; - similarArtist?: Array<{ id: string; name: string; albumCount?: number }>; -} - // ─── API Methods ────────────────────────────────────────────── -export interface SubsonicDirectoryEntry { - id: string; - parent?: string; - title: string; - isDir: boolean; - album?: string; - artist?: string; - albumId?: string; - artistId?: string; - coverArt?: string; - duration?: number; - track?: number; - year?: number; - bitRate?: number; - suffix?: string; - size?: number; - genre?: string; - starred?: string; - userRating?: number; -} - -export interface SubsonicDirectory { - id: string; - parent?: string; - name: string; - child: SubsonicDirectoryEntry[]; -} - export async function getMusicDirectory(id: string): Promise { const data = await api<{ directory: { id: string; parent?: string; name: string; child?: SubsonicDirectoryEntry | SubsonicDirectoryEntry[] } }>( 'getMusicDirectory.view', @@ -288,7 +100,6 @@ export async function ping(): Promise { } } -export type PingWithCredentialsResult = SubsonicServerIdentity & { ok: boolean }; /** Test a connection with explicit credentials — does NOT depend on store state. */ export async function pingWithCredentials( @@ -504,13 +315,6 @@ export async function getRandomSongs(size = 50, genre?: string, timeout = 15000) return data.randomSongs?.song ?? []; } -export interface RandomSongsFilters { - size?: number; - genre?: string; - fromYear?: number; - toYear?: number; -} - /** Extended random song fetch with server-side year/genre filtering. */ export async function getRandomSongsFiltered( filters: RandomSongsFilters, @@ -649,14 +453,6 @@ export async function prefetchAlbumUserRatings( } /** Paginated album stats for Statistics (playtime, counts, genre breakdown). Same TTL as rating prefetch. */ -export interface StatisticsLibraryAggregates { - playtimeSec: number; - albumsCounted: number; - songsCounted: number; - capped: boolean; - genres: SubsonicGenre[]; -} - /** Key `prefix:serverId:folder` — Statistics caches share scope with `libraryFilterParams()`. */ function statisticsPageCacheKey(prefix: string): string | null { const { activeServerId, musicLibraryFilterByServer } = useAuthStore.getState(); @@ -731,13 +527,6 @@ export async function fetchStatisticsLibraryAggregates(): Promise(); export async function fetchStatisticsOverview(): Promise { @@ -765,11 +554,6 @@ export async function fetchStatisticsOverview(): Promise } /** Format (suffix) histogram from a random sample for Statistics. */ -export interface StatisticsFormatSample { - rows: { format: string; count: number }[]; - sampleSize: number; -} - const statisticsFormatCache = new Map(); export async function fetchStatisticsFormatSample(): Promise { @@ -883,18 +667,6 @@ export async function getAlbumsByGenre(genre: string, size = 50, offset = 0): Pr return Array.isArray(raw) ? raw : [raw]; } -export interface SearchResults { - artists: SubsonicArtist[]; - albums: SubsonicAlbum[]; - songs: SubsonicSong[]; -} - -export interface StarredResults { - artists: SubsonicArtist[]; - albums: SubsonicAlbum[]; - songs: SubsonicSong[]; -} - export async function getStarred(): Promise { const data = await api<{ starred2: { @@ -969,7 +741,6 @@ export async function setRating(id: string, rating: number): Promise { } /** How aggressively we assume `setRating` accepts album/artist ids (OpenSubsonic-style). */ -export type EntityRatingSupportLevel = 'track_only' | 'full'; /** * Probe server for OpenSubsonic extensions. When `openSubsonic: true`, we treat album/artist @@ -1056,13 +827,6 @@ export function buildDownloadUrl(id: string): string { } // ─── Album Info (public image URLs from Last.fm/MusicBrainz) ── -export interface AlbumInfo { - largeImageUrl?: string; - mediumImageUrl?: string; - smallImageUrl?: string; - notes?: string; -} - export async function getAlbumInfo2(albumId: string): Promise { try { const data = await api<{ albumInfo: AlbumInfo }>('getAlbumInfo2.view', { id: albumId }); @@ -1276,7 +1040,6 @@ function parseRadioBrowserStations(raw: Array>): RadioBro })); } -export const RADIO_PAGE_SIZE = 25; export async function searchRadioBrowser(query: string, offset = 0): Promise { const raw = await invoke>>('search_radio_browser', { query, offset }); @@ -1292,24 +1055,6 @@ export async function fetchUrlBytes(url: string): Promise<[number[], string]> { return invoke<[number[], string]>('fetch_url_bytes', { url }); } -// ─── Structured Lyrics (OpenSubsonic / getLyricsBySongId) ──────────────────── - -export interface SubsonicLyricLine { - start?: number; // milliseconds — absent when unsynced - value: string; -} - -export interface SubsonicStructuredLyrics { - /** OpenSubsonic spec field name (Navidrome ≥ 0.50.0 / any OpenSubsonic server). */ - synced?: boolean; - /** Legacy / alternate casing used by some older Subsonic-compatible servers. */ - issynced?: boolean; - lang?: string; - offset?: number; - displayArtist?: string; - displayTitle?: string; - line: SubsonicLyricLine[]; -} /** * Fetches structured lyrics from the server's embedded tags via the diff --git a/src/api/subsonicClient.ts b/src/api/subsonicClient.ts new file mode 100644 index 00000000..32bda47a --- /dev/null +++ b/src/api/subsonicClient.ts @@ -0,0 +1,49 @@ +import axios from 'axios'; +import md5 from 'md5'; +import { version } from '../../package.json'; +import { useAuthStore } from '../store/authStore'; + +export const SUBSONIC_CLIENT = `psysonic/${version}`; + +export function secureRandomSalt(): string { + const buf = new Uint8Array(8); + crypto.getRandomValues(buf); + return Array.from(buf, b => b.toString(16).padStart(2, '0')).join(''); +} + +export function getAuthParams(username: string, password: string) { + const salt = secureRandomSalt(); + const token = md5(password + salt); + return { u: username, t: token, s: salt, v: '1.16.1', c: SUBSONIC_CLIENT, f: 'json' }; +} + +export function getClient() { + const { getBaseUrl, getActiveServer } = useAuthStore.getState(); + const server = getActiveServer(); + const baseUrl = getBaseUrl(); + if (!baseUrl) throw new Error('No server configured'); + const params = getAuthParams(server?.username ?? '', server?.password ?? ''); + return { baseUrl: `${baseUrl}/rest`, params }; +} + +export async function api(endpoint: string, extra: Record = {}, timeout = 15000): Promise { + const { baseUrl, params } = getClient(); + const resp = await axios.get(`${baseUrl}/${endpoint}`, { + params: { ...params, ...extra }, + paramsSerializer: { indexes: null }, + timeout, + }); + const data = resp.data?.['subsonic-response']; + if (!data) throw new Error('Invalid response from server (possibly not a Subsonic server)'); + if (data.status !== 'ok') throw new Error(data.error?.message ?? 'Subsonic API error'); + return data as T; +} + +/** Optional `musicFolderId` when the user narrowed browsing to one Subsonic library (see `getMusicFolders`). */ +export function libraryFilterParams(): Record { + const { activeServerId, musicLibraryFilterByServer } = useAuthStore.getState(); + if (!activeServerId) return {}; + const f = musicLibraryFilterByServer[activeServerId]; + if (f === undefined || f === 'all') return {}; + return { musicFolderId: f }; +} diff --git a/src/api/subsonicTypes.ts b/src/api/subsonicTypes.ts new file mode 100644 index 00000000..cefd20ba --- /dev/null +++ b/src/api/subsonicTypes.ts @@ -0,0 +1,244 @@ +import type { SubsonicServerIdentity } from '../utils/subsonicServerIdentity'; + +export interface SubsonicAlbum { + id: string; + name: string; + artist: string; + artistId: string; + coverArt?: string; + songCount: number; + duration: number; + playCount?: number; + year?: number; + genre?: string; + starred?: string; + recordLabel?: string; + created?: string; + /** Present on some servers (e.g. OpenSubsonic) for album-level rating. */ + userRating?: number; + /** OpenSubsonic: true when the album is tagged as a compilation. */ + isCompilation?: boolean; + /** OpenSubsonic: release types from MusicBrainz tags (e.g. "Album", "EP", "Single", "Compilation", "Live"). */ + releaseTypes?: string[]; +} + +/** OpenSubsonic `artists` / `albumArtists` entries on a child song (may include `userRating`). */ +export interface SubsonicOpenArtistRef { + id?: string; + name?: string; + userRating?: number; + /** Navidrome / alternate OpenSubsonic payloads (same meaning as `userRating`). */ + rating?: number; +} + +export interface SubsonicSong { + id: string; + title: string; + artist: string; + album: string; + albumId: string; + artistId?: string; + duration: number; + track?: number; + discNumber?: number; + coverArt?: string; + year?: number; + userRating?: number; + /** Some OpenSubsonic responses attach parent ratings on child songs. */ + albumUserRating?: number; + artistUserRating?: number; + artists?: SubsonicOpenArtistRef[]; + albumArtists?: SubsonicOpenArtistRef[]; + // Audio technical info + bitRate?: number; + suffix?: string; + contentType?: string; + size?: number; + samplingRate?: number; + bitDepth?: number; + channelCount?: number; + starred?: string; + genre?: string; + path?: string; + albumArtist?: string; + /** ISRC code when available (e.g., Navidrome) */ + isrc?: string; + replayGain?: { + trackGain?: number; + albumGain?: number; + trackPeak?: number; + albumPeak?: number; + }; + /** OpenSubsonic: structured composer credit (string for back-compat). */ + displayComposer?: string; + /** OpenSubsonic: structured contributors list — Navidrome ≥ 0.55. */ + contributors?: Array<{ + role: string; + subRole?: string; + artist: { id?: string; name: string }; + }>; +} + +export interface InternetRadioStation { + id: string; + name: string; + streamUrl: string; + homepageUrl?: string; + coverArt?: string; // Navidrome v0.61.0+ +} + +export interface RadioBrowserStation { + stationuuid: string; + name: string; + url: string; + favicon: string; + tags: string; +} + +export interface SubsonicPlaylist { + id: string; + name: string; + songCount: number; + duration: number; + created: string; + changed: string; + owner?: string; + public?: boolean; + comment?: string; + coverArt?: string; +} + +export interface SubsonicNowPlaying extends SubsonicSong { + username: string; + minutesAgo: number; + playerId: number; + playerName: string; +} + +export interface SubsonicArtist { + id: string; + name: string; + albumCount?: number; + coverArt?: string; + starred?: string; + /** Present on some servers (e.g. OpenSubsonic) for artist-level rating. */ + userRating?: number; +} + +export interface SubsonicGenre { + value: string; + songCount: number; + albumCount: number; +} + +export interface SubsonicMusicFolder { + id: string; + name: string; +} + +export interface SubsonicArtistInfo { + biography?: string; + musicBrainzId?: string; + lastFmUrl?: string; + smallImageUrl?: string; + mediumImageUrl?: string; + largeImageUrl?: string; + similarArtist?: Array<{ id: string; name: string; albumCount?: number }>; +} + +export interface SubsonicDirectoryEntry { + id: string; + parent?: string; + title: string; + isDir: boolean; + album?: string; + artist?: string; + albumId?: string; + artistId?: string; + coverArt?: string; + duration?: number; + track?: number; + year?: number; + bitRate?: number; + suffix?: string; + size?: number; + genre?: string; + starred?: string; + userRating?: number; +} + +export interface SubsonicDirectory { + id: string; + parent?: string; + name: string; + child: SubsonicDirectoryEntry[]; +} + +export type PingWithCredentialsResult = SubsonicServerIdentity & { ok: boolean }; + +export interface RandomSongsFilters { + size?: number; + genre?: string; + fromYear?: number; + toYear?: number; +} + +export interface StatisticsLibraryAggregates { + playtimeSec: number; + albumsCounted: number; + songsCounted: number; + capped: boolean; + genres: SubsonicGenre[]; +} + +export interface StatisticsOverviewData { + recent: SubsonicAlbum[]; + frequent: SubsonicAlbum[]; + highest: SubsonicAlbum[]; + artistCount: number; +} + +export interface StatisticsFormatSample { + rows: { format: string; count: number }[]; + sampleSize: number; +} + +export interface SearchResults { + artists: SubsonicArtist[]; + albums: SubsonicAlbum[]; + songs: SubsonicSong[]; +} + +export interface StarredResults { + artists: SubsonicArtist[]; + albums: SubsonicAlbum[]; + songs: SubsonicSong[]; +} + +export type EntityRatingSupportLevel = 'track_only' | 'full'; + +export interface AlbumInfo { + largeImageUrl?: string; + mediumImageUrl?: string; + smallImageUrl?: string; + notes?: string; +} + +export const RADIO_PAGE_SIZE = 25; + +export interface SubsonicLyricLine { + start?: number; // milliseconds — absent when unsynced + value: string; +} + +export interface SubsonicStructuredLyrics { + /** OpenSubsonic spec field name (Navidrome ≥ 0.50.0 / any OpenSubsonic server). */ + synced?: boolean; + /** Legacy / alternate casing used by some older Subsonic-compatible servers. */ + issynced?: boolean; + lang?: string; + offset?: number; + displayArtist?: string; + displayTitle?: string; + line: SubsonicLyricLine[]; +} diff --git a/src/components/AlbumCard.tsx b/src/components/AlbumCard.tsx index da8fda35..6fd35ab4 100644 --- a/src/components/AlbumCard.tsx +++ b/src/components/AlbumCard.tsx @@ -1,9 +1,10 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { memo, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey, getAlbum } from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey, getAlbum } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useOfflineStore } from '../store/offlineStore'; import { useAuthStore } from '../store/authStore'; diff --git a/src/components/AlbumHeader.tsx b/src/components/AlbumHeader.tsx index 7980be09..8bf54038 100644 --- a/src/components/AlbumHeader.tsx +++ b/src/components/AlbumHeader.tsx @@ -1,14 +1,14 @@ +import type { EntityRatingSupportLevel, SubsonicSong } from '../api/subsonicTypes'; import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Play, Heart, ExternalLink, X, ChevronLeft, Download, ListPlus, HardDriveDownload, Loader2, Highlighter, Shuffle, Share2 } from 'lucide-react'; -import { SubsonicSong, buildCoverArtUrl } from '../api/subsonic'; +import { Play, Heart, ExternalLink, X, ChevronLeft, Download, ListPlus, HardDriveDownload, Share2, Highlighter, Loader2, Shuffle } from 'lucide-react'; +import { buildCoverArtUrl } from '../api/subsonic'; import CachedImage from './CachedImage'; import CoverLightbox from './CoverLightbox'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '../hooks/useIsMobile'; import { useThemeStore } from '../store/themeStore'; import StarRating from './StarRating'; -import type { EntityRatingSupportLevel } from '../api/subsonic'; import { copyEntityShareLink } from '../utils/copyEntityShareLink'; import { showToast } from '../utils/toast'; import { isAlbumRecentlyAdded } from '../utils/albumRecency'; diff --git a/src/components/AlbumRow.tsx b/src/components/AlbumRow.tsx index ff727ac3..417991f2 100644 --- a/src/components/AlbumRow.tsx +++ b/src/components/AlbumRow.tsx @@ -1,5 +1,5 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React, { useRef, useState, useEffect, useMemo } from 'react'; -import { SubsonicAlbum } from '../api/subsonic'; import AlbumCard from './AlbumCard'; import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react'; import { NavLink, useNavigate } from 'react-router-dom'; diff --git a/src/components/AlbumTrackList.tsx b/src/components/AlbumTrackList.tsx index a6bc7851..7253e3a9 100644 --- a/src/components/AlbumTrackList.tsx +++ b/src/components/AlbumTrackList.tsx @@ -1,9 +1,9 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import type { Track } from '../store/playerStoreTypes'; import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Play, ChevronRight, Heart, ChevronDown, Check, RotateCcw, Square, AudioLines } from 'lucide-react'; import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns'; -import { SubsonicSong } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; diff --git a/src/components/ArtistCardLocal.tsx b/src/components/ArtistCardLocal.tsx index 28bc210e..ce0afb6d 100644 --- a/src/components/ArtistCardLocal.tsx +++ b/src/components/ArtistCardLocal.tsx @@ -1,5 +1,6 @@ +import type { SubsonicArtist } from '../api/subsonicTypes'; import React, { useMemo } from 'react'; -import { SubsonicArtist, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { useNavigate } from 'react-router-dom'; import { Users } from 'lucide-react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/ArtistRow.tsx b/src/components/ArtistRow.tsx index 661cf912..3f3db4e5 100644 --- a/src/components/ArtistRow.tsx +++ b/src/components/ArtistRow.tsx @@ -1,5 +1,5 @@ +import type { SubsonicArtist } from '../api/subsonicTypes'; import React, { useRef, useState, useEffect } from 'react'; -import { SubsonicArtist } from '../api/subsonic'; import ArtistCardLocal from './ArtistCardLocal'; import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; diff --git a/src/components/BecauseYouLikeRail.tsx b/src/components/BecauseYouLikeRail.tsx index c7235fff..108fa603 100644 --- a/src/components/BecauseYouLikeRail.tsx +++ b/src/components/BecauseYouLikeRail.tsx @@ -1,16 +1,10 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Play, ListPlus, Music } from 'lucide-react'; -import { - SubsonicAlbum, - buildCoverArtUrl, - coverArtCacheKey, - getAlbum, - getArtist, - getArtistInfo, -} from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getArtist, getArtistInfo } from '../api/subsonic'; import CachedImage, { useCachedUrl } from './CachedImage'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index 8b9bec57..e7675540 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -1,3 +1,4 @@ +import type { SubsonicAlbum, SubsonicArtist, SubsonicPlaylist } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import type { Track } from '../store/playerStoreTypes'; import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; @@ -14,7 +15,7 @@ import StarRating from './StarRating'; import { lastfmLoveTrack, lastfmUnloveTrack } from '../api/lastfm'; import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; -import { SubsonicAlbum, SubsonicArtist, star, unstar, getSimilarSongs2, getSimilarSongs, getTopSongs, buildDownloadUrl, getAlbum, getArtist, getPlaylists, getPlaylist, updatePlaylist, SubsonicPlaylist, setRating } from '../api/subsonic'; +import { star, unstar, getSimilarSongs2, getSimilarSongs, getTopSongs, buildDownloadUrl, getAlbum, getArtist, getPlaylists, getPlaylist, updatePlaylist, setRating } from '../api/subsonic'; import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '../store/authStore'; import { useDownloadModalStore } from '../store/downloadModalStore'; diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 1428c018..e97aee64 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -1,8 +1,9 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus } from 'lucide-react'; -import { getRandomAlbums, SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey, getAlbum } from '../api/subsonic'; +import { getRandomAlbums, buildCoverArtUrl, coverArtCacheKey, getAlbum } from '../api/subsonic'; import CachedImage, { useCachedUrl } from './CachedImage'; import { usePlayerStore } from '../store/playerStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/HostApprovalQueue.tsx b/src/components/HostApprovalQueue.tsx index 6f6779fe..8d61b708 100644 --- a/src/components/HostApprovalQueue.tsx +++ b/src/components/HostApprovalQueue.tsx @@ -1,3 +1,4 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { useEffect, useMemo, useState } from 'react'; import { Check, X, Inbox } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -7,12 +8,7 @@ import { declineOrbitSuggestion, suggestionKey, } from '../utils/orbit'; -import { - getSong, - buildCoverArtUrl, - coverArtCacheKey, - type SubsonicSong, -} from '../api/subsonic'; +import { getSong, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import CachedImage from './CachedImage'; import { ORBIT_DEFAULT_SETTINGS } from '../api/orbit'; diff --git a/src/components/LiveSearch.tsx b/src/components/LiveSearch.tsx index 31731876..3ea5f157 100644 --- a/src/components/LiveSearch.tsx +++ b/src/components/LiveSearch.tsx @@ -1,8 +1,9 @@ +import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Search, Disc3, Users, Music, TextSearch } from 'lucide-react'; -import { search, SearchResults, buildCoverArtUrl, coverArtCacheKey, type SubsonicArtist } from '../api/subsonic'; +import { search, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/LosslessAlbumsRail.tsx b/src/components/LosslessAlbumsRail.tsx index 3a586c1a..a196651b 100644 --- a/src/components/LosslessAlbumsRail.tsx +++ b/src/components/LosslessAlbumsRail.tsx @@ -1,8 +1,8 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ndListLosslessAlbumsPage } from '../api/navidromeBrowse'; import AlbumRow from './AlbumRow'; -import type { SubsonicAlbum } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; interface Props { diff --git a/src/components/MobileSearchOverlay.tsx b/src/components/MobileSearchOverlay.tsx index f18d68fd..29eba877 100644 --- a/src/components/MobileSearchOverlay.tsx +++ b/src/components/MobileSearchOverlay.tsx @@ -1,9 +1,10 @@ +import type { SearchResults, SubsonicArtist } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { X, Search, Disc3, Users, Music, Music2, Clock, ChevronRight } from 'lucide-react'; -import { search, SearchResults, buildCoverArtUrl, coverArtCacheKey, type SubsonicArtist } from '../api/subsonic'; +import { search, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/NowPlayingDropdown.tsx b/src/components/NowPlayingDropdown.tsx index 9e4133ce..25b1607e 100644 --- a/src/components/NowPlayingDropdown.tsx +++ b/src/components/NowPlayingDropdown.tsx @@ -1,7 +1,8 @@ +import type { SubsonicNowPlaying } from '../api/subsonicTypes'; import React, { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react'; import { createPortal } from 'react-dom'; import { PlayCircle, User, Radio, RefreshCw } from 'lucide-react'; -import { getNowPlaying, SubsonicNowPlaying, buildCoverArtUrl } from '../api/subsonic'; +import { getNowPlaying, buildCoverArtUrl } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import { usePlayerStore } from '../store/playerStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/NowPlayingInfo.tsx b/src/components/NowPlayingInfo.tsx index 306c4746..4e227aca 100644 --- a/src/components/NowPlayingInfo.tsx +++ b/src/components/NowPlayingInfo.tsx @@ -1,10 +1,11 @@ +import type { SubsonicArtistInfo, SubsonicSong } from '../api/subsonicTypes'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Info } from 'lucide-react'; import { open as shellOpen } from '@tauri-apps/plugin-shell'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; -import { getArtistInfo, getSong, type SubsonicArtistInfo, type SubsonicSong } from '../api/subsonic'; +import { getArtistInfo, getSong } from '../api/subsonic'; import { fetchBandsintownEvents, type BandsintownEvent } from '../api/bandsintown'; import CachedImage from './CachedImage'; import OverlayScrollArea from './OverlayScrollArea'; diff --git a/src/components/OrbitGuestQueue.tsx b/src/components/OrbitGuestQueue.tsx index 1a2e9056..93d43734 100644 --- a/src/components/OrbitGuestQueue.tsx +++ b/src/components/OrbitGuestQueue.tsx @@ -1,13 +1,9 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { useEffect, useMemo, useState } from 'react'; import { Radio, Clock } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useOrbitStore } from '../store/orbitStore'; -import { - getSong, - buildCoverArtUrl, - coverArtCacheKey, - type SubsonicSong, -} from '../api/subsonic'; +import { getSong, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import CachedImage from './CachedImage'; import OrbitQueueHead from './OrbitQueueHead'; diff --git a/src/components/PlayerBar.tsx b/src/components/PlayerBar.tsx index f1eb6173..1531463a 100644 --- a/src/components/PlayerBar.tsx +++ b/src/components/PlayerBar.tsx @@ -1,3 +1,4 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; @@ -11,7 +12,7 @@ import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { useAuthStore } from '../store/authStore'; import { useThemeStore } from '../store/themeStore'; -import { buildCoverArtUrl, coverArtCacheKey, star, unstar, setRating, SubsonicAlbum } from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey, star, unstar, setRating } from '../api/subsonic'; import CachedImage from './CachedImage'; import WaveformSeek from './WaveformSeek'; import Equalizer from './Equalizer'; diff --git a/src/components/QueuePanel.tsx b/src/components/QueuePanel.tsx index a6b2705b..5b7a179f 100644 --- a/src/components/QueuePanel.tsx +++ b/src/components/QueuePanel.tsx @@ -1,3 +1,4 @@ +import type { SubsonicPlaylist } from '../api/subsonicTypes'; import { registerQueueListScrollTopReader, consumePendingQueueListScrollTop } from '../store/queueUndo'; import { songToTrack } from '../utils/songToTrack'; import type { Track } from '../store/playerStoreTypes'; @@ -9,7 +10,7 @@ import OrbitGuestQueue from './OrbitGuestQueue'; import OrbitQueueHead from './OrbitQueueHead'; import HostApprovalQueue from './HostApprovalQueue'; import { Play, Music, Star, X, Trash2, Save, FolderOpen, Shuffle, Infinity, Waves, MicVocal, ListMusic, Check, ListPlus, MoveRight, Radio, HardDrive, ChevronDown, Info, Share2 } from 'lucide-react'; -import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist, SubsonicPlaylist } from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist } from '../api/subsonic'; import { usePlaylistStore } from '../store/playlistStore'; import { useCachedUrl } from './CachedImage'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/SongCard.tsx b/src/components/SongCard.tsx index b2cb9c29..2890f759 100644 --- a/src/components/SongCard.tsx +++ b/src/components/SongCard.tsx @@ -1,9 +1,10 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { memo, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus, Star } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { SubsonicSong, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import CachedImage from './CachedImage'; import { enqueueAndPlay } from '../utils/playSong'; diff --git a/src/components/SongInfoModal.tsx b/src/components/SongInfoModal.tsx index 8e227ddb..3d6b4839 100644 --- a/src/components/SongInfoModal.tsx +++ b/src/components/SongInfoModal.tsx @@ -1,9 +1,10 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; -import { getSong, SubsonicSong } from '../api/subsonic'; +import { getSong } from '../api/subsonic'; import { ndGetSongPath } from '../api/navidromeAdmin'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/SongRail.tsx b/src/components/SongRail.tsx index c10febd2..ff0e5e1e 100644 --- a/src/components/SongRail.tsx +++ b/src/components/SongRail.tsx @@ -1,6 +1,6 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import React, { useRef, useState, useEffect, useMemo } from 'react'; import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react'; -import { SubsonicSong } from '../api/subsonic'; import SongCard from './SongCard'; import { usePerfProbeFlags } from '../utils/perfFlags'; import { dedupeById } from '../utils/dedupeById'; diff --git a/src/components/SongRow.tsx b/src/components/SongRow.tsx index d694e424..8a140b6b 100644 --- a/src/components/SongRow.tsx +++ b/src/components/SongRow.tsx @@ -1,9 +1,9 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { memo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import type { SubsonicSong } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { enqueueAndPlay } from '../utils/playSong'; import { useDragDrop } from '../contexts/DragDropContext'; diff --git a/src/components/StatsExportModal.tsx b/src/components/StatsExportModal.tsx index 3f2dcffc..2cccb50e 100644 --- a/src/components/StatsExportModal.tsx +++ b/src/components/StatsExportModal.tsx @@ -1,10 +1,11 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { X } from 'lucide-react'; import { save } from '@tauri-apps/plugin-dialog'; import { writeFile } from '@tauri-apps/plugin-fs'; -import { getAlbumList, SubsonicAlbum } from '../api/subsonic'; +import { getAlbumList } from '../api/subsonic'; import { showToast } from '../utils/toast'; import { exportAlbumCardBlob, diff --git a/src/components/VirtualSongList.tsx b/src/components/VirtualSongList.tsx index e91d9e5c..eabd3926 100644 --- a/src/components/VirtualSongList.tsx +++ b/src/components/VirtualSongList.tsx @@ -1,9 +1,10 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useRefElementClientHeight } from '../hooks/useResizeClientHeight'; import { Search as SearchIcon, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { SubsonicSong, searchSongsPaged } from '../api/subsonic'; +import { searchSongsPaged } from '../api/subsonic'; import { ndListSongs } from '../api/navidromeBrowse'; import SongRow, { SongListHeader } from './SongRow'; diff --git a/src/hooks/useLyrics.ts b/src/hooks/useLyrics.ts index 4dc37304..b92c0ab0 100644 --- a/src/hooks/useLyrics.ts +++ b/src/hooks/useLyrics.ts @@ -1,10 +1,11 @@ +import type { SubsonicStructuredLyrics } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; import { useEffect, useState } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { invoke } from '@tauri-apps/api/core'; import { fetchLyrics, parseLrc, LrcLine } from '../api/lrclib'; import { fetchNeteaselyrics } from '../api/netease'; -import { getLyricsBySongId, SubsonicStructuredLyrics } from '../api/subsonic'; +import { getLyricsBySongId } from '../api/subsonic'; import { fetchLyricsPlus, hasWordSync } from '../api/lyricsplus'; import { useAuthStore } from '../store/authStore'; import { useOfflineStore } from '../store/offlineStore'; diff --git a/src/hooks/useRadioMetadata.ts b/src/hooks/useRadioMetadata.ts index b64ca616..7d071a15 100644 --- a/src/hooks/useRadioMetadata.ts +++ b/src/hooks/useRadioMetadata.ts @@ -1,6 +1,6 @@ +import type { InternetRadioStation } from '../api/subsonicTypes'; import { useEffect, useRef, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import type { InternetRadioStation } from '../api/subsonic'; import { usePerfProbeFlags } from '../utils/perfFlags'; import { guessAzuraCastApiUrl, diff --git a/src/pages/AdvancedSearch.tsx b/src/pages/AdvancedSearch.tsx index 4be77da9..635cb3c9 100644 --- a/src/pages/AdvancedSearch.tsx +++ b/src/pages/AdvancedSearch.tsx @@ -1,10 +1,8 @@ +import type { SubsonicGenre, SubsonicArtist, SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { SlidersVertical } from 'lucide-react'; -import { - search, searchSongsPaged, getGenres, getAlbumsByGenre, getAlbumList, getRandomSongs, - SubsonicGenre, SubsonicArtist, SubsonicAlbum, SubsonicSong, -} from '../api/subsonic'; +import { search, searchSongsPaged, getGenres, getAlbumsByGenre, getAlbumList, getRandomSongs } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import AlbumRow from '../components/AlbumRow'; import ArtistRow from '../components/ArtistRow'; diff --git a/src/pages/AlbumDetail.tsx b/src/pages/AlbumDetail.tsx index eb16f8de..0cbc6272 100644 --- a/src/pages/AlbumDetail.tsx +++ b/src/pages/AlbumDetail.tsx @@ -1,9 +1,10 @@ +import type { SubsonicSong, SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Search, X, ListPlus } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; -import { getAlbum, getArtist, getArtistInfo, setRating, buildCoverArtUrl, coverArtCacheKey, buildDownloadUrl, star, unstar, SubsonicSong, SubsonicAlbum } from '../api/subsonic'; +import { getAlbum, getArtist, getArtistInfo, setRating, buildCoverArtUrl, coverArtCacheKey, buildDownloadUrl, star, unstar } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior'; diff --git a/src/pages/Albums.tsx b/src/pages/Albums.tsx index cefbefee..b6d0e20a 100644 --- a/src/pages/Albums.tsx +++ b/src/pages/Albums.tsx @@ -1,3 +1,4 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useState, useEffect, useRef, useCallback, useMemo, useLayoutEffect } from 'react'; import AlbumCard from '../components/AlbumCard'; @@ -5,7 +6,7 @@ import GenreFilterBar from '../components/GenreFilterBar'; import YearFilterButton from '../components/YearFilterButton'; import StarFilterButton from '../components/StarFilterButton'; import SortDropdown from '../components/SortDropdown'; -import { getAlbumList, getAlbumsByGenre, getAlbum, SubsonicAlbum, buildDownloadUrl } from '../api/subsonic'; +import { getAlbumList, getAlbumsByGenre, getAlbum, buildDownloadUrl } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; import { useOfflineStore } from '../store/offlineStore'; diff --git a/src/pages/ArtistDetail.tsx b/src/pages/ArtistDetail.tsx index 2f3e4b6c..1154b97e 100644 --- a/src/pages/ArtistDetail.tsx +++ b/src/pages/ArtistDetail.tsx @@ -1,7 +1,8 @@ +import type { SubsonicArtist, SubsonicAlbum, SubsonicSong, SubsonicArtistInfo } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import { useEffect, useState, useRef, Fragment, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { getArtist, getArtistInfo, getTopSongs, getSimilarSongs2, getAlbum, search, setRating, SubsonicArtist, SubsonicAlbum, SubsonicSong, SubsonicArtistInfo, buildCoverArtUrl, coverArtCacheKey, star, unstar, uploadArtistImage } from '../api/subsonic'; +import { getArtist, getArtistInfo, getTopSongs, getSimilarSongs2, getAlbum, search, setRating, buildCoverArtUrl, coverArtCacheKey, star, unstar, uploadArtistImage } from '../api/subsonic'; import AlbumCard from '../components/AlbumCard'; import CachedImage from '../components/CachedImage'; import CoverLightbox from '../components/CoverLightbox'; diff --git a/src/pages/Artists.tsx b/src/pages/Artists.tsx index 6247ff80..b5302757 100644 --- a/src/pages/Artists.tsx +++ b/src/pages/Artists.tsx @@ -1,6 +1,7 @@ +import type { SubsonicArtist } from '../api/subsonicTypes'; import React, { useEffect, useState, useCallback, useRef, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; -import { getArtists, SubsonicArtist, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; +import { getArtists, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { LayoutGrid, List, Images, CheckSquare2, ListMusic, Check } from 'lucide-react'; import StarFilterButton from '../components/StarFilterButton'; import { usePlayerStore } from '../store/playerStore'; diff --git a/src/pages/ComposerDetail.tsx b/src/pages/ComposerDetail.tsx index 23ab0291..4b885f9d 100644 --- a/src/pages/ComposerDetail.tsx +++ b/src/pages/ComposerDetail.tsx @@ -1,10 +1,7 @@ +import type { SubsonicArtist, SubsonicAlbum, SubsonicArtistInfo } from '../api/subsonicTypes'; import { useEffect, useState, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { - getArtist, getArtistInfo, star, unstar, - SubsonicArtist, SubsonicAlbum, SubsonicArtistInfo, - buildCoverArtUrl, coverArtCacheKey, -} from '../api/subsonic'; +import { getArtist, getArtistInfo, star, unstar, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { ndListAlbumsByArtistRole } from '../api/navidromeBrowse'; import AlbumCard from '../components/AlbumCard'; import CachedImage from '../components/CachedImage'; diff --git a/src/pages/Composers.tsx b/src/pages/Composers.tsx index 2775795b..7116d8d2 100644 --- a/src/pages/Composers.tsx +++ b/src/pages/Composers.tsx @@ -1,6 +1,6 @@ +import type { SubsonicArtist } from '../api/subsonicTypes'; import React, { useEffect, useState, useCallback, useRef, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; -import { SubsonicArtist } from '../api/subsonic'; import { ndListArtistsByRole } from '../api/navidromeBrowse'; import { LayoutGrid, List } from 'lucide-react'; import StarFilterButton from '../components/StarFilterButton'; diff --git a/src/pages/DeviceSync.tsx b/src/pages/DeviceSync.tsx index 37096fd0..86371674 100644 --- a/src/pages/DeviceSync.tsx +++ b/src/pages/DeviceSync.tsx @@ -1,3 +1,4 @@ +import type { SubsonicSong, SubsonicAlbum, SubsonicPlaylist, SubsonicArtist } from '../api/subsonicTypes'; import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; @@ -11,11 +12,7 @@ import CustomSelect from '../components/CustomSelect'; import { useTranslation } from 'react-i18next'; import { useDeviceSyncStore, DeviceSyncSource } from '../store/deviceSyncStore'; import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore'; -import { - getPlaylists, getAlbumList, getArtists, getAlbum, getPlaylist, getArtist, - buildDownloadUrl, search as searchSubsonic, - SubsonicSong, SubsonicAlbum, SubsonicPlaylist, SubsonicArtist, -} from '../api/subsonic'; +import { getPlaylists, getAlbumList, getArtists, getAlbum, getPlaylist, getArtist, buildDownloadUrl, search as searchSubsonic } from '../api/subsonic'; import { showToast } from '../utils/toast'; import { IS_WINDOWS } from '../utils/platform'; @@ -598,7 +595,7 @@ export default function DeviceSync() { setPreSyncOpen(true); try { - const { getClient } = await import('../api/subsonic'); + const { getClient } = await import('../api/subsonicClient'); const { baseUrl, params } = getClient(); const payload = await invoke<{ addBytes: number; addCount: number; delBytes: number; delCount: number; availableBytes: number; tracks: SubsonicSong[]; diff --git a/src/pages/Favorites.tsx b/src/pages/Favorites.tsx index 6e5d0b7a..70ea9775 100644 --- a/src/pages/Favorites.tsx +++ b/src/pages/Favorites.tsx @@ -1,14 +1,11 @@ +import type { SubsonicAlbum, SubsonicArtist, SubsonicSong, InternetRadioStation } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns'; import AlbumRow from '../components/AlbumRow'; import ArtistRow from '../components/ArtistRow'; import CachedImage from '../components/CachedImage'; -import { - getStarred, getInternetRadioStations, setRating, - SubsonicAlbum, SubsonicArtist, SubsonicSong, InternetRadioStation, - buildCoverArtUrl, coverArtCacheKey, -} from '../api/subsonic'; +import { getStarred, getInternetRadioStations, setRating, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { usePreviewStore } from '../store/previewStore'; import StarRating from '../components/StarRating'; diff --git a/src/pages/FolderBrowser.tsx b/src/pages/FolderBrowser.tsx index 90aa07dc..3c29dd44 100644 --- a/src/pages/FolderBrowser.tsx +++ b/src/pages/FolderBrowser.tsx @@ -1,13 +1,7 @@ +import type { SubsonicDirectoryEntry, SubsonicArtist, SubsonicAlbum } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'; -import { - getMusicFolders, - getMusicDirectory, - getMusicIndexes, - SubsonicDirectoryEntry, - SubsonicArtist, - SubsonicAlbum, -} from '../api/subsonic'; +import { getMusicFolders, getMusicDirectory, getMusicIndexes } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useTranslation } from 'react-i18next'; import { Folder, FolderOpen, Music, ChevronRight } from 'lucide-react'; diff --git a/src/pages/GenreDetail.tsx b/src/pages/GenreDetail.tsx index ab4b008f..05b87d1a 100644 --- a/src/pages/GenreDetail.tsx +++ b/src/pages/GenreDetail.tsx @@ -1,8 +1,9 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React, { useEffect, useState, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ArrowLeft, Disc3 } from 'lucide-react'; -import { getAlbumsByGenre, SubsonicAlbum } from '../api/subsonic'; +import { getAlbumsByGenre } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import AlbumCard from '../components/AlbumCard'; diff --git a/src/pages/Genres.tsx b/src/pages/Genres.tsx index 99f1656c..25fdd1f2 100644 --- a/src/pages/Genres.tsx +++ b/src/pages/Genres.tsx @@ -1,8 +1,9 @@ +import type { SubsonicGenre } from '../api/subsonicTypes'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Tags } from 'lucide-react'; -import { getGenres, SubsonicGenre } from '../api/subsonic'; +import { getGenres } from '../api/subsonic'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll'; const CTP_COLORS = [ diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 451f09ac..dd3164d9 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,10 +1,11 @@ +import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import Hero from '../components/Hero'; import AlbumRow from '../components/AlbumRow'; import SongRail from '../components/SongRail'; import BecauseYouLikeRail from '../components/BecauseYouLikeRail'; import LosslessAlbumsRail from '../components/LosslessAlbumsRail'; -import { getAlbumList, getArtists, getRandomSongs, SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsonic'; +import { getAlbumList, getArtists, getRandomSongs } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { NavLink, useNavigate } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; diff --git a/src/pages/InternetRadio.tsx b/src/pages/InternetRadio.tsx index 33b313a9..cff4bb62 100644 --- a/src/pages/InternetRadio.tsx +++ b/src/pages/InternetRadio.tsx @@ -1,14 +1,9 @@ +import { type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE } from '../api/subsonicTypes'; import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { Cast, Plus, Trash2, X, Globe, Camera, Loader2, Search, Heart, Check } from 'lucide-react'; import { useDragSource, useDragDrop } from '../contexts/DragDropContext'; -import { - getInternetRadioStations, createInternetRadioStation, - updateInternetRadioStation, deleteInternetRadioStation, - uploadRadioCoverArt, deleteRadioCoverArt, - uploadRadioCoverArtBytes, searchRadioBrowser, getTopRadioStations, fetchUrlBytes, - InternetRadioStation, RadioBrowserStation, buildCoverArtUrl, coverArtCacheKey, RADIO_PAGE_SIZE, -} from '../api/subsonic'; +import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt, uploadRadioCoverArtBytes, searchRadioBrowser, getTopRadioStations, fetchUrlBytes, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import CachedImage from '../components/CachedImage'; import { invalidateCoverArt } from '../utils/imageCache'; diff --git a/src/pages/LabelAlbums.tsx b/src/pages/LabelAlbums.tsx index 299974d1..fd256b99 100644 --- a/src/pages/LabelAlbums.tsx +++ b/src/pages/LabelAlbums.tsx @@ -1,8 +1,9 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { ChevronLeft } from 'lucide-react'; import AlbumCard from '../components/AlbumCard'; -import { search, SubsonicAlbum } from '../api/subsonic'; +import { search } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; diff --git a/src/pages/LosslessAlbums.tsx b/src/pages/LosslessAlbums.tsx index 55defc92..9e1949f5 100644 --- a/src/pages/LosslessAlbums.tsx +++ b/src/pages/LosslessAlbums.tsx @@ -1,8 +1,9 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import AlbumCard from '../components/AlbumCard'; import { ndListLosslessAlbumsPage } from '../api/navidromeBrowse'; -import { getAlbum, type SubsonicAlbum, buildDownloadUrl } from '../api/subsonic'; +import { getAlbum, buildDownloadUrl } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; import { useOfflineStore } from '../store/offlineStore'; diff --git a/src/pages/MostPlayed.tsx b/src/pages/MostPlayed.tsx index a480a3ed..05f3ce7e 100644 --- a/src/pages/MostPlayed.tsx +++ b/src/pages/MostPlayed.tsx @@ -1,8 +1,9 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp, UsersRound, Play, ListPlus } from 'lucide-react'; -import { getAlbumList, getAlbum, SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; +import { getAlbumList, getAlbum, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import { usePlayerStore } from '../store/playerStore'; import CachedImage from '../components/CachedImage'; diff --git a/src/pages/NewReleases.tsx b/src/pages/NewReleases.tsx index 1ebd8f79..dc8fb4f1 100644 --- a/src/pages/NewReleases.tsx +++ b/src/pages/NewReleases.tsx @@ -1,8 +1,9 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React, { useEffect, useState, useCallback, useRef } from 'react'; import { CheckSquare2, Download, HardDriveDownload, ListMusic } from 'lucide-react'; import AlbumCard from '../components/AlbumCard'; import GenreFilterBar from '../components/GenreFilterBar'; -import { getAlbumList, getAlbumsByGenre, getAlbum, SubsonicAlbum, buildDownloadUrl } from '../api/subsonic'; +import { getAlbumList, getAlbumsByGenre, getAlbum, buildDownloadUrl } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; import { useOfflineStore } from '../store/offlineStore'; diff --git a/src/pages/NowPlaying.tsx b/src/pages/NowPlaying.tsx index bb74ee24..e28daed3 100644 --- a/src/pages/NowPlaying.tsx +++ b/src/pages/NowPlaying.tsx @@ -1,3 +1,4 @@ +import type { SubsonicSong, SubsonicArtistInfo, SubsonicAlbum } from '../api/subsonicTypes'; import React, { useState, useRef, useEffect, useCallback, useLayoutEffect, useMemo, memo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -6,11 +7,7 @@ import { open as shellOpen } from '@tauri-apps/plugin-shell'; import { usePlayerStore } from '../store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useLyricsStore } from '../store/lyricsStore'; -import { - buildCoverArtUrl, coverArtCacheKey, getSong, star, unstar, - getAlbum, getArtist, getArtistInfo, getTopSongs, - SubsonicSong, SubsonicArtistInfo, SubsonicAlbum, -} from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey, getSong, star, unstar, getAlbum, getArtist, getArtistInfo, getTopSongs } from '../api/subsonic'; import { songToTrack } from '../utils/songToTrack'; import { lastfmIsConfigured, diff --git a/src/pages/PlaylistDetail.tsx b/src/pages/PlaylistDetail.tsx index 5d811dd4..1952cd3c 100644 --- a/src/pages/PlaylistDetail.tsx +++ b/src/pages/PlaylistDetail.tsx @@ -1,3 +1,4 @@ +import type { SubsonicPlaylist, SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; @@ -5,11 +6,7 @@ import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { ChevronDown, ChevronLeft, ChevronRight, Play, ListPlus, Trash2, Search, X, Loader2, Plus, GripVertical, Star, RefreshCw, Shuffle, Heart, HardDriveDownload, Check, Pencil, Globe, Lock, Camera, Download, FileUp, RotateCcw, Sparkles, Square, AudioLines } from 'lucide-react'; import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns'; import { AddToPlaylistSubmenu } from '../components/ContextMenu'; -import { - getPlaylist, updatePlaylist, updatePlaylistMeta, uploadPlaylistCoverArt, - search, setRating, star, unstar, - getRandomSongs, buildDownloadUrl, filterSongsToActiveLibrary, SubsonicPlaylist, SubsonicSong, -} from '../api/subsonic'; +import { getPlaylist, updatePlaylist, updatePlaylistMeta, uploadPlaylistCoverArt, search, setRating, star, unstar, getRandomSongs, buildDownloadUrl, filterSongsToActiveLibrary } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { usePlaylistStore } from '../store/playlistStore'; diff --git a/src/pages/Playlists.tsx b/src/pages/Playlists.tsx index 4cc0032f..c4869820 100644 --- a/src/pages/Playlists.tsx +++ b/src/pages/Playlists.tsx @@ -1,8 +1,9 @@ +import type { SubsonicPlaylist, SubsonicGenre } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ListMusic, Play, Plus, Trash2, CheckSquare2, Check, Clock3, Sparkles, Pencil } from 'lucide-react'; -import { deletePlaylist, SubsonicPlaylist, getPlaylist, buildCoverArtUrl, coverArtCacheKey, updatePlaylist, getGenres, SubsonicGenre, filterSongsToActiveLibrary } from '../api/subsonic'; +import { deletePlaylist, getPlaylist, buildCoverArtUrl, coverArtCacheKey, updatePlaylist, getGenres, filterSongsToActiveLibrary } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { usePlaylistStore } from '../store/playlistStore'; import { useAuthStore } from '../store/authStore'; diff --git a/src/pages/RandomAlbums.tsx b/src/pages/RandomAlbums.tsx index 12fe1474..5eb26f8f 100644 --- a/src/pages/RandomAlbums.tsx +++ b/src/pages/RandomAlbums.tsx @@ -1,6 +1,7 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React, { useEffect, useState, useCallback, useRef } from 'react'; import { RefreshCw, CheckSquare2, Download, HardDriveDownload } from 'lucide-react'; -import { getAlbumList, getAlbumsByGenre, getAlbum, SubsonicAlbum, buildDownloadUrl } from '../api/subsonic'; +import { getAlbumList, getAlbumsByGenre, getAlbum, buildDownloadUrl } from '../api/subsonic'; import AlbumCard from '../components/AlbumCard'; import GenreFilterBar from '../components/GenreFilterBar'; import { useTranslation } from 'react-i18next'; diff --git a/src/pages/RandomMix.tsx b/src/pages/RandomMix.tsx index 547559da..aa9ee696 100644 --- a/src/pages/RandomMix.tsx +++ b/src/pages/RandomMix.tsx @@ -1,7 +1,8 @@ +import type { SubsonicSong, SubsonicGenre } from '../api/subsonicTypes'; import { RANDOM_MIX_SIZE_OPTIONS } from '../store/authStoreDefaults'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useMemo, useState } from 'react'; -import { getGenres, SubsonicSong, SubsonicGenre, star, unstar } from '../api/subsonic'; +import { getGenres, star, unstar } from '../api/subsonic'; import { usePlayerStore } from '../store/playerStore'; import { usePreviewStore } from '../store/previewStore'; import { useAuthStore } from '../store/authStore'; diff --git a/src/pages/SearchResults.tsx b/src/pages/SearchResults.tsx index 8229ff4f..bdf6873a 100644 --- a/src/pages/SearchResults.tsx +++ b/src/pages/SearchResults.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Search } from 'lucide-react'; -import { search, searchSongsPaged, SearchResults as ISearchResults } from '../api/subsonic'; +import { search, searchSongsPaged } from '../api/subsonic'; +import type { SearchResults as ISearchResults } from '../api/subsonicTypes'; import AlbumRow from '../components/AlbumRow'; import ArtistRow from '../components/ArtistRow'; import SongRow, { SongListHeader } from '../components/SongRow'; diff --git a/src/pages/Statistics.tsx b/src/pages/Statistics.tsx index ea06f3f4..08bcf529 100644 --- a/src/pages/Statistics.tsx +++ b/src/pages/Statistics.tsx @@ -1,13 +1,7 @@ +import type { SubsonicAlbum, SubsonicGenre } from '../api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { Share2 } from 'lucide-react'; -import { - fetchStatisticsFormatSample, - fetchStatisticsLibraryAggregates, - fetchStatisticsOverview, - getAlbumList, - SubsonicAlbum, - SubsonicGenre, -} from '../api/subsonic'; +import { fetchStatisticsFormatSample, fetchStatisticsLibraryAggregates, fetchStatisticsOverview, getAlbumList } from '../api/subsonic'; import { formatHumanHoursMinutes } from '../utils/formatHumanDuration'; import AlbumRow from '../components/AlbumRow'; import StatsExportModal from '../components/StatsExportModal'; diff --git a/src/pages/Tracks.tsx b/src/pages/Tracks.tsx index 30b597a4..5f300d37 100644 --- a/src/pages/Tracks.tsx +++ b/src/pages/Tracks.tsx @@ -1,14 +1,10 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { - SubsonicSong, - getRandomSongs, - buildCoverArtUrl, - coverArtCacheKey, -} from '../api/subsonic'; +import { getRandomSongs, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import { usePlayerStore } from '../store/playerStore'; import CachedImage from '../components/CachedImage'; diff --git a/src/store/authStoreTypes.ts b/src/store/authStoreTypes.ts index 3f539bd4..47948c3a 100644 --- a/src/store/authStoreTypes.ts +++ b/src/store/authStoreTypes.ts @@ -1,4 +1,4 @@ -import type { EntityRatingSupportLevel } from '../api/subsonic'; +import type { EntityRatingSupportLevel } from '../api/subsonicTypes'; import type { InstantMixProbeResult, SubsonicServerIdentity, diff --git a/src/store/offlineStore.ts b/src/store/offlineStore.ts index 880768cd..911d6837 100644 --- a/src/store/offlineStore.ts +++ b/src/store/offlineStore.ts @@ -1,8 +1,8 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import { invoke } from '@tauri-apps/api/core'; import { buildStreamUrl, getArtist, getAlbum } from '../api/subsonic'; -import type { SubsonicSong } from '../api/subsonic'; import { useAuthStore } from './authStore'; import { showToast } from '../utils/toast'; import { useOfflineJobStore, cancelledDownloads } from './offlineJobStore'; diff --git a/src/store/playerStoreTypes.ts b/src/store/playerStoreTypes.ts index c045741e..0777d146 100644 --- a/src/store/playerStoreTypes.ts +++ b/src/store/playerStoreTypes.ts @@ -1,4 +1,4 @@ -import type { InternetRadioStation } from '../api/subsonic'; +import type { InternetRadioStation } from '../api/subsonicTypes'; import type { PlaybackSourceKind } from '../utils/resolvePlaybackUrl'; export interface Track { diff --git a/src/store/playlistStore.ts b/src/store/playlistStore.ts index cc6e36bf..356cc2b6 100644 --- a/src/store/playlistStore.ts +++ b/src/store/playlistStore.ts @@ -1,7 +1,7 @@ +import type { SubsonicPlaylist } from '../api/subsonicTypes'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import { getPlaylists, createPlaylist as apiCreatePlaylist, SubsonicPlaylist } from '../api/subsonic'; - +import { getPlaylists, createPlaylist as apiCreatePlaylist } from '../api/subsonic'; interface PlaylistStore { recentIds: string[]; playlists: SubsonicPlaylist[]; diff --git a/src/test/helpers/factories.ts b/src/test/helpers/factories.ts index 2fcc6b28..446018cd 100644 --- a/src/test/helpers/factories.ts +++ b/src/test/helpers/factories.ts @@ -5,10 +5,9 @@ * the fields the test cares about. Keeps tests focused on behaviour rather * than on assembling boilerplate. */ +import type { SubsonicSong } from '@/api/subsonicTypes'; import type { ServerProfile } from '@/store/authStoreTypes'; import type { Track } from '@/store/playerStoreTypes'; -import type { SubsonicSong } from '@/api/subsonic'; - let trackCounter = 0; let songCounter = 0; let serverCounter = 0; diff --git a/src/test/mocks/subsonic.ts b/src/test/mocks/subsonic.ts index 2a15d3ae..46dae9b9 100644 --- a/src/test/mocks/subsonic.ts +++ b/src/test/mocks/subsonic.ts @@ -18,7 +18,7 @@ * Realistic shape matters more than perfect coverage — these fixtures * mirror what Navidrome actually returns for common queries. */ -import type { SubsonicSong, SubsonicAlbum, SubsonicPlaylist } from '@/api/subsonic'; +import type { SubsonicSong, SubsonicAlbum, SubsonicPlaylist } from '@/api/subsonicTypes'; import { makeSubsonicSong } from '@/test/helpers/factories'; export const sampleSubsonicSong: SubsonicSong = makeSubsonicSong({ diff --git a/src/utils/applySharePaste.ts b/src/utils/applySharePaste.ts index dfe5dd71..bfee930e 100644 --- a/src/utils/applySharePaste.ts +++ b/src/utils/applySharePaste.ts @@ -1,7 +1,8 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import type { NavigateFunction } from 'react-router-dom'; import type { TFunction } from 'i18next'; -import { getAlbum, getArtist, getSong, type SubsonicSong } from '../api/subsonic'; +import { getAlbum, getArtist, getSong } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import { usePlayerStore } from '../store/playerStore'; import { findServerIdForShareUrl, type EntitySharePayloadV1 } from './shareLink'; diff --git a/src/utils/exportAlbumCard.ts b/src/utils/exportAlbumCard.ts index cceb5837..8083cb7f 100644 --- a/src/utils/exportAlbumCard.ts +++ b/src/utils/exportAlbumCard.ts @@ -1,6 +1,7 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; -import { buildCoverArtUrl, coverArtCacheKey, SubsonicAlbum } from '../api/subsonic'; +import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { getCachedBlob } from './imageCache'; import PsysonicLogo from '../components/PsysonicLogo'; diff --git a/src/utils/exportNewAlbums.ts b/src/utils/exportNewAlbums.ts index 72cd7798..89b5f35c 100644 --- a/src/utils/exportNewAlbums.ts +++ b/src/utils/exportNewAlbums.ts @@ -1,9 +1,8 @@ +import type { SubsonicAlbum } from '../api/subsonicTypes'; import { writeFile } from '@tauri-apps/plugin-fs'; import { downloadDir, join } from '@tauri-apps/api/path'; import { getAlbumList, buildCoverArtUrl } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; -import type { SubsonicAlbum } from '../api/subsonic'; - // Catppuccin Macchiato palette const M = { crust: '#181926', diff --git a/src/utils/luckyMix.ts b/src/utils/luckyMix.ts index 19ff3e61..ce6c9f76 100644 --- a/src/utils/luckyMix.ts +++ b/src/utils/luckyMix.ts @@ -1,15 +1,7 @@ +import type { SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; import { songToTrack } from '../utils/songToTrack'; -import { - filterSongsToActiveLibrary, - getAlbum, - getAlbumList, - getRandomSongs, - getSimilarSongs, - getTopSongs, - type SubsonicAlbum, - type SubsonicSong, -} from '../api/subsonic'; +import { filterSongsToActiveLibrary, getAlbum, getAlbumList, getRandomSongs, getSimilarSongs, getTopSongs } from '../api/subsonic'; import { invoke } from '@tauri-apps/api/core'; import i18n from '../i18n'; import { useAuthStore } from '../store/authStore'; diff --git a/src/utils/mixRatingFilter.ts b/src/utils/mixRatingFilter.ts index 00703bc0..384f27ac 100644 --- a/src/utils/mixRatingFilter.ts +++ b/src/utils/mixRatingFilter.ts @@ -1,11 +1,5 @@ -import { - getRandomSongs, - parseSubsonicEntityStarRating, - prefetchAlbumUserRatings, - prefetchArtistUserRatings, - type SubsonicAlbum, - type SubsonicSong, -} from '../api/subsonic'; +import type { SubsonicAlbum, SubsonicSong } from '../api/subsonicTypes'; +import { getRandomSongs, parseSubsonicEntityStarRating, prefetchAlbumUserRatings, prefetchArtistUserRatings } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; /** Default target list size for Random Mix; per-call override via `fetchRandomMixSongsUntilFull(c, { targetSize })`. */ diff --git a/src/utils/playSong.ts b/src/utils/playSong.ts index 2490b06b..5bba1a23 100644 --- a/src/utils/playSong.ts +++ b/src/utils/playSong.ts @@ -1,7 +1,6 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import { songToTrack } from '../utils/songToTrack'; import { usePlayerStore } from '../store/playerStore'; -import type { SubsonicSong } from '../api/subsonic'; - function fadeOut(setVolume: (v: number) => void, from: number, durationMs: number): Promise { return new Promise(resolve => { const steps = 16; diff --git a/src/utils/songToTrack.ts b/src/utils/songToTrack.ts index 26017918..6c7df16e 100644 --- a/src/utils/songToTrack.ts +++ b/src/utils/songToTrack.ts @@ -1,5 +1,5 @@ +import type { SubsonicSong } from '../api/subsonicTypes'; import type { Track } from '../store/playerStoreTypes'; -import type { SubsonicSong } from '../api/subsonic'; export function songToTrack(song: SubsonicSong): Track { return { id: song.id,