import { Check, LoaderCircle, Search, Sparkles, X } from 'lucide-react' import { imageURL } from '../api/client' import type { ManualScrapeCandidate } from '../api/library' import { candidateIDText, candidateKey, manualSearchProviders, toggleProvider, } from './ManualScrapeDialogModel' import { EpisodeArtworkToggle } from './EpisodeArtworkToggle' export function ManualScrapeDialogHeader({ title, targetCount, onClose, }: { title: string targetCount: number onClose: () => void }) { return (

手动搜索刮削

{title} · {targetCount > 1 ? `将应用到 ${targetCount} 个媒体` : '单个媒体'}

) } interface ManualScrapeSearchControlsProps { query: string selectedProviders: string[] searching: boolean includeEpisodeArtwork: boolean showEpisodeArtworkToggle: boolean onQueryChange: (value: string) => void onProviderChange: (value: string[] | ((current: string[]) => string[])) => void onSearch: () => void onEpisodeArtworkChange: (checked: boolean) => void } export function ManualScrapeSearchControls({ query, selectedProviders, searching, includeEpisodeArtwork, showEpisodeArtworkToggle, onQueryChange, onProviderChange, onSearch, onEpisodeArtworkChange, }: ManualScrapeSearchControlsProps) { return (
{showEpisodeArtworkToggle && (
)}
) } function ProviderSelector({ selectedProviders, onProviderChange, }: { selectedProviders: string[] onProviderChange: (value: string[] | ((current: string[]) => string[])) => void }) { return (
刮削源
{manualSearchProviders.map((item) => { const active = selectedProviders.includes(item.value) return ( ) })}
) } function ManualScrapeQueryBar({ query, searching, onQueryChange, onSearch, }: { query: string searching: boolean onQueryChange: (value: string) => void onSearch: () => void }) { return (
onQueryChange(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') onSearch() }} className="h-11 w-full rounded-xl border border-sand-200 bg-white pl-9 pr-3 text-sm font-semibold text-ink-600 outline-none transition focus:border-brand-300 focus:ring-2 focus:ring-brand-100" placeholder="输入标题或 TMDb / 豆瓣 / Bangumi / TheTVDB ID" />
) } export function ManualScrapeCandidateList({ items, applyingKey, onApply, }: { items: ManualScrapeCandidate[] applyingKey: string onApply: (item: ManualScrapeCandidate) => void }) { if (items.length === 0) { return (
搜索后在这里选择正确的元数据
) } return (
{items.map((item) => { const key = candidateKey(item) return ( ) })}
) } function ManualScrapeCandidateRow({ item, applying, disabled, onApply, }: { item: ManualScrapeCandidate applying: boolean disabled: boolean onApply: (item: ManualScrapeCandidate) => void }) { return (
{item.poster_url ? ( {item.title} ) : (
无海报
)}

{item.title}

{item.source} {item.nsfw ? 成人 : null} {item.year ? {item.year} : null}

{item.overview || '暂无简介'}

{candidateIDText(item)}

) } function providerButtonClass(active: boolean): string { return ( 'inline-flex h-9 min-w-0 shrink-0 items-center justify-center gap-1.5 rounded-lg border px-3 text-xs font-bold transition ' + (active ? 'border-brand-300 bg-brand-50 text-brand-700' : 'border-sand-200 bg-white text-sand-600 hover:border-brand-200 hover:text-brand-600') ) }