mmpSearch/assets/js/creations/ui.js

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);
}