diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-25 16:47:22 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-29 15:48:22 +0200 |
| commit | 8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch) | |
| tree | e9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/hooks/timeline/use-selection-box.ts | |
| parent | 39ea298a9ccba3104c549c0ca079c231981811f5 (diff) | |
| download | opencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz | |
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/hooks/timeline/use-selection-box.ts')
| -rw-r--r-- | apps/web/src/hooks/timeline/use-selection-box.ts | 255 |
1 files changed, 0 insertions, 255 deletions
diff --git a/apps/web/src/hooks/timeline/use-selection-box.ts b/apps/web/src/hooks/timeline/use-selection-box.ts deleted file mode 100644 index c4f8d6ce..00000000 --- a/apps/web/src/hooks/timeline/use-selection-box.ts +++ /dev/null @@ -1,255 +0,0 @@ -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: { trackId: string; elementId: string }[], - ) => void; - isEnabled?: boolean; - tracksScrollRef: React.RefObject<HTMLDivElement | null>; - zoomLevel: number; -} - -interface SelectionBoxState { - startPos: { x: number; y: number }; - currentPos: { x: number; y: number }; - isActive: 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 scrollLeft = scrollContainer?.scrollLeft ?? 0; - const scrollTop = scrollContainer?.scrollTop ?? 0; - - const adjustedStart = { - x: startPos.x - containerRect.left + scrollLeft, - y: startPos.y - containerRect.top + scrollTop, - }; - const adjustedEnd = { - x: endPos.x - containerRect.left + scrollLeft, - y: endPos.y - containerRect.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 tracks = editor.timeline.getTracks(); - const [selectionBox, setSelectionBox] = useState<SelectionBoxState | null>( - null, - ); - const justFinishedSelectingRef = useRef(false); - - const handleMouseDown = useCallback( - ({ clientX, clientY }: React.MouseEvent) => { - if (!isEnabled) return; - - setSelectionBox({ - startPos: { x: clientX, y: clientY }, - currentPos: { x: clientX, y: clientY }, - isActive: false, - }); - }, - [isEnabled], - ); - - const selectElementsInBox = useCallback( - ({ - startPos, - endPos, - }: { - startPos: { x: number; y: number }; - endPos: { x: number; y: number }; - }) => { - if (!containerRef.current) return; - - 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(selectedElements); - }, - [ - containerRef, - headerRef, - onSelectionComplete, - tracks, - 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, - }); - } - }; - - 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, - }; -} |
