feat: Takeout walkthrough, design-system restyle, replacing import
Adds a seven-step in-app guide to exporting subscriptions from Google Takeout, with the real URLs opened in the system browser. Restyles the app onto the supplied design system: slate/sky palette, 9-15px type ladder, outline-first controls, tiered radii, borders for separation and shadows only for elevation. Light and dark are both designed, with a System/Light/Dark control and a pre-paint script so the window does not flash light on a dark machine. Importing now replaces the subscription list rather than merging, per request. Because that can delete downloaded files, a confirmation dialog names exactly what will go first.
This commit is contained in:
+26
-19
@@ -1,6 +1,7 @@
|
||||
import { fileUrl } from "../api";
|
||||
import type { FeedItem } from "../types";
|
||||
import { compactViews, relativeTime } from "./format";
|
||||
import { BTN, BTN_CHROME } from "./ui";
|
||||
|
||||
interface Props {
|
||||
item: FeedItem;
|
||||
@@ -15,40 +16,46 @@ interface Props {
|
||||
*/
|
||||
export default function Player({ item, path, onClose, onDelete }: Props) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 bg-ink/97 flex flex-col">
|
||||
<div className="flex items-center gap-3 px-5 py-3 border-b border-edge">
|
||||
<button onClick={onClose}
|
||||
className="rounded-full px-3 py-1.5 text-xs font-medium bg-surface hover:bg-raised cursor-pointer">
|
||||
<div className="fixed inset-0 z-50 flex flex-col bg-slate-100 dark:bg-slate-950">
|
||||
<header
|
||||
className="flex items-center gap-2 border-b border-slate-200 bg-white px-4 py-3
|
||||
dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<button onClick={onClose} className={`${BTN} cursor-pointer py-1.5`}>
|
||||
← Back
|
||||
</button>
|
||||
<span className="text-sm text-muted truncate flex-1">{item.channel_title}</span>
|
||||
<button onClick={onDelete}
|
||||
className="rounded-full px-3 py-1.5 text-xs font-medium bg-surface text-muted
|
||||
hover:bg-red-500/20 hover:text-red-300 cursor-pointer">
|
||||
<span className="min-w-0 flex-1 truncate text-[12px] text-slate-500 dark:text-slate-400">
|
||||
{item.channel_title}
|
||||
</span>
|
||||
<button
|
||||
onClick={onDelete}
|
||||
className={`${BTN_CHROME} cursor-pointer hover:bg-red-500/10! hover:text-red-600! dark:hover:text-red-400!`}
|
||||
>
|
||||
Delete download
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Absolute fill + object-contain, so portrait Shorts and landscape
|
||||
videos are both letterboxed to the pane instead of overflowing it. */}
|
||||
<div className="flex-1 min-h-0 bg-black relative">
|
||||
<div className="relative min-h-0 flex-1 bg-slate-950">
|
||||
<video key={path} src={fileUrl(path)} controls autoPlay
|
||||
className="absolute inset-0 h-full w-full object-contain" />
|
||||
className="absolute inset-0 size-full object-contain" />
|
||||
</div>
|
||||
|
||||
<div className="px-5 py-4 max-h-56 overflow-y-auto border-t border-edge">
|
||||
<h2 className="font-semibold text-lg leading-snug">{item.title}</h2>
|
||||
<div className="mt-1 text-xs text-muted">
|
||||
{[compactViews(item.views), relativeTime(item.published)]
|
||||
.filter(Boolean)
|
||||
.join(" · ")}
|
||||
<footer
|
||||
className="max-h-52 overflow-y-auto border-t border-slate-200 bg-white px-4 py-4
|
||||
dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<h2 className="text-[15px] font-semibold leading-snug tracking-tight">{item.title}</h2>
|
||||
<div className="mt-1 text-[11px] text-slate-400 dark:text-slate-500">
|
||||
{[compactViews(item.views), relativeTime(item.published)].filter(Boolean).join(" · ")}
|
||||
</div>
|
||||
{item.description && (
|
||||
<p className="mt-3 text-sm text-muted whitespace-pre-wrap leading-relaxed">
|
||||
<p className="mt-3 whitespace-pre-wrap text-[12.5px] leading-relaxed text-slate-600 dark:text-slate-300">
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user