From bd9ec023eefc4b5d26fa8b66c4338e97104f0f96 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 1 Apr 2026 19:27:49 +0200 Subject: refactor: split constants by domain and isolate timeline panel from core --- apps/web/src/lib/timeline/creation.ts | 1 + apps/web/src/lib/timeline/defaults.ts | 4 +- apps/web/src/lib/timeline/drag-utils.ts | 4 +- apps/web/src/lib/timeline/drop-utils.ts | 257 --------------------- apps/web/src/lib/timeline/element-utils.ts | 10 +- apps/web/src/lib/timeline/index.ts | 2 +- apps/web/src/lib/timeline/pixel-utils.ts | 4 +- .../src/lib/timeline/placement/track-factory.ts | 4 +- apps/web/src/lib/timeline/ruler-utils.ts | 4 +- apps/web/src/lib/timeline/scale.ts | 3 + apps/web/src/lib/timeline/selection-hit-testing.ts | 133 ----------- apps/web/src/lib/timeline/snap-utils.ts | 4 +- apps/web/src/lib/timeline/track-capabilities.ts | 20 ++ apps/web/src/lib/timeline/track-utils.ts | 62 ----- apps/web/src/lib/timeline/tracks.ts | 9 + apps/web/src/lib/timeline/zoom-utils.ts | 15 +- 16 files changed, 60 insertions(+), 476 deletions(-) create mode 100644 apps/web/src/lib/timeline/creation.ts delete mode 100644 apps/web/src/lib/timeline/drop-utils.ts create mode 100644 apps/web/src/lib/timeline/scale.ts delete mode 100644 apps/web/src/lib/timeline/selection-hit-testing.ts create mode 100644 apps/web/src/lib/timeline/track-capabilities.ts delete mode 100644 apps/web/src/lib/timeline/track-utils.ts create mode 100644 apps/web/src/lib/timeline/tracks.ts (limited to 'apps/web/src/lib/timeline') diff --git a/apps/web/src/lib/timeline/creation.ts b/apps/web/src/lib/timeline/creation.ts new file mode 100644 index 00000000..335ab912 --- /dev/null +++ b/apps/web/src/lib/timeline/creation.ts @@ -0,0 +1 @@ +export const DEFAULT_NEW_ELEMENT_DURATION_SECONDS = 5; diff --git a/apps/web/src/lib/timeline/defaults.ts b/apps/web/src/lib/timeline/defaults.ts index 01dbe912..bc968a3c 100644 --- a/apps/web/src/lib/timeline/defaults.ts +++ b/apps/web/src/lib/timeline/defaults.ts @@ -1,4 +1,4 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { DEFAULT_NEW_ELEMENT_DURATION_SECONDS } from "@/lib/timeline/creation"; import type { TTimelineViewState } from "@/lib/project/types"; import type { BlendMode, Transform } from "@/lib/rendering"; import type { TextElement } from "./types"; @@ -41,7 +41,7 @@ const defaultTextElement: Omit = { textDecoration: "none", letterSpacing: defaultTextLetterSpacing, lineHeight: defaultTextLineHeight, - duration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION, + duration: DEFAULT_NEW_ELEMENT_DURATION_SECONDS, startTime: 0, trimStart: 0, trimEnd: 0, diff --git a/apps/web/src/lib/timeline/drag-utils.ts b/apps/web/src/lib/timeline/drag-utils.ts index cf59a20d..8efeab79 100644 --- a/apps/web/src/lib/timeline/drag-utils.ts +++ b/apps/web/src/lib/timeline/drag-utils.ts @@ -1,4 +1,4 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; export function getMouseTimeFromClientX({ clientX, @@ -14,6 +14,6 @@ export function getMouseTimeFromClientX({ const mouseX = clientX - containerRect.left + scrollLeft; return Math.max( 0, - mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel), + mouseX / (BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel), ); } diff --git a/apps/web/src/lib/timeline/drop-utils.ts b/apps/web/src/lib/timeline/drop-utils.ts deleted file mode 100644 index 161d2714..00000000 --- a/apps/web/src/lib/timeline/drop-utils.ts +++ /dev/null @@ -1,257 +0,0 @@ -import type { - TimelineTrack, - TimelineElement, -} from "@/lib/timeline"; -import { TRACK_CONFIG, TRACK_GAP } from "@/constants/timeline-constants"; -import type { ComputeDropTargetParams, DropTarget } from "@/lib/timeline"; -import { resolveTrackPlacement } from "@/lib/timeline/placement"; - -function findElementAtPosition({ - mouseX, - tracks, - trackIndex, - targetElementTypes, - pixelsPerSecond, - zoomLevel, -}: { - mouseX: number; - tracks: TimelineTrack[]; - trackIndex: number; - targetElementTypes: string[]; - pixelsPerSecond: number; - zoomLevel: number; -}): { elementId: string; trackId: string } | null { - const time = mouseX / (pixelsPerSecond * zoomLevel); - const track = tracks[trackIndex]; - if (!track || !("elements" in track)) return null; - - const hit = track.elements.find( - (element: TimelineElement) => - targetElementTypes.includes(element.type) && - element.startTime <= time && - time < element.startTime + element.duration, - ); - if (!hit) return null; - return { elementId: hit.id, trackId: track.id }; -} - -function getTrackAtY({ - mouseY, - tracks, - verticalDragDirection, -}: { - mouseY: number; - tracks: TimelineTrack[]; - verticalDragDirection?: "up" | "down" | null; -}): { trackIndex: number; relativeY: number } | null { - let cumulativeHeight = 0; - - for (let i = 0; i < tracks.length; i++) { - const trackHeight = TRACK_CONFIG[tracks[i].type].height; - const trackTop = cumulativeHeight; - const trackBottom = trackTop + trackHeight; - - if (mouseY >= trackTop && mouseY < trackBottom) { - return { - trackIndex: i, - relativeY: mouseY - trackTop, - }; - } - - if (i < tracks.length - 1 && verticalDragDirection) { - const gapTop = trackBottom; - const gapBottom = gapTop + TRACK_GAP; - if (mouseY >= gapTop && mouseY < gapBottom) { - const isDraggingUp = verticalDragDirection === "up"; - return { - trackIndex: isDraggingUp ? i : i + 1, - relativeY: isDraggingUp ? trackHeight - 1 : 0, - }; - } - } - - cumulativeHeight += trackHeight + TRACK_GAP; - } - - return null; -} - -const EMPTY_TARGET_ELEMENT = null; - -function fallbackNewTrackDropTarget({ - xPosition, -}: { - xPosition: number; -}): DropTarget { - return { - trackIndex: 0, - isNewTrack: true, - insertPosition: null, - xPosition, - targetElement: EMPTY_TARGET_ELEMENT, - }; -} - -export function computeDropTarget({ - elementType, - mouseX, - mouseY, - tracks, - playheadTime, - isExternalDrop, - elementDuration, - pixelsPerSecond, - zoomLevel, - verticalDragDirection, - startTimeOverride, - excludeElementId, - targetElementTypes, -}: ComputeDropTargetParams): DropTarget { - const xPosition = - typeof startTimeOverride === "number" - ? startTimeOverride - : isExternalDrop - ? playheadTime - : Math.max(0, mouseX / (pixelsPerSecond * zoomLevel)); - - if (tracks.length === 0) { - const placementResult = resolveTrackPlacement({ - tracks, - elementType, - timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }], - strategy: { - type: "preferIndex", - trackIndex: 0, - hoverDirection: "below", - createNewTrackOnly: true, - }, - }); - const emptyTimelineResult = - placementResult?.kind === "newTrack" ? placementResult : null; - if (!emptyTimelineResult) { - return fallbackNewTrackDropTarget({ xPosition }); - } - - return { - trackIndex: emptyTimelineResult.insertIndex, - isNewTrack: true, - insertPosition: emptyTimelineResult.insertPosition, - xPosition, - targetElement: EMPTY_TARGET_ELEMENT, - }; - } - - const trackAtMouse = getTrackAtY({ mouseY, tracks, verticalDragDirection }); - - if (!trackAtMouse) { - const isAboveAllTracks = mouseY < 0; - - const placementResult = resolveTrackPlacement({ - tracks, - elementType, - timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }], - strategy: { - type: "preferIndex", - trackIndex: isAboveAllTracks ? 0 : tracks.length - 1, - hoverDirection: isAboveAllTracks ? "above" : "below", - createNewTrackOnly: true, - }, - }); - const outOfBoundsResult = - placementResult?.kind === "newTrack" ? placementResult : null; - if (!outOfBoundsResult) { - return fallbackNewTrackDropTarget({ xPosition }); - } - - return { - trackIndex: outOfBoundsResult.insertIndex, - isNewTrack: true, - insertPosition: outOfBoundsResult.insertPosition, - xPosition, - targetElement: EMPTY_TARGET_ELEMENT, - }; - } - - const { trackIndex, relativeY } = trackAtMouse; - const track = tracks[trackIndex]; - - if ( - targetElementTypes && - targetElementTypes.length > 0 - ) { - const targetElement = findElementAtPosition({ - mouseX, - tracks, - trackIndex, - targetElementTypes, - pixelsPerSecond, - zoomLevel, - }); - if (targetElement) { - return { - trackIndex, - isNewTrack: false, - insertPosition: null, - xPosition, - targetElement, - }; - } - } - - const trackHeight = TRACK_CONFIG[track.type].height; - const placementResult = resolveTrackPlacement({ - tracks, - elementType, - timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }], - strategy: { - type: "preferIndex", - trackIndex, - hoverDirection: relativeY < trackHeight / 2 ? "above" : "below", - verticalDragDirection, - }, - }); - if (!placementResult) { - return fallbackNewTrackDropTarget({ xPosition }); - } - - if (placementResult.kind === "existingTrack") { - const adjustedXPosition = - placementResult.adjustedStartTime ?? xPosition; - - return { - trackIndex: placementResult.trackIndex, - isNewTrack: false, - insertPosition: null, - xPosition: adjustedXPosition, - targetElement: EMPTY_TARGET_ELEMENT, - }; - } - - return { - trackIndex: placementResult.insertIndex, - isNewTrack: true, - insertPosition: placementResult.insertPosition, - xPosition, - targetElement: EMPTY_TARGET_ELEMENT, - }; -} - -export function getDropLineY({ - dropTarget, - tracks, -}: { - dropTarget: DropTarget; - tracks: TimelineTrack[]; -}): number { - const safeTrackIndex = Math.min( - Math.max(dropTarget.trackIndex, 0), - tracks.length, - ); - let y = 0; - - for (let i = 0; i < safeTrackIndex; i++) { - y += TRACK_CONFIG[tracks[i].type].height + TRACK_GAP; - } - - return y; -} diff --git a/apps/web/src/lib/timeline/element-utils.ts b/apps/web/src/lib/timeline/element-utils.ts index 5820b96d..96588903 100644 --- a/apps/web/src/lib/timeline/element-utils.ts +++ b/apps/web/src/lib/timeline/element-utils.ts @@ -1,4 +1,4 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { DEFAULT_NEW_ELEMENT_DURATION_SECONDS } from "@/lib/timeline/creation"; import { MASKABLE_ELEMENT_TYPES, RETIMABLE_ELEMENT_TYPES, @@ -115,7 +115,7 @@ export function buildTextElement({ type: "text", name: t.name ?? DEFAULTS.text.element.name, content: t.content ?? DEFAULTS.text.element.content, - duration: t.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION, + duration: t.duration ?? DEFAULT_NEW_ELEMENT_DURATION_SECONDS, startTime, trimStart: 0, trimEnd: 0, @@ -150,7 +150,7 @@ export function buildEffectElement({ name: capitalizeFirstLetter({ string: instance.type }), effectType, params: instance.params, - duration: duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION, + duration: duration ?? DEFAULT_NEW_ELEMENT_DURATION_SECONDS, startTime, trimStart: 0, trimEnd: 0, @@ -178,7 +178,7 @@ export function buildStickerElement({ stickerId, intrinsicWidth, intrinsicHeight, - duration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION, + duration: DEFAULT_NEW_ELEMENT_DURATION_SECONDS, startTime, trimStart: 0, trimEnd: 0, @@ -208,7 +208,7 @@ export function buildGraphicElement({ name: name ?? capitalizeFirstLetter({ string: instance.definitionId }), definitionId: instance.definitionId, params: { ...instance.params, ...(params ?? {}) } as ParamValues, - duration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION, + duration: DEFAULT_NEW_ELEMENT_DURATION_SECONDS, startTime, trimStart: 0, trimEnd: 0, diff --git a/apps/web/src/lib/timeline/index.ts b/apps/web/src/lib/timeline/index.ts index 83a6a0f0..469b916e 100644 --- a/apps/web/src/lib/timeline/index.ts +++ b/apps/web/src/lib/timeline/index.ts @@ -2,7 +2,7 @@ import type { TimelineTrack } from "./types"; export * from "./types"; export * from "./drag"; -export * from "./track-utils"; +export * from "./track-capabilities"; export * from "./track-element-update"; export * from "./element-utils"; export * from "./audio-separation"; diff --git a/apps/web/src/lib/timeline/pixel-utils.ts b/apps/web/src/lib/timeline/pixel-utils.ts index c4635e52..e93b5ab5 100644 --- a/apps/web/src/lib/timeline/pixel-utils.ts +++ b/apps/web/src/lib/timeline/pixel-utils.ts @@ -1,4 +1,4 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; export const TIMELINE_INDICATOR_LINE_WIDTH_PX = 2; @@ -31,7 +31,7 @@ export function getTimelinePixelsPerSecond({ }: { zoomLevel: number; }): number { - return TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + return BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel; } export function timelineTimeToPixels({ diff --git a/apps/web/src/lib/timeline/placement/track-factory.ts b/apps/web/src/lib/timeline/placement/track-factory.ts index e77a8a96..ee056261 100644 --- a/apps/web/src/lib/timeline/placement/track-factory.ts +++ b/apps/web/src/lib/timeline/placement/track-factory.ts @@ -1,4 +1,4 @@ -import { TRACK_CONFIG } from "@/constants/timeline-constants"; +import { DEFAULT_TRACK_NAMES } from "@/lib/timeline/tracks"; import type { TrackType, TimelineTrack } from "@/lib/timeline"; export function buildEmptyTrack({ @@ -10,7 +10,7 @@ export function buildEmptyTrack({ type: TrackType; name?: string; }): TimelineTrack { - const trackName = name ?? TRACK_CONFIG[type].defaultName; + const trackName = name ?? DEFAULT_TRACK_NAMES[type]; switch (type) { case "video": diff --git a/apps/web/src/lib/timeline/ruler-utils.ts b/apps/web/src/lib/timeline/ruler-utils.ts index 94ef416a..732d9ea4 100644 --- a/apps/web/src/lib/timeline/ruler-utils.ts +++ b/apps/web/src/lib/timeline/ruler-utils.ts @@ -1,4 +1,4 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; /** * frame intervals for labels - starts at 2 so there's always at least @@ -56,7 +56,7 @@ export function getRulerConfig({ zoomLevel: number; fps: number; }): RulerConfig { - const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel; const pixelsPerFrame = pixelsPerSecond / fps; const labelIntervalSeconds = findOptimalInterval({ diff --git a/apps/web/src/lib/timeline/scale.ts b/apps/web/src/lib/timeline/scale.ts new file mode 100644 index 00000000..7770f59f --- /dev/null +++ b/apps/web/src/lib/timeline/scale.ts @@ -0,0 +1,3 @@ +export const BASE_TIMELINE_PIXELS_PER_SECOND = 50; +export const TIMELINE_ZOOM_MIN = 0.1; +export const TIMELINE_ZOOM_MAX = 100; diff --git a/apps/web/src/lib/timeline/selection-hit-testing.ts b/apps/web/src/lib/timeline/selection-hit-testing.ts deleted file mode 100644 index 3c0f597e..00000000 --- a/apps/web/src/lib/timeline/selection-hit-testing.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; -import { getCumulativeHeightBefore, getTrackHeight } from "@/lib/timeline"; -import type { TimelineTrack } from "@/lib/timeline"; - -type TimelineElementRef = { trackId: string; elementId: string }; - -interface SelectionRectangle { - left: number; - top: number; - right: number; - bottom: number; -} - -function getNormalizedRectangle({ - startPos, - endPos, -}: { - startPos: { x: number; y: number }; - endPos: { x: number; y: number }; -}): SelectionRectangle { - return { - left: Math.min(startPos.x, endPos.x), - top: Math.min(startPos.y, endPos.y), - right: Math.max(startPos.x, endPos.x), - bottom: Math.max(startPos.y, endPos.y), - }; -} - -function getSelectionRectangleInContent({ - container, - scrollContainer, - startPos, - endPos, -}: { - container: HTMLElement; - scrollContainer: HTMLDivElement | null; - startPos: { x: number; y: number }; - endPos: { x: number; y: number }; -}): SelectionRectangle { - const containerRect = container.getBoundingClientRect(); - const scrollRect = scrollContainer?.getBoundingClientRect() ?? containerRect; - const scrollLeft = scrollContainer?.scrollLeft ?? 0; - const scrollTop = scrollContainer?.scrollTop ?? 0; - - const adjustedStart = { - x: startPos.x - containerRect.left + scrollLeft, - y: startPos.y - scrollRect.top + scrollTop, - }; - const adjustedEnd = { - x: endPos.x - containerRect.left + scrollLeft, - y: endPos.y - scrollRect.top + scrollTop, - }; - - return getNormalizedRectangle({ - startPos: adjustedStart, - endPos: adjustedEnd, - }); -} - -function isRectangleIntersecting({ - elementRectangle, - selectionRectangle, -}: { - elementRectangle: SelectionRectangle; - selectionRectangle: SelectionRectangle; -}): boolean { - return !( - elementRectangle.right < selectionRectangle.left || - elementRectangle.left > selectionRectangle.right || - elementRectangle.bottom < selectionRectangle.top || - elementRectangle.top > selectionRectangle.bottom - ); -} - -export function resolveTimelineElementIntersections({ - container, - scrollContainer, - tracks, - zoomLevel, - startPos, - currentPos, -}: { - container: HTMLElement; - scrollContainer: HTMLDivElement | null; - tracks: TimelineTrack[]; - zoomLevel: number; - startPos: { x: number; y: number }; - currentPos: { x: number; y: number }; -}): TimelineElementRef[] { - const selectionRectangle = getSelectionRectangleInContent({ - container, - scrollContainer, - startPos, - endPos: currentPos, - }); - const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; - const selectedElements: TimelineElementRef[] = []; - - for (const [trackIndex, track] of tracks.entries()) { - const trackTop = getCumulativeHeightBefore({ - tracks, - trackIndex, - }); - const trackHeight = getTrackHeight({ type: track.type }); - const elementTop = TIMELINE_CONSTANTS.PADDING_TOP_PX + trackTop; - const elementBottom = elementTop + trackHeight; - - for (const element of track.elements) { - const elementLeft = element.startTime * pixelsPerSecond; - const elementRight = elementLeft + element.duration * pixelsPerSecond; - const elementRectangle = { - left: elementLeft, - top: elementTop, - right: elementRight, - bottom: elementBottom, - }; - - if ( - isRectangleIntersecting({ - elementRectangle, - selectionRectangle, - }) - ) { - selectedElements.push({ - trackId: track.id, - elementId: element.id, - }); - } - } - } - - return selectedElements; -} diff --git a/apps/web/src/lib/timeline/snap-utils.ts b/apps/web/src/lib/timeline/snap-utils.ts index 2efe24e4..ef5d6e11 100644 --- a/apps/web/src/lib/timeline/snap-utils.ts +++ b/apps/web/src/lib/timeline/snap-utils.ts @@ -1,5 +1,5 @@ import type { Bookmark, TimelineTrack } from "@/lib/timeline"; -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; import { BOOKMARK_TIME_EPSILON } from "@/lib/timeline/bookmarks"; import { getElementKeyframes } from "@/lib/animation"; @@ -107,7 +107,7 @@ export function snapToNearestPoint({ zoomLevel: number; snapThreshold?: number; }): SnapResult { - const pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel; const thresholdInSeconds = snapThreshold / pixelsPerSecond; let closestSnapPoint: SnapPoint | null = null; diff --git a/apps/web/src/lib/timeline/track-capabilities.ts b/apps/web/src/lib/timeline/track-capabilities.ts new file mode 100644 index 00000000..08ee841c --- /dev/null +++ b/apps/web/src/lib/timeline/track-capabilities.ts @@ -0,0 +1,20 @@ +import type { + TimelineTrack, + VideoTrack, + AudioTrack, + GraphicTrack, + TextTrack, + EffectTrack, +} from "@/lib/timeline"; + +export function canTrackHaveAudio( + track: TimelineTrack, +): track is VideoTrack | AudioTrack { + return track.type === "audio" || track.type === "video"; +} + +export function canTrackBeHidden( + track: TimelineTrack, +): track is VideoTrack | TextTrack | GraphicTrack | EffectTrack { + return track.type !== "audio"; +} diff --git a/apps/web/src/lib/timeline/track-utils.ts b/apps/web/src/lib/timeline/track-utils.ts deleted file mode 100644 index cc9e67c4..00000000 --- a/apps/web/src/lib/timeline/track-utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { - TrackType, - TimelineTrack, - VideoTrack, - AudioTrack, - GraphicTrack, - TextTrack, - EffectTrack, -} from "@/lib/timeline"; -import { - TRACK_CONFIG, - ELEMENT_TYPE_CONFIG, - TRACK_GAP, -} from "@/constants/timeline-constants"; - -export function canTracktHaveAudio( - track: TimelineTrack, -): track is VideoTrack | AudioTrack { - return track.type === "audio" || track.type === "video"; -} - -export function canTrackBeHidden( - track: TimelineTrack, -): track is VideoTrack | TextTrack | GraphicTrack | EffectTrack { - return track.type !== "audio"; -} - -export function getElementClasses({ type }: { type: TrackType }) { - return ELEMENT_TYPE_CONFIG[type].background.trim(); -} - -export function getTrackHeight({ type }: { type: TrackType }): number { - return TRACK_CONFIG[type].height; -} - -export function getCumulativeHeightBefore({ - tracks, - trackIndex, -}: { - tracks: Array<{ type: TrackType }>; - trackIndex: number; -}): number { - return tracks - .slice(0, trackIndex) - .reduce( - (sum, track) => sum + getTrackHeight({ type: track.type }) + TRACK_GAP, - 0, - ); -} - -export function getTotalTracksHeight({ - tracks, -}: { - tracks: Array<{ type: TrackType }>; -}): number { - const tracksHeight = tracks.reduce( - (sum, track) => sum + getTrackHeight({ type: track.type }), - 0, - ); - const gapsHeight = Math.max(0, tracks.length - 1) * TRACK_GAP; - return tracksHeight + gapsHeight; -} diff --git a/apps/web/src/lib/timeline/tracks.ts b/apps/web/src/lib/timeline/tracks.ts new file mode 100644 index 00000000..181b9fcf --- /dev/null +++ b/apps/web/src/lib/timeline/tracks.ts @@ -0,0 +1,9 @@ +import type { TrackType } from "@/lib/timeline"; + +export const DEFAULT_TRACK_NAMES: Record = { + video: "Video track", + text: "Text track", + audio: "Audio track", + graphic: "Graphic track", + effect: "Effect track", +} as const; diff --git a/apps/web/src/lib/timeline/zoom-utils.ts b/apps/web/src/lib/timeline/zoom-utils.ts index bbc0b6ae..c06957d0 100644 --- a/apps/web/src/lib/timeline/zoom-utils.ts +++ b/apps/web/src/lib/timeline/zoom-utils.ts @@ -1,4 +1,7 @@ -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import { + BASE_TIMELINE_PIXELS_PER_SECOND, + TIMELINE_ZOOM_MAX, +} from "@/lib/timeline/scale"; const PADDING_MAX_RATIO = 0.75; const PADDING_MIN_RATIO = 0.15; @@ -16,9 +19,9 @@ export function getTimelineZoomMin({ const contentRatioAtMinZoom = 1 - PADDING_MAX_RATIO; const availableWidth = safeContainerWidth * contentRatioAtMinZoom; const zoomToFit = - availableWidth / (safeDuration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND); + availableWidth / (safeDuration * BASE_TIMELINE_PIXELS_PER_SECOND); - return Math.min(TIMELINE_CONSTANTS.ZOOM_MAX, zoomToFit); + return Math.min(TIMELINE_ZOOM_MAX, zoomToFit); } export function getTimelinePaddingPx({ @@ -49,7 +52,7 @@ export function getZoomPercent({ zoomLevel: number; minZoom: number; }): number { - return (zoomLevel - minZoom) / (TIMELINE_CONSTANTS.ZOOM_MAX - minZoom); + return (zoomLevel - minZoom) / (TIMELINE_ZOOM_MAX - minZoom); } /** @@ -59,7 +62,7 @@ export function getZoomPercent({ export function sliderToZoom({ sliderPosition, minZoom, - maxZoom = TIMELINE_CONSTANTS.ZOOM_MAX, + maxZoom = TIMELINE_ZOOM_MAX, }: { sliderPosition: number; minZoom: number; @@ -75,7 +78,7 @@ export function sliderToZoom({ export function zoomToSlider({ zoomLevel, minZoom, - maxZoom = TIMELINE_CONSTANTS.ZOOM_MAX, + maxZoom = TIMELINE_ZOOM_MAX, }: { zoomLevel: number; minZoom: number; -- cgit v1.3.1