mmpSearch/assets/js/creations/main.js

441 lines
16 KiB
JavaScript

// js/main.js (ESM com import absoluto de socket.js + ROOM_NAME local)
import { appState, resetProjectState } from "./state.js";
import {
updateTransportLoop,
restartAudioEditorIfPlaying,
} from "./audio/audio_audio.js";
import { initializeAudioContext } from "./audio.js";
import { handleFileLoad, generateMmpFile } from "./file.js";
import {
renderAll,
loadAndRenderSampleBrowser,
showOpenProjectModal,
closeOpenProjectModal,
} from "./ui.js";
import { renderAudioEditor } from "./audio/audio_ui.js";
import { adjustValue, enforceNumericInput } from "./utils.js";
import { ZOOM_LEVELS } from "./config.js";
// ⚠️ IMPORT ABSOLUTO para evitar 404/text/html quando a página estiver em /creation/ ou fora dela.
// Ajuste o prefixo abaixo para o caminho real onde seus assets vivem no servidor:
import { sendAction, joinRoom, setUserName } from "./socket.js";
// Descobre a sala pela URL (local ao main.js) e expõe no window para debug
const ROOM_NAME = new URLSearchParams(window.location.search).get("room");
window.ROOM_NAME = ROOM_NAME;
// ✅ NOVO: se tem sala na URL, entra já na sala (independe do áudio)
if (ROOM_NAME) {
// entra na sala para receber estado/broadcasts imediatamente
joinRoom();
}
// Função util para alternar estado dos botões de ferramenta
function updateToolButtons() {
const sliceToolBtn = document.getElementById("slice-tool-btn");
const trimToolBtn = document.getElementById("resize-tool-trim");
const stretchToolBtn = document.getElementById("resize-tool-stretch");
if (sliceToolBtn)
sliceToolBtn.classList.toggle("active", appState.global.sliceToolActive);
if (trimToolBtn)
trimToolBtn.classList.toggle(
"active",
!appState.global.sliceToolActive && appState.global.resizeMode === "trim"
);
if (stretchToolBtn)
stretchToolBtn.classList.toggle(
"active",
!appState.global.sliceToolActive &&
appState.global.resizeMode === "stretch"
);
document.body.classList.toggle(
"slice-tool-active",
appState.global.sliceToolActive
);
}
document.addEventListener("DOMContentLoaded", () => {
// Botões e elementos
const newProjectBtn = document.getElementById("new-project-btn");
const openMmpBtn = document.getElementById("open-mmp-btn");
const saveMmpBtn = document.getElementById("save-mmp-btn");
const uploadSampleBtn = document.getElementById("upload-sample-btn");
const addInstrumentBtn = document.getElementById("add-instrument-btn");
const removeInstrumentBtn = document.getElementById("remove-instrument-btn");
const playBtn = document.getElementById("play-btn");
const stopBtn = document.getElementById("stop-btn");
const audioEditorPlayBtn = document.getElementById("audio-editor-play-btn");
const audioEditorStopBtn = document.getElementById("audio-editor-stop-btn");
const audioEditorLoopBtn = document.getElementById("audio-editor-loop-btn");
const addAudioTrackBtn = document.getElementById("add-audio-track-btn");
const rewindBtn = document.getElementById("rewind-btn");
const metronomeBtn = document.getElementById("metronome-btn");
const sliceToolBtn = document.getElementById("slice-tool-btn");
const resizeToolTrimBtn = document.getElementById("resize-tool-trim");
const resizeToolStretchBtn = document.getElementById("resize-tool-stretch");
const createRoomBtn = document.getElementById("create-room-btn");
const mmpFileInput = document.getElementById("mmp-file-input");
const sampleFileInput = document.getElementById("sample-file-input");
const openProjectModal = document.getElementById("open-project-modal");
const openModalCloseBtn = document.getElementById("open-modal-close-btn");
const loadFromComputerBtn = document.getElementById("load-from-computer-btn");
const sidebarToggle = document.getElementById("sidebar-toggle");
const addBarBtn = document.getElementById("add-bar-btn");
const zoomInBtn = document.getElementById("zoom-in-btn");
const zoomOutBtn = document.getElementById("zoom-out-btn");
const deleteClipBtn = document.getElementById("delete-clip");
// =================================================================
// 👇 INÍCIO DA CORREÇÃO (Botão de Sincronia - Agora envia Ação)
// =================================================================
const syncModeBtn = document.getElementById("sync-mode-btn"); //
if (syncModeBtn) {
//
// Define o estado inicial (global por padrão)
appState.global.syncMode = "global"; //
syncModeBtn.classList.add("active"); //
syncModeBtn.textContent = "Global"; //
syncModeBtn.addEventListener("click", () => {
//
// 1. Determina qual será o *novo* modo
const newMode =
appState.global.syncMode === "global" ? "local" : "global"; //
// 2. Envia a ação para sincronizar. O handleActionBroadcast
// cuidará de atualizar o appState, o botão e mostrar o toast.
sendAction({
type: "SET_SYNC_MODE",
mode: newMode,
});
// Lógica antiga removida daqui (movida para o handler)
/*
const isNowLocal = appState.global.syncMode === "global";
appState.global.syncMode = isNowLocal ? "local" : "global";
syncModeBtn.classList.toggle("active", !isNowLocal);
syncModeBtn.textContent = isNowLocal ? "Local" : "Global";
showToast( `🎧 Modo de Playback: ${isNowLocal ? "Local" : "Global"}`, "info" );
*/
});
// Esconde o botão se não estiver em uma sala (lógica movida do socket.js)
if (!ROOM_NAME) {
//
//syncModeBtn.style.display = 'none'; // REMOVIDO PARA TESTE VISUAL
}
}
// =================================================================
// 👆 FIM DA CORREÇÃO
// =================================================================
// Excluir clipe
if (deleteClipBtn) {
deleteClipBtn.addEventListener("click", () => {
initializeAudioContext();
const clipId = appState.global.selectedClipId;
if (clipId) {
sendAction({ type: "REMOVE_AUDIO_CLIP", clipId });
appState.global.selectedClipId = null;
}
const menu = document.getElementById("timeline-context-menu");
if (menu) menu.style.display = "none";
});
}
// Delete/Backspace
document.addEventListener("keydown", (e) => {
if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return;
const clipId = appState.global.selectedClipId;
if ((e.key === "Delete" || e.key === "Backspace") && clipId) {
e.preventDefault();
sendAction({ type: "REMOVE_AUDIO_CLIP", clipId });
appState.global.selectedClipId = null;
}
});
// Fechar menu contexto
document.addEventListener("click", (e) => {
const menu = document.getElementById("timeline-context-menu");
if (menu && !e.target.closest("#timeline-context-menu")) {
menu.style.display = "none";
}
});
// Ações principais (broadcast)
newProjectBtn?.addEventListener("click", () => {
initializeAudioContext();
if (
(appState.pattern.tracks.length > 0 || appState.audio.clips.length > 0) &&
!confirm(
"Você tem certeza? Isso irá resetar o projeto para TODOS na sala."
)
)
return;
sendAction({ type: "RESET_PROJECT" });
});
addBarBtn?.addEventListener("click", () => {
const barsInput = document.getElementById("bars-input");
if (barsInput) {
adjustValue(barsInput, 1);
barsInput.dispatchEvent(new Event("change", { bubbles: true }));
}
});
openMmpBtn?.addEventListener("click", showOpenProjectModal);
loadFromComputerBtn?.addEventListener("click", () => mmpFileInput?.click());
mmpFileInput?.addEventListener("change", (event) => {
const file = event.target.files[0];
if (file) handleFileLoad(file).then(() => closeOpenProjectModal());
});
uploadSampleBtn?.addEventListener("click", () => sampleFileInput?.click());
saveMmpBtn?.addEventListener("click", generateMmpFile);
addInstrumentBtn?.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "ADD_TRACK" });
});
removeInstrumentBtn?.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "REMOVE_LAST_TRACK" });
});
playBtn?.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "TOGGLE_PLAYBACK" });
});
stopBtn?.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "STOP_PLAYBACK" });
});
rewindBtn?.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "REWIND_PLAYBACK" });
});
metronomeBtn?.addEventListener("click", () => {
initializeAudioContext();
appState.global.metronomeEnabled = !appState.global.metronomeEnabled;
metronomeBtn.classList.toggle("active", appState.global.metronomeEnabled);
});
// Ferramentas locais
if (sliceToolBtn) {
sliceToolBtn.addEventListener("click", () => {
appState.global.sliceToolActive = !appState.global.sliceToolActive;
updateToolButtons();
});
}
if (resizeToolTrimBtn) {
resizeToolTrimBtn.addEventListener("click", () => {
appState.global.resizeMode = "trim";
appState.global.sliceToolActive = false;
updateToolButtons();
});
}
if (resizeToolStretchBtn) {
resizeToolStretchBtn.addEventListener("click", () => {
appState.global.resizeMode = "stretch";
appState.global.sliceToolActive = false;
updateToolButtons();
});
}
openModalCloseBtn?.addEventListener("click", closeOpenProjectModal);
sidebarToggle?.addEventListener("click", () => {
document.body.classList.toggle("sidebar-hidden");
const icon = sidebarToggle.querySelector("i");
if (icon) {
icon.className = document.body.classList.contains("sidebar-hidden")
? "fa-solid fa-caret-right"
: "fa-solid fa-caret-left";
}
});
const inputs = document.querySelectorAll(".value-input");
inputs.forEach((input) => {
input.addEventListener("input", (event) => {
enforceNumericInput(event);
if (
appState.global.isPlaying &&
(event.target.id.startsWith("compasso-") ||
event.target.id === "bars-input")
) {
sendAction({ type: "STOP_PLAYBACK" });
}
});
input.addEventListener("change", (event) => {
const target = event.target;
if (target.id === "bpm-input") {
sendAction({ type: "SET_BPM", value: target.value });
} else if (target.id === "bars-input") {
sendAction({ type: "SET_BARS", value: target.value });
} else if (target.id === "compasso-a-input") {
sendAction({ type: "SET_TIMESIG_A", value: target.value });
} else if (target.id === "compasso-b-input") {
sendAction({ type: "SET_TIMESIG_B", value: target.value });
}
});
input.addEventListener("wheel", (event) => {
event.preventDefault();
const step = event.deltaY < 0 ? 1 : -1;
adjustValue(event.target, step);
event.target.dispatchEvent(new Event("change", { bubbles: true }));
});
});
const buttons = document.querySelectorAll(".adjust-btn");
buttons.forEach((button) => {
button.addEventListener("click", () => {
const targetId = button.dataset.target + "-input";
const targetInput = document.getElementById(targetId);
const step = parseInt(button.dataset.step, 10) || 1;
if (targetInput) {
adjustValue(targetInput, step);
targetInput.dispatchEvent(new Event("change", { bubbles: true }));
}
});
});
// Zoom local
zoomInBtn?.addEventListener("click", () => {
if (appState.global.zoomLevelIndex < ZOOM_LEVELS.length - 1) {
appState.global.zoomLevelIndex++;
renderAll();
}
});
zoomOutBtn?.addEventListener("click", () => {
if (appState.global.zoomLevelIndex > 0) {
appState.global.zoomLevelIndex--;
renderAll();
}
});
// Editor de Áudio
audioEditorPlayBtn?.addEventListener("click", () => {
initializeAudioContext();
if (appState.global.isAudioEditorPlaying) {
sendAction({ type: "STOP_AUDIO_PLAYBACK", rewind: false });
} else {
sendAction({
type: "START_AUDIO_PLAYBACK",
seekTime: appState.audio.audioEditorSeekTime, // Corrigido
loopState: {
isLoopActive: appState.global.isLoopActive,
loopStartTime: appState.global.loopStartTime,
loopEndTime: appState.global.loopEndTime,
},
});
}
});
audioEditorStopBtn?.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "STOP_AUDIO_PLAYBACK", rewind: true });
});
// Loop Button (agora envia ação)
audioEditorLoopBtn?.addEventListener("click", () => {
initializeAudioContext(); // Garante contexto
const newLoopState = !appState.global.isLoopActive;
sendAction({
type: "SET_LOOP_STATE",
isLoopActive: newLoopState,
loopStartTime: appState.global.loopStartTime,
loopEndTime: appState.global.loopEndTime,
});
});
if (addAudioTrackBtn) {
addAudioTrackBtn.addEventListener("click", () => {
initializeAudioContext();
sendAction({ type: "ADD_AUDIO_LANE" });
});
}
// Navegador de Samples (local)
loadAndRenderSampleBrowser();
const browserContent = document.getElementById("browser-content");
if (browserContent) {
browserContent.addEventListener("click", function (event) {
const folderName = event.target.closest(".folder-name");
if (folderName) {
const folderItem = folderName.parentElement;
folderItem.classList.toggle("open");
}
});
}
// Criar sala (gera link com ?room=...)
if (createRoomBtn) {
createRoomBtn.addEventListener("click", () => {
initializeAudioContext();
const currentParams = new URLSearchParams(window.location.search);
if (currentParams.has("room")) {
alert(
`Você já está na sala: ${currentParams.get(
"room"
)}\n\nCopie o link da barra de endereços para convidar.`
);
return;
}
const defaultName = `sessao-${Math.random()
.toString(36)
.substring(2, 7)}`;
const roomName = prompt(
"Digite um nome para a sala compartilhada:",
defaultName
);
if (!roomName) return;
const currentUrl = window.location.origin + window.location.pathname;
const shareableLink = `${currentUrl}?room=${encodeURIComponent(
roomName
)}`;
try {
navigator.clipboard.writeText(shareableLink);
alert(
`Link da sala copiado para a área de transferência!\n\n${shareableLink}\n\nA página será recarregada agora para entrar na nova sala.`
);
} catch (err) {
alert(
`Link da sala: ${shareableLink}\n\nA página será recarregada agora para entrar na nova sala.`
);
}
window.location.href = shareableLink;
});
}
// Modal “destravar áudio” + entrar na sala
const audioUnlockModal = document.getElementById("audio-unlock-modal");
const audioUnlockBtn = document.getElementById("audio-unlock-btn");
if (ROOM_NAME && audioUnlockModal && audioUnlockBtn) {
audioUnlockModal.style.display = "flex";
audioUnlockBtn.addEventListener("click", () => {
const userName = prompt(
"Qual o seu nome?",
`Alicer-${Math.floor(Math.random() * 999)}`
);
if (!userName) return;
setUserName(userName);
initializeAudioContext();
// joinRoom() já foi chamado no início se ROOM_NAME existe
audioUnlockModal.style.display = "none";
});
} else {
console.log("Modo local. Áudio será iniciado no primeiro clique.");
// Comentado para permitir teste visual
// if (syncModeBtn) syncModeBtn.style.display = "none";
}
renderAll();
updateToolButtons();
});