1
0

Double tap to toggle moved MediaControl

This commit is contained in:
James Ketr 2025-10-09 12:51:07 -07:00
parent 0d1024ff61
commit d12d87a796

View File

@ -1322,6 +1322,14 @@ const MediaControl: React.FC<MediaControlProps> = ({
width?: number; width?: number;
height?: number; height?: number;
}>({ translate: [0, 0] }); }>({ translate: [0, 0] });
// Remember last released moveable position/size so we can restore to it
const lastSavedRef = useRef<{
translate: [number, number];
width?: number;
height?: number;
} | null>(null);
// Whether the target is currently snapped to the spacer (true) or in a free position (false)
const [isAttached, setIsAttached] = useState<boolean>(true);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const targetRef = useRef<HTMLDivElement>(null); const targetRef = useRef<HTMLDivElement>(null);
const spacerRef = useRef<HTMLDivElement>(null); const spacerRef = useRef<HTMLDivElement>(null);
@ -1341,6 +1349,40 @@ const MediaControl: React.FC<MediaControlProps> = ({
}; };
}, []); }, []);
// Double-click toggles between spacer-attached and last saved free position
const handleDoubleClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
// Ignore double-clicks on control buttons
const targetEl = e.target as HTMLElement | null;
if (targetEl && (targetEl.closest("button") || targetEl.closest(".MuiIconButton-root"))) return;
if (!targetRef.current || !spacerRef.current) return;
// If currently attached to spacer -> restore to last saved moveable position
if (isAttached) {
const last = lastSavedRef.current;
if (last) {
targetRef.current.style.transform = `translate(${last.translate[0]}px, ${last.translate[1]}px)`;
if (typeof last.width === "number") targetRef.current.style.width = `${last.width}px`;
if (typeof last.height === "number") targetRef.current.style.height = `${last.height}px`;
setFrame(last);
setIsAttached(false);
}
return;
}
// If not attached -> move back to spacer (origin)
const spacerRect = spacerRef.current.getBoundingClientRect();
targetRef.current.style.transform = "translate(0px, 0px)";
targetRef.current.style.width = `${spacerRect.width}px`;
targetRef.current.style.height = `${spacerRect.height}px`;
setFrame({ translate: [0, 0], width: spacerRect.width, height: spacerRect.height });
setIsAttached(true);
},
[isAttached]
);
useEffect(() => { useEffect(() => {
console.log( console.log(
`media-agent - MediaControl mounted for peer ${peer?.peer_name}, local=${peer?.local}, hasSrcObject=${!!peer `media-agent - MediaControl mounted for peer ${peer?.peer_name}, local=${peer?.local}, hasSrcObject=${!!peer
@ -1579,10 +1621,11 @@ const MediaControl: React.FC<MediaControlProps> = ({
opacity: isDragging ? 1 : 0.3, opacity: isDragging ? 1 : 0.3,
transition: "opacity 0.2s", transition: "opacity 0.2s",
}} }}
onDoubleClick={handleDoubleClick}
> >
{isDragging && ( {isDragging && (
<div <Box
style={{ sx={{
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
@ -1590,10 +1633,11 @@ const MediaControl: React.FC<MediaControlProps> = ({
fontSize: "0.7em", fontSize: "0.7em",
color: "#888", color: "#888",
pointerEvents: "none", pointerEvents: "none",
userSelect: "none",
}} }}
> >
Drop here Drop here
</div> </Box>
)} )}
</div> </div>
@ -1602,6 +1646,7 @@ const MediaControl: React.FC<MediaControlProps> = ({
ref={targetRef} ref={targetRef}
className={`MediaControl ${className}`} className={`MediaControl ${className}`}
data-peer={peer.session_id} data-peer={peer.session_id}
onDoubleClick={handleDoubleClick}
style={{ style={{
position: "absolute", position: "absolute",
top: "0px", top: "0px",
@ -1651,6 +1696,7 @@ const MediaControl: React.FC<MediaControlProps> = ({
srcObject={peer.attributes.srcObject} srcObject={peer.attributes.srcObject}
local={peer.local} local={peer.local}
muted={peer.local || muted} muted={peer.local || muted}
onDoubleClick={handleDoubleClick}
/> />
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} /> <WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} /> <WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
@ -1711,19 +1757,29 @@ const MediaControl: React.FC<MediaControlProps> = ({
const shouldSnap = checkSnapBack(matrix.m41, matrix.m42); const shouldSnap = checkSnapBack(matrix.m41, matrix.m42);
if (shouldSnap) { if (shouldSnap) {
targetRef.current.style.transform = "translate(0px, 0px)"; targetRef.current.style.transform = "translate(0px, 0px)";
setFrame({ translate: [0, 0], width: frame.width, height: frame.height }); // Snap back to spacer origin
setFrame((prev) => ({ translate: [0, 0], width: prev.width, height: prev.height }));
if (spacerRef.current) { if (spacerRef.current) {
const spacerRect = spacerRef.current.getBoundingClientRect(); const spacerRect = spacerRef.current.getBoundingClientRect();
targetRef.current.style.width = `${spacerRect.width}px`; targetRef.current.style.width = `${spacerRect.width}px`;
targetRef.current.style.height = `${spacerRect.height}px`; targetRef.current.style.height = `${spacerRect.height}px`;
setFrame({ translate: [0, 0] }); setFrame({ translate: [0, 0], width: spacerRect.width, height: spacerRect.height });
} }
// Remember that we're attached to spacer
setIsAttached(true);
} else { } else {
setFrame({ setFrame({
translate: [matrix.m41, matrix.m42], translate: [matrix.m41, matrix.m42],
width: frame.width, width: frame.width,
height: frame.height, height: frame.height,
}); });
// Save last free position
lastSavedRef.current = {
translate: [matrix.m41, matrix.m42],
width: frame.width,
height: frame.height,
};
setIsAttached(false);
} }
} else { } else {
setFrame({ translate: [0, 0], width: frame.width, height: frame.height }); setFrame({ translate: [0, 0], width: frame.width, height: frame.height });
@ -1748,6 +1804,26 @@ const MediaControl: React.FC<MediaControlProps> = ({
}} }}
onResizeEnd={() => { onResizeEnd={() => {
setIsDragging(false); setIsDragging(false);
// Save last size when user finishes resizing; preserve translate
if (targetRef.current) {
const computedStyle = getComputedStyle(targetRef.current);
const transform = computedStyle.transform;
let tx = 0,
ty = 0;
if (transform && transform !== "none") {
const matrix = new DOMMatrix(transform);
tx = matrix.m41;
ty = matrix.m42;
}
lastSavedRef.current = {
translate: [tx, ty],
width: frame.width,
height: frame.height,
};
// If we resized while attached to spacer, consider that we are free
if (tx !== 0 || ty !== 0) setIsAttached(false);
else setIsAttached(true);
}
}} }}
/> />
</div> </div>