diff --git a/src/components/launch/UpdateToastWindow.tsx b/src/components/launch/UpdateToastWindow.tsx index 7fa89630..1e63e6c6 100644 --- a/src/components/launch/UpdateToastWindow.tsx +++ b/src/components/launch/UpdateToastWindow.tsx @@ -1,5 +1,5 @@ import { AlertCircle, Download, LoaderCircle, Rocket } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; type UpdateToastPayload = { version: string; @@ -10,6 +10,8 @@ type UpdateToastPayload = { progressPercent?: number; }; +const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000; + function formatDelayHours(delayMs: number) { const hours = Math.max(1, Math.round(delayMs / (60 * 60 * 1000))); return `${hours}h`; @@ -47,6 +49,16 @@ function getIcon(payload: UpdateToastPayload) { export function UpdateToastWindow() { const [payload, setPayload] = useState(null); + const [dragOffsetX, setDragOffsetX] = useState(0); + const dragState = useRef<{ + pointerId: number | null; + startX: number; + active: boolean; + }>({ + pointerId: null, + startX: 0, + active: false, + }); useEffect(() => { let mounted = true; @@ -67,15 +79,87 @@ export function UpdateToastWindow() { }; }, []); + useEffect(() => { + setDragOffsetX(0); + dragState.current = { + pointerId: null, + startX: 0, + active: false, + }; + }, [payload?.phase, payload?.version, payload?.progressPercent]); + if (!payload) { return
; } const normalizedProgress = Math.max(0, Math.min(100, Math.round(payload.progressPercent ?? 0))); + const swipeThreshold = 96; + const handleSwipeDismiss = async () => { + setDragOffsetX(0); + dragState.current = { + pointerId: null, + startX: 0, + active: false, + }; + await window.electronAPI.dismissUpdateToast(); + }; return (
-
+
{ + const target = event.target as HTMLElement | null; + if (target?.closest("button")) { + return; + } + + dragState.current = { + pointerId: event.pointerId, + startX: event.clientX, + active: true, + }; + event.currentTarget.setPointerCapture(event.pointerId); + }} + onPointerMove={(event) => { + if (!dragState.current.active || dragState.current.pointerId !== event.pointerId) { + return; + } + + setDragOffsetX(event.clientX - dragState.current.startX); + }} + onPointerUp={async (event) => { + if (!dragState.current.active || dragState.current.pointerId !== event.pointerId) { + return; + } + + const nextOffset = event.clientX - dragState.current.startX; + dragState.current = { + pointerId: null, + startX: 0, + active: false, + }; + + if (Math.abs(nextOffset) >= swipeThreshold) { + await handleSwipeDismiss(); + return; + } + + setDragOffsetX(0); + }} + onPointerCancel={() => { + dragState.current = { + pointerId: null, + startX: 0, + active: false, + }; + setDragOffsetX(0); + }} + >
{getIcon(payload)}
@@ -161,6 +245,23 @@ export function UpdateToastWindow() { ) : null} + {payload.phase !== "downloading" ? ( + + ) : null} + {!payload.isPreview && payload.phase !== "downloading" ? (