- Add TimeScale component with canvas-based rendering - Use 5 pixels per second base scale (duration * zoom * 5) - Implement viewport-based rendering for performance - Add scroll synchronization with waveforms - Add 240px padding for alignment with track controls and master area - Apply custom scrollbar styling - Update all waveform width calculations to match timeline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
/**
|
|
* Timeline coordinate conversion and formatting utilities
|
|
*/
|
|
|
|
/**
|
|
* Base pixels per second at zoom level 1
|
|
* zoom=1: 5 pixels per second
|
|
* zoom=2: 10 pixels per second, etc.
|
|
*/
|
|
const PIXELS_PER_SECOND_BASE = 5;
|
|
|
|
/**
|
|
* Convert time (in seconds) to pixel position
|
|
*/
|
|
export function timeToPixel(time: number, duration: number, zoom: number): number {
|
|
if (duration === 0) return 0;
|
|
const totalWidth = duration * zoom * PIXELS_PER_SECOND_BASE;
|
|
return (time / duration) * totalWidth;
|
|
}
|
|
|
|
/**
|
|
* Convert pixel position to time (in seconds)
|
|
*/
|
|
export function pixelToTime(pixel: number, duration: number, zoom: number): number {
|
|
if (duration === 0) return 0;
|
|
const totalWidth = duration * zoom * PIXELS_PER_SECOND_BASE;
|
|
return (pixel / totalWidth) * duration;
|
|
}
|
|
|
|
/**
|
|
* Calculate appropriate tick interval based on visible duration
|
|
* Returns interval in seconds
|
|
*/
|
|
export function calculateTickInterval(visibleDuration: number): {
|
|
major: number;
|
|
minor: number;
|
|
} {
|
|
// Very zoomed in: show sub-second intervals
|
|
if (visibleDuration < 5) {
|
|
return { major: 1, minor: 0.5 };
|
|
}
|
|
// Zoomed in: show every second
|
|
if (visibleDuration < 20) {
|
|
return { major: 5, minor: 1 };
|
|
}
|
|
// Medium zoom: show every 5 seconds
|
|
if (visibleDuration < 60) {
|
|
return { major: 10, minor: 5 };
|
|
}
|
|
// Zoomed out: show every 10 seconds
|
|
if (visibleDuration < 300) {
|
|
return { major: 30, minor: 10 };
|
|
}
|
|
// Very zoomed out: show every minute
|
|
return { major: 60, minor: 30 };
|
|
}
|
|
|
|
/**
|
|
* Format time in seconds to display format
|
|
* Returns format like "0:00", "1:23", "12:34.5"
|
|
*/
|
|
export function formatTimeLabel(seconds: number, showMillis: boolean = false): string {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
|
|
if (showMillis) {
|
|
const wholeSecs = Math.floor(secs);
|
|
const decimalPart = Math.floor((secs - wholeSecs) * 10);
|
|
return `${mins}:${wholeSecs.toString().padStart(2, '0')}.${decimalPart}`;
|
|
}
|
|
|
|
return `${mins}:${Math.floor(secs).toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
/**
|
|
* Calculate visible time range based on scroll position
|
|
*/
|
|
export function getVisibleTimeRange(
|
|
scrollLeft: number,
|
|
viewportWidth: number,
|
|
duration: number,
|
|
zoom: number
|
|
): { start: number; end: number } {
|
|
const totalWidth = duration * zoom * 100;
|
|
|
|
const start = pixelToTime(scrollLeft, duration, zoom);
|
|
const end = pixelToTime(scrollLeft + viewportWidth, duration, zoom);
|
|
|
|
return {
|
|
start: Math.max(0, start),
|
|
end: Math.min(duration, end),
|
|
};
|
|
}
|