221 lines
7.3 KiB
JavaScript
221 lines
7.3 KiB
JavaScript
// js/ui.js
|
|
import { appState } from "./state.js"; // <-- CORREÇÃO: Importação adicionada
|
|
import { playSample } from "./pattern/pattern_audio.js";
|
|
import { renderPatternEditor } from "./pattern/pattern_ui.js";
|
|
import { renderAudioEditor } from "./audio/audio_ui.js";
|
|
import { loadProjectFromServer } from "./file.js";
|
|
|
|
let samplePathMap = {};
|
|
|
|
export function renderAll() {
|
|
renderPatternEditor();
|
|
renderAudioEditor();
|
|
const loopArea = document.getElementById("loop-region");
|
|
|
|
if (loopArea) {
|
|
// Sincroniza a visibilidade da área de loop com o estado atual
|
|
loopArea.classList.toggle("visible", appState.global.isLoopActive);
|
|
}
|
|
}
|
|
|
|
export function getSamplePathMap() {
|
|
return samplePathMap;
|
|
}
|
|
|
|
function buildSamplePathMap(tree, currentPath) {
|
|
for (const key in tree) {
|
|
if (key === "_isFile") continue;
|
|
const node = tree[key];
|
|
const newPath = `${currentPath}/${key}`;
|
|
if (node._isFile) {
|
|
samplePathMap[key] = newPath;
|
|
} else {
|
|
buildSamplePathMap(node, newPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function loadAndRenderSampleBrowser() {
|
|
const browserContent = document.getElementById("browser-content");
|
|
try {
|
|
const response = await fetch(`metadata/samples-manifest.json?v=${Date.now()}`);
|
|
if (!response.ok) throw new Error("Arquivo samples-manifest.json não encontrado.");
|
|
const fileTree = await response.json();
|
|
|
|
samplePathMap = {};
|
|
buildSamplePathMap(fileTree, "src/samples");
|
|
|
|
renderFileTree(fileTree, browserContent, "src/samples");
|
|
} catch (error) {
|
|
console.error("Erro ao carregar samples:", error);
|
|
browserContent.innerHTML = `<p style="color:var(--accent-red); padding: 10px;">${error.message}</p>`;
|
|
}
|
|
}
|
|
|
|
// Em ui.js, substitua a função antiga por esta:
|
|
|
|
function renderFileTree(tree, parentElement, currentPath) {
|
|
parentElement.innerHTML = ""; // Limpa o conteúdo anterior
|
|
const ul = document.createElement("ul");
|
|
|
|
// Ordena para que as pastas sempre apareçam antes dos arquivos
|
|
const sortedKeys = Object.keys(tree).sort((a, b) => {
|
|
const aIsFile = tree[a]._isFile;
|
|
const bIsFile = tree[b]._isFile;
|
|
if (aIsFile === bIsFile) return a.localeCompare(b); // Ordena alfabeticamente se ambos forem do mesmo tipo
|
|
return aIsFile ? 1 : -1; // Pastas (-1) vêm antes de arquivos (1)
|
|
});
|
|
|
|
for (const key of sortedKeys) {
|
|
if (key === '_isFile') continue; // Pula a propriedade de metadados
|
|
|
|
const node = tree[key];
|
|
const li = document.createElement("li");
|
|
const newPath = `${currentPath}/${key}`;
|
|
|
|
if (node._isFile) {
|
|
// --- LÓGICA PARA ARQUIVOS ---
|
|
li.className = "file-item draggable-sample"; // CORREÇÃO: Adiciona classe para consistência
|
|
li.innerHTML = `<i class="fa-solid fa-volume-high"></i> ${key}`; // Ícone mais apropriado
|
|
li.setAttribute("draggable", true);
|
|
li.dataset.path = newPath; // Guarda o caminho para o drag-and-drop
|
|
|
|
li.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
playSample(newPath, null);
|
|
});
|
|
|
|
li.addEventListener("dragstart", (e) => {
|
|
e.dataTransfer.setData("text/plain", newPath);
|
|
e.dataTransfer.effectAllowed = "copy";
|
|
});
|
|
|
|
ul.appendChild(li);
|
|
|
|
} else {
|
|
// --- LÓGICA CORRIGIDA PARA PASTAS ---
|
|
li.className = "folder-item"; // CORREÇÃO 1: Usa a classe CSS correta
|
|
|
|
// CORREÇÃO 2: Cria o <span> clicável para o nome da pasta, que o CSS e o main.js esperam
|
|
const folderNameSpan = document.createElement("span");
|
|
folderNameSpan.className = "folder-name";
|
|
folderNameSpan.innerHTML = `<i class="folder-icon fa-solid fa-folder"></i> ${key}`;
|
|
li.appendChild(folderNameSpan);
|
|
|
|
const nestedUl = document.createElement("ul");
|
|
nestedUl.className = "file-list"; // CORREÇÃO: Adiciona classe para o CSS
|
|
|
|
// Chama a função recursivamente para os conteúdos da pasta
|
|
renderFileTree(node, nestedUl, newPath);
|
|
li.appendChild(nestedUl);
|
|
|
|
// CORREÇÃO 3: Remove o addEventListener de clique daqui. O main.js já cuida disso.
|
|
|
|
ul.appendChild(li);
|
|
}
|
|
}
|
|
parentElement.appendChild(ul);
|
|
}
|
|
|
|
export async function showOpenProjectModal() {
|
|
const openProjectModal = document.getElementById("open-project-modal");
|
|
const serverProjectsList = document.getElementById("server-projects-list");
|
|
serverProjectsList.innerHTML = "<p>Carregando...</p>";
|
|
openProjectModal.classList.add("visible");
|
|
try {
|
|
const response = await fetch("metadata/mmp-manifest.json");
|
|
if (!response.ok) throw new Error("Arquivo mmp-manifest.json não encontrado.");
|
|
const projects = await response.json();
|
|
|
|
serverProjectsList.innerHTML = "";
|
|
if (projects.length === 0) {
|
|
serverProjectsList.innerHTML = '<p style="color:var(--text-dark);">Nenhum projeto encontrado no servidor.</p>';
|
|
}
|
|
|
|
projects.forEach((projectName) => {
|
|
const projectItem = document.createElement("div");
|
|
projectItem.className = "project-item";
|
|
projectItem.textContent = projectName;
|
|
projectItem.addEventListener("click", async () => {
|
|
const success = await loadProjectFromServer(projectName);
|
|
if (success) {
|
|
closeOpenProjectModal();
|
|
}
|
|
});
|
|
serverProjectsList.appendChild(projectItem);
|
|
});
|
|
} catch (error) {
|
|
console.error("Erro ao carregar lista de projetos:", error);
|
|
serverProjectsList.innerHTML = `<p style="color:var(--accent-red);">${error.message}</p>`;
|
|
}
|
|
}
|
|
|
|
export function closeOpenProjectModal() {
|
|
const openProjectModal = document.getElementById("open-project-modal");
|
|
openProjectModal.classList.remove("visible");
|
|
}
|
|
|
|
// ui.js — sistema global de avisos / toasts
|
|
export function showToast(message, type = "info", duration = 3000) {
|
|
// cria o contêiner se não existir
|
|
let container = document.getElementById("toast-container");
|
|
if (!container) {
|
|
container = document.createElement("div");
|
|
container.id = "toast-container";
|
|
Object.assign(container.style, {
|
|
position: "fixed",
|
|
bottom: "20px",
|
|
right: "20px",
|
|
zIndex: 9999,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "flex-end",
|
|
gap: "8px",
|
|
});
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
// cria o toast
|
|
const toast = document.createElement("div");
|
|
toast.className = `toast toast-${type}`;
|
|
toast.textContent = message;
|
|
Object.assign(toast.style, {
|
|
padding: "10px 16px",
|
|
borderRadius: "6px",
|
|
color: "#fff",
|
|
fontFamily: "sans-serif",
|
|
fontSize: "14px",
|
|
boxShadow: "0 2px 8px rgba(0,0,0,0.25)",
|
|
opacity: "0",
|
|
transform: "translateY(10px)",
|
|
transition: "all 0.3s ease",
|
|
maxWidth: "260px",
|
|
textAlign: "left",
|
|
wordWrap: "break-word",
|
|
});
|
|
|
|
// cores por tipo
|
|
const colors = {
|
|
info: "#3498db",
|
|
success: "#2ecc71",
|
|
warning: "#f1c40f",
|
|
error: "#e74c3c",
|
|
};
|
|
toast.style.backgroundColor = colors[type] || colors.info;
|
|
|
|
container.appendChild(toast);
|
|
|
|
// animação de entrada
|
|
setTimeout(() => {
|
|
toast.style.opacity = "1";
|
|
toast.style.transform = "translateY(0)";
|
|
}, 50);
|
|
|
|
// remover após tempo
|
|
setTimeout(() => {
|
|
toast.style.opacity = "0";
|
|
toast.style.transform = "translateY(10px)";
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, duration);
|
|
}
|