fix: cleanup old position system, add proper useRef import
This commit is contained in:
1 parent
7f7e11adcf
commit
b77dbb65f6
1 file changed
+104
-39
+104
-39
@@ -7,6 +7,7 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
@@ -96,47 +97,99 @@ export function ClientView({
|
|||||||
const sep = base.includes("?") ? "&" : "?";
|
const sep = base.includes("?") ? "&" : "?";
|
||||||
const clientSrc = `${base}${sep}sso=${encodeURIComponent(ticket)}`;
|
const clientSrc = `${base}${sep}sso=${encodeURIComponent(ticket)}`;
|
||||||
|
|
||||||
const POSITIONS = ["top-left", "top-right", "bottom-right", "bottom-left"] as const;
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||||
type Position = (typeof POSITIONS)[number];
|
const dragRef = useRef({ startX: 0, startY: 0, startTop: 0, startLeft: 0 });
|
||||||
|
const [pos, setPos] = useState({ top: 8, left: -1 });
|
||||||
const [position, setPosition] = useState<Position>("top-right");
|
const [dragging, setDragging] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const urlPos = params.get("toolbar");
|
const ux = params.get("toolbarX"), uy = params.get("toolbarY");
|
||||||
if ((POSITIONS as readonly string[]).includes(urlPos ?? "")) {
|
if (ux && uy) { setPos({ top: Number(uy), left: Number(ux) }); return; }
|
||||||
setPosition(urlPos as Position);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem("client_toolbar_position");
|
const sx = localStorage.getItem("client_toolbar_x");
|
||||||
if ((POSITIONS as readonly string[]).includes(stored ?? "")) {
|
const sy = localStorage.getItem("client_toolbar_y");
|
||||||
setPosition(stored as Position);
|
if (sx && sy) { setPos({ top: Number(sy), left: Number(sx) }); return; }
|
||||||
return;
|
} catch {}
|
||||||
}
|
|
||||||
} catch { /* private mode */ }
|
|
||||||
if ((POSITIONS as readonly string[]).includes(defaultToolbarPosition))
|
|
||||||
setPosition(defaultToolbarPosition as Position);
|
|
||||||
}, [defaultToolbarPosition]);
|
|
||||||
|
|
||||||
function cyclePosition() {
|
const w = toolbarRef.current?.offsetWidth ?? 220;
|
||||||
const idx = POSITIONS.indexOf(position);
|
setPos({ top: 8, left: window.innerWidth - w - 8 });
|
||||||
const next = POSITIONS[(idx + 1) % POSITIONS.length];
|
}, []);
|
||||||
setPosition(next);
|
|
||||||
try { localStorage.setItem("client_toolbar_position", next); } catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const posStyle: React.CSSProperties = {};
|
const startDrag = useCallback((clientX: number, clientY: number) => {
|
||||||
if (position === "top-left") { posStyle.top = posStyle.left = "0.5rem"; }
|
const el = toolbarRef.current;
|
||||||
else if (position === "top-right") { posStyle.top = posStyle.right = "0.5rem"; }
|
if (!el) return;
|
||||||
else if (position === "bottom-right") { posStyle.bottom = posStyle.right = "0.5rem"; }
|
dragRef.current = {
|
||||||
else if (position === "bottom-left") { posStyle.bottom = posStyle.left = "0.5rem"; }
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="fixed z-[9999] flex items-center gap-1 sm:gap-2"
|
ref={toolbarRef}
|
||||||
style={posStyle}
|
className={`fixed z-[9999] flex items-center gap-1 sm:gap-2 select-none ${dragging ? "cursor-grabbing" : "cursor-grab"}`}
|
||||||
|
style={{ top: pos.top, left: pos.left >= 0 ? pos.left : undefined, right: pos.left < 0 ? 8 : undefined }}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
>
|
>
|
||||||
<ToolbarBtn href="/" title="Home">
|
<ToolbarBtn href="/" title="Home">
|
||||||
<svg
|
<svg
|
||||||
@@ -222,10 +275,21 @@ export function ClientView({
|
|||||||
</svg>
|
</svg>
|
||||||
<span className="text-[10px] sm:text-xs font-bold">{onlineCount}</span>
|
<span className="text-[10px] sm:text-xs font-bold">{onlineCount}</span>
|
||||||
</div>
|
</div>
|
||||||
<ToolbarBtn onClick={cyclePosition} title={position}>
|
<div
|
||||||
|
className="h-7 sm:h-8 px-1 rounded-lg border-2 flex items-center justify-center shadow-lg opacity-50"
|
||||||
|
style={{
|
||||||
|
borderColor:
|
||||||
|
"color-mix(in srgb, var(--color-primary, #eeb425) 75%, black)",
|
||||||
|
background:
|
||||||
|
"linear-gradient(135deg, var(--color-primary, #eeb425) 0%, color-mix(in srgb, var(--color-primary, #eeb425) 80%, black) 100%)",
|
||||||
|
color:
|
||||||
|
"var(--button-text-color-readable, var(--button-text-color, #1a1a2e))",
|
||||||
|
}}
|
||||||
|
title="Drag to move"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
width="14"
|
width="12"
|
||||||
height="14"
|
height="12"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -233,13 +297,14 @@ export function ClientView({
|
|||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="Move toolbar"
|
aria-label="Drag"
|
||||||
>
|
>
|
||||||
<title>Move toolbar</title>
|
<title>Drag</title>
|
||||||
<path d="M12 5v14M5 12h14" />
|
<circle cx="9" cy="5" r="1" /><circle cx="15" cy="5" r="1" />
|
||||||
<path d="M15 9l-3-3-3 3M9 15l3 3 3-3" />
|
<circle cx="9" cy="12" r="1" /><circle cx="15" cy="12" r="1" />
|
||||||
|
<circle cx="9" cy="19" r="1" /><circle cx="15" cy="19" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
</ToolbarBtn>
|
</div>
|
||||||
<ToolbarBtn
|
<ToolbarBtn
|
||||||
onClick={() => signOut({ callbackUrl: "/" })}
|
onClick={() => signOut({ callbackUrl: "/" })}
|
||||||
title="Logout"
|
title="Logout"
|
||||||
|
|||||||
Reference in new issue
Block a user