Improving Moveable
This commit is contained in:
parent
ac1ca4ec8f
commit
a65f46a818
@ -9,12 +9,16 @@
|
|||||||
|
|
||||||
.MediaControlSpacer {
|
.MediaControlSpacer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
width: 5rem;
|
width: 5rem;
|
||||||
min-width: 5rem;
|
min-width: 5rem;
|
||||||
height: 3.75rem;
|
height: 3.75rem;
|
||||||
min-height: 3.75rem;
|
min-height: 3.75rem;
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
border: 2px dashed #666; /* Visual indicator for drop zone */
|
||||||
}
|
}
|
||||||
|
|
||||||
.MediaControlSpacer.Medium {
|
.MediaControlSpacer.Medium {
|
||||||
@ -24,18 +28,16 @@
|
|||||||
min-height: 8.625em;
|
min-height: 8.625em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.MediaControl {
|
.MediaControl {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 5rem;
|
width: 5rem;
|
||||||
height: 3.75rem;
|
height: 3.75rem;
|
||||||
min-width: 5rem;
|
min-width: 5rem;
|
||||||
min-height: 3.75rem;
|
min-height: 3.75rem;
|
||||||
z-index: 50000;
|
z-index: 50000;
|
||||||
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MediaControl .Video {
|
.MediaControl .Video {
|
||||||
|
@ -1303,26 +1303,15 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
|||||||
const [muted, setMuted] = useState<boolean>(peer?.muted || false);
|
const [muted, setMuted] = useState<boolean>(peer?.muted || false);
|
||||||
const [videoOn, setVideoOn] = useState<boolean>(peer?.video_on !== false);
|
const [videoOn, setVideoOn] = useState<boolean>(peer?.video_on !== false);
|
||||||
const [isValid, setIsValid] = useState<boolean>(false);
|
const [isValid, setIsValid] = useState<boolean>(false);
|
||||||
const [frame, setFrame] = useState<{ translate: [number, number] }>({ translate: [0, 0] });
|
const [frame, setFrame] = useState<{
|
||||||
|
translate: [number, number];
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
}>({ translate: [0, 0] });
|
||||||
const targetRef = useRef<HTMLDivElement>(null);
|
const targetRef = useRef<HTMLDivElement>(null);
|
||||||
|
const spacerRef = useRef<HTMLDivElement>(null);
|
||||||
const moveableRef = useRef<any>(null);
|
const moveableRef = useRef<any>(null);
|
||||||
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
// Initialize position based on peer session_id to avoid stacking
|
|
||||||
useEffect(() => {
|
|
||||||
if (!peer) return;
|
|
||||||
|
|
||||||
// Create a simple hash of the session_id to get a position offset
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < peer.session_id.length; i++) {
|
|
||||||
hash = (hash << 5) - hash + peer.session_id.charCodeAt(i);
|
|
||||||
hash = hash & hash; // Convert to 32-bit integer
|
|
||||||
}
|
|
||||||
|
|
||||||
const x = (Math.abs(hash) % 300) + 50; // Random x between 50-350px
|
|
||||||
const y = (Math.abs(hash) % 200) + 50; // Random y between 50-250px
|
|
||||||
|
|
||||||
setFrame({ translate: [x, y] });
|
|
||||||
}, [peer]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!peer) return;
|
if (!peer) return;
|
||||||
@ -1428,7 +1417,7 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
|||||||
top: getComputedStyle(targetRef.current).top,
|
top: getComputedStyle(targetRef.current).top,
|
||||||
transform: getComputedStyle(targetRef.current).transform,
|
transform: getComputedStyle(targetRef.current).transform,
|
||||||
width: getComputedStyle(targetRef.current).width,
|
width: getComputedStyle(targetRef.current).width,
|
||||||
height: getComputedStyle(targetRef.current).height
|
height: getComputedStyle(targetRef.current).height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [peer?.session_id]);
|
}, [peer?.session_id]);
|
||||||
@ -1449,22 +1438,62 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Snap-back functionality
|
||||||
|
const checkSnapBack = (x: number, y: number) => {
|
||||||
|
if (!spacerRef.current) return false;
|
||||||
|
|
||||||
|
const spacerRect = spacerRef.current.getBoundingClientRect();
|
||||||
|
const threshold = 50; // pixels from original position to trigger snap
|
||||||
|
|
||||||
|
// Check if close to original position
|
||||||
|
const closeToOrigin = Math.abs(x) < threshold && Math.abs(y) < threshold;
|
||||||
|
|
||||||
|
return closeToOrigin;
|
||||||
|
};
|
||||||
|
|
||||||
if (!peer) return null;
|
if (!peer) return null;
|
||||||
|
|
||||||
const colorAudio = isValid ? "primary" : "disabled";
|
const colorAudio = isValid ? "primary" : "disabled";
|
||||||
const colorVideo = isValid ? "primary" : "disabled";
|
const colorVideo = isValid ? "primary" : "disabled";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", minWidth: "200px", minHeight: "100px" }}>
|
<div className="MediaControlContainer">
|
||||||
<div className={`MediaControlSpacer ${className}`} />
|
{/* Drop target / spacer that stays in place */}
|
||||||
<div
|
<div
|
||||||
ref={targetRef}
|
ref={spacerRef}
|
||||||
className={`MediaControl ${className}`}
|
className={`MediaControlSpacer ${className}`}
|
||||||
|
style={{
|
||||||
|
opacity: isDragging ? 1 : 0.3, // Make more visible when dragging
|
||||||
|
transition: "opacity 0.2s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isDragging && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
fontSize: "0.7em",
|
||||||
|
color: "#888",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Drop here
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Moveable element - now absolute positioned */}
|
||||||
|
<div
|
||||||
|
ref={targetRef}
|
||||||
|
className={`MediaControl ${className}`}
|
||||||
data-peer={peer.session_id}
|
data-peer={peer.session_id}
|
||||||
style={{
|
style={{
|
||||||
left: '0px',
|
left: "0px",
|
||||||
top: '0px',
|
top: "0px",
|
||||||
transform: `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`
|
width: frame.width ? `${frame.width}px` : undefined,
|
||||||
|
height: frame.height ? `${frame.height}px` : undefined,
|
||||||
|
transform: `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="Controls">
|
<div className="Controls">
|
||||||
@ -1491,7 +1520,7 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
|||||||
autoPlay
|
autoPlay
|
||||||
srcObject={peer.attributes.srcObject}
|
srcObject={peer.attributes.srcObject}
|
||||||
local={peer.local}
|
local={peer.local}
|
||||||
muted={peer.local || muted} // Pass muted state
|
muted={peer.local || muted}
|
||||||
/>
|
/>
|
||||||
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
||||||
</Box>
|
</Box>
|
||||||
@ -1502,55 +1531,97 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
|||||||
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Moveable
|
|
||||||
ref={moveableRef}
|
|
||||||
flushSync={flushSync}
|
|
||||||
pinchable
|
|
||||||
draggable
|
|
||||||
target={targetRef.current}
|
|
||||||
resizable
|
|
||||||
keepRatio
|
|
||||||
hideDefaultLines={false}
|
|
||||||
edge
|
|
||||||
onDragStart={(e) => {
|
|
||||||
console.log("Moveable drag start", { target: targetRef.current, event: e });
|
|
||||||
}}
|
|
||||||
onDrag={(e) => {
|
|
||||||
console.log("Moveable drag", { beforeTranslate: e.beforeTranslate, transform: e.transform });
|
|
||||||
// Apply the transform directly to the target element
|
|
||||||
if (targetRef.current) {
|
|
||||||
targetRef.current.style.transform = e.transform;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onDragEnd={(e) => {
|
|
||||||
console.log("Moveable drag end", { target: targetRef.current });
|
|
||||||
// Get the final transform from the element
|
|
||||||
if (targetRef.current) {
|
|
||||||
const computedStyle = getComputedStyle(targetRef.current);
|
|
||||||
const transform = computedStyle.transform;
|
|
||||||
console.log("Final computed transform:", transform);
|
|
||||||
|
|
||||||
// Parse the transform matrix to get translate values
|
|
||||||
if (transform && transform !== "none") {
|
|
||||||
const matrix = new DOMMatrix(transform);
|
|
||||||
console.log("Parsed matrix translate:", [matrix.m41, matrix.m42]);
|
|
||||||
setFrame({ translate: [matrix.m41, matrix.m42] });
|
|
||||||
} else {
|
|
||||||
// If no transform, reset to 0,0
|
|
||||||
setFrame({ translate: [0, 0] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onResizeStart={(e) => {
|
|
||||||
e.setOrigin(["%", "%"]);
|
|
||||||
}}
|
|
||||||
onResize={(e) => {
|
|
||||||
e.target.style.width = `${e.width}px`;
|
|
||||||
e.target.style.height = `${e.height}px`;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
|
||||||
|
<Moveable
|
||||||
|
ref={moveableRef}
|
||||||
|
flushSync={flushSync}
|
||||||
|
pinchable={true}
|
||||||
|
draggable={true}
|
||||||
|
target={targetRef.current}
|
||||||
|
resizable={true}
|
||||||
|
keepRatio={true}
|
||||||
|
hideDefaultLines={false}
|
||||||
|
snappable={true}
|
||||||
|
snapThreshold={5}
|
||||||
|
edge
|
||||||
|
onDragStart={(e) => {
|
||||||
|
setIsDragging(true);
|
||||||
|
}}
|
||||||
|
onDrag={(e) => {
|
||||||
|
if (targetRef.current) {
|
||||||
|
targetRef.current.style.transform = e.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for snap-back
|
||||||
|
const matrix = new DOMMatrix(e.transform);
|
||||||
|
const shouldSnap = checkSnapBack(matrix.m41, matrix.m42);
|
||||||
|
|
||||||
|
if (shouldSnap && spacerRef.current) {
|
||||||
|
// Add visual feedback for snap zone
|
||||||
|
spacerRef.current.style.borderColor = "#0088ff";
|
||||||
|
} else if (spacerRef.current) {
|
||||||
|
spacerRef.current.style.borderColor = "#666";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragEnd={(e) => {
|
||||||
|
setIsDragging(false);
|
||||||
|
|
||||||
|
if (targetRef.current) {
|
||||||
|
const computedStyle = getComputedStyle(targetRef.current);
|
||||||
|
const transform = computedStyle.transform;
|
||||||
|
|
||||||
|
if (transform && transform !== "none") {
|
||||||
|
const matrix = new DOMMatrix(transform);
|
||||||
|
const shouldSnap = checkSnapBack(matrix.m41, matrix.m42);
|
||||||
|
|
||||||
|
if (shouldSnap) {
|
||||||
|
// Snap back to origin
|
||||||
|
targetRef.current.style.transform = "translate(0px, 0px)";
|
||||||
|
setFrame({ translate: [0, 0], width: frame.width, height: frame.height });
|
||||||
|
|
||||||
|
// Reset size if needed
|
||||||
|
if (spacerRef.current) {
|
||||||
|
const spacerRect = spacerRef.current.getBoundingClientRect();
|
||||||
|
targetRef.current.style.width = `${spacerRect.width}px`;
|
||||||
|
targetRef.current.style.height = `${spacerRect.height}px`;
|
||||||
|
setFrame({ translate: [0, 0] });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setFrame({
|
||||||
|
translate: [matrix.m41, matrix.m42],
|
||||||
|
width: frame.width,
|
||||||
|
height: frame.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setFrame({ translate: [0, 0], width: frame.width, height: frame.height });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset spacer border color
|
||||||
|
if (spacerRef.current) {
|
||||||
|
spacerRef.current.style.borderColor = "#666";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onResizeStart={(e) => {
|
||||||
|
e.setOrigin(["%", "%"]);
|
||||||
|
setIsDragging(true);
|
||||||
|
}}
|
||||||
|
onResize={(e) => {
|
||||||
|
e.target.style.width = `${e.width}px`;
|
||||||
|
e.target.style.height = `${e.height}px`;
|
||||||
|
setFrame({
|
||||||
|
...frame,
|
||||||
|
width: e.width,
|
||||||
|
height: e.height,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onResizeEnd={() => {
|
||||||
|
setIsDragging(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
min-width: 11em;
|
min-width: 11em;
|
||||||
padding: 0 1px;
|
padding: 0 1px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.UserList .UserSelector .UserEntry > div:first-child {
|
.UserList .UserSelector .UserEntry > div:first-child {
|
||||||
|
@ -155,7 +155,7 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
|||||||
sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}
|
sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}
|
||||||
className={`UserEntry ${user.local ? "UserSelf" : ""}`}
|
className={`UserEntry ${user.local ? "UserSelf" : ""}`}
|
||||||
>
|
>
|
||||||
<div>
|
<Box>
|
||||||
<Box style={{ display: "flex-wrap", alignItems: "center", justifyContent: "space-between" }}>
|
<Box style={{ display: "flex-wrap", alignItems: "center", justifyContent: "space-between" }}>
|
||||||
<Box style={{ display: "flex-wrap", alignItems: "center" }}>
|
<Box style={{ display: "flex-wrap", alignItems: "center" }}>
|
||||||
<div className="Name">{user.name ? user.name : user.session_id}</div>
|
<div className="Name">{user.name ? user.name : user.session_id}</div>
|
||||||
@ -174,7 +174,7 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{user.bot_instance_id && (
|
{user.bot_instance_id && (
|
||||||
<Box style={{ display: "flex-wrap", gap: "4px", border: "3px solid magenta" }}>
|
<Paper elevation={2} sx={{ display: "flex-wrap", gap: "4px", p: 1 }}>
|
||||||
{user.bot_run_id && (
|
{user.bot_run_id && (
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
@ -196,11 +196,11 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
|||||||
>
|
>
|
||||||
{leavingBots.has(user.session_id) ? "..." : "Leave"}
|
{leavingBots.has(user.session_id) ? "..." : "Leave"}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{user.name && !user.live && <div className="NoNetwork"></div>}
|
{user.name && !user.live && <div className="NoNetwork"></div>}
|
||||||
</div>
|
</Box>
|
||||||
{user.name && user.live && peers[user.session_id] && (user.local || user.has_media !== false) ? (
|
{user.name && user.live && peers[user.session_id] && (user.local || user.has_media !== false) ? (
|
||||||
<MediaControl
|
<MediaControl
|
||||||
className={videoClass}
|
className={videoClass}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user