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:
@@ -24,6 +24,7 @@ const ImportTrackDialog = React.lazy(() => import('@/components/tracks/ImportTra
|
||||
const KeyboardShortcutsDialog = React.lazy(() => import('@/components/dialogs/KeyboardShortcutsDialog').then(m => ({ default: m.KeyboardShortcutsDialog })));
|
||||
const MarkerTimeline = React.lazy(() => import('@/components/markers/MarkerTimeline').then(m => ({ default: m.MarkerTimeline })));
|
||||
const MarkerDialog = React.lazy(() => import('@/components/markers/MarkerDialog').then(m => ({ default: m.MarkerDialog })));
|
||||
const TimeScale = React.lazy(() => import('@/components/timeline/TimeScale').then(m => ({ default: m.TimeScale })));
|
||||
|
||||
// Lazy load analysis components (shown conditionally based on analyzerView)
|
||||
const FrequencyAnalyzer = React.lazy(() => import('@/components/analysis/FrequencyAnalyzer').then(m => ({ default: m.FrequencyAnalyzer })));
|
||||
@@ -197,6 +198,16 @@ export function AudioEditor() {
|
||||
// Track last recorded values to detect changes
|
||||
const lastRecordedValuesRef = React.useRef<Map<string, { value: number; time: number }>>(new Map());
|
||||
|
||||
// Time scale scroll synchronization
|
||||
const timeScaleScrollRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const timeScaleScrollHandlerRef = React.useRef<(() => void) | null>(null);
|
||||
|
||||
const handleTimeScaleScroll = React.useCallback(() => {
|
||||
if (timeScaleScrollHandlerRef.current) {
|
||||
timeScaleScrollHandlerRef.current();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Automation recording callback
|
||||
const handleAutomationRecording = React.useCallback((
|
||||
trackId: string,
|
||||
@@ -2056,23 +2067,22 @@ export function AudioEditor() {
|
||||
</header>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Main canvas area */}
|
||||
<main className="flex-1 flex flex-col overflow-hidden bg-background">
|
||||
{/* Multi-Track View */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Marker Timeline */}
|
||||
<React.Suspense fallback={null}>
|
||||
<MarkerTimeline
|
||||
markers={markers}
|
||||
duration={duration}
|
||||
currentTime={currentTime}
|
||||
onMarkerEdit={handleEditMarker}
|
||||
onMarkerDelete={handleDeleteMarker}
|
||||
onSeek={seek}
|
||||
/>
|
||||
</React.Suspense>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* Time Scale - Full width */}
|
||||
<React.Suspense fallback={null}>
|
||||
<TimeScale
|
||||
duration={duration}
|
||||
zoom={zoom}
|
||||
currentTime={currentTime}
|
||||
onSeek={seek}
|
||||
scrollRef={timeScaleScrollRef}
|
||||
onScroll={handleTimeScaleScroll}
|
||||
/>
|
||||
</React.Suspense>
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Main canvas area */}
|
||||
<main className="flex-1 flex flex-col overflow-hidden bg-background">
|
||||
<TrackList
|
||||
tracks={tracks}
|
||||
zoom={zoom}
|
||||
@@ -2087,18 +2097,20 @@ export function AudioEditor() {
|
||||
onSeek={seek}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onToggleRecordEnable={handleToggleRecordEnable}
|
||||
timeScaleScrollRef={timeScaleScrollRef}
|
||||
onTimeScaleScroll={handleTimeScaleScroll}
|
||||
timeScaleScrollHandlerRef={timeScaleScrollHandlerRef}
|
||||
recordingTrackId={recordingTrackId}
|
||||
recordingLevel={recordingState.inputLevel}
|
||||
trackLevels={trackLevels}
|
||||
onParameterTouched={setParameterTouched}
|
||||
isPlaying={isPlaying}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
{/* Right Sidebar - Master Controls & Analyzers - Hidden on mobile */}
|
||||
<aside className="hidden lg:flex flex-shrink-0 border-l border-border bg-card flex-col pt-5 px-4 pb-4 gap-4 w-60">
|
||||
{/* Master Controls */}
|
||||
{/* Right Sidebar - Master Controls & Analyzers - Hidden on mobile */}
|
||||
<aside className="hidden lg:flex flex-shrink-0 border-l border-border bg-card flex-col pt-5 px-4 pb-4 gap-4 w-60">
|
||||
{/* Master Controls */}
|
||||
<div className="flex items-center justify-center">
|
||||
<MasterControls
|
||||
volume={masterVolume}
|
||||
@@ -2215,7 +2227,8 @@ export function AudioEditor() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar - Stacked on mobile (Master then Transport), Side-by-side on desktop */}
|
||||
|
||||
Reference in New Issue
Block a user