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
|
|
@ -187,6 +187,12 @@ export function renderAudioEditor() {
|
|||
const existingTrackContainer = document.getElementById("audio-track-container");
|
||||
|
||||
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();
|
||||
_installPlaylistKeybindOnce();
|
||||
|
|
@ -427,6 +433,9 @@ export function renderAudioEditor() {
|
|||
tracksParent.replaceChild(newTrackContainer, existingTrackContainer);
|
||||
// ✅ único scroller horizontal/vertical do editor
|
||||
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) ===
|
||||
|
||||
|
|
@ -604,6 +613,19 @@ export function renderAudioEditor() {
|
|||
clipDiv.appendChild(leftHandle);
|
||||
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)
|
||||
const loopSteps = getLoopStepsForBasslineLane(trackData);
|
||||
const loopPx = loopSteps * stepWidthPx;
|
||||
|
|
@ -777,6 +799,10 @@ export function renderAudioEditor() {
|
|||
newTrackContainer.addEventListener("scroll", () => {
|
||||
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
|
||||
const mainRuler = tracksParent.querySelector(".timeline-ruler");
|
||||
if (mainRuler && mainRuler.scrollLeft !== scrollPos) {
|
||||
|
|
@ -817,6 +843,7 @@ export function renderAudioEditor() {
|
|||
|
||||
const currentPixelsPerSecond = getPixelsPerSecond();
|
||||
const handle = e.target.closest(".clip-resize-handle");
|
||||
const patternHandle = e.target.closest(".pattern-resize-handle");
|
||||
|
||||
// Slice Tool
|
||||
if (appState.global.sliceToolActive && clipElement && !clipElement.classList.contains("bassline-clip")) {
|
||||
|
|
@ -843,6 +870,119 @@ export function renderAudioEditor() {
|
|||
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
|
||||
if (handle) {
|
||||
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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue