PDF: visual page thumbnails for delete/rotate/reorder

- Replace text inputs with visual page grid (thumbnails)
- Click to select pages for delete/rotate
- Drag to reorder pages (visual)
- Thumbnails loaded lazily from /pdf/preview/:id/:page
- Selected pages highlighted with blue border + checkmark
- Grid auto-fills, max-height with scroll
This commit is contained in:
treamz 2026-03-22 01:24:44 +03:00
parent 4bd90b26bc
commit f03262fbec

View File

@ -193,6 +193,18 @@
justify-content: center;
transition: color .15s, background .15s;
}
/* Page thumbnails grid */
.page-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(80px,1fr)); gap:8px; margin-top:10px; max-height:400px; overflow-y:auto; }
.page-thumb { position:relative; border:2px solid var(--surface-600); border-radius:8px; overflow:hidden; cursor:pointer; transition:all .15s; aspect-ratio:3/4; background:var(--surface-700); }
.page-thumb:hover { border-color:var(--text-muted); }
.page-thumb.selected { border-color:var(--accent); box-shadow:0 0 0 2px rgba(0,84,230,.3); }
.page-thumb img { width:100%; height:100%; object-fit:cover; }
.page-thumb-num { position:absolute; bottom:0; left:0; right:0; background:rgba(0,0,0,.6); color:#fff; font-size:10px; text-align:center; padding:2px 0; font-family:'JetBrains Mono',monospace; }
.page-thumb-check { position:absolute; top:4px; right:4px; width:18px; height:18px; border-radius:50%; border:2px solid rgba(255,255,255,.5); background:rgba(0,0,0,.3); display:none; align-items:center; justify-content:center; }
.page-thumb.selected .page-thumb-check { display:flex; background:var(--accent); border-color:var(--accent); }
.page-thumb-check svg { width:12px; height:12px; }
.page-grid-hint { font-size:11px; color:var(--text-muted); margin-top:6px; font-family:'JetBrains Mono',monospace; }
.file-card-remove:hover {
color: #ff5252;
background: rgba(255,82,82,.1);
@ -715,27 +727,27 @@
<button class="angle-btn" data-angle="180">180°</button>
<button class="angle-btn" data-angle="270">270° (90°)</button>
</div>
<span class="option-label" style="margin-top:8px;">Страницы</span>
<input type="text" id="rotatePages" class="wa-input" value="all" placeholder="all или 1,3,5" style="width:100%;box-sizing:border-box;" />
<div class="hint-note">Введите <code style="color:var(--accent)">all</code> для всех страниц или номера через запятую.</div>
<span class="option-label" style="margin-top:8px;">Страницы (нажмите для выбора)</span>
<div class="page-grid" id="rotatePageGrid"></div>
<div class="page-grid-hint" id="rotateHint">Загрузите файл для просмотра страниц</div>
</div>
</div>
<!-- Delete sub-panel -->
<div id="subpanel-delete" class="sub-panel wa-card">
<div class="option-group">
<span class="option-label">Номера страниц для удаления</span>
<input type="text" id="deletePages" class="wa-input" placeholder="Например: 2, 4, 6" style="width:100%;box-sizing:border-box;" />
<div class="hint-note">Страницы нумеруются с 1. Разделяйте номера запятыми.</div>
<span class="option-label">Выберите страницы для удаления</span>
<div class="page-grid" id="deletePageGrid"></div>
<div class="page-grid-hint" id="deleteHint">Загрузите файл для просмотра страниц</div>
</div>
</div>
<!-- Reorder sub-panel -->
<div id="subpanel-reorder" class="sub-panel wa-card">
<div class="option-group">
<span class="option-label">Новый порядок страниц</span>
<input type="text" id="reorderPages" class="wa-input" placeholder="Например: 3,1,2,4" style="width:100%;box-sizing:border-box;" />
<div class="hint-note">Перечислите все номера страниц в новом порядке через запятую.</div>
<span class="option-label">Перетаскивайте страницы для изменения порядка</span>
<div class="page-grid" id="reorderPageGrid"></div>
<div class="page-grid-hint" id="reorderHint">Загрузите файл для просмотра страниц</div>
</div>
</div>
</div>
@ -1646,6 +1658,52 @@
state.securitySub = 'protect';
})();
// ─── Page Grid Thumbnails ─────────────────────────────────────────────────
function renderPageGrid(gridId, hintId, fileId, pages, mode) {
const grid = document.getElementById(gridId);
const hint = document.getElementById(hintId);
if (!grid) return;
if (!fileId || pages < 1) {
grid.innerHTML = '';
if (hint) hint.textContent = 'Выберите файл';
return;
}
if (hint) hint.textContent = mode === 'delete' ? 'Нажмите на страницы для выбора' : mode === 'rotate' ? 'Нажмите для выбора страниц' : 'Перетаскивайте для изменения порядка';
grid.innerHTML = '';
for (let i = 1; i <= pages; i++) {
const thumb = document.createElement('div');
thumb.className = 'page-thumb';
thumb.dataset.page = i;
thumb.innerHTML = `<img src="/pdf/preview/${fileId}/${i}" alt="Page ${i}" loading="lazy"><div class="page-thumb-num">${i}</div><div class="page-thumb-check"><svg fill="none" stroke="#fff" viewBox="0 0 24 24" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg></div>`;
if (mode === 'delete' || mode === 'rotate') {
thumb.addEventListener('click', () => {
thumb.classList.toggle('selected');
});
}
grid.appendChild(thumb);
}
}
function getSelectedPages(gridId) {
const grid = document.getElementById(gridId);
if (!grid) return [];
return Array.from(grid.querySelectorAll('.page-thumb.selected')).map(t => parseInt(t.dataset.page));
}
// Re-render grids when file selection changes
const origRenderSingle = renderSingleFileSelectors;
renderSingleFileSelectors = function() {
origRenderSingle();
const sel = state.files.find(f => f.id === state.selectedFileId);
if (sel) {
renderPageGrid('deletePageGrid', 'deleteHint', sel.id, sel.pages, 'delete');
renderPageGrid('rotatePageGrid', 'rotateHint', sel.id, sel.pages, 'rotate');
renderPageGrid('reorderPageGrid', 'reorderHint', sel.id, sel.pages, 'reorder');
}
};
</script>
</body>