mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
chore: normalize line endings
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user