Cameras drag and snap;
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user