mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-02 12:26:36 +08:00
处理bug
This commit is contained in:
@@ -28,7 +28,7 @@ export function AdminLibraryTable({ libs, ...actions }: LibraryTableProps) {
|
||||
const [browsingRoot, setBrowsingRoot] = useState<{ libraryID: string; root: LibraryRoot; initialPath?: string } | null>(null)
|
||||
const [addingRootLib, setAddingRootLib] = useState<Library | null>(null)
|
||||
const [draggingId, setDraggingId] = useState<string | null>(null)
|
||||
const dragOverId = useRef<string | null>(null)
|
||||
const [dragOverId, setDragOverId] = useState<string | null>(null)
|
||||
|
||||
const handleSelectRootPath = (selectedPath: string) => {
|
||||
if (browsingRoot) {
|
||||
@@ -59,7 +59,7 @@ export function AdminLibraryTable({ libs, ...actions }: LibraryTableProps) {
|
||||
|
||||
const handleDrop = (e: DragEvent, overId: string) => {
|
||||
e.preventDefault()
|
||||
dragOverId.current = null
|
||||
setDragOverId(null)
|
||||
setDraggingId(null)
|
||||
if (draggingId && overId !== draggingId) {
|
||||
handleReorder(draggingId, overId)
|
||||
@@ -90,19 +90,19 @@ export function AdminLibraryTable({ libs, ...actions }: LibraryTableProps) {
|
||||
onBrowseRoot={(root) => setBrowsingRoot({ libraryID: library.id, root, initialPath: root.path })}
|
||||
onOpenAddRoot={() => setAddingRootLib(library)}
|
||||
dragging={draggingId === library.id}
|
||||
dragOver={dragOverId.current === library.id}
|
||||
dragOver={dragOverId === library.id}
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
dragOverId.current = null
|
||||
setDragOverId(null)
|
||||
setDraggingId(library.id)
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
if (dragOverId.current !== library.id) dragOverId.current = library.id
|
||||
if (dragOverId !== library.id) setDragOverId(library.id)
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
dragOverId.current = null
|
||||
setDragOverId(null)
|
||||
setDraggingId(null)
|
||||
}}
|
||||
onDrop={(e) => handleDrop(e, library.id)}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Database, HardDrive, PieChart, RefreshCw } from 'lucide-react'
|
||||
|
||||
import { storageAPI, type StorageBreakdown } from '../api/storage'
|
||||
|
||||
export function fmtBytes(n: number): string {
|
||||
function fmtBytes(n: number): string {
|
||||
if (!n) return '0 B'
|
||||
const u = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
|
||||
let v = n
|
||||
@@ -15,7 +15,7 @@ export function fmtBytes(n: number): string {
|
||||
return `${v.toFixed(2)} ${u[i]}`
|
||||
}
|
||||
|
||||
export function fmtHours(seconds: number): string {
|
||||
function fmtHours(seconds: number): string {
|
||||
if (!seconds) return '—'
|
||||
const h = Math.floor(seconds / 3600)
|
||||
return `${h.toLocaleString()} h`
|
||||
|
||||
@@ -311,7 +311,10 @@ export function PlayerPage() {
|
||||
// Chromium 会把被中断的首次加载报告成播放错误并误触发 HLS 回退。
|
||||
const activeBurnedSubtitleStream = mode === 'hls' ? burnedSubtitleStream : undefined
|
||||
const mediaRef = useRef(media)
|
||||
mediaRef.current = media
|
||||
useEffect(() => {
|
||||
mediaRef.current = media
|
||||
}, [media])
|
||||
|
||||
useEffect(() => {
|
||||
if (!mediaId || !ref.current) return
|
||||
const currentMedia = mediaRef.current
|
||||
@@ -498,9 +501,11 @@ export function PlayerPage() {
|
||||
|
||||
// 使用 ref 实时同步进度计算所需的状态,避免每次 hlsStartSec 改变都触发 cleanup 并误上报旧进度
|
||||
const hlsStartSecRef = useRef(hlsStartSec)
|
||||
hlsStartSecRef.current = hlsStartSec
|
||||
const modeRef = useRef(mode)
|
||||
modeRef.current = mode
|
||||
useEffect(() => {
|
||||
hlsStartSecRef.current = hlsStartSec
|
||||
modeRef.current = mode
|
||||
}, [hlsStartSec, mode])
|
||||
|
||||
// Persist resume position every 10 seconds while playing, and immediately upon pause/unmount.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -23,7 +23,8 @@ import { imageURL } from '../api/client'
|
||||
import { scraperAPI } from '../api/scraper'
|
||||
import type { ScrapeQueueSnapshot, ScrapeTask, ScrapeTaskStatus } from '../types/scraper'
|
||||
import { apiErrorMessage, formatTime, taskStatusMeta } from './StrmManagePage'
|
||||
import { PROVIDER_LABELS, ScrapeDetailModal } from './scraper-queue/ScrapeDetailModal'
|
||||
import { ScrapeDetailModal } from './scraper-queue/ScrapeDetailModal'
|
||||
import { PROVIDER_LABELS } from './scraper-queue/scrapeLabels'
|
||||
import { copyToClipboard, useTaskSelection } from './queue-shared'
|
||||
|
||||
const FILTERS: { key: 'all' | ScrapeTaskStatus; label: string; icon: typeof Clock; color: string }[] = [
|
||||
@@ -196,9 +197,8 @@ export function ScraperQueuePage({ embedded = false }: { embedded?: boolean }) {
|
||||
}
|
||||
|
||||
// Filter and search
|
||||
const tasks = snapshot?.tasks ?? []
|
||||
const filteredTasks = useMemo(() => {
|
||||
let list = tasks
|
||||
let list = snapshot?.tasks ?? []
|
||||
if (filter !== 'all') {
|
||||
list = list.filter((t) => t.status === filter)
|
||||
}
|
||||
@@ -214,7 +214,7 @@ export function ScraperQueuePage({ embedded = false }: { embedded?: boolean }) {
|
||||
)
|
||||
}
|
||||
return list
|
||||
}, [tasks, filter, search])
|
||||
}, [snapshot?.tasks, filter, search])
|
||||
|
||||
const counts = snapshot?.counts
|
||||
const pendingCount = counts?.pending ?? 0
|
||||
|
||||
@@ -21,7 +21,8 @@ import { strmAPI } from '../api/strm'
|
||||
import type { StrmQueueSnapshot, StrmTask, StrmTaskStatus } from '../types/strm'
|
||||
import { STRM_PROVIDER_LABELS } from '../types/strm'
|
||||
import { apiErrorMessage, formatBytes, formatTime, taskStatusMeta } from './StrmManagePage'
|
||||
import { TaskDetailModal, getFileIcon } from './strm-queue/TaskDetailModal'
|
||||
import { TaskDetailModal } from './strm-queue/TaskDetailModal'
|
||||
import { getFileIcon } from './strm-queue/taskFileIcon'
|
||||
import { copyToClipboard, useTaskSelection } from './queue-shared'
|
||||
|
||||
const FILTERS: { key: 'all' | StrmTaskStatus; label: string; icon: typeof Clock; color: string }[] = [
|
||||
@@ -190,9 +191,8 @@ export function StrmQueuePanel({
|
||||
}
|
||||
|
||||
// Filter and search tasks in memory
|
||||
const tasks = snapshot?.tasks ?? []
|
||||
const filteredTasks = useMemo(() => {
|
||||
let list = tasks
|
||||
let list = snapshot?.tasks ?? []
|
||||
if (filter !== 'all') {
|
||||
list = list.filter((t) => t.status === filter)
|
||||
}
|
||||
@@ -207,7 +207,7 @@ export function StrmQueuePanel({
|
||||
)
|
||||
}
|
||||
return list
|
||||
}, [tasks, filter, search])
|
||||
}, [snapshot?.tasks, filter, search])
|
||||
|
||||
const counts = snapshot?.counts
|
||||
const pendingCount = counts?.pending ?? 0
|
||||
|
||||
@@ -4,21 +4,7 @@ import { AlertCircle, Ban, Copy, ExternalLink, RefreshCw, Sparkles, Trash2, X }
|
||||
import { imageURL } from '../../api/client'
|
||||
import type { ScrapeTask } from '../../types/scraper'
|
||||
import { formatTime, taskStatusMeta } from '../StrmManagePage'
|
||||
|
||||
export const PROVIDER_LABELS: Record<string, string> = {
|
||||
tmdb: 'TheMovieDB',
|
||||
douban: '豆瓣 Douban',
|
||||
bangumi: 'Bangumi 番组计划',
|
||||
thetvdb: 'TheTVDB',
|
||||
metatube: 'MetaTube',
|
||||
}
|
||||
|
||||
export const TYPE_LABELS: Record<string, string> = {
|
||||
movie: '电影',
|
||||
tv: '剧集',
|
||||
anime: '动漫',
|
||||
adult: 'Adult',
|
||||
}
|
||||
import { PROVIDER_LABELS, TYPE_LABELS } from './scrapeLabels'
|
||||
|
||||
export function ScrapeDetailModal({
|
||||
task,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export const PROVIDER_LABELS: Record<string, string> = {
|
||||
tmdb: 'TheMovieDB',
|
||||
douban: '豆瓣 Douban',
|
||||
bangumi: 'Bangumi 番组计划',
|
||||
thetvdb: 'TheTVDB',
|
||||
metatube: 'MetaTube',
|
||||
}
|
||||
|
||||
export const TYPE_LABELS: Record<string, string> = {
|
||||
movie: '电影',
|
||||
tv: '剧集',
|
||||
anime: '动漫',
|
||||
adult: 'Adult',
|
||||
}
|
||||
@@ -1,26 +1,9 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { AlertCircle, Ban, Copy, FileText, Film, Image as ImageIcon, File, MessageSquare, RefreshCw, Trash2, X } from 'lucide-react'
|
||||
import { AlertCircle, Ban, Copy, RefreshCw, Trash2, X } from 'lucide-react'
|
||||
|
||||
import type { StrmTask } from '../../types/strm'
|
||||
import { STRM_PROVIDER_LABELS } from '../../types/strm'
|
||||
import { formatBytes, formatTime, taskStatusMeta } from '../StrmManagePage'
|
||||
|
||||
export function getFileIcon(filename: string): ReactNode {
|
||||
const ext = filename.split('.').pop()?.toLowerCase() ?? ''
|
||||
if (['nfo', 'txt', 'xml', 'json'].includes(ext)) {
|
||||
return <FileText size={15} className="text-amber-500 shrink-0" />
|
||||
}
|
||||
if (['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'svg'].includes(ext)) {
|
||||
return <ImageIcon size={15} className="text-blue-500 shrink-0" />
|
||||
}
|
||||
if (['srt', 'ass', 'ssa', 'sub', 'vtt'].includes(ext)) {
|
||||
return <MessageSquare size={15} className="text-purple-500 shrink-0" />
|
||||
}
|
||||
if (['mkv', 'mp4', 'avi', 'mov', 'wmv', 'ts', 'flv', 'iso', 'm4v', 'strm'].includes(ext)) {
|
||||
return <Film size={15} className="text-emerald-500 shrink-0" />
|
||||
}
|
||||
return <File size={15} className="text-gray-400 shrink-0" />
|
||||
}
|
||||
import { getFileIcon } from './taskFileIcon'
|
||||
|
||||
export function TaskDetailModal({
|
||||
task,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { File, FileText, Film, Image as ImageIcon, MessageSquare } from 'lucide-react'
|
||||
|
||||
export function getFileIcon(filename: string): ReactNode {
|
||||
const ext = filename.split('.').pop()?.toLowerCase() ?? ''
|
||||
if (['nfo', 'txt', 'xml', 'json'].includes(ext)) {
|
||||
return <FileText size={15} className="text-amber-500 shrink-0" />
|
||||
}
|
||||
if (['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'svg'].includes(ext)) {
|
||||
return <ImageIcon size={15} className="text-blue-500 shrink-0" />
|
||||
}
|
||||
if (['srt', 'ass', 'ssa', 'sub', 'vtt'].includes(ext)) {
|
||||
return <MessageSquare size={15} className="text-purple-500 shrink-0" />
|
||||
}
|
||||
if (['mkv', 'mp4', 'avi', 'mov', 'wmv', 'ts', 'flv', 'iso', 'm4v', 'strm'].includes(ext)) {
|
||||
return <Film size={15} className="text-emerald-500 shrink-0" />
|
||||
}
|
||||
return <File size={15} className="text-gray-400 shrink-0" />
|
||||
}
|
||||
@@ -180,7 +180,7 @@ function useLibraryActions(refresh: () => Promise<void>) {
|
||||
}
|
||||
|
||||
const toggleCarouselLibrary = async (library: Library) => {
|
||||
const next = !Boolean(library.carousel_enabled)
|
||||
const next = !library.carousel_enabled
|
||||
await libraryAPI.update(library.id, { carousel_enabled: next })
|
||||
toast.success(next ? `「${library.name}」已加入首页轮播` : `「${library.name}」已移出首页轮播`)
|
||||
await refresh()
|
||||
|
||||
Reference in New Issue
Block a user