diff --git a/assets/js/creations/file.js b/assets/js/creations/file.js index f107b6e0..c5d0206f 100644 --- a/assets/js/creations/file.js +++ b/assets/js/creations/file.js @@ -314,7 +314,7 @@ export async function parseMmpContent(xmlString) { console.log("Restaurando estado temporário da sessão..."); const tempState = JSON.parse(tempStateJSON); - // 1. Restaura Pattern (código que já existia) + // 1. Restaura Pattern (mantido) appState.pattern.tracks.forEach((liveTrack) => { const savedTrack = tempState.pattern.tracks.find( (t) => t.id === liveTrack.id @@ -325,7 +325,6 @@ export async function parseMmpContent(xmlString) { liveTrack.activePatternIndex = savedTrack.activePatternIndex; liveTrack.volume = savedTrack.volume; liveTrack.pan = savedTrack.pan; - if (liveTrack.volumeNode) { liveTrack.volumeNode.volume.value = Tone.gainToDb(savedTrack.volume); } @@ -334,20 +333,18 @@ export async function parseMmpContent(xmlString) { } } }); - - // Filtra tracks deletadas appState.pattern.tracks = appState.pattern.tracks.filter((liveTrack) => tempState.pattern.tracks.some((t) => t.id === liveTrack.id) ); - // 2. 🔥 FIX: Restaura Áudio (Clips e Tracks) + // 2. 🔥 NOVO: Restaura Áudio (Clips e Tracks) no F5 if (tempState.audio) { console.log("Restaurando faixas de áudio e clips..."); appState.audio.tracks = tempState.audio.tracks || []; appState.audio.clips = tempState.audio.clips || []; } - // 3. Restaura Global + // 3. Restaura Global (mantido) document.getElementById("bpm-input").value = tempState.global.bpm; document.getElementById("compasso-a-input").value = tempState.global.compassoA; document.getElementById("compasso-b-input").value = tempState.global.compassoB; @@ -355,7 +352,6 @@ export async function parseMmpContent(xmlString) { if (tempState.global.syncMode) { appState.global.syncMode = tempState.global.syncMode; - // Atualiza visual do botão sync se existir (pode precisar disparar evento ou fazer manual) const syncBtn = document.getElementById("sync-mode-btn"); if (syncBtn) { syncBtn.classList.toggle("active", tempState.global.syncMode === "global"); diff --git a/assets/js/creations/main.js b/assets/js/creations/main.js index 31ded2ec..25878e3d 100644 --- a/assets/js/creations/main.js +++ b/assets/js/creations/main.js @@ -302,6 +302,26 @@ document.addEventListener("DOMContentLoaded", () => { } }); + // 🔥 CORREÇÃO DO PULO DE TELA (SCROLL JUMP) + // Adicionamos um listener global para capturar cliques em clips ou links vazios + document.body.addEventListener("click", (e) => { + // 1. Verifica links com href="#" + const targetLink = e.target.closest('a[href="#"]'); + + // 2. Verifica se clicou num clip ou container de clip + // (Mesmo sendo divs, vamos prevenir o comportamento padrão para garantir que não role) + const isAudioClip = e.target.closest('.timeline-clip') || + e.target.closest('.audio-clip-container') || + e.target.closest('.track-info'); // Às vezes clicar no header da track causa isso + + if (targetLink || isAudioClip) { + // Impede o navegador de rolar a tela ou mudar o foco bruscamente + // Nota: O 'logic' do clique (selecionar, arrastar) ainda vai funcionar + // porque os listeners do audio_ui.js usam 'mousedown', que acontece ANTES do 'click'. + e.preventDefault(); + } + }, { passive: false }); // passive: false é necessário para usar preventDefault + // Ações principais (broadcast) newProjectBtn?.addEventListener("click", () => { initializeAudioContext(); diff --git a/assets/js/creations/state.js b/assets/js/creations/state.js index 76864b57..ced85a5b 100644 --- a/assets/js/creations/state.js +++ b/assets/js/creations/state.js @@ -52,6 +52,7 @@ window.appState = appState; export function resetProjectState() { console.log("Executando resetProjectState completo..."); + // 1. Reseta o estado global Object.assign(appState.global, { sliceToolActive: false, isPlaying: false, @@ -75,40 +76,35 @@ export function resetProjectState() { justReset: false, }); + // 2. Reseta o estado do pattern Object.assign(appState.pattern, { tracks: [], activeTrackId: null, activePatternIndex: 0, }); + // 3. 🔥 CRÍTICO: Reseta o estado de áudio na força bruta + // Isso garante que os arrays fiquem vazios ANTES de qualquer salvamento de sessão + if (appState.audio) { + appState.audio.tracks = []; + appState.audio.clips = []; + appState.audio.audioEditorSeekTime = 0; + } + + // Chama a função original para garantir reinicialização de contextos se houver initializeAudioState(); } -// --- NOVA IMPLEMENTAÇÃO DE PERSISTÊNCIA --- - -/** - * Helper: Gera a string XML do estado atual. - * Precisamos "emprestar" a lógica do file.js sem baixar o arquivo. - * A maneira mais limpa é importar generateXmlFromState de file.js, - * mas como ela não é exportada lá, vamos usar uma estratégia via DOM - * ou mover a lógica. Para simplificar sem quebrar o file.js, - * vamos salvar apenas o "essencial" do patternState se o XML falhar, - * mas o ideal é garantir que o XML seja gerado. - * * NOTA: Para que isso funcione perfeitamente, certifique-se de exportar - * 'generateXmlFromState' no seu file.js ou usar a lógica abaixo. - */ -import { generateXmlFromStateExported } from "./file.js"; // Vamos assumir que você vai exportar isso no file.js - export function saveStateToSession() { if (!window.ROOM_NAME) return; - // 1. Crie uma versão "limpa" dos tracks (PATTERN) + // 1. Limpa Tracks do Pattern const cleanTracks = appState.pattern.tracks.map((track) => { return { id: track.id, name: track.name, samplePath: track.samplePath, - patterns: track.patterns, + patterns: track.patterns, activePatternIndex: track.activePatternIndex, volume: track.volume, pan: track.pan, @@ -117,24 +113,23 @@ export function saveStateToSession() { }; }); - // 2. Construa o objeto de estado final para salvar + // 2. Constrói o objeto. 🔥 AQUI ADICIONAMOS O ÁUDIO const stateToSave = { pattern: { - ...appState.pattern, - tracks: cleanTracks, + ...appState.pattern, + tracks: cleanTracks, }, - // 🔥 FIX: Salvando o estado de Áudio (Tracks e Clips) + // Salva o estado de Áudio para o F5 funcionar audio: { tracks: appState.audio.tracks || [], clips: appState.audio.clips || [], - // Adicione outras props de áudio se necessário (ex: seekTime, etc) }, global: { bpm: document.getElementById("bpm-input").value, compassoA: document.getElementById("compasso-a-input").value, compassoB: document.getElementById("compasso-b-input").value, bars: document.getElementById("bars-input").value, - syncMode: appState.global.syncMode, // É bom salvar isso também + syncMode: appState.global.syncMode, }, }; @@ -147,50 +142,4 @@ export function saveStateToSession() { } catch (e) { console.error("Falha ao salvar estado na sessão:", e); } -} -export async function loadStateFromSession() { - const roomName = window.ROOM_NAME; - if (!roomName) return false; - - try { - const raw = sessionStorage.getItem(`mmp_backup_${roomName}`); - if (!raw) return false; - - const backup = JSON.parse(raw); - console.log(`[Session] Recuperando backup de ${new Date(backup.timestamp).toLocaleTimeString()}`); - - // 1. Restaura Globais - if (backup.global) { - appState.global.bpm = backup.global.bpm; - appState.global.compassoA = backup.global.compassoA; - appState.global.compassoB = backup.global.compassoB; - appState.global.bars = backup.global.bars; - appState.global.syncMode = backup.global.syncMode || "global"; - - // Atualiza DOM imediatamente para evitar desincronia visual - const bpmInput = document.getElementById("bpm-input"); - if(bpmInput) bpmInput.value = backup.global.bpm; - // ... (outros inputs se necessário, mas o renderAll cuida da maioria) - } - - // 2. Restaura Pattern (XML é preferível) - if (backup.xml) { - await parseMmpContent(backup.xml); - } else if (backup.patternRaw) { - // Fallback simples (menos robusto que XML) - console.warn("Restaurando de Pattern Raw (backup antigo/incompleto)"); - Object.assign(appState.pattern, backup.patternRaw); - } - - // 3. Restaura Áudio (Clipes + Gravações) - if (backup.audio) { - await applyAudioSnapshot(backup.audio); - } - - return true; // Sucesso - - } catch (e) { - console.error("Erro ao restaurar sessão:", e); - return false; - } } \ No newline at end of file