feat: custom transport bar, live refresh, collapsible sidebar
Replace WebKit's native video controls with our own. WebKit puts fullscreen, Picture-in-Picture and volume as overlay buttons in the video's top corners with no way to move them; owning the bar is the only way to get every control into one strip along the bottom. Right-click on the player is suppressed — its menu acted on a video the app does not control. One spinner now covers both waits: resolving the stream URL and buffering it. It no longer sticks after a resume-seek, which fires 'waiting' after 'playing'. Also: Open on YouTube and a new Download are real buttons in the player; refresh is a compact icon; Settings is a cog; the subscription sidebar collapses and reappears on a left-edge hover; the feed auto-refreshes every 10 minutes while online, skipped while the player is open; the title-bar strip takes the panel colour, since it sits above panels rather than the page; and downloaded-only, hide-Shorts and the sidebar state are remembered between launches.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { BTN_PRIMARY, INPUT, Segmented } from "./ui";
|
||||
import { INPUT, Segmented } from "./ui";
|
||||
|
||||
export type ViewMode = "list" | "grid";
|
||||
|
||||
@@ -19,6 +19,8 @@ interface Props {
|
||||
resultCount: number;
|
||||
view: ViewMode;
|
||||
onView: (v: ViewMode) => void;
|
||||
sidebarHidden: boolean;
|
||||
onShowSidebar: () => void;
|
||||
}
|
||||
|
||||
/** Neutral outline until active; active is the one filled state. */
|
||||
@@ -54,6 +56,7 @@ export default function TopBar({
|
||||
search, onSearch, downloadedOnly, onDownloadedOnly, hideShorts, onHideShorts,
|
||||
online, reachable, forcedOffline, onToggleForcedOffline,
|
||||
onRefresh, refreshing, refreshProgress, resultCount, view, onView,
|
||||
sidebarHidden, onShowSidebar,
|
||||
}: Props) {
|
||||
const pct = refreshProgress && refreshProgress.total > 0
|
||||
? (refreshProgress.done / refreshProgress.total) * 100
|
||||
@@ -65,6 +68,21 @@ export default function TopBar({
|
||||
dark:border-slate-800 dark:bg-slate-900/95"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2 px-4 py-3">
|
||||
{sidebarHidden && (
|
||||
<button
|
||||
onClick={onShowSidebar}
|
||||
title="Show subscriptions"
|
||||
aria-label="Show subscriptions"
|
||||
className="grid size-[30px] shrink-0 cursor-pointer place-items-center rounded-lg
|
||||
border border-slate-300 text-slate-500 hover:border-sky-500
|
||||
hover:text-sky-600 dark:border-slate-700 dark:text-slate-400
|
||||
dark:hover:border-sky-500 dark:hover:text-sky-400"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="size-4" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<input
|
||||
value={search}
|
||||
onChange={(e) => onSearch(e.target.value)}
|
||||
@@ -120,13 +138,22 @@ export default function TopBar({
|
||||
</button>
|
||||
|
||||
<button onClick={onRefresh} disabled={refreshing || !online}
|
||||
title={online ? "Fetch the latest videos from every channel" : "Refreshing needs a connection"}
|
||||
className={`${BTN_PRIMARY} cursor-pointer py-1.5 font-mono text-[11px] tabular-nums`}>
|
||||
{refreshing
|
||||
? refreshProgress
|
||||
? `${refreshProgress.done}/${refreshProgress.total}`
|
||||
: "Refreshing"
|
||||
: "Refresh"}
|
||||
title={
|
||||
online
|
||||
? refreshProgress
|
||||
? `Refreshing ${refreshProgress.done}/${refreshProgress.total}`
|
||||
: "Fetch the latest videos from every channel"
|
||||
: "Refreshing needs a connection"
|
||||
}
|
||||
aria-label="Refresh"
|
||||
className="grid size-[30px] shrink-0 cursor-pointer place-items-center rounded-lg
|
||||
bg-sky-500 text-white hover:bg-sky-400 disabled:cursor-not-allowed
|
||||
disabled:opacity-40">
|
||||
<svg viewBox="0 0 24 24" className={`size-4 ${refreshing ? "animate-spin" : ""}`}
|
||||
fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round"
|
||||
d="M20 11a8 8 0 10-2.3 5.7M20 5v6h-6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user