import { findIndex } from "./search"; import { FRAME_INTERVAL, PREVIEW_FRAME_WIDTH, TIMELINE_OFFSET_X } from "../constants/constants"; import { ITimelineScaleState } from "@designcombo/types"; import { TIMELINE_ZOOM_LEVELS } from "../constants/scale"; export function getPreviousZoomLevel( currentZoom: ITimelineScaleState ): ITimelineScaleState { const previousZoom = getPreviousZoom(currentZoom); return previousZoom || TIMELINE_ZOOM_LEVELS[0]; } export function getZoomByIndex(index: number) { return TIMELINE_ZOOM_LEVELS[index]; } export function getNextZoomLevel( currentZoom: ITimelineScaleState ): ITimelineScaleState { const nextZoom = getNextZoom(currentZoom); return nextZoom || TIMELINE_ZOOM_LEVELS[TIMELINE_ZOOM_LEVELS.length - 1]; } export const getPreviousZoom = ( currentZoom: ITimelineScaleState ): ITimelineScaleState | null => { // Filter zoom levels that are smaller than the current zoom const smallerZoomLevels = TIMELINE_ZOOM_LEVELS.filter( (level) => level.zoom < currentZoom.zoom ); // If there are no smaller zoom levels, return null (no previous zoom) if (smallerZoomLevels.length === 0) { return null; } // Get the zoom level with the largest zoom value that's still smaller than the current zoom const previousZoom = smallerZoomLevels.reduce((prev, curr) => curr.zoom > prev.zoom ? curr : prev ); return previousZoom; }; export const getNextZoom = ( currentZoom: ITimelineScaleState ): ITimelineScaleState | null => { // Filter zoom levels that are larger than the current zoom const largerZoomLevels = TIMELINE_ZOOM_LEVELS.filter( (level) => level.zoom > currentZoom.zoom ); // If there are no larger zoom levels, return null (no next zoom) if (largerZoomLevels.length === 0) { return null; } // Get the zoom level with the smallest zoom value that's still larger than the current zoom const nextZoom = largerZoomLevels.reduce((prev, curr) => curr.zoom < prev.zoom ? curr : prev ); return nextZoom; }; export function getFitZoomLevel( totalLengthMs: number, zoom = 1, scrollOffset = 8 // Default fallback value ): ITimelineScaleState { const getVisibleWidth = () => { const clampedScrollOffset = Math.max(0, scrollOffset); const timelineCanvas = document.getElementById( "designcombo-timeline-canvas" ) as HTMLElement; const offsetWidth = timelineCanvas?.offsetWidth ?? document.body.offsetWidth; // Use 1 to prevent NaN because of dividing by 0. return Math.max(1, offsetWidth - clampedScrollOffset); }; const getFullWidth = () => { if (typeof totalLengthMs === "number") { return timeMsToUnits(totalLengthMs, zoom); } return calculateTimelineWidth(totalLengthMs, zoom); }; const multiplier = getVisibleWidth() / getFullWidth(); const targetZoom = zoom * multiplier; const fitZoomIndex = findIndex(TIMELINE_ZOOM_LEVELS, (level) => { return level.zoom > targetZoom; }); // const clampedIndex = clamp(fitZoomIndex, 0, TIMELINE_ZOOM_LEVELS.length - 1); return { segments: 5, index: fitZoomIndex, zoom: targetZoom, unit: 1 / targetZoom }; } export function timeMsToUnits(timeMs: number, zoom = 1): number { const zoomedFrameWidth = PREVIEW_FRAME_WIDTH * zoom; const frames = timeMs * (60 / 1000); return frames * zoomedFrameWidth; } export function unitsToTimeMs(units: number, zoom = 1): number { const zoomedFrameWidth = PREVIEW_FRAME_WIDTH * zoom; const frames = units / zoomedFrameWidth; return frames * FRAME_INTERVAL; } export function calculateTimelineWidth( totalLengthMs: number, zoom = 1 ): number { return timeMsToUnits(totalLengthMs, zoom); }