diff --git a/apps/web/src/components/editor/timeline-element.tsx b/apps/web/src/components/editor/timeline-element.tsx index 211c4e9..0d25860 100644 --- a/apps/web/src/components/editor/timeline-element.tsx +++ b/apps/web/src/components/editor/timeline-element.tsx @@ -19,6 +19,10 @@ import AudioWaveform from "./audio-waveform"; 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, @@ -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} >
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 f98ab5e..6a94ed6 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`, + }} >
@@ -1014,13 +1025,7 @@ export function Timeline() { >
{track.name} @@ -1043,7 +1048,7 @@ export function Timeline() {
{ // If clicking empty area (not on a element), deselect all elements @@ -1092,8 +1097,8 @@ export function Timeline() {
diff --git a/apps/web/src/lib/timeline-constants.ts b/apps/web/src/lib/timeline-constants.ts new file mode 100644 index 0000000..fbbfe08 --- /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;