manipulando patterns na playlist
Deploy / Deploy (push) Successful in 2m2s
Details
Deploy / Deploy (push) Successful in 2m2s
Details
This commit is contained in:
parent
f7c81dd1de
commit
8b811aee07
|
|
@ -188,6 +188,12 @@ export function renderAudioEditor() {
|
||||||
|
|
||||||
if (!audioEditor || !existingTrackContainer) return;
|
if (!audioEditor || !existingTrackContainer) return;
|
||||||
|
|
||||||
|
// ✅ salva scroll atual para não “pular” pro início após render
|
||||||
|
const prevScrollLeft =
|
||||||
|
(existingTrackContainer.scrollLeft ?? 0) || (appState.audio.editorScrollLeft ?? 0);
|
||||||
|
const prevScrollTop =
|
||||||
|
(existingTrackContainer.scrollTop ?? 0) || (appState.audio.editorScrollTop ?? 0);
|
||||||
|
|
||||||
_ensureGlobalPlaylistSelectionFields();
|
_ensureGlobalPlaylistSelectionFields();
|
||||||
_installPlaylistKeybindOnce();
|
_installPlaylistKeybindOnce();
|
||||||
|
|
||||||
|
|
@ -427,6 +433,9 @@ export function renderAudioEditor() {
|
||||||
tracksParent.replaceChild(newTrackContainer, existingTrackContainer);
|
tracksParent.replaceChild(newTrackContainer, existingTrackContainer);
|
||||||
// ✅ único scroller horizontal/vertical do editor
|
// ✅ único scroller horizontal/vertical do editor
|
||||||
const scrollEl = newTrackContainer; // #audio-track-container
|
const scrollEl = newTrackContainer; // #audio-track-container
|
||||||
|
// ✅ restaura scroll imediatamente após recriar o container
|
||||||
|
newTrackContainer.scrollLeft = prevScrollLeft;
|
||||||
|
newTrackContainer.scrollTop = prevScrollTop;
|
||||||
|
|
||||||
// === RENDERIZAÇÃO DAS PISTAS (LANES) ===
|
// === RENDERIZAÇÃO DAS PISTAS (LANES) ===
|
||||||
|
|
||||||
|
|
@ -604,6 +613,19 @@ export function renderAudioEditor() {
|
||||||
clipDiv.appendChild(leftHandle);
|
clipDiv.appendChild(leftHandle);
|
||||||
clipDiv.appendChild(rightHandle);
|
clipDiv.appendChild(rightHandle);
|
||||||
|
|
||||||
|
// ✅ garante área clicável dos handles mesmo sem CSS
|
||||||
|
[leftHandle, rightHandle].forEach((h) => {
|
||||||
|
h.style.position = "absolute";
|
||||||
|
h.style.top = "0";
|
||||||
|
h.style.bottom = "0";
|
||||||
|
h.style.width = "8px";
|
||||||
|
h.style.cursor = "ew-resize";
|
||||||
|
h.style.zIndex = "8";
|
||||||
|
h.style.background = "rgba(255,255,255,0.10)";
|
||||||
|
});
|
||||||
|
leftHandle.style.left = "0";
|
||||||
|
rightHandle.style.right = "0";
|
||||||
|
|
||||||
// ✅ overlay de “marquinhas pretas” (loop interno da pattern)
|
// ✅ overlay de “marquinhas pretas” (loop interno da pattern)
|
||||||
const loopSteps = getLoopStepsForBasslineLane(trackData);
|
const loopSteps = getLoopStepsForBasslineLane(trackData);
|
||||||
const loopPx = loopSteps * stepWidthPx;
|
const loopPx = loopSteps * stepWidthPx;
|
||||||
|
|
@ -777,6 +799,10 @@ export function renderAudioEditor() {
|
||||||
newTrackContainer.addEventListener("scroll", () => {
|
newTrackContainer.addEventListener("scroll", () => {
|
||||||
const scrollPos = scrollEl.scrollLeft;
|
const scrollPos = scrollEl.scrollLeft;
|
||||||
|
|
||||||
|
// ✅ persiste scroll para sobreviver a qualquer render
|
||||||
|
appState.audio.editorScrollLeft = scrollPos;
|
||||||
|
appState.audio.editorScrollTop = scrollEl.scrollTop || 0;
|
||||||
|
|
||||||
// sincroniza régua com o container
|
// sincroniza régua com o container
|
||||||
const mainRuler = tracksParent.querySelector(".timeline-ruler");
|
const mainRuler = tracksParent.querySelector(".timeline-ruler");
|
||||||
if (mainRuler && mainRuler.scrollLeft !== scrollPos) {
|
if (mainRuler && mainRuler.scrollLeft !== scrollPos) {
|
||||||
|
|
@ -817,6 +843,7 @@ export function renderAudioEditor() {
|
||||||
|
|
||||||
const currentPixelsPerSecond = getPixelsPerSecond();
|
const currentPixelsPerSecond = getPixelsPerSecond();
|
||||||
const handle = e.target.closest(".clip-resize-handle");
|
const handle = e.target.closest(".clip-resize-handle");
|
||||||
|
const patternHandle = e.target.closest(".pattern-resize-handle");
|
||||||
|
|
||||||
// Slice Tool
|
// Slice Tool
|
||||||
if (appState.global.sliceToolActive && clipElement && !clipElement.classList.contains("bassline-clip")) {
|
if (appState.global.sliceToolActive && clipElement && !clipElement.classList.contains("bassline-clip")) {
|
||||||
|
|
@ -843,6 +870,119 @@ export function renderAudioEditor() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// ✅ PATTERN (bassline) drag horizontal + resize L/R
|
||||||
|
// (sem perder duplo clique)
|
||||||
|
// =========================================================
|
||||||
|
if (clipElement && clipElement.classList.contains("bassline-clip") && e.button === 0) {
|
||||||
|
// ✅ não inicia drag/resize no 2º clique do duplo clique
|
||||||
|
if (e.detail && e.detail > 1) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const patternIndex = Number(clipElement.dataset.patternIndex ?? 0);
|
||||||
|
const plClipId = String(clipElement.dataset.plClipId || "");
|
||||||
|
if (!plClipId) return;
|
||||||
|
|
||||||
|
_ensureGlobalPlaylistSelectionFields();
|
||||||
|
|
||||||
|
// seleção
|
||||||
|
appState.global.selectedPlaylistClipId = plClipId;
|
||||||
|
appState.global.selectedPlaylistPatternIndex = patternIndex;
|
||||||
|
|
||||||
|
newTrackContainer
|
||||||
|
.querySelectorAll(".bassline-clip.selected")
|
||||||
|
.forEach((c) => c.classList.remove("selected"));
|
||||||
|
clipElement.classList.add("selected");
|
||||||
|
|
||||||
|
const model = _getPlaylistClipModel(patternIndex, plClipId);
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
const initialMouseX = e.clientX;
|
||||||
|
const initialScrollLeft = scrollEl.scrollLeft;
|
||||||
|
|
||||||
|
const initialPosTicks = Number(model.pos || 0);
|
||||||
|
const initialLenTicks = Math.max(PL_MIN_LEN_TICKS, Number(model.len || 192));
|
||||||
|
const initialEndTicks = initialPosTicks + initialLenTicks;
|
||||||
|
|
||||||
|
const preview = (posTicks, lenTicks) => {
|
||||||
|
const leftPx = _ticksToPx(posTicks, stepWidthPx);
|
||||||
|
const widthPx = Math.max(1, _ticksToPx(lenTicks, stepWidthPx));
|
||||||
|
clipElement.style.left = `${leftPx}px`;
|
||||||
|
clipElement.style.width = `${widthPx}px`;
|
||||||
|
clipElement.style.backgroundPosition = `-${leftPx}px 0px`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleType = patternHandle
|
||||||
|
? (patternHandle.classList.contains("left") ? "left" : "right")
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const onMouseMove = (moveEvent) => {
|
||||||
|
const deltaX =
|
||||||
|
(moveEvent.clientX - initialMouseX) +
|
||||||
|
(scrollEl.scrollLeft - initialScrollLeft);
|
||||||
|
|
||||||
|
const deltaTicks = _pxToTicks(deltaX, stepWidthPx);
|
||||||
|
|
||||||
|
// drag
|
||||||
|
if (!handleType) {
|
||||||
|
let newPos = _snapTicks(initialPosTicks + deltaTicks, PL_SNAP_TICKS);
|
||||||
|
newPos = Math.max(0, newPos);
|
||||||
|
preview(newPos, initialLenTicks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// resize right
|
||||||
|
if (handleType === "right") {
|
||||||
|
let newEnd = _snapTicks(initialEndTicks + deltaTicks, PL_SNAP_TICKS);
|
||||||
|
newEnd = Math.max(initialPosTicks + PL_MIN_LEN_TICKS, newEnd);
|
||||||
|
preview(initialPosTicks, newEnd - initialPosTicks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// resize left
|
||||||
|
let newPos = _snapTicks(initialPosTicks + deltaTicks, PL_SNAP_TICKS);
|
||||||
|
newPos = Math.max(0, newPos);
|
||||||
|
newPos = Math.min(newPos, initialEndTicks - PL_MIN_LEN_TICKS);
|
||||||
|
preview(newPos, initialEndTicks - newPos);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = () => {
|
||||||
|
document.removeEventListener("mousemove", onMouseMove);
|
||||||
|
document.removeEventListener("mouseup", onMouseUp);
|
||||||
|
|
||||||
|
const finalLeftPx = clipElement.offsetLeft;
|
||||||
|
const finalWidthPx = clipElement.offsetWidth;
|
||||||
|
|
||||||
|
let posTicks = _snapTicks(_pxToTicks(finalLeftPx, stepWidthPx), PL_SNAP_TICKS);
|
||||||
|
let lenTicks = _snapTicks(_pxToTicks(finalWidthPx, stepWidthPx), PL_SNAP_TICKS);
|
||||||
|
|
||||||
|
posTicks = Math.max(0, posTicks);
|
||||||
|
lenTicks = Math.max(PL_MIN_LEN_TICKS, lenTicks);
|
||||||
|
|
||||||
|
_updatePlaylistClipLocal(patternIndex, plClipId, { pos: posTicks, len: lenTicks });
|
||||||
|
|
||||||
|
sendActionSafe({
|
||||||
|
type: "UPDATE_PLAYLIST_PATTERN_CLIP",
|
||||||
|
patternIndex,
|
||||||
|
clipId: plClipId,
|
||||||
|
pos: posTicks,
|
||||||
|
len: lenTicks,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ mantém scroll onde você estava antes do render
|
||||||
|
appState.audio.editorScrollLeft = scrollEl.scrollLeft;
|
||||||
|
appState.audio.editorScrollTop = scrollEl.scrollTop || 0;
|
||||||
|
|
||||||
|
renderAudioEditor();
|
||||||
|
restartAudioEditorIfPlaying();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", onMouseMove);
|
||||||
|
document.addEventListener("mouseup", onMouseUp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Resize Handle
|
// Resize Handle
|
||||||
if (handle) {
|
if (handle) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -1097,6 +1237,17 @@ export function renderAudioEditor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ garante scroll mesmo após montar tudo (importante em DOM grande)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
try {
|
||||||
|
newTrackContainer.scrollLeft = prevScrollLeft;
|
||||||
|
newTrackContainer.scrollTop = prevScrollTop;
|
||||||
|
|
||||||
|
const mainRuler = tracksParent.querySelector(".timeline-ruler");
|
||||||
|
if (mainRuler) mainRuler.scrollLeft = prevScrollLeft;
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateAudioEditorUI() {
|
export function updateAudioEditorUI() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue