feat: disable localStorage persistence and add auto-load last project
**useMultiTrack.ts:** - Removed localStorage persistence (tracks, effects, automation) - Now relies entirely on IndexedDB via project management system - Simpler state management without dual persistence **AudioEditor.tsx:** - Store last project ID in localStorage when saving - Auto-load last project on page mount - Only runs once per session with hasAutoLoaded flag - Falls back to empty state if project can't be loaded **Benefits:** - No more conflicts between localStorage and IndexedDB - Effects and automation properly persisted - Seamless experience - reload page and your project is ready - Single source of truth (IndexedDB) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -911,6 +911,9 @@ export function AudioEditor() {
|
|||||||
|
|
||||||
setCurrentProjectId(projectId);
|
setCurrentProjectId(projectId);
|
||||||
|
|
||||||
|
// Save last project ID to localStorage for auto-load on next visit
|
||||||
|
localStorage.setItem('audio-ui-last-project', projectId);
|
||||||
|
|
||||||
addToast({
|
addToast({
|
||||||
title: 'Project Saved',
|
title: 'Project Saved',
|
||||||
description: `"${currentProjectName}" saved successfully`,
|
description: `"${currentProjectName}" saved successfully`,
|
||||||
@@ -1009,6 +1012,29 @@ export function AudioEditor() {
|
|||||||
}
|
}
|
||||||
}, [loadTracks, addToast]);
|
}, [loadTracks, addToast]);
|
||||||
|
|
||||||
|
// Auto-load last project on mount
|
||||||
|
const [hasAutoLoaded, setHasAutoLoaded] = React.useState(false);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (hasAutoLoaded) return; // Only run once
|
||||||
|
|
||||||
|
const loadLastProject = async () => {
|
||||||
|
const lastProjectId = localStorage.getItem('audio-ui-last-project');
|
||||||
|
if (lastProjectId) {
|
||||||
|
try {
|
||||||
|
console.log('[Auto-load] Loading last project:', lastProjectId);
|
||||||
|
await handleLoadProject(lastProjectId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Auto-load] Failed to load last project:', error);
|
||||||
|
// Clear invalid project ID
|
||||||
|
localStorage.removeItem('audio-ui-last-project');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setHasAutoLoaded(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadLastProject();
|
||||||
|
}, [hasAutoLoaded, handleLoadProject]);
|
||||||
|
|
||||||
// Delete project
|
// Delete project
|
||||||
const handleDeleteProject = React.useCallback(async (projectId: string) => {
|
const handleDeleteProject = React.useCallback(async (projectId: string) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,79 +1,10 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import type { Track } from '@/types/track';
|
import type { Track } from '@/types/track';
|
||||||
import { createTrack, createTrackFromBuffer } from '@/lib/audio/track-utils';
|
import { createTrack, createTrackFromBuffer } from '@/lib/audio/track-utils';
|
||||||
import { createEffectChain } from '@/lib/audio/effects/chain';
|
|
||||||
import { DEFAULT_TRACK_HEIGHT } from '@/types/track';
|
|
||||||
|
|
||||||
const STORAGE_KEY = 'audio-ui-multi-track';
|
|
||||||
|
|
||||||
export function useMultiTrack() {
|
export function useMultiTrack() {
|
||||||
const [tracks, setTracks] = useState<Track[]>(() => {
|
// Note: localStorage persistence disabled in favor of IndexedDB project management
|
||||||
if (typeof window === 'undefined') return [];
|
const [tracks, setTracks] = useState<Track[]>([]);
|
||||||
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
|
||||||
if (saved) {
|
|
||||||
const parsed = JSON.parse(saved);
|
|
||||||
|
|
||||||
// Clear corrupted data immediately if we detect issues
|
|
||||||
const hasInvalidData = parsed.some((t: any) =>
|
|
||||||
typeof t.name !== 'string' || t.name === '[object Object]'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasInvalidData) {
|
|
||||||
console.warn('Detected corrupted track data in localStorage, clearing...');
|
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: AudioBuffers can't be serialized, but EffectChains and Automation can
|
|
||||||
return parsed.map((t: any) => ({
|
|
||||||
...t,
|
|
||||||
name: String(t.name || 'Untitled Track'), // Ensure name is always a string
|
|
||||||
height: t.height && t.height >= DEFAULT_TRACK_HEIGHT ? t.height : DEFAULT_TRACK_HEIGHT, // Migrate old heights
|
|
||||||
audioBuffer: null, // Will need to be reloaded
|
|
||||||
effectChain: t.effectChain || createEffectChain(`${t.name} Effects`), // Restore effect chain or create new
|
|
||||||
automation: t.automation || { lanes: [], showAutomation: false }, // Restore automation or create new
|
|
||||||
selection: t.selection || null, // Initialize selection
|
|
||||||
showEffects: t.showEffects || false, // Restore showEffects state
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load tracks from localStorage:', error);
|
|
||||||
// Clear corrupted data
|
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save tracks to localStorage (without audio buffers)
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Only save serializable fields, excluding audioBuffer and any DOM references
|
|
||||||
const trackData = tracks.map((track) => ({
|
|
||||||
id: track.id,
|
|
||||||
name: String(track.name || 'Untitled Track'),
|
|
||||||
color: track.color,
|
|
||||||
height: track.height,
|
|
||||||
volume: track.volume,
|
|
||||||
pan: track.pan,
|
|
||||||
mute: track.mute,
|
|
||||||
solo: track.solo,
|
|
||||||
recordEnabled: track.recordEnabled,
|
|
||||||
collapsed: track.collapsed,
|
|
||||||
selected: track.selected,
|
|
||||||
showEffects: track.showEffects, // Save effects panel state
|
|
||||||
effectChain: track.effectChain, // Save effect chain
|
|
||||||
automation: track.automation, // Save automation data
|
|
||||||
}));
|
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(trackData));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save tracks to localStorage:', error);
|
|
||||||
}
|
|
||||||
}, [tracks]);
|
|
||||||
|
|
||||||
const addTrack = useCallback((name?: string) => {
|
const addTrack = useCallback((name?: string) => {
|
||||||
const track = createTrack(name);
|
const track = createTrack(name);
|
||||||
|
|||||||
Reference in New Issue
Block a user