myYouTube/frontend/src/components/DownloadButton.tsx

114 lines
3.9 KiB
TypeScript
Raw Normal View History

import { useEffect, useRef, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { FeedVideoDto } from '../api/client'
import { deleteDownload, downloadVideo, getDownloadStatus } from '../api/client'
const ACTIVE_STATUSES = ['queued', 'downloading', 'postprocessing']
const STATUS_LABELS: Record<string, string> = {
queued: 'В очереди...',
postprocessing: 'Обработка...',
}
interface Props {
video: FeedVideoDto
}
function DownloadButton({ video }: Props) {
const queryClient = useQueryClient()
// Once we start tracking a download (it was already active on load, or the
// user just clicked "Скачать"), the query stays enabled for good --
// refetchInterval alone stops the ticking once the status is terminal.
// Disabling the query on completion used to make the button fall back to
// the *stale* `video` prop instead of the query's own fresh cache, which
// showed "Скачать" again until the page was manually reloaded.
const [tracking, setTracking] = useState(ACTIVE_STATUSES.includes(video.local.status))
const statusQuery = useQuery({
queryKey: ['download-status', video.youtube_video_id],
queryFn: () => getDownloadStatus(video.youtube_video_id),
enabled: tracking,
refetchInterval: (query) =>
query.state.data && ACTIVE_STATUSES.includes(query.state.data.status) ? 2000 : false,
})
const downloadMutation = useMutation({
mutationFn: () => downloadVideo(video.youtube_video_id),
onSuccess: (data) => {
queryClient.setQueryData(['download-status', video.youtube_video_id], data)
setTracking(true)
},
})
const deleteMutation = useMutation({
mutationFn: () => deleteDownload(video.youtube_video_id),
onSuccess: (data) => {
queryClient.setQueryData(['download-status', video.youtube_video_id], data)
},
})
const status = statusQuery.data?.status ?? video.local.status
const percent = statusQuery.data?.progress_percent ?? video.local.progress_percent
const prevStatus = useRef(status)
useEffect(() => {
if (prevStatus.current !== status) {
prevStatus.current = status
if (status === 'completed' || status === 'failed' || status === 'deleted') {
queryClient.invalidateQueries({ queryKey: ['feed'] })
queryClient.invalidateQueries({ queryKey: ['video', video.youtube_video_id] })
}
}
}, [status, queryClient, video.youtube_video_id])
if (status === 'completed') {
return (
<span className="download-inline-actions">
<span className="download-badge completed">✓ На сервере</span>
<button
onClick={() => {
if (confirm('Удалить скачанную копию с сервера?')) {
deleteMutation.mutate()
}
}}
disabled={deleteMutation.isPending}
>
Удалить
</button>
{deleteMutation.isError && (
<span className="download-badge error" title={(deleteMutation.error as Error).message}>
{(deleteMutation.error as Error).message}
</span>
)}
</span>
)
}
if (status === 'downloading') {
return <span className="download-badge">{percent ?? 0}%</span>
}
if (status === 'queued' || status === 'postprocessing') {
return <span className="download-badge">{STATUS_LABELS[status]}</span>
}
if (status === 'failed') {
return (
<span className="download-error">
<span className="download-badge error">Ошибка</span>
<button onClick={() => downloadMutation.mutate()} disabled={downloadMutation.isPending}>
Повторить
</button>
</span>
)
}
return (
<button onClick={() => downloadMutation.mutate()} disabled={downloadMutation.isPending}>
⬇ Скачать
</button>
)
}
export default DownloadButton