diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-07-07 23:21:06 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-07-07 23:21:06 +0200 |
| commit | 9d2fd50fbc764308b1ebad8a43fa0556e77b9937 (patch) | |
| tree | 6444bce96324c6a7e2b25d8afebea78fbc7f093e /apps/web/src | |
| parent | d36df2fb6273bb16d4874972a0dc832bad6836ab (diff) | |
| download | opencut-9d2fd50fbc764308b1ebad8a43fa0556e77b9937.tar.gz | |
refactor: centralize track colors and timeline constants
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/components/editor/timeline-element.tsx | 24 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-track.tsx | 17 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline.tsx | 53 | ||||
| -rw-r--r-- | apps/web/src/lib/timeline-constants.ts | 48 |
4 files changed, 97 insertions, 45 deletions
diff --git a/apps/web/src/components/editor/timeline-element.tsx b/apps/web/src/components/editor/timeline-element.tsx index 211c4e9b..0d258607 100644 --- a/apps/web/src/components/editor/timeline-element.tsx +++ b/apps/web/src/components/editor/timeline-element.tsx @@ -20,6 +20,10 @@ import { toast } from "sonner"; import { TimelineElementProps, TrackType } from "@/types/timeline"; import { useTimelineElementResize } from "@/hooks/use-timeline-element-resize"; import { + getTrackElementClasses, + TIMELINE_CONSTANTS, +} from "@/lib/timeline-constants"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -67,7 +71,10 @@ export function TimelineElement({ const effectiveDuration = element.duration - element.trimStart - element.trimEnd; - const elementWidth = Math.max(80, effectiveDuration * 50 * zoomLevel); + const elementWidth = Math.max( + TIMELINE_CONSTANTS.ELEMENT_MIN_WIDTH, + effectiveDuration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel + ); // Use real-time position during drag, otherwise use stored position const isBeingDragged = dragState.elementId === element.id; @@ -77,19 +84,6 @@ export function TimelineElement({ : element.startTime; const elementLeft = elementStartTime * 50 * zoomLevel; - const getTrackColor = (type: TrackType) => { - switch (type) { - case "media": - return "bg-blue-500/20 border-blue-500/30"; - case "text": - return "bg-purple-500/20 border-purple-500/30"; - case "audio": - return "bg-green-500/20 border-green-500/30"; - default: - return "bg-gray-500/20 border-gray-500/30"; - } - }; - const handleDeleteElement = () => { removeElementFromTrack(track.id, element.id); setElementMenuOpen(false); @@ -271,7 +265,7 @@ export function TimelineElement({ onMouseLeave={resizing ? handleResizeEnd : undefined} > <div - className={`relative h-full rounded-[0.15rem] cursor-pointer overflow-hidden ${getTrackColor( + className={`relative h-full rounded-[0.15rem] cursor-pointer overflow-hidden ${getTrackElementClasses( track.type )} ${isSelected ? "border-b-[0.5px] border-t-[0.5px] border-foreground" : ""} ${ isBeingDragged ? "z-50" : "z-10" diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 2751fa5a..f1766593 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -25,6 +25,7 @@ import type { TimelineElement as TimelineElementType, DragData, } from "@/types/timeline"; +import { TIMELINE_CONSTANTS } from "@/lib/timeline-constants"; export function TimelineTrackContent({ track, @@ -82,7 +83,10 @@ export function TimelineTrackContent({ const timelineRect = timelineRef.current.getBoundingClientRect(); const mouseX = e.clientX - timelineRect.left; - const mouseTime = Math.max(0, mouseX / (50 * zoomLevel)); + const mouseTime = Math.max( + 0, + mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel) + ); const adjustedTime = Math.max(0, mouseTime - dragState.clickOffsetTime); const snappedTime = Math.round(adjustedTime * 10) / 10; @@ -212,7 +216,8 @@ export function TimelineTrackContent({ const elementElement = e.currentTarget as HTMLElement; const elementRect = elementElement.getBoundingClientRect(); const clickOffsetX = e.clientX - elementRect.left; - const clickOffsetTime = clickOffsetX / (50 * zoomLevel); + const clickOffsetTime = + clickOffsetX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel); startDragAction( element.id, @@ -278,7 +283,7 @@ export function TimelineTrackContent({ if (trackContainer) { const rect = trackContainer.getBoundingClientRect(); const mouseX = Math.max(0, e.clientX - rect.left); - dropTime = mouseX / (50 * zoomLevel); + dropTime = mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel); } // Check for potential overlaps and show appropriate feedback @@ -453,11 +458,11 @@ export function TimelineTrackContent({ const rect = trackContainer.getBoundingClientRect(); const mouseX = Math.max(0, e.clientX - rect.left); const mouseY = e.clientY - rect.top; // Get Y position relative to this track - const newStartTime = mouseX / (50 * zoomLevel); + const newStartTime = + mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel); const snappedTime = Math.round(newStartTime * 10) / 10; // Calculate drop position relative to tracks - const TRACK_HEIGHT = 60; const currentTrackIndex = tracks.findIndex((t) => t.id === track.id); // Determine drop zone within the track (top 20px, middle 20px, bottom 20px) @@ -619,7 +624,7 @@ export function TimelineTrackContent({ type: "text", name: dragData.name || "Text", content: dragData.content || "Default Text", - duration: 5, + duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION, startTime: snappedTime, trimStart: 0, trimEnd: 0, diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index f98ab5ec..6a94ed60 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -44,6 +44,10 @@ import { } from "../ui/select"; import { TimelineTrackContent } from "./timeline-track"; import type { DragData } from "@/types/timeline"; +import { + getTrackLabelColor, + TIMELINE_CONSTANTS, +} from "@/lib/timeline-constants"; export function Timeline() { // Timeline shows all tracks (video, audio, effects) and their elements. @@ -105,8 +109,8 @@ export function Timeline() { // Dynamic timeline width calculation based on playhead position and duration const dynamicTimelineWidth = Math.max( - (duration || 0) * 50 * zoomLevel, // Base width from duration - (currentTime + 30) * 50 * zoomLevel, // Width to show current time + 30 seconds buffer + (duration || 0) * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel, // Base width from duration + (currentTime + 30) * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel, // Width to show current time + 30 seconds buffer timelineRef.current?.clientWidth || 1000 // Minimum width ); @@ -236,10 +240,11 @@ export function Timeline() { let newSelection: { trackId: string; elementId: string }[] = []; tracks.forEach((track, trackIdx) => { track.elements.forEach((element) => { - const clipLeft = element.startTime * 50 * zoomLevel; - const clipTop = trackIdx * 60; - const clipBottom = clipTop + 60; - const clipRight = clipLeft + 60; + const clipLeft = + element.startTime * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const clipTop = trackIdx * TIMELINE_CONSTANTS.TRACK_HEIGHT; + const clipBottom = clipTop + TIMELINE_CONSTANTS.TRACK_HEIGHT; + const clipRight = clipLeft + TIMELINE_CONSTANTS.TRACK_HEIGHT; if ( bx1 < clipRight && bx2 > clipLeft && @@ -334,7 +339,7 @@ export function Timeline() { type: "text", name: dragData.name || "Text", content: dragData.content || "Default Text", - duration: 5, + duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION, startTime: 0, trimStart: 0, trimEnd: 0, @@ -721,7 +726,8 @@ export function Timeline() { "[data-radix-scroll-area-viewport]" ) as HTMLElement; if (!rulerViewport || !tracksViewport) return; - const playheadPx = playheadPosition * 50 * zoomLevel; + const playheadPx = + playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; const viewportWidth = rulerViewport.clientWidth; const scrollMin = 0; const scrollMax = rulerViewport.scrollWidth - viewportWidth; @@ -792,7 +798,7 @@ export function Timeline() { type: "media", mediaId: "test", name: "Test Clip", - duration: 5, + duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION, startTime: 0, trimStart: 0, trimEnd: 0, @@ -927,7 +933,8 @@ export function Timeline() { {(() => { // Calculate appropriate time interval based on zoom level const getTimeInterval = (zoom: number) => { - const pixelsPerSecond = 50 * zoom; + 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 @@ -955,7 +962,9 @@ export function Timeline() { ? "border-l border-muted-foreground/40" : "border-l border-muted-foreground/20" }`} - style={{ left: `${time * 50 * zoomLevel}px` }} + style={{ + left: `${time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`, + }} > <span className={`absolute top-1 left-1 text-xs ${ @@ -991,7 +1000,9 @@ export function Timeline() { {/* Playhead in ruler (scrubbable) */} <div className="playhead absolute top-0 bottom-0 w-0.5 bg-red-500 pointer-events-auto z-50 cursor-col-resize" - style={{ left: `${playheadPosition * 50 * zoomLevel}px` }} + style={{ + left: `${playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`, + }} onMouseDown={handlePlayheadMouseDown} > <div className="absolute top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-red-500 rounded-full border-2 border-white shadow-sm" /> @@ -1014,13 +1025,7 @@ export function Timeline() { > <div className="flex items-center flex-1 min-w-0"> <div - className={`w-3 h-3 rounded-full flex-shrink-0 ${ - track.type === "media" - ? "bg-blue-500" - : track.type === "audio" - ? "bg-green-500" - : "bg-purple-500" - }`} + className={`w-3 h-3 rounded-full flex-shrink-0 ${getTrackLabelColor(track.type)}`} /> <span className="ml-2 text-sm font-medium truncate"> {track.name} @@ -1043,7 +1048,7 @@ export function Timeline() { <div className="relative flex-1" style={{ - height: `${Math.max(200, Math.min(800, tracks.length * 60))}px`, + height: `${Math.max(200, Math.min(800, tracks.length * TIMELINE_CONSTANTS.TRACK_HEIGHT))}px`, width: `${dynamicTimelineWidth}px`, }} onClick={handleTimelineClick} @@ -1059,8 +1064,8 @@ export function Timeline() { <div className="absolute left-0 right-0 border-b border-muted/30" style={{ - top: `${index * 60}px`, - height: "60px", + top: `${index * TIMELINE_CONSTANTS.TRACK_HEIGHT}px`, + height: `${TIMELINE_CONSTANTS.TRACK_HEIGHT}px`, }} onClick={(e) => { // If clicking empty area (not on a element), deselect all elements @@ -1092,8 +1097,8 @@ export function Timeline() { <div className="absolute top-0 w-0.5 bg-red-500 pointer-events-auto z-50 cursor-col" style={{ - left: `${playheadPosition * 50 * zoomLevel}px`, - height: `${tracks.length * 60}px`, + left: `${playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`, + height: `${tracks.length * TIMELINE_CONSTANTS.TRACK_HEIGHT}px`, }} onMouseDown={handlePlayheadMouseDown} /> diff --git a/apps/web/src/lib/timeline-constants.ts b/apps/web/src/lib/timeline-constants.ts new file mode 100644 index 00000000..fbbfe081 --- /dev/null +++ b/apps/web/src/lib/timeline-constants.ts @@ -0,0 +1,48 @@ +import type { TrackType } from "@/types/timeline";
+
+// Track color definitions
+export const TRACK_COLORS = {
+ media: {
+ solid: "bg-blue-500",
+ background: "bg-blue-500/20",
+ border: "border-blue-500/30",
+ },
+ text: {
+ solid: "bg-purple-500",
+ background: "bg-purple-500/20",
+ border: "border-purple-500/30",
+ },
+ audio: {
+ solid: "bg-green-500",
+ background: "bg-green-500/20",
+ border: "border-green-500/30",
+ },
+ default: {
+ solid: "bg-gray-500",
+ background: "bg-gray-500/20",
+ border: "border-gray-500/30",
+ },
+} as const;
+
+// Utility functions
+export function getTrackColors(type: TrackType) {
+ return TRACK_COLORS[type] || TRACK_COLORS.default;
+}
+
+export function getTrackElementClasses(type: TrackType) {
+ const colors = getTrackColors(type);
+ return `${colors.background} ${colors.border}`;
+}
+
+export function getTrackLabelColor(type: TrackType) {
+ return getTrackColors(type).solid;
+}
+
+// Other timeline constants
+export const TIMELINE_CONSTANTS = {
+ ELEMENT_MIN_WIDTH: 80,
+ PIXELS_PER_SECOND: 50,
+ TRACK_HEIGHT: 60,
+ DEFAULT_TEXT_DURATION: 5,
+ ZOOM_LEVELS: [0.25, 0.5, 1, 1.5, 2, 3, 4],
+} as const;
|
