diff --git a/public/pdf.html b/public/pdf.html index f76241f..10eb724 100644 --- a/public/pdf.html +++ b/public/pdf.html @@ -203,6 +203,21 @@ .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-thumb.rotate-90 img { transform: rotate(90deg); } + .page-thumb.rotate-180 img { transform: rotate(180deg); } + .page-thumb.rotate-270 img { transform: rotate(270deg); } + .page-thumb.dragging { opacity: 0.4; border-color: var(--accent); } + .page-thumb.drag-over { border-color: var(--accent); transform: scale(1.05); } + .page-thumb.marked-delete { border-color: #ef4444; opacity: 0.6; } + .page-thumb.marked-delete .page-thumb-check { display: flex; background: #ef4444; border-color: #ef4444; } + .page-thumb.marked-delete .page-thumb-num { background: rgba(239,68,68,.8); } + .grid-action-bar { display: flex; align-items: center; gap: 8px; margin-top: 10px; } + .grid-action-btn { padding: 8px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; border: none; cursor: pointer; font-family: 'Manrope', sans-serif; transition: all .15s; } + .grid-action-btn.danger { background: rgba(239,68,68,.1); color: #ef4444; border: 1px solid rgba(239,68,68,.3); } + .grid-action-btn.danger:hover { background: rgba(239,68,68,.2); } + .grid-action-btn.primary { background: var(--accent); color: #fff; } + .grid-action-btn.primary:hover { background: var(--accent-dim); } + .grid-action-btn:disabled { opacity: 0.4; cursor: not-allowed; } .page-grid-hint { font-size:11px; color:var(--text-muted); margin-top:6px; font-family:'JetBrains Mono',monospace; } .file-card-remove:hover { @@ -727,27 +742,36 @@ - Страницы (нажмите для выбора) + Нажмите на страницы для поворота
-
Загрузите файл для просмотра страниц
+
+ + +
- Выберите страницы для удаления + Нажмите на страницы для удаления
-
Загрузите файл для просмотра страниц
+
+ + +
- Перетаскивайте страницы для изменения порядка + Перетаскивайте страницы мышкой для нового порядка
-
Загрузите файл для просмотра страниц
+
+ + +
@@ -1676,27 +1700,88 @@ if (!grid) return; if (!fileId || pages < 1) { grid.innerHTML = ''; - if (hint) hint.textContent = 'Выберите файл'; + if (hint) hint.textContent = ''; return; } - if (hint) hint.textContent = mode === 'delete' ? 'Нажмите на страницы для выбора' : mode === 'rotate' ? 'Нажмите для выбора страниц' : 'Перетаскивайте для изменения порядка'; + if (hint) hint.textContent = ''; grid.innerHTML = ''; - for (let i = 1; i <= pages; i++) { + for (let i = 1; i <= Math.min(pages, 100); i++) { const thumb = document.createElement('div'); thumb.className = 'page-thumb'; thumb.dataset.page = i; - thumb.innerHTML = `Page ${i}
${i}
`; + thumb.innerHTML = '' + i + '
' + i + '
'; - if (mode === 'delete' || mode === 'rotate') { + if (mode === 'delete') { thumb.addEventListener('click', () => { thumb.classList.toggle('selected'); + thumb.classList.toggle('marked-delete'); + updateDeleteCount(); + }); + } else if (mode === 'rotate') { + thumb.addEventListener('click', () => { + thumb.classList.toggle('selected'); + // Visual rotation preview + if (thumb.classList.contains('selected')) { + const angle = state.rotateAngle || 90; + thumb.classList.add('rotate-' + angle); + } else { + thumb.classList.remove('rotate-90', 'rotate-180', 'rotate-270'); + } + updateRotateCount(); + }); + } else if (mode === 'reorder') { + // Drag and drop + thumb.draggable = true; + thumb.addEventListener('dragstart', e => { + e.dataTransfer.setData('text/plain', i); + thumb.classList.add('dragging'); + }); + thumb.addEventListener('dragend', () => thumb.classList.remove('dragging')); + thumb.addEventListener('dragover', e => { e.preventDefault(); thumb.classList.add('drag-over'); }); + thumb.addEventListener('dragleave', () => thumb.classList.remove('drag-over')); + thumb.addEventListener('drop', e => { + e.preventDefault(); + thumb.classList.remove('drag-over'); + 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); + } else { + parent.insertBefore(fromEl, thumb); + parent.insertBefore(thumb, thumbNext); + } + // Update visual numbers + Array.from(grid.children).forEach((el, idx) => { + el.querySelector('.page-thumb-num').textContent = idx + 1; + }); + } }); } grid.appendChild(thumb); } } + function updateDeleteCount() { + const count = getSelectedPages('deletePageGrid').length; + const el = document.getElementById('deleteCount'); + const btn = document.getElementById('btnDeleteSelected'); + if (el) el.textContent = count; + if (btn) btn.disabled = count === 0; + } + + function updateRotateCount() { + const count = getSelectedPages('rotatePageGrid').length; + const el = document.getElementById('rotateCount'); + const btn = document.getElementById('btnRotateSelected'); + if (el) el.textContent = count; + if (btn) btn.disabled = count === 0; + } + function getSelectedPages(gridId) { const grid = document.getElementById(gridId); if (!grid) return []; @@ -1705,6 +1790,60 @@ + + // Direct execute for page operations (from grid action buttons) + window.executeDeletePages = async function() { + const pages = getSelectedPages('deletePageGrid'); + if (!pages.length) { showError('Выберите страницы'); return; } + const fileId = state.selectedFileId; + if (!fileId) { showError('Выберите файл'); return; } + clearError(); + 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); } + }; + + window.executeRotatePages = async function() { + const pages = getSelectedPages('rotatePageGrid'); + const fileId = state.selectedFileId; + if (!fileId) { showError('Выберите файл'); return; } + clearError(); + try { + const res = await fetch('/pdf/rotate', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ fileId, pages: pages.length ? pages.join(',') : 'all', angle: state.rotateAngle || 90 }) }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error); + showResult(data); + } catch(e) { showError(e.message); } + }; + + window.executeReorderPages = async function() { + 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) { showError('Выберите файл'); return; } + clearError(); + 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); + showResult(data); + } catch(e) { showError(e.message); } + }; + + function showResult(data) { + if (data.downloadUrl) { + document.getElementById('downloadLink').href = data.downloadUrl; + document.getElementById('downloadLink').style.display = ''; + if (data.size) document.getElementById('resultSize').textContent = formatSize(data.size); + stepConfig.style.display = 'none'; + stepResult.style.display = 'block'; + } + } + })();