refactor: useLayoutEffect for no-flash position init, clean localStorage save, hide toolbar until ready
This commit is contained in:
1 parent
b77dbb65f6
commit
ea71a574a4
1 file changed
+22
-19
@@ -7,6 +7,7 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
@@ -99,10 +100,11 @@ export function ClientView({
|
|||||||
|
|
||||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||||
const dragRef = useRef({ startX: 0, startY: 0, startTop: 0, startLeft: 0 });
|
const dragRef = useRef({ startX: 0, startY: 0, startTop: 0, startLeft: 0 });
|
||||||
const [pos, setPos] = useState({ top: 8, left: -1 });
|
const posRef = useRef({ top: 8, left: 8 });
|
||||||
|
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const ux = params.get("toolbarX"), uy = params.get("toolbarY");
|
const ux = params.get("toolbarX"), uy = params.get("toolbarY");
|
||||||
if (ux && uy) { setPos({ top: Number(uy), left: Number(ux) }); return; }
|
if (ux && uy) { setPos({ top: Number(uy), left: Number(ux) }); return; }
|
||||||
@@ -117,9 +119,10 @@ export function ClientView({
|
|||||||
setPos({ top: 8, left: window.innerWidth - w - 8 });
|
setPos({ top: 8, left: window.innerWidth - w - 8 });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => { if (pos) posRef.current = pos; }, [pos]);
|
||||||
|
|
||||||
const startDrag = useCallback((clientX: number, clientY: number) => {
|
const startDrag = useCallback((clientX: number, clientY: number) => {
|
||||||
const el = toolbarRef.current;
|
if (!pos) return;
|
||||||
if (!el) return;
|
|
||||||
dragRef.current = {
|
dragRef.current = {
|
||||||
startX: clientX,
|
startX: clientX,
|
||||||
startY: clientY,
|
startY: clientY,
|
||||||
@@ -127,7 +130,7 @@ export function ClientView({
|
|||||||
startLeft: pos.left,
|
startLeft: pos.left,
|
||||||
};
|
};
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
}, [pos.top, pos.left]);
|
}, [pos]);
|
||||||
|
|
||||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
if ((e.target as HTMLElement).closest("button, a")) return;
|
if ((e.target as HTMLElement).closest("button, a")) return;
|
||||||
@@ -162,13 +165,11 @@ export function ClientView({
|
|||||||
}
|
}
|
||||||
function onEnd() {
|
function onEnd() {
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
setPos(p => {
|
setPos(p => p);
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("client_toolbar_x", String(p.left));
|
localStorage.setItem("client_toolbar_x", String(posRef.current.left));
|
||||||
localStorage.setItem("client_toolbar_y", String(p.top));
|
localStorage.setItem("client_toolbar_y", String(posRef.current.top));
|
||||||
} catch {}
|
} catch {}
|
||||||
return p;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
window.addEventListener("mousemove", onMove);
|
window.addEventListener("mousemove", onMove);
|
||||||
window.addEventListener("mouseup", onEnd);
|
window.addEventListener("mouseup", onEnd);
|
||||||
@@ -184,13 +185,14 @@ export function ClientView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
{pos ? (
|
||||||
ref={toolbarRef}
|
<div
|
||||||
className={`fixed z-[9999] flex items-center gap-1 sm:gap-2 select-none ${dragging ? "cursor-grabbing" : "cursor-grab"}`}
|
ref={toolbarRef}
|
||||||
style={{ top: pos.top, left: pos.left >= 0 ? pos.left : undefined, right: pos.left < 0 ? 8 : undefined }}
|
className={`fixed z-[9999] flex items-center gap-1 sm:gap-2 select-none ${dragging ? "cursor-grabbing" : "cursor-grab"}`}
|
||||||
onMouseDown={handleMouseDown}
|
style={{ top: pos.top, left: pos.left }}
|
||||||
onTouchStart={handleTouchStart}
|
onMouseDown={handleMouseDown}
|
||||||
>
|
onTouchStart={handleTouchStart}
|
||||||
|
>
|
||||||
<ToolbarBtn href="/" title="Home">
|
<ToolbarBtn href="/" title="Home">
|
||||||
<svg
|
<svg
|
||||||
width="14"
|
width="14"
|
||||||
@@ -312,6 +314,7 @@ export function ClientView({
|
|||||||
<LogOut size={14} />
|
<LogOut size={14} />
|
||||||
</ToolbarBtn>
|
</ToolbarBtn>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="relative w-screen h-screen overflow-hidden"
|
className="relative w-screen h-screen overflow-hidden"
|
||||||
|
|||||||
Reference in new issue
Block a user