Cameras drag and snap;

This commit is contained in:
2025-10-16 17:42:05 +05:00
parent 34c9b58d8f
commit a2d19fe646
4 changed files with 98 additions and 9 deletions
+94 -3
View File
@@ -1,5 +1,7 @@
import { useState, useRef, useEffect } from "react";
import UserCamera from "./ui/UserCamera";
import UserDevicesControls from "./ui/UserDevicesControls";
import clsx from "clsx";
export default function SessionUsersPanel() {
const users = [
@@ -40,9 +42,98 @@ export default function SessionUsersPanel() {
console.log(`Can control user ${id}`);
}
const [isTop, setIsTop] = useState(false);
const [isLeft, setIsLeft] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 });
const containerRef = useRef<HTMLDivElement>(null);
const dragOffset = useRef({ x: 0, y: 0 });
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging || !containerRef.current) return;
setDragPosition({
x: e.clientX - dragOffset.current.x,
y: e.clientY - dragOffset.current.y,
});
};
const handleMouseUp = () => {
if (!isDragging || !containerRef.current) return;
// Определяем, к какой стороне прилипнуть
const rect = containerRef.current.getBoundingClientRect();
const centerY = rect.top + rect.height / 2;
const centerX = rect.left + rect.width / 2;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const shouldBeTop = centerY < viewportHeight / 2;
const shouldBeLeft = centerX < viewportWidth / 2;
setIsDragging(false);
setIsTop(shouldBeTop);
setIsLeft(shouldBeLeft);
};
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
dragOffset.current = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
setDragPosition({
x: rect.left,
y: rect.top,
});
setIsDragging(true);
};
useEffect(() => {
if (isDragging) {
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
}
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [isDragging]);
const getStyle = (): React.CSSProperties => {
if (isDragging) {
return {
left: `${dragPosition.x}px`,
top: `${dragPosition.y}px`,
transition: "none",
};
}
return {
left: isLeft ? "1.111vw" : "calc(100vw - 1.111vw)",
top: isTop ? "1.111vw" : "calc(100vh - 1.111vw)",
transform: `translate(${isLeft ? "0" : "-100%"}, ${
isTop ? "0" : "-100%"
})`,
transition: "all 0.3s ease-out",
};
};
return (
<div className="flex gap-4 items-end">
<div className="flex gap-4 w-max items-end">
<div
ref={containerRef}
onMouseDown={handleMouseDown}
className={`flex gap-4 absolute ${
isDragging ? "cursor-grabbing" : "cursor-grab"
}`}
style={getStyle()}
>
<div
className={clsx(
"flex gap-4 w-max",
isLeft ? "flex-row-reverse" : "flex-row",
isTop ? "items-start" : "items-end"
)}
>
{users.map((user) => (
<UserCamera
key={user.id}
@@ -52,8 +143,8 @@ export default function SessionUsersPanel() {
{...user}
/>
))}
<UserDevicesControls />
</div>
<UserDevicesControls />
</div>
);
}
+1 -1
View File
@@ -54,7 +54,7 @@ export default function UserCamera({
}}
className={clsx(
"aspect-square rounded-[1.667vw] bg-yellow-500 relative flex-shrink-0 pointer-events-auto",
isAdmin && "order-last"
isAdmin && "order-3"
)}
>
{isAdmin && <Admin className="absolute top-0 right-0" />}
@@ -23,7 +23,7 @@ export default function UserDevicesControls() {
}
return (
<div className="hidden 2xl:flex aspect-square p-[0.556vw] flex-wrap justify-between items-center size-[6.944vw] rounded-[1.667vw] bg-[#00000040] shadow-[0_4px_40px_0_#0F10111A] backdrop-blur-[10px] pointer-events-auto">
<div className="hidden order-4 2xl:grid grid-cols-2 gap-[0.278vw] aspect-square p-[0.556vw] flex-wrap justify-between items-center size-[6.944vw] rounded-[1.667vw] bg-[#00000040] shadow-[0_4px_40px_0_#0F10111A] backdrop-blur-[10px] pointer-events-auto">
<ControlButton
size="large"
icon={<MicrophoneFilledIcon />}
+2 -4
View File
@@ -126,7 +126,7 @@ function NewSessionPage() {
}
return (
<div className="relative w-screen h-screen bg-black order-3 overflow-hidden flex justify-center items-center">
<div className="relative w-screen h-screen bg-black order-3 overflow-hidden flex justify-center_items-center">
{session.status === "started" &&
session.mode === "stream" &&
session.server?.localIp &&
@@ -201,9 +201,7 @@ function NewSessionPage() {
</FloatingActionButton>
<ControlsPopover />
</ActionsSidebarWrapper>
<div className="absolute 2xl:!bottom-[1.111vw] 2xl:!right-[1.111vw] max-2xl:top-2 max-2xl:right-2 pointer-events-none">
<SessionUsersPanel />
</div>
<SessionUsersPanel />
</div>
);
}