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:
@@ -671,8 +671,8 @@ export default function Watch() {
|
|||||||
const { data: subtitleFiles = [] } = useQuery({
|
const { data: subtitleFiles = [] } = useQuery({
|
||||||
queryKey: ["subtitle-files", youtubeVideoId],
|
queryKey: ["subtitle-files", youtubeVideoId],
|
||||||
queryFn: () => getSubtitleFiles(youtubeVideoId).then(r => r.data),
|
queryFn: () => getSubtitleFiles(youtubeVideoId).then(r => r.data),
|
||||||
enabled: fileReady,
|
enabled: !!youtubeVideoId,
|
||||||
staleTime: Infinity,
|
staleTime: 60_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: dlStatus } = useQuery({
|
const { data: dlStatus } = useQuery({
|
||||||
@@ -995,41 +995,22 @@ export default function Watch() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
// Subs already on disk → show langs + "+" to add more
|
const onDisk = subtitleFiles.map(s => s.lang);
|
||||||
if (subtitleFiles.length > 0 && !subsRequested) return (
|
const onDiskSet = new Set(onDisk);
|
||||||
<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
|
|
||||||
if (!subsRequested) return (
|
if (!subsRequested) return (
|
||||||
<button
|
<button
|
||||||
onClick={() => setSubsRequested(true)}
|
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"
|
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>
|
</button>
|
||||||
);
|
);
|
||||||
// Loading YouTube subtitle list
|
|
||||||
if (subsLoading) return (
|
const ytManual = (availableSubs?.manual ?? []).filter(l => !onDiskSet.has(l));
|
||||||
<span className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-zinc-800 text-zinc-500 text-xs">
|
const ytAuto = (availableSubs?.auto ?? []).filter(l => !onDiskSet.has(l) && !ytManual.includes(l));
|
||||||
<span className="w-3 h-3 border border-zinc-600 border-t-transparent rounded-full animate-spin inline-block" />
|
const needsDownload = selectedSubLang && !onDiskSet.has(selectedSubLang);
|
||||||
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>
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<select
|
<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"
|
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>
|
<option value="">No subtitles</option>
|
||||||
{[...manual].map(l => <option key={l} value={l}>{l}</option>)}
|
{onDisk.length > 0 && (
|
||||||
{auto.map(l => <option key={l} value={l}>{l} (auto)</option>)}
|
<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>
|
</select>
|
||||||
{dlComplete && selectedSubLang && (
|
{dlComplete && needsDownload && (
|
||||||
<button
|
<button
|
||||||
onClick={() => addSubsMut.mutate()}
|
onClick={() => addSubsMut.mutate()}
|
||||||
disabled={addSubsMut.isPending}
|
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"
|
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 ? (
|
{addSubsMut.isPending && <span className="w-3 h-3 border-2 border-black/40 border-t-transparent rounded-full animate-spin inline-block" />}
|
||||||
<span className="w-3 h-3 border-2 border-black/40 border-t-transparent rounded-full animate-spin inline-block" />
|
|
||||||
) : null}
|
|
||||||
{addSubsMut.isPending ? "Fetching…" : "Add subtitles"}
|
{addSubsMut.isPending ? "Fetching…" : "Add subtitles"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user