build fixes - type errors, missing icons, tauri externals, unused imports

This commit is contained in:
2026-04-03 08:48:28 +03:00
parent b43aef0f73
commit a8349c3f18
11 changed files with 51 additions and 39 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { useLibraryGenreDistribution, useLibraryByTmdbId } from '../../hooks/use-jellyfin' 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 { usePreferencesStore } from '../../stores/preferences-store'
import { mapTmdbToJf } from '../../lib/tmdb-mapping' import { mapTmdbToJf } from '../../lib/tmdb-mapping'
import { tmdbMovieGenreId } from '../../lib/tmdb-genres' import { tmdbMovieGenreId } from '../../lib/tmdb-genres'
+5 -5
View File
@@ -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<HwDecodeState> { async function checkHwDecode(stream: { Codec?: string | null; Width?: number | null; Height?: number | null; BitRate?: number | null } | null): Promise<HwDecodeState> {
if (!stream || typeof (navigator as any).mediaCapabilities?.decodingInfo !== 'function') { if (!stream || typeof (navigator as any).mediaCapabilities?.decodingInfo !== 'function') {
return { supported: null, hwAccelerated: null } return { supported: null, hwAccelerated: null }
} }
const codec = (stream.codec || stream.Codec || '').toLowerCase() const codec = (stream.Codec || '').toLowerCase()
const w = stream.width ?? stream.Width ?? 1920 const w = stream.Width ?? 1920
const h = stream.height ?? stream.Height ?? 1080 const h = stream.Height ?? 1080
const br = stream.bitrate ?? stream.BitRate ?? 8000000 const br = stream.BitRate ?? 8000000
// Map common Jellyfin codec names to MIME codec strings // Map common Jellyfin codec names to MIME codec strings
const mimeCodec = const mimeCodec =
codec === 'h264' ? 'avc1.640033' codec === 'h264' ? 'avc1.640033'
+6 -3
View File
@@ -11,18 +11,21 @@ import { toast } from '../stores/toast-store'
*/ */
export function useNewReleaseNotifications(enabled: boolean) { export function useNewReleaseNotifications(enabled: boolean) {
const qc = useQueryClient() const qc = useQueryClient()
const lastNotifiedRef = useRef<string | null>(() => { const lastNotifiedRef = useRef<string | null>(
(() => {
try { return localStorage.getItem('jf_last_notify_date') } catch { return null } try { return localStorage.getItem('jf_last_notify_date') } catch { return null }
}()) })()
)
useEffect(() => { useEffect(() => {
if (!enabled) return if (!enabled) return
const api = jellyfinClient.getApi() const api = jellyfinClient.getApi()
if (!api) return if (!api) return
const apiRef = api
async function check() { async function check() {
try { try {
const res = await getItemsApi(api).getItems({ const res = await getItemsApi(apiRef).getItems({
userId: jellyfinClient.getAuthState()!.userId, userId: jellyfinClient.getAuthState()!.userId,
sortBy: ['DateCreated'], sortBy: ['DateCreated'],
sortOrder: ['Descending'], sortOrder: ['Descending'],
+9 -6
View File
@@ -31,15 +31,18 @@ export async function startDownload(args: {
// Tauri path: fetch via the Rust-backed HTTP client so we avoid // Tauri path: fetch via the Rust-backed HTTP client so we avoid
// CORS + we can stream large files without the browser's memory // CORS + we can stream large files without the browser's memory
// pressure. // pressure.
const { fetch } = await import('@tauri-apps/api/http') // @ts-ignore
const { appLocalDataDir } = await import('@tauri-apps/api/path') const { fetch } = await import('@tauri-apps/api/http') as any
const { writeBinaryFile, BaseDirectory } = await import('@tauri-apps/api/fs') // @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<Uint8Array>(streamUrl, { const res = await (fetch as any)(streamUrl, {
method: 'GET', method: 'GET',
responseType: 3, // ResponseType.Binary responseType: 3, // ResponseType.Binary
}) })
const bytes = res.data const bytes: Uint8Array = res.data
const dir = await appLocalDataDir() const dir = await appLocalDataDir()
const fileName = `download_${itemId}_${Date.now()}.mp4` const fileName = `download_${itemId}_${Date.now()}.mp4`
await writeBinaryFile(fileName, bytes, { dir: BaseDirectory.AppLocalData }) await writeBinaryFile(fileName, bytes, { dir: BaseDirectory.AppLocalData })
@@ -77,7 +80,7 @@ export async function startDownload(args: {
} }
// Assemble the full blob // Assemble the full blob
const blob = new Blob(chunks) const blob = new Blob(chunks as any)
const objectUrl = URL.createObjectURL(blob) const objectUrl = URL.createObjectURL(blob)
store.update(dl.id, { store.update(dl.id, {
status: 'done', status: 'done',
+2
View File
@@ -79,6 +79,7 @@ export {
IconBuilding as Building2, IconBuilding as Building2,
IconTicket as Ticket, IconTicket as Ticket,
IconAlertCircle as AlertCircle, IconAlertCircle as AlertCircle,
IconBell as Bell,
IconLoader2 as Loader2, IconLoader2 as Loader2,
IconWifiOff as WifiOff, IconWifiOff as WifiOff,
IconInfoCircle as Info, IconInfoCircle as Info,
@@ -140,6 +141,7 @@ export {
IconStethoscope as Stethoscope, IconStethoscope as Stethoscope,
IconLeaf as Leaf, IconLeaf as Leaf,
IconMoonStars as MoonStars, IconMoonStars as MoonStars,
IconMoon as Moon,
IconPlane as Plane, IconPlane as Plane,
// People / places // People / places
-2
View File
@@ -3,12 +3,10 @@ import { useNavigate } from 'react-router-dom'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Film, Tv, AlertCircle } from '../lib/icons' import { Film, Tv, AlertCircle } from '../lib/icons'
import { useLibraryItems } from '../hooks/use-jellyfin' import { useLibraryItems } from '../hooks/use-jellyfin'
import { getBestImage, getStoredServerUrl } from '../api/jellyfin'
import PosterCard from '../components/ui/PosterCard' import PosterCard from '../components/ui/PosterCard'
export default function DuplicatesPage() { export default function DuplicatesPage() {
const navigate = useNavigate() const navigate = useNavigate()
const serverUrl = getStoredServerUrl()
const { data, isLoading } = useLibraryItems(undefined, { const { data, isLoading } = useLibraryItems(undefined, {
includeItemTypes: ['Movie', 'Series'], includeItemTypes: ['Movie', 'Series'],
sortBy: ['SortName'], sortBy: ['SortName'],
+2 -1
View File
@@ -191,6 +191,7 @@ export default function PlayerPage() {
const stillWatchingTargetRef = useRef<string | null>(null) const stillWatchingTargetRef = useRef<string | null>(null)
/* Preferences */ /* Preferences */
const sleepTimerMinutes = usePreferencesStore(s => s.sleepTimerMinutes)
const autoplayNext = usePreferencesStore(s => s.autoplayNext) const autoplayNext = usePreferencesStore(s => s.autoplayNext)
const subtitleMode = usePreferencesStore(s => s.subtitleMode) const subtitleMode = usePreferencesStore(s => s.subtitleMode)
const subtitleLanguage = usePreferencesStore(s => s.subtitleLanguage) const subtitleLanguage = usePreferencesStore(s => s.subtitleLanguage)
@@ -1520,7 +1521,7 @@ export default function PlayerPage() {
streamUrl, 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) */} {/* Center play/pause indicator (only shown briefly when paused) */}
@@ -1,9 +1,9 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { motion } from 'framer-motion' 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 { jellyfinClient, getSystemApi, getSessionApi, getActivityLogApi } from '../../../api/jellyfin'
import { Section, Row, SubHeading } from '../_ui' import { Section, SubHeading } from '../_ui'
function useApi() { function useApi() {
return jellyfinClient.getApi() return jellyfinClient.getApi()
@@ -49,12 +49,13 @@ export function ServerDashboardSection() {
const transcodes = activeSessions.filter(s => s.PlayState?.PlayMethod === 'Transcode') const transcodes = activeSessions.filter(s => s.PlayState?.PlayMethod === 'Transcode')
const uptime = useMemo(() => { const uptime = useMemo(() => {
if (!info?.ServerStartTime) return null const start = (info as any)?.ServerStartTime
const ms = Date.now() - new Date(info.ServerStartTime).getTime() if (!start) return null
const ms = Date.now() - new Date(start).getTime()
const days = Math.floor(ms / 86_400_000) const days = Math.floor(ms / 86_400_000)
const hrs = Math.floor((ms % 86_400_000) / 3_600_000) const hrs = Math.floor((ms % 86_400_000) / 3_600_000)
return days > 0 ? `${days}d ${hrs}h` : `${hrs}h` return days > 0 ? `${days}d ${hrs}h` : `${hrs}h`
}, [info?.ServerStartTime]) }, [info])
return ( return (
<Section id="server-dashboard" title="Server dashboard" description="Live stats and activity"> <Section id="server-dashboard" title="Server dashboard" description="Live stats and activity">
@@ -62,7 +63,7 @@ export function ServerDashboardSection() {
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
<StatCard label="Version" value={info?.Version || '-'} icon={<ServerIcon size={12} className="text-accent" />} /> <StatCard label="Version" value={info?.Version || '-'} icon={<ServerIcon size={12} className="text-accent" />} />
<StatCard label="Uptime" value={uptime || '-'} icon={<Clock size={12} className="text-accent" />} /> <StatCard label="Uptime" value={uptime || '-'} icon={<Clock size={12} className="text-accent" />} />
<StatCard label="Active streams" value={String(activeSessions.length)} icon={<Monitor size={12} className="text-accent" />} /> <StatCard label="Active streams" value={String(activeSessions.length)} icon={<MonitorPlay size={12} className="text-accent" />} />
<StatCard label="Transcoding" value={String(transcodes.length)} icon={<Activity size={12} className="text-accent" />} /> <StatCard label="Transcoding" value={String(transcodes.length)} icon={<Activity size={12} className="text-accent" />} />
</div> </div>
+1 -1
View File
@@ -3,7 +3,7 @@ import { ExternalLink, Loader2, Check, Trash2 } from '../../../lib/icons'
import { Section, Row, Input, Toggle } from '../_ui' import { Section, Row, Input, Toggle } from '../_ui'
import { useTrakt } from '../../../stores/trakt-store' import { useTrakt } from '../../../stores/trakt-store'
import { useWatchlist } from '../../../hooks/use-watchlist' 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 { toast } from '../../../stores/toast-store'
import { useLibraryByTmdbId } from '../../../hooks/use-jellyfin' import { useLibraryByTmdbId } from '../../../hooks/use-jellyfin'
+7 -6
View File
@@ -20,7 +20,7 @@ export interface DownloadItem {
interface State { interface State {
items: DownloadItem[] items: DownloadItem[]
add: (item: Omit<DownloadItem, 'id' | 'status' | 'progress' | 'createdAt'>) => void add: (item: Omit<DownloadItem, 'id' | 'status' | 'progress' | 'createdAt'>) => DownloadItem
update: (id: string, patch: Partial<DownloadItem>) => void update: (id: string, patch: Partial<DownloadItem>) => void
remove: (id: string) => void remove: (id: string) => void
clearCompleted: () => void clearCompleted: () => void
@@ -35,9 +35,9 @@ export const useDownloads = create<State>()(
persist( persist(
(set, get) => ({ (set, get) => ({
items: [], items: [],
add: item => add: item => {
set(s => { const existing = get().items.find(i => i.itemId === item.itemId && i.status !== 'error')
if (s.items.some(i => i.itemId === item.itemId && i.status !== 'error')) return s if (existing) return existing
const next: DownloadItem = { const next: DownloadItem = {
...item, ...item,
id: uid(), id: uid(),
@@ -45,8 +45,9 @@ export const useDownloads = create<State>()(
progress: 0, progress: 0,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
} }
return { items: [...s.items, next] } set(s => ({ items: [...s.items, next] }))
}), return next
},
update: (id, patch) => update: (id, patch) =>
set(s => ({ set(s => ({
items: s.items.map(i => (i.id === id ? { ...i, ...patch } : i)), items: s.items.map(i => (i.id === id ? { ...i, ...patch } : i)),
+3
View File
@@ -6,5 +6,8 @@ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
build: { build: {
chunkSizeWarningLimit: 900, chunkSizeWarningLimit: 900,
rollupOptions: {
external: [/^@tauri-apps\/.*/],
},
}, },
}) })