From 93952d084406dcd5810d04ea1c589a6b30a61454 Mon Sep 17 00:00:00 2001 From: treamz Date: Sun, 22 Mar 2026 01:46:48 +0300 Subject: [PATCH] PDF UX: auto-reorder on drag, visual delete animation, remove apply button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder: drag-drop auto-saves after 800ms, no button needed - Delete: selected pages fade out + shrink on delete click - Delete button shows Удаление... state - Reorder hint shows Сохранение.../Порядок сохранён - Better drop positioning (left/right of target) --- public/pdf.html | 58 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/public/pdf.html b/public/pdf.html index 8d04eb8..8e7259a 100644 --- a/public/pdf.html +++ b/public/pdf.html @@ -769,8 +769,7 @@ Перетаскивайте страницы мышкой для нового порядка
- - + Перетащите и отпустите — порядок сохранится автоматически
@@ -1746,19 +1745,20 @@ const fromPage = e.dataTransfer.getData('text/plain'); const fromEl = grid.querySelector('[data-page="' + fromPage + '"]'); if (fromEl && fromEl !== thumb) { - // Swap positions in DOM - const parent = thumb.parentNode; - const thumbNext = thumb.nextSibling; - if (fromEl.nextSibling === thumb) { - parent.insertBefore(thumb, fromEl); + // Insert dragged element before or after drop target + const rect = thumb.getBoundingClientRect(); + const midX = rect.left + rect.width / 2; + if (e.clientX < midX) { + grid.insertBefore(fromEl, thumb); } else { - parent.insertBefore(fromEl, thumb); - parent.insertBefore(thumb, thumbNext); + grid.insertBefore(fromEl, thumb.nextSibling); } - // Update visual numbers + // Update visual order numbers Array.from(grid.children).forEach((el, idx) => { el.querySelector('.page-thumb-num').textContent = idx + 1; }); + // Auto-save reorder + autoSaveReorder(); } }); } @@ -1798,12 +1798,25 @@ const fileId = state.selectedFileId; if (!fileId) { showError('Выберите файл'); return; } clearError(); + // Hide deleted pages visually + const grid = document.getElementById('deletePageGrid'); + if (grid) { + grid.querySelectorAll('.page-thumb.selected').forEach(el => { + el.style.transition = 'opacity .3s, transform .3s'; + el.style.opacity = '0'; + el.style.transform = 'scale(0.8)'; + setTimeout(() => el.remove(), 300); + }); + } + document.getElementById('btnDeleteSelected').disabled = true; + document.getElementById('btnDeleteSelected').textContent = 'Удаление...'; try { const res = await fetch('/pdf/delete', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ fileId, pages: pages.join(',') }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error); showResult(data); } catch(e) { showError(e.message); } + document.getElementById('btnDeleteSelected').textContent = 'Удалить выбранные (0)'; }; window.executeRotatePages = async function() { @@ -1847,6 +1860,31 @@ } } + + // Auto-save reorder on drop + let _reorderTimeout = null; + function autoSaveReorder() { + clearTimeout(_reorderTimeout); + const hint = document.getElementById('reorderHint'); + if (hint) hint.textContent = 'Сохранение...'; + _reorderTimeout = setTimeout(async () => { + const grid = document.getElementById('reorderPageGrid'); + if (!grid) return; + const order = Array.from(grid.querySelectorAll('.page-thumb')).map(t => parseInt(t.dataset.page)); + const fileId = state.selectedFileId; + if (!fileId) return; + try { + const res = await fetch('/pdf/reorder', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ fileId, order }) }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error); + if (hint) hint.textContent = 'Порядок сохранён'; + showResult(data); + } catch(e) { + if (hint) hint.textContent = 'Ошибка: ' + e.message; + } + }, 800); + } + })();