summaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
authorSimon Orzel <simonorzel@gmail.com>2025-07-17 02:53:25 +0200
committerSimon Orzel <simonorzel@gmail.com>2025-07-17 02:53:25 +0200
commit1505c1b12f516c65b6a3d076f0e9623a294031d6 (patch)
tree3bf87f9358e24fcec719dd07c8975fb41f33aac4 /apps/web/src/components
parent98b2103286f04d67db1d57f9e3330efa5d41fdb7 (diff)
downloadopencut-1505c1b12f516c65b6a3d076f0e9623a294031d6.tar.gz
feat: timeline changes
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/editor/timeline.tsx1
-rw-r--r--apps/web/src/components/editor/timeline/index.ts11
-rw-r--r--apps/web/src/components/editor/timeline/timeline-playhead.tsx48
-rw-r--r--apps/web/src/components/editor/timeline/timeline-ruler.tsx8
-rw-r--r--apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx68
-rw-r--r--apps/web/src/components/editor/timeline/timeline-tracks-area.tsx2
-rw-r--r--apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx3
7 files changed, 48 insertions, 93 deletions
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx
index 932896f4..bfc6512a 100644
--- a/apps/web/src/components/editor/timeline.tsx
+++ b/apps/web/src/components/editor/timeline.tsx
@@ -129,7 +129,6 @@ export function Timeline() {
useTimelineScrollSync({
rulerScrollRef,
tracksScrollRef,
- trackLabelsScrollRef,
});
// Wheel event handling
diff --git a/apps/web/src/components/editor/timeline/index.ts b/apps/web/src/components/editor/timeline/index.ts
deleted file mode 100644
index 984293ce..00000000
--- a/apps/web/src/components/editor/timeline/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export { Timeline } from "../timeline";
-export { TimelineToolbar } from "./timeline-toolbar";
-export { TimelineContent } from "./timeline-content";
-export { TimelineRuler } from "./timeline-ruler";
-export { TimelineTracksArea } from "./timeline-tracks-area";
-export { TrackIcon } from "./track-icon";
-export { useTimelineDragHandlers } from "./timeline-drag-handlers";
-export { useTimelineActionHandlers } from "./timeline-action-handlers";
-export { useTimelineScrollSync } from "./timeline-scroll-sync";
-export { useTimelineContentClick } from "./timeline-content-click";
-export { useTimelineWheelHandler } from "./timeline-wheel-handler";
diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
index a4d035f6..9cd1429c 100644
--- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
@@ -59,27 +59,39 @@ export function TimelinePlayhead({
const leftPosition = playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
return (
- <div
- ref={playheadRef}
- className="absolute pointer-events-auto z-[95]"
- style={{
- left: `${trackLabelsWidth + leftPosition}px`,
- top: 0,
- bottom: 0,
- width: "2px", // Slightly wider for better click target
- }}
- onMouseDown={handlePlayheadMouseDown}
- >
- {/* The playhead line spanning full height */}
+ <>
+ {/* Playhead line container */}
<div
- className={`absolute left-0 w-0.5 cursor-col-resize h-full ${isSnappingToPlayhead ? "bg-primary" : "bg-foreground"}`}
- />
+ ref={playheadRef}
+ className="absolute pointer-events-auto z-[95]"
+ style={{
+ left: `${trackLabelsWidth + leftPosition}px`,
+ top: 0,
+ bottom: 0,
+ width: "2px", // Slightly wider for better click target
+ }}
+ onMouseDown={handlePlayheadMouseDown}
+ >
+ {/* The playhead line spanning full height */}
+ <div
+ className={`absolute left-0 w-0.5 cursor-col-resize h-full ${isSnappingToPlayhead ? "bg-primary" : "bg-foreground"}`}
+ />
+ </div>
- {/* Playhead dot indicator at the top (in ruler area) */}
+ {/* Playhead dot indicator - separate container with highest z-index */}
<div
- className={`sticky top-1 left-1 -translate-x-[40%] transform w-3 h-3 rounded-full shadow-sm ${isSnappingToPlayhead ? "bg-primary border-primary" : "bg-foreground border-foreground"}`}
- />
- </div>
+ className="absolute pointer-events-none z-[100]"
+ style={{
+ left: `${trackLabelsWidth + leftPosition}px`,
+ top: 0,
+ bottom: 0,
+ }}
+ >
+ <div
+ className={`sticky top-1 left-1 -translate-x-[40%] transform w-3 h-3 rounded-full shadow-sm ${isSnappingToPlayhead ? "bg-primary border-primary" : "bg-foreground border-foreground"}`}
+ />
+ </div>
+ </>
);
}
diff --git a/apps/web/src/components/editor/timeline/timeline-ruler.tsx b/apps/web/src/components/editor/timeline/timeline-ruler.tsx
index 07a4ad5d..a875fe0f 100644
--- a/apps/web/src/components/editor/timeline/timeline-ruler.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-ruler.tsx
@@ -83,7 +83,7 @@ export function TimelineRuler({
markers.push(
<div
key={i}
- className={`absolute top-0 bottom-0 z-[97] ${
+ className={`absolute top-0 bottom-0 z-[99] ${
isMainMarker
? "border-l border-muted-foreground/40"
: "border-l border-muted-foreground/20"
@@ -91,7 +91,7 @@ export function TimelineRuler({
style={{ left: `${leftPosition}px` }}
>
<span
- className={`absolute top-1 left-1 text-[0.6rem] z-[97] ${
+ className={`absolute top-1 left-1 text-[0.6rem] z-[99] ${
isMainMarker
? "text-muted-foreground font-medium"
: "text-muted-foreground/70"
@@ -108,14 +108,14 @@ export function TimelineRuler({
return (
<div
- className="sticky top-0 bg-card/[0.99] border-b border-muted/30 z-[97]"
+ className="sticky top-0 bg-card/[0.99] border-b border-muted/30 z-[99]"
onMouseDown={handleSelectionMouseDown}
onClick={handleTimelineContentClick}
data-ruler-area
>
<div
ref={rulerRef}
- className="relative h-5 select-none cursor-default pb-1 z-[97]"
+ className="relative h-5 select-none cursor-default pb-1 z-[99]"
onMouseDown={handleRulerMouseDown}
>
{rulerMarkers}
diff --git a/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx b/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
index 2318f977..473c6f3e 100644
--- a/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
@@ -3,20 +3,16 @@ import { useEffect, useRef } from "react";
export interface TimelineScrollSyncProps {
rulerScrollRef: React.RefObject<HTMLDivElement>;
tracksScrollRef: React.RefObject<HTMLDivElement>;
- trackLabelsScrollRef: React.RefObject<HTMLDivElement>;
}
export function useTimelineScrollSync({
rulerScrollRef,
tracksScrollRef,
- trackLabelsScrollRef,
}: TimelineScrollSyncProps) {
const isUpdatingRef = useRef(false);
- const lastRulerSync = useRef(0);
- const lastTracksSync = useRef(0);
- const lastVerticalSync = useRef(0);
+ const lastSyncRef = useRef(0);
- // --- Scroll synchronization effect ---
+ // --- Horizontal scroll synchronization between ruler and tracks ---
useEffect(() => {
const rulerViewport = rulerScrollRef.current?.querySelector(
"[data-radix-scroll-area-viewport]"
@@ -24,76 +20,38 @@ export function useTimelineScrollSync({
const tracksViewport = tracksScrollRef.current?.querySelector(
"[data-radix-scroll-area-viewport]"
) as HTMLElement;
- const trackLabelsViewport = trackLabelsScrollRef.current?.querySelector(
- "[data-radix-scroll-area-viewport]"
- ) as HTMLElement;
if (!rulerViewport || !tracksViewport) return;
- // Horizontal scroll synchronization between ruler and tracks
+ // Throttled scroll handlers for better performance
const handleRulerScroll = () => {
const now = Date.now();
- if (isUpdatingRef.current || now - lastRulerSync.current < 16) return;
- lastRulerSync.current = now;
+ if (isUpdatingRef.current || now - lastSyncRef.current < 16) return; // 60fps throttling
+ lastSyncRef.current = now;
+
isUpdatingRef.current = true;
tracksViewport.scrollLeft = rulerViewport.scrollLeft;
isUpdatingRef.current = false;
};
+
const handleTracksScroll = () => {
const now = Date.now();
- if (isUpdatingRef.current || now - lastTracksSync.current < 16) return;
- lastTracksSync.current = now;
+ if (isUpdatingRef.current || now - lastSyncRef.current < 16) return; // 60fps throttling
+ lastSyncRef.current = now;
+
isUpdatingRef.current = true;
rulerViewport.scrollLeft = tracksViewport.scrollLeft;
isUpdatingRef.current = false;
};
- rulerViewport.addEventListener("scroll", handleRulerScroll);
- tracksViewport.addEventListener("scroll", handleTracksScroll);
-
- // Vertical scroll synchronization between track labels and tracks content
- if (trackLabelsViewport) {
- const handleTrackLabelsScroll = () => {
- const now = Date.now();
- if (isUpdatingRef.current || now - lastVerticalSync.current < 16)
- return;
- lastVerticalSync.current = now;
- isUpdatingRef.current = true;
- tracksViewport.scrollTop = trackLabelsViewport.scrollTop;
- isUpdatingRef.current = false;
- };
- const handleTracksVerticalScroll = () => {
- const now = Date.now();
- if (isUpdatingRef.current || now - lastVerticalSync.current < 16)
- return;
- lastVerticalSync.current = now;
- isUpdatingRef.current = true;
- trackLabelsViewport.scrollTop = tracksViewport.scrollTop;
- isUpdatingRef.current = false;
- };
-
- trackLabelsViewport.addEventListener("scroll", handleTrackLabelsScroll);
- tracksViewport.addEventListener("scroll", handleTracksVerticalScroll);
-
- return () => {
- rulerViewport.removeEventListener("scroll", handleRulerScroll);
- tracksViewport.removeEventListener("scroll", handleTracksScroll);
- trackLabelsViewport.removeEventListener(
- "scroll",
- handleTrackLabelsScroll
- );
- tracksViewport.removeEventListener(
- "scroll",
- handleTracksVerticalScroll
- );
- };
- }
+ rulerViewport.addEventListener("scroll", handleRulerScroll, { passive: true });
+ tracksViewport.addEventListener("scroll", handleTracksScroll, { passive: true });
return () => {
rulerViewport.removeEventListener("scroll", handleRulerScroll);
tracksViewport.removeEventListener("scroll", handleTracksScroll);
};
- }, []);
+ }, [rulerScrollRef, tracksScrollRef]);
return null;
}
diff --git a/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx b/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx
index 0e11f366..144eb0f3 100644
--- a/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx
@@ -35,7 +35,7 @@ export function TimelineTracksArea({
<Fragment key={track.id}>
{/* Left Column (Sticky Track Labels) */}
<div
- className="sticky left-0 flex items-center border-b border-panel border-inset group bg-card/[0.99] z-[96]"
+ className="sticky left-0 flex items-center border-b border-panel border-inset group bg-card/[0.99] z-[98]"
style={{ height: `${getTrackHeight(track.type)}px` }}
>
<div className="flex items-center gap-2 px-2">
diff --git a/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx b/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx
index 430d65ae..417cbbe8 100644
--- a/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx
@@ -11,17 +11,14 @@ export function useTimelineWheelHandler({
isInTimeline,
handleWheel,
}: TimelineWheelHandlerProps) {
- // Add wheel event listeners with passive: false to allow preventDefault
useEffect(() => {
const timelineContainer = timelineRef.current;
if (!timelineContainer || !isInTimeline) return;
const handleWheelCapture = (e: WheelEvent) => {
- // Call the existing handleWheel function
handleWheel(e as any);
};
- // Add wheel event listener with passive: false to allow preventDefault
timelineContainer.addEventListener("wheel", handleWheelCapture, { passive: false });
return () => {