summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline/index.tsx
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-08-31 13:26:53 +0200
committerMaze Winther <mazewinther@gmail.com>2025-08-31 13:26:53 +0200
commite222f15d1dd50aa50be8c7424d86aa7fedf0defc (patch)
tree5ad5d12118695135f59c8e90c71adff1a09b116c /apps/web/src/components/editor/timeline/index.tsx
parent21dac9f09e17f4f1be4ddfad7474400787051a7c (diff)
downloadopencut-e222f15d1dd50aa50be8c7424d86aa7fedf0defc.tar.gz
feat: implement timeline caching with cache indicator
Diffstat (limited to 'apps/web/src/components/editor/timeline/index.tsx')
-rw-r--r--apps/web/src/components/editor/timeline/index.tsx102
1 files changed, 50 insertions, 52 deletions
diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx
index c994d707..90a19d61 100644
--- a/apps/web/src/components/editor/timeline/index.tsx
+++ b/apps/web/src/components/editor/timeline/index.tsx
@@ -57,6 +57,9 @@ import { useSelectionBox } from "@/hooks/use-selection-box";
import { SnapIndicator } from "../snap-indicator";
import { SnapPoint } from "@/hooks/use-timeline-snapping";
import type { DragData, TimelineTrack, TrackType } from "@/types/timeline";
+import { TimelineCacheIndicator } from "./timeline-cache-indicator";
+import { TimelineMarker } from "./timeline-marker";
+import { useFrameCache } from "@/hooks/use-frame-cache";
import {
getTrackHeight,
getCumulativeHeightBefore,
@@ -85,6 +88,7 @@ export function Timeline() {
const { mediaFiles, addMediaFile } = useMediaStore();
const { activeProject } = useProjectStore();
const { currentTime, duration, seek, setDuration } = usePlaybackStore();
+ const { getRenderStatus } = useFrameCache();
const [isDragOver, setIsDragOver] = useState(false);
const { addElementToNewTrack } = useTimelineStore();
const dragCounterRef = useRef(0);
@@ -637,7 +641,7 @@ export function Timeline() {
{/* Timeline Header with Ruler */}
<div className="flex bg-panel sticky top-0 z-10">
{/* Track Labels Header */}
- <div className="w-28 shrink-0 bg-panel border-r flex items-center justify-between px-3 py-2">
+ <div className="w-28 shrink-0 bg-panel border-r border-t flex items-center justify-between px-3 py-2">
{/* Empty space */}
<span className="text-sm font-medium text-muted-foreground opacity-0">
.
@@ -658,7 +662,21 @@ export function Timeline() {
onClick={handleTimelineContentClick}
data-ruler-area
>
- <ScrollArea className="w-full" ref={rulerScrollRef}>
+ <ScrollArea
+ className="w-full"
+ ref={rulerScrollRef}
+ onScroll={(e) => {
+ if (isUpdatingRef.current) return;
+ isUpdatingRef.current = true;
+ const tracksViewport = tracksScrollRef.current;
+ if (tracksViewport) {
+ tracksViewport.scrollLeft = (
+ e.currentTarget as HTMLDivElement
+ ).scrollLeft;
+ }
+ isUpdatingRef.current = false;
+ }}
+ >
<div
ref={rulerRef}
className="relative h-10 select-none cursor-default"
@@ -667,6 +685,14 @@ export function Timeline() {
}}
onMouseDown={handleRulerMouseDown}
>
+ <TimelineCacheIndicator
+ duration={duration}
+ zoomLevel={zoomLevel}
+ tracks={tracks}
+ mediaFiles={mediaFiles}
+ activeProject={activeProject}
+ getRenderStatus={getRenderStatus}
+ />
{/* Time markers */}
{(() => {
// Calculate appropriate time interval based on zoom level
@@ -693,55 +719,13 @@ export function Timeline() {
time % (interval >= 1 ? Math.max(1, interval) : 1) === 0;
return (
- <div
+ <TimelineMarker
key={i}
- className={`absolute top-0 h-4 ${
- isMainMarker
- ? "border-l border-muted-foreground/40"
- : "border-l border-muted-foreground/20"
- }`}
- style={{
- left: `${
- time *
- TIMELINE_CONSTANTS.PIXELS_PER_SECOND *
- zoomLevel
- }px`,
- }}
- >
- <span
- className={`absolute top-1 left-1 text-[0.6rem] ${
- isMainMarker
- ? "text-muted-foreground font-medium"
- : "text-muted-foreground/70"
- }`}
- >
- {(() => {
- const formatTime = (seconds: number) => {
- const hours = Math.floor(seconds / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
- const secs = seconds % 60;
-
- if (hours > 0) {
- return `${hours}:${minutes
- .toString()
- .padStart(2, "0")}:${Math.floor(secs)
- .toString()
- .padStart(2, "0")}`;
- }
- if (minutes > 0) {
- return `${minutes}:${Math.floor(secs)
- .toString()
- .padStart(2, "0")}`;
- }
- if (interval >= 1) {
- return `${Math.floor(secs)}s`;
- }
- return `${secs.toFixed(1)}s`;
- };
- return formatTime(time);
- })()}
- </span>
- </div>
+ time={time}
+ zoomLevel={zoomLevel}
+ interval={interval}
+ isMainMarker={isMainMarker}
+ />
);
}).filter(Boolean);
})()}
@@ -836,7 +820,21 @@ export function Timeline() {
containerRef={tracksContainerRef}
isActive={selectionBox?.isActive || false}
/>
- <ScrollArea className="w-full h-full" ref={tracksScrollRef}>
+ <ScrollArea
+ className="w-full h-full"
+ ref={tracksScrollRef}
+ onScroll={(e) => {
+ if (isUpdatingRef.current) return;
+ isUpdatingRef.current = true;
+ const rulerViewport = rulerScrollRef.current;
+ if (rulerViewport) {
+ rulerViewport.scrollLeft = (
+ e.currentTarget as HTMLDivElement
+ ).scrollLeft;
+ }
+ isUpdatingRef.current = false;
+ }}
+ >
<div
className="relative flex-1"
style={{
@@ -1085,7 +1083,7 @@ function TimelineToolbar({
const currentBookmarked = isBookmarked(currentTime);
return (
- <div className="border-b flex items-center justify-between px-2 py-1">
+ <div className=" flex items-center justify-between px-2 py-1">
<div className="flex items-center gap-1 w-full">
<TooltipProvider delayDuration={500}>
<Tooltip>