/
diz
/
lens
Обзор
Документация
Войти
/
diz
/
lens
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/app.js
259 строк
9 KB
Diman
feat: загрузка PDF перетаскиванием файла в окно (drag & drop) с оверлеем-индикатором; подсказка на стартовой карточке
03 авг 2026, 10:32
03 авг 2026, 10:32
305233a
Код
Авторство
О чём код?
'use strict'; // ═══════════════════════════════════════════════════════════════ // Карта модуля (диспетчер режимов и глобальных событий) // • Переключение режимов: switchMode / doSwitchMode (currentMode) // • Глобальные события: мышь (onWrapMouseDown/onWindowMouseMove/ // onWindowMouseUp), колесо (onWrapWheel), resize → fitToScreen // • Загрузка/сброс PDF: fileInput handler, fullResetBtn // • renderPage-диспетчер по режиму (sharedRender → режим-модуль) // • Скачивание PDF: downloadBtn (диспетчер на linesMode/textMode.export) // • Начальное состояние страницы // ═══════════════════════════════════════════════════════════════ // ── Mode switching ── const linesControls = $('linesControls'); const textControls = $('textControls'); const sidePanel = $('sidePanel'); let currentMode = 'lines'; function doSwitchMode(mode) { // Deactivate current mode if (currentMode === 'lines' && window.linesMode) { window.linesMode.deactivate(); } else if (currentMode === 'text' && window.textMode) { window.textMode.deactivate(); } currentMode = mode; // UI toggle document.querySelectorAll('.mode-tab').forEach(t => t.classList.toggle('active', t.dataset.mode === mode)); linesControls.classList.toggle('active', mode === 'lines'); textControls.classList.toggle('active', mode === 'text'); sidePanel.classList.toggle('visible', mode === 'text'); // Activate new mode if (mode === 'lines' && window.linesMode) { window.linesMode.activate(); } else if (mode === 'text' && window.textMode) { window.textMode.activate(); } } document.querySelectorAll('.mode-tab').forEach(tab => { tab.addEventListener('click', () => switchMode(tab.dataset.mode)); }); async function switchMode(mode) { if (mode === currentMode) return; if (currentMode === 'lines' && mode === 'text' && window.linesMode && window.linesMode.hasEdits()) { await applyLinesEditsAndReload(); return; } doSwitchMode(mode); } // ── Attach global events ── wrap.addEventListener('mousedown', onWrapMouseDown); window.addEventListener('mousemove', onWindowMouseMove); window.addEventListener('mouseup', onWindowMouseUp); wrap.addEventListener('wheel', onWrapWheel, { passive: false }); window.addEventListener('resize', () => { requestAnimationFrame(() => fitToScreen()); }); // ── File loading (shared) ── async function loadPdfFile(file) { if (!file) return; if (file.type && file.type !== 'application/pdf' && !/\.pdf$/i.test(file.name)) { showToast('Файл не является PDF'); return; } if (window.linesMode) window.linesMode.clearAll(); if (window.textMode) window.textMode.clearAll(); changesApplied = false; try { await loadAndRender(file); } catch (e) { showToast('Ошибка загрузки: ' + e.message); console.error(e); return; } hasUserZoomed = true; $('rescanBtn').disabled = false; $('drawBtn').disabled = false; $('fullResetBtn').disabled = false; document.querySelectorAll('#linesControls .color-btn').forEach(b => b.disabled = false); $('customColor').disabled = false; document.querySelectorAll('#textControls .color-btn').forEach(b => b.disabled = false); } fileInput.addEventListener('change', () => { loadPdfFile(fileInput.files[0]); fileInput.value = ''; }); // ── Drag & drop PDF ── const dropOverlay = $('dropOverlay'); let dragDepth = 0; function isPdfFile(item) { if (item.type === 'application/pdf') return true; if (/\.pdf$/i.test(item.name || '')) return true; return false; } window.addEventListener('dragenter', e => { if (![...(e.dataTransfer.items || [])].some(it => it.kind === 'file' && isPdfFile(it))) return; dragDepth++; dropOverlay.classList.add('show'); }); window.addEventListener('dragleave', e => { dragDepth = Math.max(0, dragDepth - 1); if (dragDepth === 0) dropOverlay.classList.remove('show'); }); window.addEventListener('dragover', e => { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }); window.addEventListener('drop', e => { e.preventDefault(); dragDepth = 0; dropOverlay.classList.remove('show'); const file = [...e.dataTransfer.files].find(isPdfFile); if (!file) { showToast('Файл не является PDF'); return; } loadPdfFile(file); }); prevBtn.addEventListener('click', prevPage); nextBtn.addEventListener('click', nextPage); rotateCcwBtn.addEventListener('click', () => rotatePage(-90)); rotateCwBtn.addEventListener('click', () => rotatePage(90)); $('fullResetBtn').addEventListener('click', async () => { if (!pdfDoc || !originalBytes) return; $('loadingOverlay').style.display = 'flex'; await new Promise(r => setTimeout(r, 50)); try { if (window.linesMode) window.linesMode.clearAll(); if (window.textMode) window.textMode.clearAll(); const lc = $('labelCanvas'); if (lc) { lc.width = lc.width; } srcBytes = originalBytes.slice(0); pdfDoc = await pdfjsLib.getDocument({ data: srcBytes.slice(0) }).promise; numPages = pdfDoc.numPages; curPage = 1; renderLevel = 1; screenScale = RENDER_SCALE; renderTask = null; changesApplied = false; await reloadPdfAndRender(); showToast('Исходный PDF загружен'); } finally { $('loadingOverlay').style.display = 'none'; } }); // ── renderPage dispatch ── async function renderPage() { if (!pdfDoc) return; const token = ++renderToken; prevBtn.disabled = true; nextBtn.disabled = true; try { const page = await pdfDoc.getPage(curPage); if (token !== renderToken) { updateNav(); return; } await sharedRender(page); if (token !== renderToken) { updateNav(); return; } if (currentMode === 'lines' && window.linesMode) { await window.linesMode.renderPage(); if (token !== renderToken) { updateNav(); return; } } else if (currentMode === 'text' && window.textMode) { // Пересчёт canvas-координат надписей при смене масштаба/поворота if (window.textMode.recalcCanvasCoords) window.textMode.recalcCanvasCoords(curPage); await window.textMode.renderPage(); if (token !== renderToken) { updateNav(); return; } } updateNav(); downloadBtn.disabled = false; } finally { // Индикатор «подождите» показан в sharedRender при смене уровня // рендеринга; скрываем только если этот рендер актуален — свежий // renderPage (новый renderToken) скроет его сам по завершении. if (token === renderToken) { const rw = $('renderWaiting'); if (rw) rw.classList.remove('show'); } } } // ── Download dispatch ── downloadBtn.addEventListener('click', async () => { if (!pdfDoc) return; downloadBtn.disabled = true; downloadBtn.textContent = 'Сохранение…'; try { let result; if (currentMode === 'lines' && window.linesMode) { result = await window.linesMode.export(); } else if (currentMode === 'text' && window.textMode) { result = await window.textMode.export(); } else { showToast('Нет данных для сохранения'); downloadBtn.disabled = false; downloadBtn.textContent = 'Скачать PDF'; return; } const { doc, editedPages } = result; let outBytes; if (editedPages > 0) { if (layersModified) { await applyOCGToExport(doc); layersModified = false; } outBytes = await doc.save(); } else if (changesApplied) { changesApplied = false; if (layersModified) { const odoc = await PDFLib.PDFDocument.load(srcBytes.slice(0)); await applyOCGToExport(odoc); layersModified = false; outBytes = await odoc.save(); } else { outBytes = srcBytes.slice(0); } } else if (layersModified) { const odoc = await PDFLib.PDFDocument.load(srcBytes.slice(0)); await applyOCGToExport(odoc); layersModified = false; outBytes = await odoc.save(); } else { showToast('Нет правок для сохранения'); downloadBtn.disabled = false; downloadBtn.textContent = 'Скачать PDF'; return; } downloadBlob(new Blob([outBytes], { type: 'application/pdf' }), 'edited.pdf'); showToast('Готово: edited.pdf'); setStatus(editedPages > 0 ? `Сохранено. Изменено страниц: ${editedPages}.` : 'Сохранено.'); } catch (e) { showToast('Ошибка: ' + e.message); setStatus('Ошибка сохранения: ' + e.message); console.error(e); } downloadBtn.disabled = false; downloadBtn.textContent = 'Скачать PDF'; }); // ── Initial state ── doSwitchMode('lines');