From b77dbb65f6db98ad96a78a929a6e47f11e45223b Mon Sep 17 00:00:00 2001 From: openhands Date: Sun, 26 Jul 2026 18:04:50 +0200 Subject: [PATCH] fix: cleanup old position system, add proper useRef import --- src/app/client/client-view.tsx | 143 ++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 39 deletions(-) diff --git a/src/app/client/client-view.tsx b/src/app/client/client-view.tsx index 26907060..8c361a36 100644 --- a/src/app/client/client-view.tsx +++ b/src/app/client/client-view.tsx @@ -7,6 +7,7 @@ import { type ReactNode, useCallback, useEffect, + useRef, useState, } from "react"; @@ -96,47 +97,99 @@ export function ClientView({ const sep = base.includes("?") ? "&" : "?"; const clientSrc = `${base}${sep}sso=${encodeURIComponent(ticket)}`; - const POSITIONS = ["top-left", "top-right", "bottom-right", "bottom-left"] as const; - type Position = (typeof POSITIONS)[number]; - - const [position, setPosition] = useState("top-right"); + const toolbarRef = useRef(null); + const dragRef = useRef({ startX: 0, startY: 0, startTop: 0, startLeft: 0 }); + const [pos, setPos] = useState({ top: 8, left: -1 }); + const [dragging, setDragging] = useState(false); useEffect(() => { const params = new URLSearchParams(window.location.search); - const urlPos = params.get("toolbar"); - if ((POSITIONS as readonly string[]).includes(urlPos ?? "")) { - setPosition(urlPos as Position); - return; - } + const ux = params.get("toolbarX"), uy = params.get("toolbarY"); + if (ux && uy) { setPos({ top: Number(uy), left: Number(ux) }); return; } + try { - const stored = localStorage.getItem("client_toolbar_position"); - if ((POSITIONS as readonly string[]).includes(stored ?? "")) { - setPosition(stored as Position); - return; - } - } catch { /* private mode */ } - if ((POSITIONS as readonly string[]).includes(defaultToolbarPosition)) - setPosition(defaultToolbarPosition as Position); - }, [defaultToolbarPosition]); + const sx = localStorage.getItem("client_toolbar_x"); + const sy = localStorage.getItem("client_toolbar_y"); + if (sx && sy) { setPos({ top: Number(sy), left: Number(sx) }); return; } + } catch {} - function cyclePosition() { - const idx = POSITIONS.indexOf(position); - const next = POSITIONS[(idx + 1) % POSITIONS.length]; - setPosition(next); - try { localStorage.setItem("client_toolbar_position", next); } catch {} - } + const w = toolbarRef.current?.offsetWidth ?? 220; + setPos({ top: 8, left: window.innerWidth - w - 8 }); + }, []); - const posStyle: React.CSSProperties = {}; - if (position === "top-left") { posStyle.top = posStyle.left = "0.5rem"; } - else if (position === "top-right") { posStyle.top = posStyle.right = "0.5rem"; } - else if (position === "bottom-right") { posStyle.bottom = posStyle.right = "0.5rem"; } - else if (position === "bottom-left") { posStyle.bottom = posStyle.left = "0.5rem"; } + const startDrag = useCallback((clientX: number, clientY: number) => { + const el = toolbarRef.current; + if (!el) return; + dragRef.current = { + startX: clientX, + startY: clientY, + startTop: pos.top, + startLeft: pos.left, + }; + setDragging(true); + }, [pos.top, pos.left]); + + const handleMouseDown = useCallback((e: React.MouseEvent) => { + if ((e.target as HTMLElement).closest("button, a")) return; + startDrag(e.clientX, e.clientY); + }, [startDrag]); + + const handleTouchStart = useCallback((e: React.TouchEvent) => { + if ((e.target as HTMLElement).closest("button, a")) return; + startDrag(e.touches[0].clientX, e.touches[0].clientY); + }, [startDrag]); + + useEffect(() => { + if (!dragging) return; + const maxX = window.innerWidth - (toolbarRef.current?.offsetWidth ?? 220); + const maxY = window.innerHeight - (toolbarRef.current?.offsetHeight ?? 40); + + function onMove(e: MouseEvent) { + const dx = e.clientX - dragRef.current.startX; + const dy = e.clientY - dragRef.current.startY; + setPos({ + top: Math.max(0, Math.min(dragRef.current.startTop + dy, maxY)), + left: Math.max(0, Math.min(dragRef.current.startLeft + dx, maxX)), + }); + } + function onTouchMove(e: TouchEvent) { + const dx = e.touches[0].clientX - dragRef.current.startX; + const dy = e.touches[0].clientY - dragRef.current.startY; + setPos({ + top: Math.max(0, Math.min(dragRef.current.startTop + dy, maxY)), + left: Math.max(0, Math.min(dragRef.current.startLeft + dx, maxX)), + }); + } + function onEnd() { + setDragging(false); + setPos(p => { + try { + localStorage.setItem("client_toolbar_x", String(p.left)); + localStorage.setItem("client_toolbar_y", String(p.top)); + } catch {} + return p; + }); + } + window.addEventListener("mousemove", onMove); + window.addEventListener("mouseup", onEnd); + window.addEventListener("touchmove", onTouchMove, { passive: true }); + window.addEventListener("touchend", onEnd); + return () => { + window.removeEventListener("mousemove", onMove); + window.removeEventListener("mouseup", onEnd); + window.removeEventListener("touchmove", onTouchMove); + window.removeEventListener("touchend", onEnd); + }; + }, [dragging]); return ( <>
= 0 ? pos.left : undefined, right: pos.left < 0 ? 8 : undefined }} + onMouseDown={handleMouseDown} + onTouchStart={handleTouchStart} > {onlineCount}
- +
- Move toolbar - - + Drag + + + - +
signOut({ callbackUrl: "/" })} title="Logout"