feat(downloads): S1 UI quick wins — queue default, dates, auto-link, thumb fallback

- Always land on the Queue tab (drop the persisted last-tab) so new work/failures are the default view
- Show the download date (relative) on Library rows
- Share dialog opens with a default link already generated when none exists; all link controls stay available
- Thumbnail falls back to the self-hosted poster when a remote (i.ytimg/fbcdn) thumbnail 404s
- Add a clipboard-paste affordance to the add-URL box (explicit button + best-effort on focus)
This commit is contained in:
2026-07-28 01:21:50 +02:00
parent 0d655e6f88
commit 3f15f35256
4 changed files with 72 additions and 9 deletions
+14 -2
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { qk } from "../lib/queryKeys";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -271,6 +271,18 @@ function LinkShare({ job }: { job: DownloadJob }) {
},
});
// Open with a link already generated: sharing is almost always the goal, so mint a default
// (no password, no expiry, view-only) link the moment the dialog finds none — as if the user
// had pressed "New link" → "Create". All further controls (password, download toggle, expiry,
// extra links, revoke) stay available on the created link and via the "New link" button.
const autoTried = useRef(false);
useEffect(() => {
if (links.data && links.data.length === 0 && !autoTried.current && !create.isPending) {
autoTried.current = true;
create.mutate();
}
}, [links.data, create]);
return (
<div>
<div className="flex items-center gap-2 text-sm font-medium mb-1">
@@ -282,7 +294,7 @@ function LinkShare({ job }: { job: DownloadJob }) {
{(links.data ?? []).map((l) => (
<LinkRow key={l.id} link={l} onChanged={invalidate} />
))}
{links.data && links.data.length === 0 && !creating && (
{links.data && links.data.length === 0 && !creating && !create.isPending && (
<p className="text-xs text-muted">{t("downloads.share.noLinks")}</p>
)}
</div>