feat: Native video display (#5866)

This commit is contained in:
Tom Moor
2023-09-28 20:28:09 -04:00
committed by GitHub
parent bd06e03b1e
commit f4fd9dae5f
24 changed files with 840 additions and 344 deletions

View File

@@ -0,0 +1,63 @@
import { useState, useEffect } from "react";
const defaultRect = {
top: 0,
left: 0,
bottom: 0,
right: 0,
x: 0,
y: 0,
width: 0,
height: 0,
};
export default function useComponentSize(
element: HTMLElement | null
): DOMRect | typeof defaultRect {
const [size, setSize] = useState(element?.getBoundingClientRect());
useEffect(() => {
const sizeObserver = new ResizeObserver((entries) => {
entries.forEach(({ target }) => {
const rect = target?.getBoundingClientRect();
setSize((state) =>
state?.width === rect?.width &&
state?.height === rect?.height &&
state?.x === rect?.x &&
state?.y === rect?.y
? state
: rect
);
});
});
if (element) {
sizeObserver.observe(element);
}
return () => sizeObserver.disconnect();
}, [element]);
useEffect(() => {
const handleResize = () => {
const rect = element?.getBoundingClientRect();
setSize((state) =>
state?.width === rect?.width &&
state?.height === rect?.height &&
state?.x === rect?.x &&
state?.y === rect?.y
? state
: rect
);
};
window.addEventListener("click", handleResize);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("click", handleResize);
window.removeEventListener("resize", handleResize);
};
});
return size ?? defaultRect;
}

View File

@@ -0,0 +1,126 @@
import * as React from "react";
type DragDirection = "left" | "right";
type SizeState = { width: number; height?: number };
type ReturnValue = {
handlePointerDown: (
dragging: DragDirection
) => (event: React.PointerEvent<HTMLDivElement>) => void;
setSize: React.Dispatch<React.SetStateAction<SizeState>>;
dragging: boolean;
width: number;
height?: number;
};
type Props = {
onChangeSize?: undefined | ((size: SizeState) => void);
width: number;
height: number;
naturalWidth: number;
naturalHeight: number;
minWidth: number;
maxWidth: number;
gridWidth: number;
};
export default function useDragResize(props: Props): ReturnValue {
const [size, setSize] = React.useState<SizeState>({
width: props.width,
height: props.height,
});
const [offset, setOffset] = React.useState(0);
const [sizeAtDragStart, setSizeAtDragStart] = React.useState(size);
const [dragging, setDragging] = React.useState<DragDirection>();
const isResizable = !!props.onChangeSize;
const constrainWidth = (width: number) =>
Math.round(Math.min(props.maxWidth, Math.max(width, props.minWidth)));
const handlePointerMove = (event: PointerEvent) => {
event.preventDefault();
let diff;
if (dragging === "left") {
diff = offset - event.pageX;
} else {
diff = event.pageX - offset;
}
const newWidth = sizeAtDragStart.width + diff * 2;
const widthOnGrid =
Math.round(newWidth / props.gridWidth) * props.gridWidth;
const constrainedWidth = constrainWidth(widthOnGrid);
const aspectRatio = props.naturalHeight / props.naturalWidth;
setSize({
width: constrainedWidth,
height: props.naturalWidth
? Math.round(constrainedWidth * aspectRatio)
: undefined,
});
};
const handlePointerUp = (event: PointerEvent) => {
event.preventDefault();
event.stopPropagation();
setOffset(0);
setDragging(undefined);
props.onChangeSize?.(size);
document.removeEventListener("mousemove", handlePointerMove);
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
event.preventDefault();
event.stopPropagation();
setSize(sizeAtDragStart);
setDragging(undefined);
}
};
const handlePointerDown =
(dragging: "left" | "right") =>
(event: React.PointerEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setSizeAtDragStart({
width: constrainWidth(size.width),
height: size.height,
});
setOffset(event.pageX);
setDragging(dragging);
};
React.useEffect(() => {
if (!isResizable) {
return;
}
if (dragging) {
document.body.style.cursor = "ew-resize";
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("pointermove", handlePointerMove);
document.addEventListener("pointerup", handlePointerUp);
}
return () => {
document.body.style.cursor = "initial";
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("pointermove", handlePointerMove);
document.removeEventListener("pointerup", handlePointerUp);
};
}, [dragging, handlePointerMove, handlePointerUp, isResizable]);
return {
handlePointerDown,
dragging: !!dragging,
setSize,
width: size.width,
height: size.height,
};
}