Fixed Moveable
This commit is contained in:
parent
a65f46a818
commit
18a31704e8
@ -7,6 +7,13 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.MediaControlContainer {
|
||||
position: relative; /* CRITICAL: This creates the positioning context */
|
||||
display: inline-block;
|
||||
width: max-content; /* Ensure container sizes to content */
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.MediaControlSpacer {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@ -30,25 +37,34 @@
|
||||
|
||||
.MediaControl {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
position: absolute; /* Out of flow */
|
||||
top: 0; /* Start at top of container */
|
||||
left: 0; /* Start at left of container */
|
||||
width: 5rem;
|
||||
height: 3.75rem;
|
||||
min-width: 5rem;
|
||||
min-height: 3.75rem;
|
||||
z-index: 50000;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.MediaControl .Video {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #444;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
video {
|
||||
border: 5px solid purple;
|
||||
}
|
||||
|
||||
.MediaControl.Medium {
|
||||
width: 11.5em;
|
||||
height: 8.625em;
|
||||
@ -56,37 +72,25 @@
|
||||
min-height: 8.625em;
|
||||
}
|
||||
|
||||
.MediaControl > div {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.MediaControl .Controls {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
left: 0.5em;
|
||||
bottom: 0.5em;
|
||||
justify-content: flex-end;
|
||||
gap: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.MediaControl.Small .Controls {
|
||||
left: 0;
|
||||
bottom: unset;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.MediaControl .Controls > div {
|
||||
display: flex;
|
||||
border-radius: 0.25em;
|
||||
cursor: pointer;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.MediaControl .Controls > div:hover {
|
||||
@ -100,4 +104,4 @@
|
||||
|
||||
.moveable-control-box .moveable-direction {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
@ -1308,6 +1308,7 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
||||
width?: number;
|
||||
height?: number;
|
||||
}>({ translate: [0, 0] });
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const targetRef = useRef<HTMLDivElement>(null);
|
||||
const spacerRef = useRef<HTMLDivElement>(null);
|
||||
const moveableRef = useRef<any>(null);
|
||||
@ -1319,6 +1320,15 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
||||
setVideoOn(peer.video_on);
|
||||
}, [peer]);
|
||||
|
||||
// Initialize size to match spacer
|
||||
useEffect(() => {
|
||||
if (spacerRef.current && targetRef.current && !frame.width) {
|
||||
const spacerRect = spacerRef.current.getBoundingClientRect();
|
||||
targetRef.current.style.width = `${spacerRect.width}px`;
|
||||
targetRef.current.style.height = `${spacerRect.height}px`;
|
||||
}
|
||||
}, [frame.width]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!peer || peer.dead || !peer.attributes?.srcObject) {
|
||||
console.log(
|
||||
@ -1457,171 +1467,191 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
|
||||
const colorVideo = isValid ? "primary" : "disabled";
|
||||
|
||||
return (
|
||||
<div className="MediaControlContainer">
|
||||
{/* Drop target / spacer that stays in place */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
minWidth: "200px",
|
||||
minHeight: "100px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={spacerRef}
|
||||
className={`MediaControlSpacer ${className}`}
|
||||
ref={containerRef}
|
||||
className="MediaControlContainer"
|
||||
style={{
|
||||
opacity: isDragging ? 1 : 0.3, // Make more visible when dragging
|
||||
transition: "opacity 0.2s",
|
||||
position: "relative", // Ensure this is set inline too
|
||||
width: "max-content",
|
||||
height: "max-content",
|
||||
}}
|
||||
>
|
||||
{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}
|
||||
style={{
|
||||
left: "0px",
|
||||
top: "0px",
|
||||
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">
|
||||
{isSelf ? (
|
||||
<div onTouchStart={toggleMute} onClick={toggleMute}>
|
||||
{muted ? <MicOff color={colorAudio} /> : <Mic color={colorAudio} />}
|
||||
</div>
|
||||
) : (
|
||||
<div onTouchStart={toggleMute} onClick={toggleMute}>
|
||||
{muted ? <VolumeOff color={colorAudio} /> : <VolumeUp color={colorAudio} />}
|
||||
{/* Drop target spacer */}
|
||||
<div
|
||||
ref={spacerRef}
|
||||
className={`MediaControlSpacer ${className}`}
|
||||
style={{
|
||||
opacity: isDragging ? 1 : 0.3,
|
||||
transition: "opacity 0.2s",
|
||||
}}
|
||||
>
|
||||
{isDragging && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
fontSize: "0.7em",
|
||||
color: "#888",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
Drop here
|
||||
</div>
|
||||
)}
|
||||
<div onTouchStart={toggleVideo} onClick={toggleVideo}>
|
||||
{videoOn ? <Videocam color={colorVideo} /> : <VideocamOff color={colorVideo} />}
|
||||
</div>
|
||||
</div>
|
||||
{isValid ? (
|
||||
peer.attributes?.srcObject && (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<Video
|
||||
key={`video-${peer.session_id}-${peer.attributes.srcObject.id}`}
|
||||
className="Video"
|
||||
data-id={peer.peer_name}
|
||||
autoPlay
|
||||
srcObject={peer.attributes.srcObject}
|
||||
local={peer.local}
|
||||
muted={peer.local || muted}
|
||||
/>
|
||||
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
||||
</Box>
|
||||
)
|
||||
) : (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<div className="Video">Waiting for media…</div>
|
||||
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
||||
|
||||
{/* Moveable element - positioned absolute relative to container */}
|
||||
<div
|
||||
ref={targetRef}
|
||||
className={`MediaControl ${className}`}
|
||||
data-peer={peer.session_id}
|
||||
style={{
|
||||
position: "absolute", // Ensure this is set
|
||||
top: "0px",
|
||||
left: "0px",
|
||||
width: frame.width ? `${frame.width}px` : undefined,
|
||||
height: frame.height ? `${frame.height}px` : undefined,
|
||||
transform: `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`,
|
||||
}}
|
||||
>
|
||||
<Box className="Controls">
|
||||
{isSelf ? (
|
||||
// onTouchStart={toggleMute}
|
||||
<div onClick={toggleMute}>{muted ? <MicOff color={colorAudio} /> : <Mic color={colorAudio} />}</div>
|
||||
) : (
|
||||
<div onClick={toggleMute}>
|
||||
{muted ? <VolumeOff color={colorAudio} /> : <VolumeUp color={colorAudio} />}
|
||||
</div>
|
||||
)}
|
||||
<div onClick={toggleVideo}>
|
||||
{videoOn ? <Videocam color={colorVideo} /> : <VideocamOff color={colorVideo} />}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
{isValid ? (
|
||||
peer.attributes?.srcObject && (
|
||||
<>
|
||||
<Video
|
||||
key={`video-${peer.session_id}-${peer.attributes.srcObject.id}`}
|
||||
className="Video"
|
||||
data-id={peer.peer_name}
|
||||
autoPlay
|
||||
srcObject={peer.attributes.srcObject}
|
||||
local={peer.local}
|
||||
muted={peer.local || muted}
|
||||
/>
|
||||
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<div className="Video">Waiting for media…</div>
|
||||
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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;
|
||||
}
|
||||
<Moveable
|
||||
ref={moveableRef}
|
||||
flushSync={flushSync}
|
||||
container={containerRef.current} // Constrain to container if needed
|
||||
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);
|
||||
// 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 (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 (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 (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 });
|
||||
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] });
|
||||
// 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: [matrix.m41, matrix.m42],
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
});
|
||||
setFrame({ translate: [0, 0], 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>
|
||||
// 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>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user