mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
fix(desktop): inline update control in header (#561)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -61,7 +61,6 @@ import {
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/shared/ui/sidebar";
|
||||
import { UpdateIndicator } from "@/features/settings/UpdateIndicator";
|
||||
|
||||
type AppView =
|
||||
| "home"
|
||||
@@ -599,11 +598,6 @@ export function AppShell() {
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="fixed right-[16px] top-[8px] z-50">
|
||||
<UpdateIndicator
|
||||
onOpenUpdates={() => handleOpenSettings("updates")}
|
||||
/>
|
||||
</div>
|
||||
<AppSidebar
|
||||
activeWorkspace={workspacesHook.activeWorkspace}
|
||||
channels={sidebarChannels}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import type * as React from "react";
|
||||
|
||||
import type { ChannelType, ChannelVisibility } from "@/shared/api/types";
|
||||
import { UpdateIndicator } from "@/features/settings/UpdateIndicator";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { useSidebar } from "@/shared/ui/sidebar";
|
||||
|
||||
@@ -118,7 +119,10 @@ export function ChatHeader({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<UpdateIndicator />
|
||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { Download, Loader2, RefreshCw } from "lucide-react";
|
||||
import { Loader2, RefreshCcw, RotateCw } from "lucide-react";
|
||||
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
|
||||
import { useUpdaterContext } from "./hooks/UpdaterProvider";
|
||||
import type { UpdateStatus } from "./hooks/use-updater";
|
||||
|
||||
const indicatorButtonClass =
|
||||
"relative h-7 px-2 text-xs text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground";
|
||||
"relative h-8 w-8 text-muted-foreground/80 hover:bg-muted/60 hover:text-foreground";
|
||||
|
||||
const iconClass = "h-3 w-3";
|
||||
const iconClass = "h-4 w-4";
|
||||
|
||||
const variants: Record<
|
||||
"available" | "downloading" | "installing" | "ready",
|
||||
{
|
||||
Icon: typeof Download;
|
||||
Icon: typeof RefreshCcw;
|
||||
label: string;
|
||||
badgeColor: string;
|
||||
iconClass: string;
|
||||
}
|
||||
> = {
|
||||
available: {
|
||||
Icon: Download,
|
||||
Icon: RefreshCcw,
|
||||
label: "Update available",
|
||||
badgeColor: "bg-primary",
|
||||
iconClass: iconClass,
|
||||
@@ -38,7 +39,7 @@ const variants: Record<
|
||||
iconClass: `${iconClass} animate-spin`,
|
||||
},
|
||||
ready: {
|
||||
Icon: RefreshCw,
|
||||
Icon: RotateCw,
|
||||
label: "Restart to update",
|
||||
badgeColor: "bg-emerald-500",
|
||||
iconClass: iconClass,
|
||||
@@ -57,12 +58,8 @@ function getVariant(state: UpdateStatus["state"]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function UpdateIndicator({
|
||||
onOpenUpdates,
|
||||
}: {
|
||||
onOpenUpdates: () => void;
|
||||
}) {
|
||||
const { status } = useUpdaterContext();
|
||||
export function UpdateIndicator({ className }: { className?: string }) {
|
||||
const { status, downloadAndInstall, relaunch } = useUpdaterContext();
|
||||
const variant = getVariant(status.state);
|
||||
|
||||
if (!variant) {
|
||||
@@ -70,20 +67,37 @@ export function UpdateIndicator({
|
||||
}
|
||||
|
||||
const { Icon, label, badgeColor, iconClass: variantIconClass } = variant;
|
||||
const isActionable = status.state === "available" || status.state === "ready";
|
||||
const handleClick =
|
||||
status.state === "ready"
|
||||
? relaunch
|
||||
: status.state === "available"
|
||||
? downloadAndInstall
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Button
|
||||
aria-label={label}
|
||||
className={indicatorButtonClass}
|
||||
onClick={onOpenUpdates}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icon className={variantIconClass} />
|
||||
{label}
|
||||
<span
|
||||
className={`absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full ${badgeColor} animate-pulse`}
|
||||
/>
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
aria-label={label}
|
||||
className={`${indicatorButtonClass} ${className ?? ""}`}
|
||||
disabled={!isActionable}
|
||||
onClick={() => {
|
||||
if (handleClick) {
|
||||
void handleClick();
|
||||
}
|
||||
}}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icon className={variantIconClass} />
|
||||
<span
|
||||
className={`absolute right-1 top-1 h-1.5 w-1.5 rounded-full ${badgeColor} animate-pulse`}
|
||||
/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,13 @@ function canRunBackgroundCheck(status: UpdateStatus): boolean {
|
||||
return !BACKGROUND_BLOCKED_STATES.has(status.state);
|
||||
}
|
||||
|
||||
function initialUpdateStatus(): UpdateStatus {
|
||||
return { state: "idle" };
|
||||
}
|
||||
|
||||
export function useUpdater() {
|
||||
const [status, setStatusState] = useState<UpdateStatus>({ state: "idle" });
|
||||
const statusRef = useRef<UpdateStatus>({ state: "idle" });
|
||||
const [status, setStatusState] = useState<UpdateStatus>(initialUpdateStatus);
|
||||
const statusRef = useRef<UpdateStatus>(initialUpdateStatus());
|
||||
const updateRef = useRef<Update | null>(null);
|
||||
const checkInFlightRef = useRef(false);
|
||||
const downloadInFlightRef = useRef(false);
|
||||
|
||||
Reference in New Issue
Block a user