diff --git a/src/components/discover/LibraryGapFinder.tsx b/src/components/discover/LibraryGapFinder.tsx index ac79bc4..b0779e2 100644 --- a/src/components/discover/LibraryGapFinder.tsx +++ b/src/components/discover/LibraryGapFinder.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useLibraryGenreDistribution, useLibraryByTmdbId } from '../../hooks/use-jellyfin' -import { useTmdbDiscoverMovies, useTmdbTopRatedMovies } from '../../hooks/use-tmdb' +import { useTmdbDiscoverMovies } from '../../hooks/use-tmdb' import { usePreferencesStore } from '../../stores/preferences-store' import { mapTmdbToJf } from '../../lib/tmdb-mapping' import { tmdbMovieGenreId } from '../../lib/tmdb-genres' diff --git a/src/components/player/StreamInfo.tsx b/src/components/player/StreamInfo.tsx index 377ffc0..429bde8 100644 --- a/src/components/player/StreamInfo.tsx +++ b/src/components/player/StreamInfo.tsx @@ -58,14 +58,14 @@ function readLiveStats(): LiveStats { } } -async function checkHwDecode(stream: { codec?: string | null; width?: number | null; height?: number | null; bitrate?: number | null } | null): Promise { +async function checkHwDecode(stream: { Codec?: string | null; Width?: number | null; Height?: number | null; BitRate?: number | null } | null): Promise { if (!stream || typeof (navigator as any).mediaCapabilities?.decodingInfo !== 'function') { return { supported: null, hwAccelerated: null } } - const codec = (stream.codec || stream.Codec || '').toLowerCase() - const w = stream.width ?? stream.Width ?? 1920 - const h = stream.height ?? stream.Height ?? 1080 - const br = stream.bitrate ?? stream.BitRate ?? 8000000 + const codec = (stream.Codec || '').toLowerCase() + const w = stream.Width ?? 1920 + const h = stream.Height ?? 1080 + const br = stream.BitRate ?? 8000000 // Map common Jellyfin codec names to MIME codec strings const mimeCodec = codec === 'h264' ? 'avc1.640033' diff --git a/src/hooks/use-new-releases.ts b/src/hooks/use-new-releases.ts index 14eedbe..9eba15d 100644 --- a/src/hooks/use-new-releases.ts +++ b/src/hooks/use-new-releases.ts @@ -11,18 +11,21 @@ import { toast } from '../stores/toast-store' */ export function useNewReleaseNotifications(enabled: boolean) { const qc = useQueryClient() - const lastNotifiedRef = useRef(() => { - try { return localStorage.getItem('jf_last_notify_date') } catch { return null } - }()) + const lastNotifiedRef = useRef( + (() => { + try { return localStorage.getItem('jf_last_notify_date') } catch { return null } + })() + ) useEffect(() => { if (!enabled) return const api = jellyfinClient.getApi() if (!api) return + const apiRef = api async function check() { try { - const res = await getItemsApi(api).getItems({ + const res = await getItemsApi(apiRef).getItems({ userId: jellyfinClient.getAuthState()!.userId, sortBy: ['DateCreated'], sortOrder: ['Descending'], diff --git a/src/lib/downloads.ts b/src/lib/downloads.ts index 5b60f76..a7cb501 100644 --- a/src/lib/downloads.ts +++ b/src/lib/downloads.ts @@ -31,15 +31,18 @@ export async function startDownload(args: { // Tauri path: fetch via the Rust-backed HTTP client so we avoid // CORS + we can stream large files without the browser's memory // pressure. - const { fetch } = await import('@tauri-apps/api/http') - const { appLocalDataDir } = await import('@tauri-apps/api/path') - const { writeBinaryFile, BaseDirectory } = await import('@tauri-apps/api/fs') + // @ts-ignore + const { fetch } = await import('@tauri-apps/api/http') as any + // @ts-ignore + const { appLocalDataDir } = await import('@tauri-apps/api/path') as any + // @ts-ignore + const { writeBinaryFile, BaseDirectory } = await import('@tauri-apps/api/fs') as any - const res = await fetch(streamUrl, { + const res = await (fetch as any)(streamUrl, { method: 'GET', responseType: 3, // ResponseType.Binary }) - const bytes = res.data + const bytes: Uint8Array = res.data const dir = await appLocalDataDir() const fileName = `download_${itemId}_${Date.now()}.mp4` await writeBinaryFile(fileName, bytes, { dir: BaseDirectory.AppLocalData }) @@ -77,7 +80,7 @@ export async function startDownload(args: { } // Assemble the full blob - const blob = new Blob(chunks) + const blob = new Blob(chunks as any) const objectUrl = URL.createObjectURL(blob) store.update(dl.id, { status: 'done', diff --git a/src/lib/icons.ts b/src/lib/icons.ts index ff45b45..be67348 100644 --- a/src/lib/icons.ts +++ b/src/lib/icons.ts @@ -79,6 +79,7 @@ export { IconBuilding as Building2, IconTicket as Ticket, IconAlertCircle as AlertCircle, + IconBell as Bell, IconLoader2 as Loader2, IconWifiOff as WifiOff, IconInfoCircle as Info, @@ -140,6 +141,7 @@ export { IconStethoscope as Stethoscope, IconLeaf as Leaf, IconMoonStars as MoonStars, + IconMoon as Moon, IconPlane as Plane, // People / places diff --git a/src/pages/DuplicatesPage.tsx b/src/pages/DuplicatesPage.tsx index 2cbc374..cca2e8c 100644 --- a/src/pages/DuplicatesPage.tsx +++ b/src/pages/DuplicatesPage.tsx @@ -3,12 +3,10 @@ import { useNavigate } from 'react-router-dom' import { motion } from 'framer-motion' import { Film, Tv, AlertCircle } from '../lib/icons' import { useLibraryItems } from '../hooks/use-jellyfin' -import { getBestImage, getStoredServerUrl } from '../api/jellyfin' import PosterCard from '../components/ui/PosterCard' export default function DuplicatesPage() { const navigate = useNavigate() - const serverUrl = getStoredServerUrl() const { data, isLoading } = useLibraryItems(undefined, { includeItemTypes: ['Movie', 'Series'], sortBy: ['SortName'], diff --git a/src/pages/PlayerPage.tsx b/src/pages/PlayerPage.tsx index 25d687d..7fa74a3 100644 --- a/src/pages/PlayerPage.tsx +++ b/src/pages/PlayerPage.tsx @@ -191,6 +191,7 @@ export default function PlayerPage() { const stillWatchingTargetRef = useRef(null) /* Preferences */ + const sleepTimerMinutes = usePreferencesStore(s => s.sleepTimerMinutes) const autoplayNext = usePreferencesStore(s => s.autoplayNext) const subtitleMode = usePreferencesStore(s => s.subtitleMode) const subtitleLanguage = usePreferencesStore(s => s.subtitleLanguage) @@ -1520,7 +1521,7 @@ export default function PlayerPage() { streamUrl, }) }} - isDownloaded={!!item && useDownloads.getState().items.some(d => d.itemId === item.Id)}, + isDownloaded={!!item && useDownloads.getState().items.some(d => d.itemId === item.Id)} /> {/* Center play/pause indicator (only shown briefly when paused) */} diff --git a/src/pages/settings/sections/ServerDashboard.tsx b/src/pages/settings/sections/ServerDashboard.tsx index e7ca0a7..e68db43 100644 --- a/src/pages/settings/sections/ServerDashboard.tsx +++ b/src/pages/settings/sections/ServerDashboard.tsx @@ -1,9 +1,9 @@ import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { motion } from 'framer-motion' -import { Activity, Monitor, Users, Server as ServerIcon, Clock, Film } from '../../../lib/icons' +import { Activity, MonitorPlay, Users, Server as ServerIcon, Clock, Film } from '../../../lib/icons' import { jellyfinClient, getSystemApi, getSessionApi, getActivityLogApi } from '../../../api/jellyfin' -import { Section, Row, SubHeading } from '../_ui' +import { Section, SubHeading } from '../_ui' function useApi() { return jellyfinClient.getApi() @@ -49,12 +49,13 @@ export function ServerDashboardSection() { const transcodes = activeSessions.filter(s => s.PlayState?.PlayMethod === 'Transcode') const uptime = useMemo(() => { - if (!info?.ServerStartTime) return null - const ms = Date.now() - new Date(info.ServerStartTime).getTime() + const start = (info as any)?.ServerStartTime + if (!start) return null + const ms = Date.now() - new Date(start).getTime() const days = Math.floor(ms / 86_400_000) const hrs = Math.floor((ms % 86_400_000) / 3_600_000) return days > 0 ? `${days}d ${hrs}h` : `${hrs}h` - }, [info?.ServerStartTime]) + }, [info]) return (
@@ -62,7 +63,7 @@ export function ServerDashboardSection() {
} /> } /> - } /> + } /> } />
diff --git a/src/pages/settings/sections/Trakt.tsx b/src/pages/settings/sections/Trakt.tsx index fa297b0..f98227c 100644 --- a/src/pages/settings/sections/Trakt.tsx +++ b/src/pages/settings/sections/Trakt.tsx @@ -3,7 +3,7 @@ import { ExternalLink, Loader2, Check, Trash2 } from '../../../lib/icons' import { Section, Row, Input, Toggle } from '../_ui' import { useTrakt } from '../../../stores/trakt-store' import { useWatchlist } from '../../../hooks/use-watchlist' -import { requestDeviceCode, pollDeviceToken, fetchTraktWatchlist, addToTraktWatchlist } from '../../../lib/trakt' +import { requestDeviceCode, pollDeviceToken, fetchTraktWatchlist } from '../../../lib/trakt' import { toast } from '../../../stores/toast-store' import { useLibraryByTmdbId } from '../../../hooks/use-jellyfin' diff --git a/src/stores/downloads-store.ts b/src/stores/downloads-store.ts index 661af39..12925e7 100644 --- a/src/stores/downloads-store.ts +++ b/src/stores/downloads-store.ts @@ -20,7 +20,7 @@ export interface DownloadItem { interface State { items: DownloadItem[] - add: (item: Omit) => void + add: (item: Omit) => DownloadItem update: (id: string, patch: Partial) => void remove: (id: string) => void clearCompleted: () => void @@ -35,18 +35,19 @@ export const useDownloads = create()( persist( (set, get) => ({ items: [], - add: item => - set(s => { - if (s.items.some(i => i.itemId === item.itemId && i.status !== 'error')) return s - const next: DownloadItem = { - ...item, - id: uid(), - status: 'queued', - progress: 0, - createdAt: new Date().toISOString(), - } - return { items: [...s.items, next] } - }), + add: item => { + const existing = get().items.find(i => i.itemId === item.itemId && i.status !== 'error') + if (existing) return existing + const next: DownloadItem = { + ...item, + id: uid(), + status: 'queued', + progress: 0, + createdAt: new Date().toISOString(), + } + set(s => ({ items: [...s.items, next] })) + return next + }, update: (id, patch) => set(s => ({ items: s.items.map(i => (i.id === id ? { ...i, ...patch } : i)), diff --git a/vite.config.ts b/vite.config.ts index 81ebd12..bb7012a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,5 +6,8 @@ export default defineConfig({ plugins: [react(), tailwindcss()], build: { chunkSizeWarningLimit: 900, + rollupOptions: { + external: [/^@tauri-apps\/.*/], + }, }, })