versão 2.0
This commit is contained in:
1 parent
d506332268
commit
fe735bf0a6
16 files changed
+1735
-1626
No files matched your search
+47
-498
@@ -1,110 +1,22 @@
|
||||
// js/ui.js
|
||||
import {
|
||||
appState,
|
||||
toggleStepState,
|
||||
updateTrackSample,
|
||||
updateTrackVolume,
|
||||
updateTrackPan,
|
||||
addAudioTrack,
|
||||
toggleAudioTrackSolo,
|
||||
} from "./state.js";
|
||||
import { playSample, stopPlayback, seekAudioEditor } from "./audio.js";
|
||||
import { getTotalSteps } from "./utils.js";
|
||||
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";
|
||||
import { drawWaveform } from "./waveform.js";
|
||||
import { PIXELS_PER_STEP, PIXELS_PER_BAR } from "./config.js";
|
||||
|
||||
export function updateAudioEditorUI() {
|
||||
const playBtn = document.getElementById('audio-editor-play-btn');
|
||||
if (playBtn) {
|
||||
if (appState.isAudioEditorPlaying) {
|
||||
playBtn.classList.remove('fa-play');
|
||||
playBtn.classList.add('fa-pause');
|
||||
} else {
|
||||
playBtn.classList.remove('fa-pause');
|
||||
playBtn.classList.add('fa-play');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let samplePathMap = {};
|
||||
const globalPatternSelector = document.getElementById('global-pattern-selector');
|
||||
|
||||
if (globalPatternSelector) {
|
||||
globalPatternSelector.addEventListener('change', () => {
|
||||
stopPlayback();
|
||||
appState.activePatternIndex = parseInt(globalPatternSelector.value, 10);
|
||||
|
||||
const firstTrack = appState.tracks[0];
|
||||
if (firstTrack) {
|
||||
const activePattern = firstTrack.patterns[appState.activePatternIndex];
|
||||
if (activePattern) {
|
||||
const stepsPerBar = 16;
|
||||
const requiredBars = Math.ceil(activePattern.steps.length / stepsPerBar);
|
||||
document.getElementById("bars-input").value = requiredBars > 0 ? requiredBars : 1;
|
||||
}
|
||||
}
|
||||
redrawSequencer();
|
||||
});
|
||||
}
|
||||
export function renderAll() {
|
||||
renderPatternEditor();
|
||||
renderAudioEditor();
|
||||
const loopArea = document.getElementById("loop-region");
|
||||
|
||||
export function updateGlobalPatternSelector() {
|
||||
if (!globalPatternSelector) return;
|
||||
const referenceTrack = appState.tracks[0];
|
||||
globalPatternSelector.innerHTML = '';
|
||||
if (referenceTrack && referenceTrack.patterns.length > 0) {
|
||||
referenceTrack.patterns.forEach((pattern, index) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = index;
|
||||
option.textContent = pattern.name;
|
||||
globalPatternSelector.appendChild(option);
|
||||
});
|
||||
globalPatternSelector.selectedIndex = appState.activePatternIndex;
|
||||
globalPatternSelector.disabled = false;
|
||||
} else {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = 'Sem patterns';
|
||||
globalPatternSelector.appendChild(option);
|
||||
globalPatternSelector.disabled = true;
|
||||
if (loopArea) {
|
||||
// Sincroniza a visibilidade da área de loop com o estado atual
|
||||
loopArea.classList.toggle("visible", appState.global.isLoopActive);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSampleUpload(file) {
|
||||
const validExtensions = ['.wav', '.flac', '.ogg', '.mp3'];
|
||||
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
|
||||
|
||||
if (!validExtensions.includes(fileExtension)) {
|
||||
alert("Formato de arquivo inválido. Por favor, envie .wav, .flac, .ogg, ou .mp3.");
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const dataURL = e.target.result;
|
||||
const browserContent = document.getElementById("browser-content");
|
||||
const list = browserContent.querySelector("ul");
|
||||
|
||||
if (list) {
|
||||
const li = document.createElement("li");
|
||||
li.innerHTML = `<i class="fa-solid fa-file-audio"></i> ${file.name}`;
|
||||
li.setAttribute("draggable", true);
|
||||
|
||||
li.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
playSample(dataURL, null);
|
||||
});
|
||||
|
||||
li.addEventListener("dragstart", (event) => {
|
||||
event.dataTransfer.setData("text/plain", dataURL);
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
|
||||
list.prepend(li);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
export function getSamplePathMap() {
|
||||
return samplePathMap;
|
||||
}
|
||||
@@ -122,383 +34,11 @@ function buildSamplePathMap(tree, currentPath) {
|
||||
}
|
||||
}
|
||||
|
||||
export function renderAudioEditor() {
|
||||
const audioEditor = document.querySelector('.audio-editor');
|
||||
const audioTrackContainer = document.getElementById('audio-track-container');
|
||||
if (!audioEditor || !audioTrackContainer) return;
|
||||
|
||||
audioEditor.ondragover = (e) => {
|
||||
e.preventDefault();
|
||||
audioEditor.classList.add("drag-over");
|
||||
};
|
||||
audioEditor.ondragleave = () => {
|
||||
audioEditor.classList.remove("drag-over");
|
||||
};
|
||||
audioEditor.ondrop = (e) => {
|
||||
e.preventDefault();
|
||||
audioEditor.classList.remove("drag-over");
|
||||
const filePath = e.dataTransfer.getData("text/plain");
|
||||
if (filePath) {
|
||||
addAudioTrack(filePath);
|
||||
}
|
||||
};
|
||||
|
||||
audioTrackContainer.innerHTML = '';
|
||||
|
||||
appState.audioTracks.forEach(trackData => {
|
||||
const audioTrackLane = document.createElement('div');
|
||||
audioTrackLane.className = 'audio-track-lane';
|
||||
audioTrackLane.dataset.trackId = trackData.id;
|
||||
|
||||
audioTrackLane.innerHTML = `
|
||||
<div class="track-info">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<div class="track-solo-btn"></div>
|
||||
<span class="track-name">${trackData.name}</span>
|
||||
</div>
|
||||
<div class="track-controls">
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="volume" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div>
|
||||
<span>VOL</span>
|
||||
</div>
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="pan" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div>
|
||||
<span>PAN</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spectrogram-view-wrapper">
|
||||
<div class="spectrogram-view-grid">
|
||||
<div class="playhead"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const grid = audioTrackLane.querySelector('.spectrogram-view-grid');
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.className = 'waveform-canvas';
|
||||
canvas.height = 60;
|
||||
grid.prepend(canvas);
|
||||
|
||||
audioTrackContainer.appendChild(audioTrackLane);
|
||||
|
||||
if (trackData.audioBuffer) {
|
||||
const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
|
||||
const sampleDuration = trackData.audioBuffer.duration;
|
||||
|
||||
const stepsPerSecond = (bpm / 60) * 4;
|
||||
const totalSteps = sampleDuration * stepsPerSecond;
|
||||
|
||||
const canvasWidth = totalSteps * PIXELS_PER_STEP;
|
||||
canvas.width = canvasWidth;
|
||||
|
||||
drawWaveform(canvas, trackData.audioBuffer, 'var(--accent-green)');
|
||||
|
||||
const numberOfBars = Math.ceil(canvasWidth / PIXELS_PER_BAR);
|
||||
for (let i = 0; i < numberOfBars; i++) {
|
||||
if (i === 0) continue;
|
||||
const marker = document.createElement('div');
|
||||
marker.className = 'bar-marker';
|
||||
marker.textContent = i + 1;
|
||||
marker.style.left = `${i * PIXELS_PER_BAR}px`;
|
||||
grid.appendChild(marker);
|
||||
}
|
||||
}
|
||||
|
||||
const soloButton = audioTrackLane.querySelector('.track-solo-btn');
|
||||
if (soloButton) {
|
||||
if (trackData.isSoloed) {
|
||||
soloButton.classList.add('active');
|
||||
}
|
||||
soloButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleAudioTrackSolo(trackData.id);
|
||||
});
|
||||
}
|
||||
|
||||
const volumeKnob = audioTrackLane.querySelector('.knob[data-control="volume"]');
|
||||
addKnobInteraction(volumeKnob);
|
||||
updateKnobVisual(volumeKnob, "volume");
|
||||
|
||||
const panKnob = audioTrackLane.querySelector('.knob[data-control="pan"]');
|
||||
addKnobInteraction(panKnob);
|
||||
updateKnobVisual(panKnob, "pan");
|
||||
|
||||
const waveformWrapper = audioTrackLane.querySelector('.spectrogram-view-wrapper');
|
||||
const handleSeek = (event) => {
|
||||
const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
|
||||
const stepsPerSecond = (bpm / 60) * 4;
|
||||
const pixelsPerSecond = stepsPerSecond * PIXELS_PER_STEP;
|
||||
|
||||
const rect = waveformWrapper.getBoundingClientRect();
|
||||
const clickX = event.clientX - rect.left;
|
||||
const scrollLeft = waveformWrapper.scrollLeft;
|
||||
const absoluteX = clickX + scrollLeft;
|
||||
|
||||
const newTime = absoluteX / pixelsPerSecond;
|
||||
|
||||
seekAudioEditor(newTime);
|
||||
};
|
||||
|
||||
waveformWrapper.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
handleSeek(e);
|
||||
const onMouseMove = (moveEvent) => handleSeek(moveEvent);
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function renderApp() {
|
||||
const trackContainer = document.getElementById("track-container");
|
||||
trackContainer.innerHTML = "";
|
||||
|
||||
appState.tracks.forEach((trackData) => {
|
||||
const trackLane = document.createElement("div");
|
||||
trackLane.className = "track-lane";
|
||||
trackLane.dataset.trackId = trackData.id;
|
||||
|
||||
if (trackData.id === appState.activeTrackId) {
|
||||
trackLane.classList.add('active-track');
|
||||
}
|
||||
|
||||
trackLane.innerHTML = `
|
||||
<div class="track-info">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<div class="track-mute"></div>
|
||||
<span class="track-name">${trackData.name}</span>
|
||||
</div>
|
||||
<div class="track-controls">
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="volume" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div>
|
||||
<span>VOL</span>
|
||||
</div>
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="pan" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div>
|
||||
<span>PAN</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-sequencer-wrapper"></div>
|
||||
`;
|
||||
|
||||
trackLane.addEventListener('click', () => {
|
||||
if (appState.activeTrackId === trackData.id) return;
|
||||
stopPlayback();
|
||||
appState.activeTrackId = trackData.id;
|
||||
document.querySelectorAll('.track-lane').forEach(lane => lane.classList.remove('active-track'));
|
||||
trackLane.classList.add('active-track');
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
});
|
||||
|
||||
trackLane.addEventListener("dragover", (e) => { e.preventDefault(); trackLane.classList.add("drag-over"); });
|
||||
trackLane.addEventListener("dragleave", () => trackLane.classList.remove("drag-over"));
|
||||
trackLane.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
trackLane.classList.remove("drag-over");
|
||||
const filePath = e.dataTransfer.getData("text/plain");
|
||||
if (filePath) {
|
||||
updateTrackSample(trackData.id, filePath);
|
||||
}
|
||||
});
|
||||
|
||||
trackContainer.appendChild(trackLane);
|
||||
const volumeKnob = trackLane.querySelector(".knob[data-control='volume']");
|
||||
addKnobInteraction(volumeKnob);
|
||||
updateKnobVisual(volumeKnob, "volume");
|
||||
const panKnob = trackLane.querySelector(".knob[data-control='pan']");
|
||||
addKnobInteraction(panKnob);
|
||||
updateKnobVisual(panKnob, "pan");
|
||||
});
|
||||
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
renderAudioEditor();
|
||||
}
|
||||
|
||||
export function redrawSequencer() {
|
||||
const totalGridSteps = getTotalSteps();
|
||||
document.querySelectorAll(".step-sequencer-wrapper").forEach((wrapper) => {
|
||||
let sequencerContainer = wrapper.querySelector(".step-sequencer");
|
||||
if (!sequencerContainer) {
|
||||
sequencerContainer = document.createElement("div");
|
||||
sequencerContainer.className = "step-sequencer";
|
||||
wrapper.appendChild(sequencerContainer);
|
||||
}
|
||||
|
||||
const parentTrackElement = wrapper.closest(".track-lane");
|
||||
const trackId = parentTrackElement.dataset.trackId;
|
||||
const trackData = appState.tracks.find((t) => t.id == trackId);
|
||||
|
||||
if (!trackData || !trackData.patterns || trackData.patterns.length === 0) {
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
|
||||
const activePattern = trackData.patterns[appState.activePatternIndex];
|
||||
if (!activePattern) {
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
const patternSteps = activePattern.steps;
|
||||
|
||||
sequencerContainer.innerHTML = "";
|
||||
for (let i = 0; i < totalGridSteps; i++) {
|
||||
const stepWrapper = document.createElement("div");
|
||||
stepWrapper.className = "step-wrapper";
|
||||
const stepElement = document.createElement("div");
|
||||
stepElement.className = "step";
|
||||
|
||||
if (patternSteps[i] === true) {
|
||||
stepElement.classList.add("active");
|
||||
}
|
||||
|
||||
stepElement.addEventListener("click", () => {
|
||||
toggleStepState(trackData.id, i);
|
||||
stepElement.classList.toggle("active");
|
||||
if (trackData && trackData.samplePath) {
|
||||
playSample(trackData.samplePath, trackData.id);
|
||||
}
|
||||
});
|
||||
|
||||
const beatsPerBar = parseInt(document.getElementById("compasso-a-input").value, 10) || 4;
|
||||
const groupIndex = Math.floor(i / beatsPerBar);
|
||||
if (groupIndex % 2 === 0) {
|
||||
stepElement.classList.add("step-dark");
|
||||
}
|
||||
|
||||
const stepsPerBar = 16;
|
||||
if (i > 0 && i % stepsPerBar === 0) {
|
||||
const marker = document.createElement("div");
|
||||
marker.className = "step-marker";
|
||||
marker.textContent = Math.floor(i / stepsPerBar) + 1;
|
||||
stepWrapper.appendChild(marker);
|
||||
}
|
||||
|
||||
stepWrapper.appendChild(stepElement);
|
||||
sequencerContainer.appendChild(stepWrapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addKnobInteraction(knobElement) {
|
||||
const controlType = knobElement.dataset.control;
|
||||
knobElement.addEventListener("mousedown", (e) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const defaultValue = controlType === "volume" ? 0.8 : 0.0;
|
||||
if (controlType === "volume") {
|
||||
updateTrackVolume(trackId, defaultValue);
|
||||
} else {
|
||||
updateTrackPan(trackId, defaultValue);
|
||||
}
|
||||
updateKnobVisual(knobElement, controlType);
|
||||
}
|
||||
});
|
||||
knobElement.addEventListener("mousedown", (e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const track = appState.tracks.find((t) => t.id == trackId) || appState.audioTracks.find((t) => t.id == trackId);
|
||||
if (!track) return;
|
||||
const startY = e.clientY;
|
||||
const startValue = controlType === "volume" ? track.volume : track.pan;
|
||||
document.body.classList.add("knob-dragging");
|
||||
function onMouseMove(moveEvent) {
|
||||
const deltaY = startY - moveEvent.clientY;
|
||||
const sensitivity = controlType === "volume" ? 150 : 200;
|
||||
const newValue = startValue + deltaY / sensitivity;
|
||||
if (controlType === "volume") {
|
||||
updateTrackVolume(trackId, newValue);
|
||||
} else {
|
||||
updateTrackPan(trackId, newValue);
|
||||
}
|
||||
updateKnobVisual(knobElement, controlType);
|
||||
}
|
||||
function onMouseUp() {
|
||||
document.body.classList.remove("knob-dragging");
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
}
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
});
|
||||
knobElement.addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const track = appState.tracks.find((t) => t.id == trackId) || appState.audioTracks.find((t) => t.id == trackId);
|
||||
if (!track) return;
|
||||
const step = 0.05;
|
||||
const direction = e.deltaY < 0 ? 1 : -1;
|
||||
if (controlType === "volume") {
|
||||
const newValue = track.volume + direction * step;
|
||||
updateTrackVolume(trackId, newValue);
|
||||
} else {
|
||||
const newValue = track.pan + direction * step;
|
||||
updateTrackPan(trackId, newValue);
|
||||
}
|
||||
updateKnobVisual(knobElement, controlType);
|
||||
});
|
||||
}
|
||||
|
||||
function updateKnobVisual(knobElement, controlType) {
|
||||
const trackId = knobElement.dataset.trackId;
|
||||
const track = appState.tracks.find((t) => t.id == trackId) || appState.audioTracks.find((t) => t.id == trackId);
|
||||
if (!track) return;
|
||||
const indicator = knobElement.querySelector(".knob-indicator");
|
||||
if (!indicator) return;
|
||||
const minAngle = -135;
|
||||
const maxAngle = 135;
|
||||
let percentage = 0.5;
|
||||
let title = "";
|
||||
if (controlType === "volume") {
|
||||
const value = track.volume;
|
||||
const clampedValue = Math.max(0, Math.min(1.5, value));
|
||||
percentage = clampedValue / 1.5;
|
||||
title = `Volume: ${Math.round(clampedValue * 100)}%`;
|
||||
} else {
|
||||
const value = track.pan;
|
||||
const clampedValue = Math.max(-1, Math.min(1, value));
|
||||
percentage = (clampedValue + 1) / 2;
|
||||
const panDisplay = Math.round(clampedValue * 100);
|
||||
title = `Pan: ${
|
||||
panDisplay === 0
|
||||
? "Centro"
|
||||
: panDisplay < 0
|
||||
? `${-panDisplay} L`
|
||||
: `${panDisplay} R`
|
||||
}`;
|
||||
}
|
||||
const angle = minAngle + percentage * (maxAngle - minAngle);
|
||||
indicator.style.transform = `translateX(-50%) rotate(${angle}deg)`;
|
||||
knobElement.title = title;
|
||||
}
|
||||
|
||||
export function highlightStep(stepIndex, isActive) {
|
||||
if (stepIndex < 0) return;
|
||||
document.querySelectorAll(".track-lane").forEach((track) => {
|
||||
const stepWrapper = track.querySelector(
|
||||
`.step-sequencer .step-wrapper:nth-child(${stepIndex + 1})`
|
||||
);
|
||||
if (stepWrapper) {
|
||||
const stepElement = stepWrapper.querySelector(".step");
|
||||
if (stepElement) {
|
||||
stepElement.classList.toggle("playing", isActive);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
if (!response.ok) throw new Error("Arquivo samples-manifest.json não encontrado.");
|
||||
const fileTree = await response.json();
|
||||
|
||||
samplePathMap = {};
|
||||
@@ -511,42 +51,65 @@ export async function loadAndRenderSampleBrowser() {
|
||||
}
|
||||
}
|
||||
|
||||
// Em ui.js, substitua a função antiga por esta:
|
||||
|
||||
function renderFileTree(tree, parentElement, currentPath) {
|
||||
parentElement.innerHTML = "";
|
||||
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);
|
||||
return aIsFile ? 1 : -1;
|
||||
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;
|
||||
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) {
|
||||
li.innerHTML = `<i class="fa-solid fa-file-audio"></i> ${key}`;
|
||||
// --- 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 {
|
||||
li.className = "directory";
|
||||
li.innerHTML = `<i class="fa-solid fa-folder"></i> ${key}`;
|
||||
// --- 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);
|
||||
li.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
li.classList.toggle("open");
|
||||
});
|
||||
|
||||
// CORREÇÃO 3: Remove o addEventListener de clique daqui. O main.js já cuida disso.
|
||||
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
@@ -560,14 +123,12 @@ export async function showOpenProjectModal() {
|
||||
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.");
|
||||
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>';
|
||||
serverProjectsList.innerHTML = '<p style="color:var(--text-dark);">Nenhum projeto encontrado no servidor.</p>';
|
||||
}
|
||||
|
||||
projects.forEach((projectName) => {
|
||||
@@ -591,16 +152,4 @@ export async function showOpenProjectModal() {
|
||||
export function closeOpenProjectModal() {
|
||||
const openProjectModal = document.getElementById("open-project-modal");
|
||||
openProjectModal.classList.remove("visible");
|
||||
}
|
||||
|
||||
export function updatePlayheadVisual(pixels) {
|
||||
document.querySelectorAll('.audio-track-lane .playhead').forEach(ph => {
|
||||
ph.style.left = `${pixels}px`;
|
||||
});
|
||||
}
|
||||
|
||||
export function resetPlayheadVisual() {
|
||||
document.querySelectorAll('.audio-track-lane .playhead').forEach(ph => {
|
||||
ph.style.left = '0px';
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user