diff options
Diffstat (limited to 'legacy/timeline/use-selection-box.ts')
| -rw-r--r-- | legacy/timeline/use-selection-box.ts | 530 |
1 files changed, 265 insertions, 265 deletions
diff --git a/legacy/timeline/use-selection-box.ts b/legacy/timeline/use-selection-box.ts index 027d3d76..979bb480 100644 --- a/legacy/timeline/use-selection-box.ts +++ b/legacy/timeline/use-selection-box.ts @@ -1,265 +1,265 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; -import { getCumulativeHeightBefore, getTrackHeight } from "@/lib/timeline"; -import { useEditor } from "../use-editor"; - -interface UseSelectionBoxProps { - containerRef: React.RefObject<HTMLElement | null>; - headerRef?: React.RefObject<HTMLElement | null>; - onSelectionComplete: ({ - elements, - isAdditive, - }: { - elements: { trackId: string; elementId: string }[]; - isAdditive: boolean; - }) => void; - isEnabled?: boolean; - tracksScrollRef: React.RefObject<HTMLDivElement | null>; - zoomLevel: number; -} - -interface SelectionBoxState { - startPos: { x: number; y: number }; - currentPos: { x: number; y: number }; - isActive: boolean; - isAdditive: boolean; -} - -interface SelectionRectangle { - left: number; - top: number; - right: number; - bottom: number; -} - -function getNormalizedRectangle({ - startPos, - endPos, -}: { - startPos: { x: number; y: number }; - endPos: { x: number; y: number }; -}): SelectionRectangle { - return { - left: Math.min(startPos.x, endPos.x), - top: Math.min(startPos.y, endPos.y), - right: Math.max(startPos.x, endPos.x), - bottom: Math.max(startPos.y, endPos.y), - }; -} - -function getSelectionRectangleInContent({ - container, - scrollContainer, - startPos, - endPos, -}: { - container: HTMLElement; - scrollContainer: HTMLDivElement | null; - startPos: { x: number; y: number }; - endPos: { x: number; y: number }; -}): SelectionRectangle { - const containerRect = container.getBoundingClientRect(); - const scrollRect = scrollContainer?.getBoundingClientRect() ?? containerRect; - const scrollLeft = scrollContainer?.scrollLeft ?? 0; - const scrollTop = scrollContainer?.scrollTop ?? 0; - - const adjustedStart = { - x: startPos.x - containerRect.left + scrollLeft, - y: startPos.y - scrollRect.top + scrollTop, - }; - const adjustedEnd = { - x: endPos.x - containerRect.left + scrollLeft, - y: endPos.y - scrollRect.top + scrollTop, - }; - - return getNormalizedRectangle({ - startPos: adjustedStart, - endPos: adjustedEnd, - }); -} - -function isRectangleIntersecting({ - elementRectangle, - selectionRectangle, -}: { - elementRectangle: SelectionRectangle; - selectionRectangle: SelectionRectangle; -}): boolean { - return !( - elementRectangle.right < selectionRectangle.left || - elementRectangle.left > selectionRectangle.right || - elementRectangle.bottom < selectionRectangle.top || - elementRectangle.top > selectionRectangle.bottom - ); -} - -export function useSelectionBox({ - containerRef, - headerRef, - onSelectionComplete, - isEnabled = true, - tracksScrollRef, - zoomLevel, -}: UseSelectionBoxProps) { - const editor = useEditor(); - const [selectionBox, setSelectionBox] = useState<SelectionBoxState | null>( - null, - ); - const justFinishedSelectingRef = useRef(false); - - const handleMouseDown = useCallback( - ({ clientX, clientY, shiftKey, ctrlKey, metaKey }: React.MouseEvent) => { - if (!isEnabled) return; - - setSelectionBox({ - startPos: { x: clientX, y: clientY }, - currentPos: { x: clientX, y: clientY }, - isActive: false, - isAdditive: shiftKey || ctrlKey || metaKey, - }); - }, - [isEnabled], - ); - - const selectElementsInBox = useCallback( - ({ - startPos, - endPos, - isAdditive, - }: { - startPos: { x: number; y: number }; - endPos: { x: number; y: number }; - isAdditive: boolean; - }) => { - if (!containerRef.current) return; - - const tracks = editor.timeline.getTracks(); - const container = containerRef.current; - const selectionRectangle = getSelectionRectangleInContent({ - container, - scrollContainer: tracksScrollRef.current, - startPos, - endPos, - }); - const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; - const timelineHeaderHeight = - headerRef?.current?.getBoundingClientRect().height ?? 0; - const selectedElements: { trackId: string; elementId: string }[] = []; - - for (const [trackIndex, track] of tracks.entries()) { - const trackTop = getCumulativeHeightBefore({ - tracks, - trackIndex, - }); - const trackHeight = getTrackHeight({ type: track.type }); - const elementTop = - timelineHeaderHeight + TIMELINE_CONSTANTS.PADDING_TOP_PX + trackTop; - const elementBottom = elementTop + trackHeight; - - for (const element of track.elements) { - const elementLeft = element.startTime * pixelsPerSecond; - const elementRight = elementLeft + element.duration * pixelsPerSecond; - - const elementRectangle = { - left: elementLeft, - top: elementTop, - right: elementRight, - bottom: elementBottom, - }; - - const intersects = isRectangleIntersecting({ - elementRectangle, - selectionRectangle, - }); - - if (intersects) { - selectedElements.push({ - trackId: track.id, - elementId: element.id, - }); - } - } - } - onSelectionComplete({ elements: selectedElements, isAdditive }); - }, - [ - containerRef, - headerRef, - onSelectionComplete, - editor, - tracksScrollRef, - zoomLevel, - ], - ); - - useEffect(() => { - if (!selectionBox) return; - - const handleMouseMove = ({ clientX, clientY }: MouseEvent) => { - const deltaX = Math.abs(clientX - selectionBox.startPos.x); - const deltaY = Math.abs(clientY - selectionBox.startPos.y); - const shouldActivate = deltaX > 5 || deltaY > 5; - - const newSelectionBox = { - ...selectionBox, - currentPos: { x: clientX, y: clientY }, - isActive: shouldActivate || selectionBox.isActive, - }; - - setSelectionBox(newSelectionBox); - - if (newSelectionBox.isActive) { - selectElementsInBox({ - startPos: newSelectionBox.startPos, - endPos: newSelectionBox.currentPos, - isAdditive: newSelectionBox.isAdditive, - }); - } - }; - - const handleMouseUp = () => { - if (selectionBox?.isActive) { - justFinishedSelectingRef.current = true; - requestAnimationFrame(() => { - justFinishedSelectingRef.current = false; - }); - } - setSelectionBox(null); - }; - - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("mouseup", handleMouseUp); - - return () => { - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("mouseup", handleMouseUp); - }; - }, [selectionBox, selectElementsInBox]); - - useEffect(() => { - if (!selectionBox) return; - - const previousBodyUserSelect = document.body.style.userSelect; - const container = containerRef.current; - const previousContainerUserSelect = container?.style.userSelect ?? ""; - - document.body.style.userSelect = "none"; - if (container) container.style.userSelect = "none"; - - return () => { - document.body.style.userSelect = previousBodyUserSelect; - if (container) container.style.userSelect = previousContainerUserSelect; - }; - }, [selectionBox, containerRef]); - - const shouldIgnoreClick = useCallback(() => { - return justFinishedSelectingRef.current; - }, []); - - return { - selectionBox, - handleMouseDown, - isSelecting: selectionBox?.isActive || false, - shouldIgnoreClick, - }; -} +import { useCallback, useEffect, useRef, useState } from "react";
+import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
+import { getCumulativeHeightBefore, getTrackHeight } from "@/lib/timeline";
+import { useEditor } from "../use-editor";
+
+interface UseSelectionBoxProps {
+ containerRef: React.RefObject<HTMLElement | null>;
+ headerRef?: React.RefObject<HTMLElement | null>;
+ onSelectionComplete: ({
+ elements,
+ isAdditive,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ isAdditive: boolean;
+ }) => void;
+ isEnabled?: boolean;
+ tracksScrollRef: React.RefObject<HTMLDivElement | null>;
+ zoomLevel: number;
+}
+
+interface SelectionBoxState {
+ startPos: { x: number; y: number };
+ currentPos: { x: number; y: number };
+ isActive: boolean;
+ isAdditive: boolean;
+}
+
+interface SelectionRectangle {
+ left: number;
+ top: number;
+ right: number;
+ bottom: number;
+}
+
+function getNormalizedRectangle({
+ startPos,
+ endPos,
+}: {
+ startPos: { x: number; y: number };
+ endPos: { x: number; y: number };
+}): SelectionRectangle {
+ return {
+ left: Math.min(startPos.x, endPos.x),
+ top: Math.min(startPos.y, endPos.y),
+ right: Math.max(startPos.x, endPos.x),
+ bottom: Math.max(startPos.y, endPos.y),
+ };
+}
+
+function getSelectionRectangleInContent({
+ container,
+ scrollContainer,
+ startPos,
+ endPos,
+}: {
+ container: HTMLElement;
+ scrollContainer: HTMLDivElement | null;
+ startPos: { x: number; y: number };
+ endPos: { x: number; y: number };
+}): SelectionRectangle {
+ const containerRect = container.getBoundingClientRect();
+ const scrollRect = scrollContainer?.getBoundingClientRect() ?? containerRect;
+ const scrollLeft = scrollContainer?.scrollLeft ?? 0;
+ const scrollTop = scrollContainer?.scrollTop ?? 0;
+
+ const adjustedStart = {
+ x: startPos.x - containerRect.left + scrollLeft,
+ y: startPos.y - scrollRect.top + scrollTop,
+ };
+ const adjustedEnd = {
+ x: endPos.x - containerRect.left + scrollLeft,
+ y: endPos.y - scrollRect.top + scrollTop,
+ };
+
+ return getNormalizedRectangle({
+ startPos: adjustedStart,
+ endPos: adjustedEnd,
+ });
+}
+
+function isRectangleIntersecting({
+ elementRectangle,
+ selectionRectangle,
+}: {
+ elementRectangle: SelectionRectangle;
+ selectionRectangle: SelectionRectangle;
+}): boolean {
+ return !(
+ elementRectangle.right < selectionRectangle.left ||
+ elementRectangle.left > selectionRectangle.right ||
+ elementRectangle.bottom < selectionRectangle.top ||
+ elementRectangle.top > selectionRectangle.bottom
+ );
+}
+
+export function useSelectionBox({
+ containerRef,
+ headerRef,
+ onSelectionComplete,
+ isEnabled = true,
+ tracksScrollRef,
+ zoomLevel,
+}: UseSelectionBoxProps) {
+ const editor = useEditor();
+ const [selectionBox, setSelectionBox] = useState<SelectionBoxState | null>(
+ null,
+ );
+ const justFinishedSelectingRef = useRef(false);
+
+ const handleMouseDown = useCallback(
+ ({ clientX, clientY, shiftKey, ctrlKey, metaKey }: React.MouseEvent) => {
+ if (!isEnabled) return;
+
+ setSelectionBox({
+ startPos: { x: clientX, y: clientY },
+ currentPos: { x: clientX, y: clientY },
+ isActive: false,
+ isAdditive: shiftKey || ctrlKey || metaKey,
+ });
+ },
+ [isEnabled],
+ );
+
+ const selectElementsInBox = useCallback(
+ ({
+ startPos,
+ endPos,
+ isAdditive,
+ }: {
+ startPos: { x: number; y: number };
+ endPos: { x: number; y: number };
+ isAdditive: boolean;
+ }) => {
+ if (!containerRef.current) return;
+
+ const tracks = editor.timeline.getTracks();
+ const container = containerRef.current;
+ const selectionRectangle = getSelectionRectangleInContent({
+ container,
+ scrollContainer: tracksScrollRef.current,
+ startPos,
+ endPos,
+ });
+ const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
+ const timelineHeaderHeight =
+ headerRef?.current?.getBoundingClientRect().height ?? 0;
+ const selectedElements: { trackId: string; elementId: string }[] = [];
+
+ for (const [trackIndex, track] of tracks.entries()) {
+ const trackTop = getCumulativeHeightBefore({
+ tracks,
+ trackIndex,
+ });
+ const trackHeight = getTrackHeight({ type: track.type });
+ const elementTop =
+ timelineHeaderHeight + TIMELINE_CONSTANTS.PADDING_TOP_PX + trackTop;
+ const elementBottom = elementTop + trackHeight;
+
+ for (const element of track.elements) {
+ const elementLeft = element.startTime * pixelsPerSecond;
+ const elementRight = elementLeft + element.duration * pixelsPerSecond;
+
+ const elementRectangle = {
+ left: elementLeft,
+ top: elementTop,
+ right: elementRight,
+ bottom: elementBottom,
+ };
+
+ const intersects = isRectangleIntersecting({
+ elementRectangle,
+ selectionRectangle,
+ });
+
+ if (intersects) {
+ selectedElements.push({
+ trackId: track.id,
+ elementId: element.id,
+ });
+ }
+ }
+ }
+ onSelectionComplete({ elements: selectedElements, isAdditive });
+ },
+ [
+ containerRef,
+ headerRef,
+ onSelectionComplete,
+ editor,
+ tracksScrollRef,
+ zoomLevel,
+ ],
+ );
+
+ useEffect(() => {
+ if (!selectionBox) return;
+
+ const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
+ const deltaX = Math.abs(clientX - selectionBox.startPos.x);
+ const deltaY = Math.abs(clientY - selectionBox.startPos.y);
+ const shouldActivate = deltaX > 5 || deltaY > 5;
+
+ const newSelectionBox = {
+ ...selectionBox,
+ currentPos: { x: clientX, y: clientY },
+ isActive: shouldActivate || selectionBox.isActive,
+ };
+
+ setSelectionBox(newSelectionBox);
+
+ if (newSelectionBox.isActive) {
+ selectElementsInBox({
+ startPos: newSelectionBox.startPos,
+ endPos: newSelectionBox.currentPos,
+ isAdditive: newSelectionBox.isAdditive,
+ });
+ }
+ };
+
+ const handleMouseUp = () => {
+ if (selectionBox?.isActive) {
+ justFinishedSelectingRef.current = true;
+ requestAnimationFrame(() => {
+ justFinishedSelectingRef.current = false;
+ });
+ }
+ setSelectionBox(null);
+ };
+
+ window.addEventListener("mousemove", handleMouseMove);
+ window.addEventListener("mouseup", handleMouseUp);
+
+ return () => {
+ window.removeEventListener("mousemove", handleMouseMove);
+ window.removeEventListener("mouseup", handleMouseUp);
+ };
+ }, [selectionBox, selectElementsInBox]);
+
+ useEffect(() => {
+ if (!selectionBox) return;
+
+ const previousBodyUserSelect = document.body.style.userSelect;
+ const container = containerRef.current;
+ const previousContainerUserSelect = container?.style.userSelect ?? "";
+
+ document.body.style.userSelect = "none";
+ if (container) container.style.userSelect = "none";
+
+ return () => {
+ document.body.style.userSelect = previousBodyUserSelect;
+ if (container) container.style.userSelect = previousContainerUserSelect;
+ };
+ }, [selectionBox, containerRef]);
+
+ const shouldIgnoreClick = useCallback(() => {
+ return justFinishedSelectingRef.current;
+ }, []);
+
+ return {
+ selectionBox,
+ handleMouseDown,
+ isSelecting: selectionBox?.isActive || false,
+ shouldIgnoreClick,
+ };
+}
|
