summaryrefslogtreecommitdiff
path: root/apps/web/src/constants/timeline-constants.ts
blob: 44773b848d2fcd369f85bc1a33c6e358243c9e62 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import type { TrackType } from "@/types/timeline";

// Track color definitions
export const TRACK_COLORS: Record<
  TrackType,
  { solid: string; background: string; border: string }
> = {
  media: {
    solid: "bg-blue-500",
    background: "bg-blue-500/20",
    border: "border-white/80",
  },
  text: {
    solid: "bg-[#9C4937]",
    background: "bg-[#9C4937]",
    border: "border-white/80",
  },
  audio: {
    solid: "bg-green-500",
    background: "bg-green-500/20",
    border: "border-white/80",
  },
} as const;

// Utility functions
export function getTrackColors(type: TrackType) {
  return TRACK_COLORS[type];
}

export function getTrackElementClasses(type: TrackType) {
  const colors = getTrackColors(type);
  return `${colors.background} ${colors.border}`;
}

// Track height definitions
export const TRACK_HEIGHTS: Record<TrackType, number> = {
  media: 65,
  text: 25,
  audio: 50,
} as const;

// Utility function for track heights
export function getTrackHeight(type: TrackType): number {
  return TRACK_HEIGHTS[type];
}

// Calculate cumulative height up to (but not including) a track index
export function getCumulativeHeightBefore(
  tracks: Array<{ type: TrackType }>,
  trackIndex: number
): number {
  const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1)
  return tracks
    .slice(0, trackIndex)
    .reduce((sum, track) => sum + getTrackHeight(track.type) + GAP, 0);
}

// Calculate total height of all tracks
export function getTotalTracksHeight(
  tracks: Array<{ type: TrackType }>
): number {
  const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1)
  const tracksHeight = tracks.reduce(
    (sum, track) => sum + getTrackHeight(track.type),
    0
  );
  const gapsHeight = Math.max(0, tracks.length - 1) * GAP; // n-1 gaps for n tracks
  return tracksHeight + gapsHeight;
}

// Other timeline constants
export const TIMELINE_CONSTANTS = {
  ELEMENT_MIN_WIDTH: 80,
  PIXELS_PER_SECOND: 50,
  TRACK_HEIGHT: 60, // Default fallback
  DEFAULT_TEXT_DURATION: 5,
  ZOOM_LEVELS: [0.25, 0.5, 1, 1.5, 2, 3, 4],
} as const;

// FPS presets for project settings
export const FPS_PRESETS = [
  { value: "24", label: "24 fps (Film)" },
  { value: "25", label: "25 fps (PAL)" },
  { value: "30", label: "30 fps (NTSC)" },
  { value: "60", label: "60 fps (High)" },
  { value: "120", label: "120 fps (Slow-mo)" },
] as const;

// Frame snapping utilities
export function timeToFrame(time: number, fps: number): number {
  return Math.round(time * fps);
}

export function frameToTime(frame: number, fps: number): number {
  return frame / fps;
}

export function snapTimeToFrame(time: number, fps: number): number {
  if (fps <= 0) return time; // Fallback for invalid FPS
  const frame = timeToFrame(time, fps);
  return frameToTime(frame, fps);
}

export function getFrameDuration(fps: number): number {
  return 1 / fps;
}

// Media thumbnail tiling constants
export const MEDIA_THUMBNAIL_CONSTANTS = {
  DEFAULT_ASPECT_RATIO: 16 / 9,
  IMAGE_PADDING: 8,
  VIDEO_PADDING: 16,
  PARTIAL_TILE_THRESHOLD_IMAGE: 0.2, // Show partial tile if >20% visible for images
  PARTIAL_TILE_THRESHOLD_VIDEO: 0.3, // Show partial tile if >30% visible for videos
  MIN_OVERLAY_WIDTH_RATIO: 1.5, // Minimum width ratio to show name overlay
} as const;

// Utility function to calculate media thumbnail tiling
export function calculateMediaTiling(
  elementWidth: number,
  trackType: TrackType,
  mediaType: "image" | "video"
) {
  const trackHeight = getTrackHeight(trackType);
  const aspectRatio = MEDIA_THUMBNAIL_CONSTANTS.DEFAULT_ASPECT_RATIO;
  const padding =
    mediaType === "image"
      ? MEDIA_THUMBNAIL_CONSTANTS.IMAGE_PADDING
      : MEDIA_THUMBNAIL_CONSTANTS.VIDEO_PADDING;

  const tileHeight = trackHeight - padding;
  const tileWidth = tileHeight * aspectRatio;

  // Calculate how many complete tiles we can fit
  const numCompleteTiles = Math.floor(elementWidth / tileWidth);
  const remainingWidth = elementWidth - numCompleteTiles * tileWidth;

  const threshold =
    mediaType === "image"
      ? MEDIA_THUMBNAIL_CONSTANTS.PARTIAL_TILE_THRESHOLD_IMAGE
      : MEDIA_THUMBNAIL_CONSTANTS.PARTIAL_TILE_THRESHOLD_VIDEO;

  const showPartialTile = remainingWidth > tileWidth * threshold;
  const totalTiles = numCompleteTiles + (showPartialTile ? 1 : 0);

  return {
    tileWidth,
    tileHeight,
    numCompleteTiles,
    remainingWidth,
    showPartialTile,
    totalTiles: Math.max(1, totalTiles), // Always show at least one tile
    canShowOverlay:
      elementWidth >
      tileWidth * MEDIA_THUMBNAIL_CONSTANTS.MIN_OVERLAY_WIDTH_RATIO,
  };
}