add download buttons to tv

This commit is contained in:
Garret Patti
2026-04-15 08:30:41 -04:00
parent 37dcb79546
commit 5b5c3453d2
4 changed files with 43 additions and 24 deletions

View File

@@ -325,17 +325,19 @@ export default function ImageLightbox({ url, name, onClose, onPrev, onNext, item
🏷 🏷
</button> </button>
)} )}
<button {!showTags && (
onClick={onClose} <button
className={smallBtn} onClick={onClose}
style={{ backgroundColor: 'var(--surface)', color: 'var(--text-primary)' }} className={smallBtn}
onMouseEnter={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface-hover)'} style={{ backgroundColor: 'var(--surface)', color: 'var(--text-primary)' }}
onMouseLeave={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface)'} onMouseEnter={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface-hover)'}
aria-label="Close" onMouseLeave={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface)'}
title="Close" aria-label="Close"
> title="Close"
>
</button>
</button>
)}
</div> </div>
{/* Text display button — bottom-right, hidden when panel open */} {/* Text display button — bottom-right, hidden when panel open */}

View File

@@ -100,17 +100,19 @@ export default function VideoPlayerModal({ url, name, onClose, onPrev, onNext, i
🏷 🏷
</button> </button>
)} )}
<button {!showTags && (
onClick={onClose} <button
className={smallBtn} onClick={onClose}
style={{ backgroundColor: 'var(--surface)', color: 'var(--text-primary)' }} className={smallBtn}
onMouseEnter={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface-hover)'} style={{ backgroundColor: 'var(--surface)', color: 'var(--text-primary)' }}
onMouseLeave={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface)'} onMouseEnter={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface-hover)'}
aria-label="Close" onMouseLeave={(e) => (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--surface)'}
title="Close" aria-label="Close"
> title="Close"
>
</button>
</button>
)}
</div> </div>
</div> </div>

View File

@@ -9,9 +9,10 @@ interface Props {
onTag?: () => void onTag?: () => void
onDelete?: () => void onDelete?: () => void
onRename?: (newName: string) => Promise<boolean> onRename?: (newName: string) => Promise<boolean>
downloadUrl?: string
} }
export default function EpisodeCard({ episode, onClick, onTag, onDelete, onRename }: Props) { export default function EpisodeCard({ episode, onClick, onTag, onDelete, onRename, downloadUrl }: Props) {
const epLabel = episode.episodeNumber !== null ? `E${String(episode.episodeNumber).padStart(2, '0')}` : null const epLabel = episode.episodeNumber !== null ? `E${String(episode.episodeNumber).padStart(2, '0')}` : null
const menuRef = useRef<HTMLDivElement>(null) const menuRef = useRef<HTMLDivElement>(null)
const [menuOpen, setMenuOpen] = useState(false) const [menuOpen, setMenuOpen] = useState(false)
@@ -79,7 +80,7 @@ export default function EpisodeCard({ episode, onClick, onTag, onDelete, onRenam
</button> </button>
)} )}
{/* Kebab menu */} {/* Kebab menu */}
{onDelete && ( {(onDelete || downloadUrl) && (
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity hidden group-hover:block" ref={menuRef}> <div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity hidden group-hover:block" ref={menuRef}>
<button <button
onClick={(e) => { e.stopPropagation(); setMenuOpen((o) => !o); setConfirming(false) }} onClick={(e) => { e.stopPropagation(); setMenuOpen((o) => !o); setConfirming(false) }}
@@ -94,6 +95,19 @@ export default function EpisodeCard({ episode, onClick, onTag, onDelete, onRenam
className="absolute right-0 top-full mt-1 rounded-lg shadow-lg overflow-hidden z-20 min-w-max" className="absolute right-0 top-full mt-1 rounded-lg shadow-lg overflow-hidden z-20 min-w-max"
style={{ backgroundColor: 'var(--surface)', border: '1px solid var(--border)' }} style={{ backgroundColor: 'var(--surface)', border: '1px solid var(--border)' }}
> >
{downloadUrl && (
<a
href={downloadUrl}
download
onClick={(e) => { e.stopPropagation(); setMenuOpen(false) }}
className="flex items-center gap-2 w-full px-4 py-2 text-sm text-left transition-colors"
style={{ color: 'var(--text-primary)' }}
onMouseEnter={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'var(--border)')}
onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'transparent')}
>
Download
</a>
)}
{onRename && ( {onRename && (
<button <button
onClick={(e) => { onClick={(e) => {

View File

@@ -931,6 +931,7 @@ export default function TvView({ libraryId }: Props) {
episode={ep} episode={ep}
onClick={() => setPlayingEpisodeIndex(episodes.indexOf(ep))} onClick={() => setPlayingEpisodeIndex(episodes.indexOf(ep))}
onTag={() => { setTagPanelItemKey(ep.item_key!); setTagPanelDisabled(false); setShowTagPanel(true) }} onTag={() => { setTagPanelItemKey(ep.item_key!); setTagPanelDisabled(false); setShowTagPanel(true) }}
downloadUrl={`/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(ep.videoPath)}`}
onDelete={() => { onDelete={() => {
fetch( fetch(
`/api/tv?libraryId=${encodeURIComponent(libraryId)}&seriesId=${encodeURIComponent(selectedSeries!.id)}&episodeKey=${encodeURIComponent(ep.item_key!)}`, `/api/tv?libraryId=${encodeURIComponent(libraryId)}&seriesId=${encodeURIComponent(selectedSeries!.id)}&episodeKey=${encodeURIComponent(ep.item_key!)}`,