feat: Native video display (#5866)
This commit is contained in:
63
shared/editor/components/hooks/useComponentSize.ts
Normal file
63
shared/editor/components/hooks/useComponentSize.ts
Normal 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;
|
||||
}
|
||||
126
shared/editor/components/hooks/useDragResize.ts
Normal file
126
shared/editor/components/hooks/useDragResize.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user