feat: implement TimeScale component with proper zoom calculation
- 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>
This commit is contained in:
@@ -36,6 +36,9 @@ export interface TrackListProps {
|
||||
trackLevels?: Record<string, number>;
|
||||
onParameterTouched?: (trackId: string, laneId: string, touched: boolean) => void;
|
||||
isPlaying?: boolean;
|
||||
timeScaleScrollRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||
onTimeScaleScroll?: () => void;
|
||||
timeScaleScrollHandlerRef?: React.MutableRefObject<(() => void) | null>;
|
||||
}
|
||||
|
||||
export function TrackList({
|
||||
@@ -57,6 +60,9 @@ export function TrackList({
|
||||
trackLevels = {},
|
||||
onParameterTouched,
|
||||
isPlaying = false,
|
||||
timeScaleScrollRef: externalTimeScaleScrollRef,
|
||||
onTimeScaleScroll,
|
||||
timeScaleScrollHandlerRef,
|
||||
}: TrackListProps) {
|
||||
const [importDialogOpen, setImportDialogOpen] = React.useState(false);
|
||||
const [effectBrowserTrackId, setEffectBrowserTrackId] = React.useState<string | null>(null);
|
||||
@@ -66,6 +72,8 @@ export function TrackList({
|
||||
// Refs for horizontal scroll synchronization (per track)
|
||||
const waveformHScrollRefs = React.useRef<Map<string, HTMLDivElement>>(new Map());
|
||||
const automationHScrollRefs = React.useRef<Map<string, HTMLDivElement>>(new Map());
|
||||
const localTimeScaleScrollRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const timeScaleScrollRef = externalTimeScaleScrollRef || localTimeScaleScrollRef;
|
||||
const [syncingScroll, setSyncingScroll] = React.useState(false);
|
||||
|
||||
// Synchronize vertical scroll between controls and waveforms
|
||||
@@ -100,6 +108,11 @@ export function TrackList({
|
||||
el.scrollLeft = scrollLeft;
|
||||
});
|
||||
|
||||
// Sync time scale
|
||||
if (timeScaleScrollRef.current) {
|
||||
timeScaleScrollRef.current.scrollLeft = scrollLeft;
|
||||
}
|
||||
|
||||
setSyncingScroll(false);
|
||||
}, [syncingScroll]);
|
||||
|
||||
@@ -127,9 +140,50 @@ export function TrackList({
|
||||
}
|
||||
});
|
||||
|
||||
// Sync time scale
|
||||
if (timeScaleScrollRef.current) {
|
||||
timeScaleScrollRef.current.scrollLeft = scrollLeft;
|
||||
}
|
||||
|
||||
setSyncingScroll(false);
|
||||
}, [syncingScroll]);
|
||||
|
||||
const handleTimeScaleScrollInternal = React.useCallback(() => {
|
||||
if (syncingScroll) return;
|
||||
setSyncingScroll(true);
|
||||
|
||||
if (!timeScaleScrollRef.current) {
|
||||
setSyncingScroll(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollLeft = timeScaleScrollRef.current.scrollLeft;
|
||||
|
||||
// Sync all waveforms
|
||||
waveformHScrollRefs.current.forEach((el) => {
|
||||
el.scrollLeft = scrollLeft;
|
||||
});
|
||||
|
||||
// Sync all automation lanes
|
||||
automationHScrollRefs.current.forEach((el) => {
|
||||
el.scrollLeft = scrollLeft;
|
||||
});
|
||||
|
||||
setSyncingScroll(false);
|
||||
|
||||
// Also call the external callback if provided
|
||||
if (onTimeScaleScroll) {
|
||||
onTimeScaleScroll();
|
||||
}
|
||||
}, [syncingScroll, onTimeScaleScroll]);
|
||||
|
||||
// Expose the scroll handler via ref so AudioEditor can call it
|
||||
React.useEffect(() => {
|
||||
if (timeScaleScrollHandlerRef) {
|
||||
timeScaleScrollHandlerRef.current = handleTimeScaleScrollInternal;
|
||||
}
|
||||
}, [handleTimeScaleScrollInternal, timeScaleScrollHandlerRef]);
|
||||
|
||||
const handleImportTrack = (buffer: AudioBuffer, name: string) => {
|
||||
if (onImportTrack) {
|
||||
onImportTrack(buffer, name);
|
||||
@@ -490,7 +544,7 @@ export function TrackList({
|
||||
<div className="overflow-x-auto custom-scrollbar">
|
||||
<div
|
||||
style={{
|
||||
minWidth: duration && zoom > 1 ? `${duration * zoom * 100}px` : '100%',
|
||||
minWidth: duration && zoom >= 1 ? `${duration * zoom * 5}px` : '100%',
|
||||
}}
|
||||
>
|
||||
{track.automation.lanes
|
||||
@@ -799,7 +853,7 @@ export function TrackList({
|
||||
<div
|
||||
className="h-full"
|
||||
style={{
|
||||
minWidth: duration && zoom > 1 ? `${duration * zoom * 100}px` : '100%',
|
||||
minWidth: duration && zoom >= 1 ? `${duration * zoom * 5}px` : '100%',
|
||||
}}
|
||||
>
|
||||
<Track
|
||||
@@ -1072,7 +1126,7 @@ export function TrackList({
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
minWidth: duration && zoom > 1 ? `${duration * zoom * 100}px` : '100%',
|
||||
minWidth: duration && zoom >= 1 ? `${duration * zoom * 5}px` : '100%',
|
||||
}}
|
||||
>
|
||||
{track.automation.lanes
|
||||
|
||||
Reference in New Issue
Block a user