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:
parent
4bd90b26bc
commit
f03262fbec
@ -193,6 +193,18 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: color .15s, background .15s;
|
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 {
|
.file-card-remove:hover {
|
||||||
color: #ff5252;
|
color: #ff5252;
|
||||||
background: rgba(255,82,82,.1);
|
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="180">180°</button>
|
||||||
<button class="angle-btn" data-angle="270">270° (−90°)</button>
|
<button class="angle-btn" data-angle="270">270° (−90°)</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="option-label" style="margin-top:8px;">Страницы</span>
|
<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="page-grid" id="rotatePageGrid"></div>
|
||||||
<div class="hint-note">Введите <code style="color:var(--accent)">all</code> для всех страниц или номера через запятую.</div>
|
<div class="page-grid-hint" id="rotateHint">Загрузите файл для просмотра страниц</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Delete sub-panel -->
|
<!-- Delete sub-panel -->
|
||||||
<div id="subpanel-delete" class="sub-panel wa-card">
|
<div id="subpanel-delete" class="sub-panel wa-card">
|
||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<span class="option-label">Номера страниц для удаления</span>
|
<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="page-grid" id="deletePageGrid"></div>
|
||||||
<div class="hint-note">Страницы нумеруются с 1. Разделяйте номера запятыми.</div>
|
<div class="page-grid-hint" id="deleteHint">Загрузите файл для просмотра страниц</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Reorder sub-panel -->
|
<!-- Reorder sub-panel -->
|
||||||
<div id="subpanel-reorder" class="sub-panel wa-card">
|
<div id="subpanel-reorder" class="sub-panel wa-card">
|
||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<span class="option-label">Новый порядок страниц</span>
|
<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="page-grid" id="reorderPageGrid"></div>
|
||||||
<div class="hint-note">Перечислите все номера страниц в новом порядке через запятую.</div>
|
<div class="page-grid-hint" id="reorderHint">Загрузите файл для просмотра страниц</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1646,6 +1658,52 @@
|
|||||||
state.securitySub = 'protect';
|
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>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user