Simplify CC dropdown: downloaded langs at top, YouTube langs below

Single CC chip shows downloaded langs inline. One click opens a dropdown
with optgroups — already-on-disk at top, YouTube-available below loading
async. No re-download needed to select an already-downloaded lang.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 21:25:41 +02:00
parent da765ce76e
commit 6311b90b21

View File

@@ -671,8 +671,8 @@ export default function Watch() {
const { data: subtitleFiles = [] } = useQuery({
queryKey: ["subtitle-files", youtubeVideoId],
queryFn: () => getSubtitleFiles(youtubeVideoId).then(r => r.data),
enabled: fileReady,
staleTime: Infinity,
enabled: !!youtubeVideoId,
staleTime: 60_000,
});
const { data: dlStatus } = useQuery({
@@ -995,41 +995,22 @@ export default function Watch() {
)}
{(() => {
// Subs already on disk → show langs + "+" to add more
if (subtitleFiles.length > 0 && !subsRequested) return (
<div className="flex items-center gap-1">
<span className="px-3 py-1.5 rounded-full bg-zinc-800 text-zinc-400 text-xs font-mono">
CC: {subtitleFiles.map(s => s.lang).join(", ")}
</span>
<button
onClick={() => setSubsRequested(true)}
className="px-2.5 py-1.5 rounded-full text-xs bg-zinc-800 text-zinc-500 hover:bg-zinc-700 transition-colors"
title="Add subtitle language"
>+</button>
</div>
);
// Not yet asked → show CC chip
const onDisk = subtitleFiles.map(s => s.lang);
const onDiskSet = new Set(onDisk);
if (!subsRequested) return (
<button
onClick={() => setSubsRequested(true)}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium bg-zinc-800 text-zinc-400 hover:bg-zinc-700 transition-colors"
title="Check available subtitles on YouTube"
>
CC
CC{onDisk.length > 0 ? ` · ${onDisk.join(", ")}` : ""}
</button>
);
// Loading YouTube subtitle list
if (subsLoading) return (
<span className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-zinc-800 text-zinc-500 text-xs">
<span className="w-3 h-3 border border-zinc-600 border-t-transparent rounded-full animate-spin inline-block" />
CC
</span>
);
const manual = new Set(availableSubs?.manual ?? []);
const auto = (availableSubs?.auto ?? []).filter(l => !manual.has(l));
if (!manual.size && !auto.length) return (
<span className="px-3 py-1.5 rounded-full bg-zinc-800 text-zinc-600 text-xs">No CC</span>
);
const ytManual = (availableSubs?.manual ?? []).filter(l => !onDiskSet.has(l));
const ytAuto = (availableSubs?.auto ?? []).filter(l => !onDiskSet.has(l) && !ytManual.includes(l));
const needsDownload = selectedSubLang && !onDiskSet.has(selectedSubLang);
return (
<>
<select
@@ -1038,18 +1019,25 @@ export default function Watch() {
className="bg-zinc-800 text-zinc-300 text-xs rounded-full px-3 py-2 border border-zinc-700 focus:outline-none focus:border-accent"
>
<option value="">No subtitles</option>
{[...manual].map(l => <option key={l} value={l}>{l}</option>)}
{auto.map(l => <option key={l} value={l}>{l} (auto)</option>)}
{onDisk.length > 0 && (
<optgroup label="— Downloaded —">
{onDisk.map(l => <option key={l} value={l}>{l}</option>)}
</optgroup>
)}
{(subsLoading || ytManual.length > 0 || ytAuto.length > 0) && (
<optgroup label={subsLoading ? "— Loading YouTube… —" : "— Available on YouTube —"}>
{ytManual.map(l => <option key={l} value={l}>{l}</option>)}
{ytAuto.map(l => <option key={l} value={l}>{l} (auto)</option>)}
</optgroup>
)}
</select>
{dlComplete && selectedSubLang && (
{dlComplete && needsDownload && (
<button
onClick={() => addSubsMut.mutate()}
disabled={addSubsMut.isPending}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium bg-accent text-black hover:bg-yellow-300 transition-colors disabled:opacity-50"
>
{addSubsMut.isPending ? (
<span className="w-3 h-3 border-2 border-black/40 border-t-transparent rounded-full animate-spin inline-block" />
) : null}
{addSubsMut.isPending && <span className="w-3 h-3 border-2 border-black/40 border-t-transparent rounded-full animate-spin inline-block" />}
{addSubsMut.isPending ? "Fetching…" : "Add subtitles"}
</button>
)}