mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: bunx biome format --write .
This commit is contained in:
@@ -20,11 +20,11 @@ export function useTimelineActionHandlers() {
|
|||||||
// Create optimized lookup maps for O(1) access instead of O(n) find operations
|
// Create optimized lookup maps for O(1) access instead of O(n) find operations
|
||||||
const trackMap = useMemo(() => {
|
const trackMap = useMemo(() => {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
tracks.forEach(track => {
|
tracks.forEach((track) => {
|
||||||
map.set(track.id, track);
|
map.set(track.id, track);
|
||||||
// Also create element lookup for this track
|
// Also create element lookup for this track
|
||||||
const elementMap = new Map();
|
const elementMap = new Map();
|
||||||
track.elements.forEach(element => {
|
track.elements.forEach((element) => {
|
||||||
elementMap.set(element.id, element);
|
elementMap.set(element.id, element);
|
||||||
});
|
});
|
||||||
map.set(`${track.id}_elements`, elementMap);
|
map.set(`${track.id}_elements`, elementMap);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useProjectStore } from "@/stores/project-store";
|
import { useProjectStore } from "@/stores/project-store";
|
||||||
import { TIMELINE_CONSTANTS, snapTimeToFrame } from "@/constants/timeline-constants";
|
import {
|
||||||
|
TIMELINE_CONSTANTS,
|
||||||
|
snapTimeToFrame,
|
||||||
|
} from "@/constants/timeline-constants";
|
||||||
|
|
||||||
export interface TimelineContentClickProps {
|
export interface TimelineContentClickProps {
|
||||||
duration: number;
|
duration: number;
|
||||||
@@ -56,7 +59,7 @@ export function useTimelineContentClick({
|
|||||||
|
|
||||||
// Determine if we're clicking in ruler or tracks area
|
// Determine if we're clicking in ruler or tracks area
|
||||||
const isRulerClick = (e.target as HTMLElement).closest(
|
const isRulerClick = (e.target as HTMLElement).closest(
|
||||||
"[data-ruler-area]"
|
"[data-ruler-area]",
|
||||||
);
|
);
|
||||||
|
|
||||||
let mouseX: number;
|
let mouseX: number;
|
||||||
@@ -65,7 +68,7 @@ export function useTimelineContentClick({
|
|||||||
if (isRulerClick) {
|
if (isRulerClick) {
|
||||||
// Calculate based on ruler position
|
// Calculate based on ruler position
|
||||||
const rulerContent = rulerScrollRef.current?.querySelector(
|
const rulerContent = rulerScrollRef.current?.querySelector(
|
||||||
"[data-radix-scroll-area-viewport]"
|
"[data-radix-scroll-area-viewport]",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
if (!rulerContent) return;
|
if (!rulerContent) return;
|
||||||
const rect = rulerContent.getBoundingClientRect();
|
const rect = rulerContent.getBoundingClientRect();
|
||||||
@@ -74,7 +77,7 @@ export function useTimelineContentClick({
|
|||||||
} else {
|
} else {
|
||||||
// Calculate based on tracks content position
|
// Calculate based on tracks content position
|
||||||
const tracksContent = tracksScrollRef.current?.querySelector(
|
const tracksContent = tracksScrollRef.current?.querySelector(
|
||||||
"[data-radix-scroll-area-viewport]"
|
"[data-radix-scroll-area-viewport]",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
if (!tracksContent) return;
|
if (!tracksContent) return;
|
||||||
const rect = tracksContent.getBoundingClientRect();
|
const rect = tracksContent.getBoundingClientRect();
|
||||||
@@ -87,8 +90,8 @@ export function useTimelineContentClick({
|
|||||||
Math.min(
|
Math.min(
|
||||||
duration,
|
duration,
|
||||||
(mouseX + scrollLeft) /
|
(mouseX + scrollLeft) /
|
||||||
(TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel)
|
(TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use frame snapping for timeline clicking
|
// Use frame snapping for timeline clicking
|
||||||
@@ -108,7 +111,7 @@ export function useTimelineContentClick({
|
|||||||
justFinishedSelecting,
|
justFinishedSelecting,
|
||||||
playheadRef,
|
playheadRef,
|
||||||
activeProject?.fps,
|
activeProject?.fps,
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { handleTimelineContentClick };
|
return { handleTimelineContentClick };
|
||||||
|
|||||||
@@ -56,9 +56,13 @@ export function TimelineContent({
|
|||||||
showSnapIndicator,
|
showSnapIndicator,
|
||||||
}: TimelineContentProps) {
|
}: TimelineContentProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative min-w-max min-h-max" style={{ minWidth: `${dynamicTimelineWidth}px` }}>
|
<div
|
||||||
<div className={`h-full grid grid-cols-[192px_1fr] ${tracks.length > 0 ? `grid-rows-[20px_repeat(${tracks.length},minmax(0,max-content))]` : 'grid-rows-[20px_1fr]'}`}>
|
className="relative min-w-max min-h-max"
|
||||||
|
style={{ minWidth: `${dynamicTimelineWidth}px` }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`h-full grid grid-cols-[192px_1fr] ${tracks.length > 0 ? `grid-rows-[20px_repeat(${tracks.length},minmax(0,max-content))]` : "grid-rows-[20px_1fr]"}`}
|
||||||
|
>
|
||||||
{/* Top-Left Corner (Empty space above track labels) */}
|
{/* Top-Left Corner (Empty space above track labels) */}
|
||||||
<div className="sticky top-0 left-0 border-inset bg-card/[0.99]"></div>
|
<div className="sticky top-0 left-0 border-inset bg-card/[0.99]"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export function useTimelineDragHandlers({
|
|||||||
|
|
||||||
// Ignore timeline element drags - they're handled by track-specific handlers
|
// Ignore timeline element drags - they're handled by track-specific handlers
|
||||||
const hasTimelineElement = e.dataTransfer.types.includes(
|
const hasTimelineElement = e.dataTransfer.types.includes(
|
||||||
"application/x-timeline-element"
|
"application/x-timeline-element",
|
||||||
);
|
);
|
||||||
if (hasTimelineElement) {
|
if (hasTimelineElement) {
|
||||||
return;
|
return;
|
||||||
@@ -83,7 +83,7 @@ export function useTimelineDragHandlers({
|
|||||||
} else {
|
} else {
|
||||||
// Handle media items
|
// Handle media items
|
||||||
const mediaItem = mediaItems.find(
|
const mediaItem = mediaItems.find(
|
||||||
(item: any) => item.id === dragData.id
|
(item: any) => item.id === dragData.id,
|
||||||
);
|
);
|
||||||
if (!mediaItem) {
|
if (!mediaItem) {
|
||||||
toast.error("Media item not found");
|
toast.error("Media item not found");
|
||||||
@@ -108,14 +108,15 @@ export function useTimelineDragHandlers({
|
|||||||
try {
|
try {
|
||||||
const processedItems = await processMediaFiles(
|
const processedItems = await processMediaFiles(
|
||||||
e.dataTransfer.files,
|
e.dataTransfer.files,
|
||||||
(p) => setProgress(p)
|
(p) => setProgress(p),
|
||||||
);
|
);
|
||||||
for (const processedItem of processedItems) {
|
for (const processedItem of processedItems) {
|
||||||
await addMediaItem(activeProject.id, processedItem);
|
await addMediaItem(activeProject.id, processedItem);
|
||||||
const currentMediaItems = useMediaStore.getState().mediaItems;
|
const currentMediaItems = useMediaStore.getState().mediaItems;
|
||||||
const addedItem = currentMediaItems.find(
|
const addedItem = currentMediaItems.find(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.name === processedItem.name && item.url === processedItem.url
|
item.name === processedItem.name &&
|
||||||
|
item.url === processedItem.url,
|
||||||
);
|
);
|
||||||
if (addedItem) {
|
if (addedItem) {
|
||||||
useTimelineStore.getState().addMediaToNewTrack(addedItem);
|
useTimelineStore.getState().addMediaToNewTrack(addedItem);
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ export function TimelinePlayhead({
|
|||||||
|
|
||||||
// Get dynamic track labels width, fallback to 192px (ml-48) if no tracks or no ref
|
// Get dynamic track labels width, fallback to 192px (ml-48) if no tracks or no ref
|
||||||
const trackLabelsWidth = 192; // Fixed width from grid layout
|
const trackLabelsWidth = 192; // Fixed width from grid layout
|
||||||
const leftPosition = playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
|
const leftPosition =
|
||||||
|
playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -77,8 +77,10 @@ export function TimelineRuler({
|
|||||||
const time = i * interval;
|
const time = i * interval;
|
||||||
if (time > duration) break;
|
if (time > duration) break;
|
||||||
|
|
||||||
const isMainMarker = time % (interval >= 1 ? Math.max(1, interval) : 1) === 0;
|
const isMainMarker =
|
||||||
const leftPosition = time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
|
time % (interval >= 1 ? Math.max(1, interval) : 1) === 0;
|
||||||
|
const leftPosition =
|
||||||
|
time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
|
||||||
|
|
||||||
markers.push(
|
markers.push(
|
||||||
<div
|
<div
|
||||||
@@ -99,7 +101,7 @@ export function TimelineRuler({
|
|||||||
>
|
>
|
||||||
{formatTime(time, interval)}
|
{formatTime(time, interval)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export function useTimelineScrollSync({
|
|||||||
// --- Horizontal scroll synchronization between ruler and tracks ---
|
// --- Horizontal scroll synchronization between ruler and tracks ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const rulerViewport = rulerScrollRef.current?.querySelector(
|
const rulerViewport = rulerScrollRef.current?.querySelector(
|
||||||
"[data-radix-scroll-area-viewport]"
|
"[data-radix-scroll-area-viewport]",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
const tracksViewport = tracksScrollRef.current?.querySelector(
|
const tracksViewport = tracksScrollRef.current?.querySelector(
|
||||||
"[data-radix-scroll-area-viewport]"
|
"[data-radix-scroll-area-viewport]",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
|
|
||||||
if (!rulerViewport || !tracksViewport) return;
|
if (!rulerViewport || !tracksViewport) return;
|
||||||
@@ -44,8 +44,12 @@ export function useTimelineScrollSync({
|
|||||||
isUpdatingRef.current = false;
|
isUpdatingRef.current = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
rulerViewport.addEventListener("scroll", handleRulerScroll, { passive: true });
|
rulerViewport.addEventListener("scroll", handleRulerScroll, {
|
||||||
tracksViewport.addEventListener("scroll", handleTracksScroll, { passive: true });
|
passive: true,
|
||||||
|
});
|
||||||
|
tracksViewport.addEventListener("scroll", handleTracksScroll, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
rulerViewport.removeEventListener("scroll", handleRulerScroll);
|
rulerViewport.removeEventListener("scroll", handleRulerScroll);
|
||||||
|
|||||||
@@ -105,20 +105,14 @@ export function TimelineToolbar({
|
|||||||
Add Test Clip
|
Add Test Clip
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>Add a test clip to try playback</TooltipContent>
|
||||||
Add a test clip to try playback
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="w-px h-6 bg-border mx-1" />
|
<div className="w-px h-6 bg-border mx-1" />
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button variant="text" size="icon" onClick={handleSplitSelected}>
|
||||||
variant="text"
|
|
||||||
size="icon"
|
|
||||||
onClick={handleSplitSelected}
|
|
||||||
>
|
|
||||||
<Scissors className="h-4 w-4" />
|
<Scissors className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -150,11 +144,7 @@ export function TimelineToolbar({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button variant="text" size="icon" onClick={handleSeparateAudio}>
|
||||||
variant="text"
|
|
||||||
size="icon"
|
|
||||||
onClick={handleSeparateAudio}
|
|
||||||
>
|
|
||||||
<SplitSquareHorizontal className="h-4 w-4" />
|
<SplitSquareHorizontal className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -174,11 +164,7 @@ export function TimelineToolbar({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button variant="text" size="icon" onClick={handleFreezeSelected}>
|
||||||
variant="text"
|
|
||||||
size="icon"
|
|
||||||
onClick={handleFreezeSelected}
|
|
||||||
>
|
|
||||||
<Snowflake className="h-4 w-4" />
|
<Snowflake className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -186,11 +172,7 @@ export function TimelineToolbar({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button variant="text" size="icon" onClick={handleDeleteSelected}>
|
||||||
variant="text"
|
|
||||||
size="icon"
|
|
||||||
onClick={handleDeleteSelected}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export function TimelineTracksArea({
|
|||||||
<TrackIcon track={track} />
|
<TrackIcon track={track} />
|
||||||
</div>
|
</div>
|
||||||
{track.muted && (
|
{track.muted && (
|
||||||
<span className="text-xs text-red-500 font-semibold">
|
<span className="text-xs text-red-500 font-semibold">Muted</span>
|
||||||
Muted
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,11 +53,7 @@ export function TimelineTracksArea({
|
|||||||
className=" h-full"
|
className=" h-full"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// If clicking empty area (not on a element), deselect all elements
|
// If clicking empty area (not on a element), deselect all elements
|
||||||
if (
|
if (!(e.target as HTMLElement).closest(".timeline-element")) {
|
||||||
!(e.target as HTMLElement).closest(
|
|
||||||
".timeline-element"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
clearSelectedElements();
|
clearSelectedElements();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -72,21 +66,17 @@ export function TimelineTracksArea({
|
|||||||
</div>
|
</div>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem onClick={() => toggleTrackMute(track.id)}>
|
||||||
onClick={() => toggleTrackMute(track.id)}
|
|
||||||
>
|
|
||||||
{track.muted ? "Unmute Track" : "Mute Track"}
|
{track.muted ? "Unmute Track" : "Mute Track"}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem>
|
<ContextMenuItem>Track settings (soon)</ContextMenuItem>
|
||||||
Track settings (soon)
|
|
||||||
</ContextMenuItem>
|
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
{/* Add Track Button - spans full width */}
|
{/* Add Track Button - spans full width */}
|
||||||
<div
|
<div
|
||||||
onClick={() => addTrack('media')}
|
onClick={() => addTrack("media")}
|
||||||
className="col-span-1 sticky left-0 w-full flex items-center border-b border-muted bg-card/[0.99] hover:bg-card/50 transition-colors cursor-pointer z-[89]"
|
className="col-span-1 sticky left-0 w-full flex items-center border-b border-muted bg-card/[0.99] hover:bg-card/50 transition-colors cursor-pointer z-[89]"
|
||||||
style={{ height: `${getTrackHeight("media")}px` }}
|
style={{ height: `${getTrackHeight("media")}px` }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ export function useTimelineWheelHandler({
|
|||||||
handleWheel(e as any);
|
handleWheel(e as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
timelineContainer.addEventListener("wheel", handleWheelCapture, { passive: false });
|
timelineContainer.addEventListener("wheel", handleWheelCapture, {
|
||||||
|
passive: false,
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
timelineContainer.removeEventListener("wheel", handleWheelCapture);
|
timelineContainer.removeEventListener("wheel", handleWheelCapture);
|
||||||
|
|||||||
Reference in New Issue
Block a user