summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline/timeline-ruler.tsx
diff options
context:
space:
mode:
authorSimon Orzel <simonorzel@gmail.com>2025-07-17 02:25:24 +0200
committerSimon Orzel <simonorzel@gmail.com>2025-07-17 02:25:24 +0200
commit98b2103286f04d67db1d57f9e3330efa5d41fdb7 (patch)
treec9b6d144d2d7e4f70de99aef1737871b044ad146 /apps/web/src/components/editor/timeline/timeline-ruler.tsx
parent9fa9f406fe2722cec1e0d65d9b3868acd1e99f87 (diff)
downloadopencut-98b2103286f04d67db1d57f9e3330efa5d41fdb7.tar.gz
feat: working
Diffstat (limited to 'apps/web/src/components/editor/timeline/timeline-ruler.tsx')
-rw-r--r--apps/web/src/components/editor/timeline/timeline-ruler.tsx137
1 files changed, 71 insertions, 66 deletions
diff --git a/apps/web/src/components/editor/timeline/timeline-ruler.tsx b/apps/web/src/components/editor/timeline/timeline-ruler.tsx
index 85972089..07a4ad5d 100644
--- a/apps/web/src/components/editor/timeline/timeline-ruler.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-ruler.tsx
@@ -1,3 +1,4 @@
+import { useMemo } from "react";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { useTimelinePlayheadRuler } from "./timeline-playhead";
@@ -38,82 +39,86 @@ export function TimelineRuler({
playheadRef,
});
+ // Memoize the expensive ruler markers calculation
+ const rulerMarkers = useMemo(() => {
+ // Calculate appropriate time interval based on zoom level
+ const getTimeInterval = (zoom: number) => {
+ const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoom;
+ if (pixelsPerSecond >= 200) return 0.1; // Every 0.1s when very zoomed in
+ if (pixelsPerSecond >= 100) return 0.5; // Every 0.5s when zoomed in
+ if (pixelsPerSecond >= 50) return 1; // Every 1s at normal zoom
+ if (pixelsPerSecond >= 25) return 2; // Every 2s when zoomed out
+ if (pixelsPerSecond >= 12) return 5; // Every 5s when more zoomed out
+ if (pixelsPerSecond >= 6) return 10; // Every 10s when very zoomed out
+ return 30; // Every 30s when extremely zoomed out
+ };
+
+ const formatTime = (seconds: number, interval: 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")}`;
+ } else if (minutes > 0) {
+ return `${minutes}:${Math.floor(secs).toString().padStart(2, "0")}`;
+ } else if (interval >= 1) {
+ return `${Math.floor(secs)}s`;
+ } else {
+ return `${secs.toFixed(1)}s`;
+ }
+ };
+
+ const interval = getTimeInterval(zoomLevel);
+ const markerCount = Math.ceil(duration / interval) + 1;
+ const markers = [];
+
+ for (let i = 0; i < markerCount; i++) {
+ const time = i * interval;
+ if (time > duration) break;
+
+ const isMainMarker = time % (interval >= 1 ? Math.max(1, interval) : 1) === 0;
+ const leftPosition = time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
+
+ markers.push(
+ <div
+ key={i}
+ className={`absolute top-0 bottom-0 z-[97] ${
+ isMainMarker
+ ? "border-l border-muted-foreground/40"
+ : "border-l border-muted-foreground/20"
+ }`}
+ style={{ left: `${leftPosition}px` }}
+ >
+ <span
+ className={`absolute top-1 left-1 text-[0.6rem] z-[97] ${
+ isMainMarker
+ ? "text-muted-foreground font-medium"
+ : "text-muted-foreground/70"
+ }`}
+ >
+ {formatTime(time, interval)}
+ </span>
+ </div>
+ );
+ }
+
+ return markers;
+ }, [duration, zoomLevel]); // Only recalculate when duration or zoomLevel changes
+
return (
<div
- className="sticky top-0 bg-card/[0.99] border-b border-muted/30 z-[95]"
+ className="sticky top-0 bg-card/[0.99] border-b border-muted/30 z-[97]"
onMouseDown={handleSelectionMouseDown}
onClick={handleTimelineContentClick}
data-ruler-area
>
<div
ref={rulerRef}
- className="relative h-5 select-none cursor-default pb-1 z-[95]"
+ className="relative h-5 select-none cursor-default pb-1 z-[97]"
onMouseDown={handleRulerMouseDown}
>
- {(() => {
- // Calculate appropriate time interval based on zoom level
- const getTimeInterval = (zoom: number) => {
- const pixelsPerSecond =
- TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoom;
- if (pixelsPerSecond >= 200) return 0.1; // Every 0.1s when very zoomed in
- if (pixelsPerSecond >= 100) return 0.5; // Every 0.5s when zoomed in
- if (pixelsPerSecond >= 50) return 1; // Every 1s at normal zoom
- if (pixelsPerSecond >= 25) return 2; // Every 2s when zoomed out
- if (pixelsPerSecond >= 12) return 5; // Every 5s when more zoomed out
- if (pixelsPerSecond >= 6) return 10; // Every 10s when very zoomed out
- return 30; // Every 30s when extremely zoomed out
- };
-
- const interval = getTimeInterval(zoomLevel);
- const markerCount = Math.ceil(duration / interval) + 1;
-
- return Array.from({ length: markerCount }, (_, i) => {
- const time = i * interval;
- if (time > duration) return null;
-
- const isMainMarker =
- time % (interval >= 1 ? Math.max(1, interval) : 1) === 0;
-
- return (
- <div
- key={i}
- className={`absolute top-0 bottom-0 z-[95] ${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] z-[95] ${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")}`;
- } else if (minutes > 0) {
- return `${minutes}:${Math.floor(secs).toString().padStart(2, "0")}`;
- } else if (interval >= 1) {
- return `${Math.floor(secs)}s`;
- } else {
- return `${secs.toFixed(1)}s`;
- }
- };
- return formatTime(time);
- })()}
- </span>
- </div>
- );
- }).filter(Boolean);
- })()}
+ {rulerMarkers}
</div>
</div>
);