diff --git a/src/lib/common/services/pdf.service.js b/src/lib/common/services/pdf.service.js index 3294379..4cad5ca 100644 --- a/src/lib/common/services/pdf.service.js +++ b/src/lib/common/services/pdf.service.js @@ -1,20 +1,24 @@ import * as pdfjsLib from 'pdfjs-dist'; -import PdfWorkerCtor from 'pdfjs-dist/build/pdf.worker.min.mjs?worker'; + +// Явно указываем путь к worker'у, чтобы избежать проблем с ?worker в PWA/Chromium +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); /** * Загрузка PDF документа * @param {File} file */ export async function loadPdfDocument(file) { - try { - const arrayBuffer = await file.arrayBuffer(); - const worker = new pdfjsLib.PDFWorker({ port: new PdfWorkerCtor() }); - const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer, worker }); - return await loadingTask.promise; - } catch (err) { - console.error('PDF load error:', err); - return null; - } + try { + const arrayBuffer = await file.arrayBuffer(); + const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer }); + return await loadingTask.promise; + } catch (err) { + console.error('PDF load error:', err); + return null; + } } /** @@ -24,19 +28,18 @@ export async function loadPdfDocument(file) { * @param {number} scale */ export async function renderPageToImage(pdfDoc, pageNum, scale = 1.0) { - const page = await pdfDoc.getPage(pageNum); - const viewport = page.getViewport({ scale }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - const outputScale = window.devicePixelRatio || 1; + const page = await pdfDoc.getPage(pageNum); + const viewport = page.getViewport({ scale }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + const outputScale = window.devicePixelRatio || 1; - canvas.width = Math.floor(viewport.width * outputScale); - canvas.height = Math.floor(viewport.height * outputScale); - canvas.style.width = `${Math.floor(viewport.width)}px`; - canvas.style.height = `${Math.floor(viewport.height)}px`; + canvas.width = Math.floor(viewport.width * outputScale); + canvas.height = Math.floor(viewport.height * outputScale); + canvas.style.width = `${Math.floor(viewport.width)}px`; + canvas.style.height = `${Math.floor(viewport.height)}px`; - const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : undefined; - - await page.render({ canvasContext: context, viewport, transform }).promise; - return canvas.toDataURL('image/jpeg', 0.85); + const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : undefined; + await page.render({ canvasContext: context, viewport, transform }).promise; + return canvas.toDataURL('image/jpeg', 0.85); } diff --git a/src/lib/pages/main/MainMenu.svelte b/src/lib/pages/main/MainMenu.svelte index 4ee5879..f290b67 100644 --- a/src/lib/pages/main/MainMenu.svelte +++ b/src/lib/pages/main/MainMenu.svelte @@ -205,5 +205,5 @@
v{backendVersion} - v1.0.4 + v1.0.5
diff --git a/src/lib/pages/print/PrintProgress.svelte b/src/lib/pages/print/PrintProgress.svelte index 472eeaf..e143cd8 100644 --- a/src/lib/pages/print/PrintProgress.svelte +++ b/src/lib/pages/print/PrintProgress.svelte @@ -1,211 +1,234 @@
-
- -

Печать

-
+
+ +

Печать

+
+
+ {#if store.job} + {#each store.job.files as f, i (i + '-' + f.name)} + {@const b = badge(f.name)} + {@const isLastDone = f.status === 'done' && !store.job.files.slice(i + 1).some(f => f.status === 'printing' || f.status === 'queued')} + {@const hasNextQueued = store.job.files.slice(i + 1).some(f => f.status === 'queued')} +
+
+ {#if b}{b}{/if} + {f.name} + {STATUS_LABEL[f.status] ?? f.status} +
+ + {#if f.status === 'awaiting_clear_output'} +
    +
  1. Уберите распечатанные листы из выходного лотка
  2. +
  3. Нажмите кнопку ниже для запуска двусторонней печати
  4. +
+ {:else if f.status === 'awaiting_flip'} +
    +
  1. Не переворачивая, положите остальные листы в слот ручной подачи
  2. +
+ {:else if isLastDone && hasNextQueued && !store.autoContinue} +
    +
  1. Возьмите готовый документ
  2. +
  3. Нажмите далее
  4. +
+ {/if} + + {#if f.error} +

{f.error}

+ {/if} +
+ {/each} + {/if} +
+
diff --git a/src/lib/pages/print/store.svelte.js b/src/lib/pages/print/store.svelte.js index 7f32092..13c3d56 100644 --- a/src/lib/pages/print/store.svelte.js +++ b/src/lib/pages/print/store.svelte.js @@ -13,325 +13,322 @@ import { loadPdfDocument, renderPageToImage } from '$lib/common/services/pdf.ser * dpi: string, totalPages: number, * selectedPages: Set, pdfDoc: any * }} FileEntry - * @typedef {{ name: string, status: 'queued'|'printing'|'awaiting'|'done'|'error'|'cancelled', error: (string|null) }} JobFileView - * @typedef {{ job_id: string, phase: 'waiting_start'|'printing'|'awaiting_pickup'|'finished'|'cancelled', files: JobFileView[] }} JobView + * @typedef {{ name: string, status: 'queued'|'printing'|'sent'|'awaiting_clear_output'|'awaiting_flip'|'awaiting_pickup'|'done'|'error'|'cancelled', error: (string|null) }} JobFileView + * @typedef {{ job_id: string, phase: 'waiting_start'|'printing'|'awaiting_clear_output'|'awaiting_flip'|'awaiting_pickup'|'finished'|'cancelled', files: JobFileView[] }} JobView */ export function createPrintStore() { - let files = $state(/** @type {FileEntry[]} */ ([])); - let activeFile = $state(/** @type {FileEntry | null} */ (null)); - let viewMode = $state(/** @type {'gallery' | 'preview'} */ ('gallery')); - let showPayment = $state(false); - let isPrinting = $state(false); - let galleryThumbnails = $state([]); - let galleryLoading = $state(false); - let previewPagesCache = $state({}); - let isPreviewRendering = $state(false); + let files = $state(/** @type {FileEntry[]} */ ([])); + let activeFile = $state(/** @type {FileEntry | null} */ (null)); + let viewMode = $state(/** @type {'gallery' | 'preview'} */ ('gallery')); + let showPayment = $state(false); + let isPrinting = $state(false); + let galleryThumbnails = $state([]); + let galleryLoading = $state(false); + let previewPagesCache = $state({}); + let isPreviewRendering = $state(false); - // ── Задание печати (страница прогресса) ── - let job = $state(/** @type {JobView | null} */ (null)); - let autoContinue = $state(false); - let advancing = false; + let job = $state(/** @type {JobView | null} */ (null)); + let autoContinue = $state(false); + let advancing = false; - const jobActive = $derived(!!job && job.phase !== 'finished' && job.phase !== 'cancelled'); + const jobActive = $derived(!!job && job.phase !== 'finished' && job.phase !== 'cancelled'); + let uid = 0; - let uid = 0; + const totalPrice = $derived( + files.reduce((sum, f) => { + const pages = f.selectedPages?.size || 0; + return sum + pages * f.copies * getPricePerPage(pages); + }, 0) + ); + const extraCopies = $derived(files.reduce((sum, f) => sum + (f.copies - 1), 0)); - const totalPrice = $derived( - files.reduce((sum, f) => { - const pages = f.selectedPages?.size || 0; - return sum + pages * f.copies * getPricePerPage(pages); - }, 0) - ); - const extraCopies = $derived(files.reduce((sum, f) => sum + (f.copies - 1), 0)); + function updateFile(id, changes) { + files = files.map((f) => (f.id === id ? { ...f, ...changes } : f)); + if (activeFile?.id === id) { + activeFile = files.find((f) => f.id === id); + } + } - function updateFile(id, changes) { - files = files.map((f) => (f.id === id ? { ...f, ...changes } : f)); - if (activeFile?.id === id) { - activeFile = files.find((f) => f.id === id); - } - } + async function ensurePdfLoaded(entry) { + if (entry.pdfDoc) return entry.pdfDoc; + const pdf = await loadPdfDocument(entry.file); + if (pdf) { + const numPages = pdf.numPages; + updateFile(entry.id, { + pdfDoc: pdf, + totalPages: numPages, + selectedPages: new Set(Array.from({ length: numPages }, (_, i) => i + 1)) + }); + } else { + updateFile(entry.id, { totalPages: 0 }); + } + return pdf; + } - async function ensurePdfLoaded(entry) { - if (entry.pdfDoc) return entry.pdfDoc; - const pdf = await loadPdfDocument(entry.file); - if (pdf) { - const numPages = pdf.numPages; - updateFile(entry.id, { - pdfDoc: pdf, - totalPages: numPages, - selectedPages: new Set(Array.from({ length: numPages }, (_, i) => i + 1)) - }); - } else { - updateFile(entry.id, { totalPages: 0 }); - } - return pdf; - } + async function addFiles(fileList) { + if (!fileList || fileList.length === 0) return; + const newFiles = []; + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + const fType = detectFileType(file); + const isOffice = fType === 'office'; + newFiles.push({ + id: ++uid, + file, + previewUrl: isOffice ? null : URL.createObjectURL(file), + fileType: fType, + expanded: false, + format: 'Авто', + colorMode: 'bw', + qualityIndex: 1, + copies: 1, + sides: 'Односторонняя', + dpi: 'Авто', + totalPages: (fType === 'image' || isOffice) ? 1 : 0, + selectedPages: (fType === 'image' || isOffice) ? new Set([1]) : new Set(), + pdfDoc: null + }); + } + files = [...files, ...newFiles]; + for (const entry of newFiles) { + if (entry.fileType === 'pdf') ensurePdfLoaded(entry); + } + } - async function addFiles(fileList) { - if (!fileList || fileList.length === 0) return; - const newFiles = []; - for (let i = 0; i < fileList.length; i++) { - const file = fileList[i]; - const fType = detectFileType(file); - const isOffice = fType === 'office'; - newFiles.push({ - id: ++uid, - file, - previewUrl: isOffice ? null : URL.createObjectURL(file), - fileType: fType, - expanded: false, - format: 'Авто', - colorMode: 'bw', - qualityIndex: 1, - copies: 1, - sides: 'Односторонняя', - dpi: 'Авто', - totalPages: (fType === 'image' || isOffice) ? 1 : 0, - selectedPages: (fType === 'image' || isOffice) ? new Set([1]) : new Set(), - pdfDoc: null - }); - } - files = [...files, ...newFiles]; - for (const entry of newFiles) { - if (entry.fileType === 'pdf') ensurePdfLoaded(entry); - } - } + function removeFile(id) { + const f = files.find((x) => x.id === id); + if (f?.previewUrl) URL.revokeObjectURL(f.previewUrl); + files = files.filter((x) => x.id !== id); + } - function removeFile(id) { - const f = files.find((x) => x.id === id); - if (f?.previewUrl) URL.revokeObjectURL(f.previewUrl); - files = files.filter((x) => x.id !== id); - } - function toggleExpand(id) { - updateFile(id, { expanded: !files.find((f) => f.id === id)?.expanded }); - } + function toggleExpand(id) { + updateFile(id, { expanded: !files.find((f) => f.id === id)?.expanded }); + } - async function openGallery(entry) { - activeFile = entry; - viewMode = 'gallery'; - galleryThumbnails = []; - galleryLoading = true; - previewPagesCache = {}; - await tick(); - const pdf = await ensurePdfLoaded(entry); - if (!pdf || entry.totalPages === 0) { - galleryLoading = false; - return; - } - const thumbs = []; - for (let i = 1; i <= entry.totalPages; i++) { - try { - thumbs.push(await renderPageToImage(pdf, i, 0.4)); - } catch { - thumbs.push(''); - } - galleryThumbnails = [...thumbs]; - } - galleryLoading = false; - } + async function openGallery(entry) { + activeFile = entry; + viewMode = 'gallery'; + galleryThumbnails = []; + galleryLoading = true; + previewPagesCache = {}; + await tick(); + const pdf = await ensurePdfLoaded(entry); + if (!pdf || entry.totalPages === 0) { + galleryLoading = false; + return; + } + const thumbs = []; + for (let i = 1; i <= entry.totalPages; i++) { + try { + thumbs.push(await renderPageToImage(pdf, i, 0.4)); + } catch { + thumbs.push(''); + } + } + galleryThumbnails = [...thumbs]; + galleryLoading = false; + } - async function openPreview(entry) { - activeFile = entry; - viewMode = 'preview'; - previewPagesCache = {}; - galleryThumbnails = []; - await tick(); - if (entry.fileType === 'pdf') { - const pdf = await ensurePdfLoaded(entry); - if (pdf && entry.totalPages > 0) { - preloadPreviewPages(entry, 1, 5); - } - } - } + async function openPreview(entry) { + activeFile = entry; + viewMode = 'preview'; + previewPagesCache = {}; + galleryThumbnails = []; + await tick(); + if (entry.fileType === 'pdf') { + const pdf = await ensurePdfLoaded(entry); + if (pdf && entry.totalPages > 0) { + preloadPreviewPages(entry, 1, 5); + } + } + } - async function preloadPreviewPages(entry, startPage, count) { - const currentFile = files.find((f) => f.id === entry.id); - const pdf = currentFile?.pdfDoc || entry.pdfDoc; - if (!pdf || isPreviewRendering) return; - isPreviewRendering = true; - const endPage = Math.min(startPage + count - 1, entry.totalPages); - const newCache = {}; - for (let i = startPage; i <= endPage; i++) { - if (previewPagesCache[i]) continue; - try { - newCache[i] = await renderPageToImage(pdf, i, 1.5); - } catch (e) { - console.error(`Failed to render preview page ${i}`, e); - } - } - if (Object.keys(newCache).length > 0) { - previewPagesCache = { ...previewPagesCache, ...newCache }; - } - isPreviewRendering = false; - } + async function preloadPreviewPages(entry, startPage, count) { + const currentFile = files.find((f) => f.id === entry.id); + const pdf = currentFile?.pdfDoc || entry.pdfDoc; + if (!pdf || isPreviewRendering) return; + isPreviewRendering = true; + const endPage = Math.min(startPage + count - 1, entry.totalPages); + const newCache = {}; + for (let i = startPage; i <= endPage; i++) { + if (previewPagesCache[i]) continue; + try { + newCache[i] = await renderPageToImage(pdf, i, 1.5); + } catch (e) { + console.error(`Failed to render preview page ${i}`, e); + } + } + if (Object.keys(newCache).length > 0) { + previewPagesCache = { ...previewPagesCache, ...newCache }; + } + isPreviewRendering = false; + } - function togglePageSelection(pageNum) { - if (!activeFile?.selectedPages) return; - const newSet = new Set(activeFile.selectedPages); - if (newSet.has(pageNum)) { - if (newSet.size > 1) newSet.delete(pageNum); - } else { - newSet.add(pageNum); - } - updateFile(activeFile.id, { selectedPages: newSet }); - } - function selectAllPages() { - if (!activeFile?.totalPages) return; - updateFile(activeFile.id, { - selectedPages: new Set(Array.from({ length: activeFile.totalPages }, (_, i) => i + 1)) - }); - } - function deselectAllPages() { - if (!activeFile) return; - updateFile(activeFile.id, { selectedPages: new Set([1]) }); - } + function togglePageSelection(pageNum) { + if (!activeFile?.selectedPages) return; + const newSet = new Set(activeFile.selectedPages); + if (newSet.has(pageNum)) { + if (newSet.size > 1) newSet.delete(pageNum); + } else { + newSet.add(pageNum); + } + updateFile(activeFile.id, { selectedPages: newSet }); + } - // ─────────────── Отправка на печать + job-протокол ─────────────── + function selectAllPages() { + if (!activeFile?.totalPages) return; + updateFile(activeFile.id, { + selectedPages: new Set(Array.from({ length: activeFile.totalPages }, (_, i) => i + 1)) + }); + } - async function submitPrint() { - if (files.length === 0 || isPrinting) return; - isPrinting = true; - const formData = new FormData(); - for (const f of files) { - formData.append('files', f.file, f.file.name); - const pagesValue = - f.fileType === 'office' - ? 'all' - : f.selectedPages && f.selectedPages.size > 0 - ? Array.from(f.selectedPages).sort((a, b) => a - b).join(',') - : 'all'; - formData.append( - 'settings', - JSON.stringify({ - filename: f.file.name, - pages: pagesValue, - copies: f.copies, - colorMode: f.colorMode, - format: f.fileType === 'office' ? 'auto' : f.format, - sides: f.sides ?? 'Односторонняя', - quality: f.qualityIndex, - dpi: f.dpi ?? 'Авто' - }) - ); - } - try { - const res = await fetch('/api/print', { method: 'POST', body: formData }); - const text = await res.text(); - if (!res.ok) { - alert(`❌ Ошибка (${res.status}):\n${text}`); - return; - } - let data; - try { - data = JSON.parse(text); - } catch { - alert('⚠️ Некорректный ответ сервера печати.'); - return; - } - job = data; - showPayment = false; - await advanceJob(); // старт первого файла - } catch { - alert('⚠️ Не удалось связаться с сервером печати.'); - } finally { - isPrinting = false; - } - } + function deselectAllPages() { + if (!activeFile) return; + updateFile(activeFile.id, { selectedPages: new Set([1]) }); + } - /** Подтверждение: старт первого файла / «забрал документ → печатай следующий» */ - async function advanceJob() { - if (!job || advancing) return; - advancing = true; - try { - const res = await fetch(`/api/print/${job.job_id}/advance`, { method: 'POST' }); - if (res.ok) job = await res.json(); - } catch (e) { - console.error('advance error:', e); - } finally { - advancing = false; - } - } + async function submitPrint() { + if (files.length === 0 || isPrinting) return; + isPrinting = true; + const formData = new FormData(); + for (const f of files) { + formData.append('files', f.file, f.file.name); + const pagesValue = + f.fileType === 'office' + ? 'all' + : f.selectedPages && f.selectedPages.size > 0 + ? Array.from(f.selectedPages).sort((a, b) => a - b).join(',') + : 'all'; + formData.append( + 'settings', + JSON.stringify({ + filename: f.file.name, + pages: pagesValue, + copies: f.copies, + colorMode: f.colorMode, + format: f.fileType === 'office' ? 'auto' : f.format, + sides: f.sides ?? 'Односторонняя', + quality: f.qualityIndex, + dpi: f.dpi ?? 'Авто' + }) + ); + } + try { + const res = await fetch('/api/print', { method: 'POST', body: formData }); + const text = await res.text(); + if (!res.ok) { + alert(`❌ Ошибка (${res.status}):\n${text}`); + return; + } + let data; + try { + data = JSON.parse(text); + } catch { + alert('⚠️ Некорректный ответ сервера печати.'); + return; + } + job = data; + showPayment = false; + await advanceJob(); + } catch { + alert('⚠️ Не удалось связаться с сервером печати.'); + } finally { + isPrinting = false; + } + } - /** Поллинг статуса; при autoContinue сами шлём подтверждение */ - async function refreshJob() { - if (!job) return; - try { - const res = await fetch(`/api/print/${job.job_id}`); - if (!res.ok) return; - job = await res.json(); - if (job.phase === 'waiting_start') { - advanceJob(); - } else if (job.phase === 'awaiting_pickup' && autoContinue) { - advanceJob(); - } - } catch (e) { - console.error('refresh error:', e); - } - } + async function advanceJob() { + if (!job || advancing) return; + advancing = true; + try { + const res = await fetch(`/api/print/${job.job_id}/advance`, { method: 'POST' }); + if (res.ok) job = await res.json(); + } catch (e) { + console.error('advance error:', e); + } finally { + advancing = false; + } + } - async function cancelJob() { - if (!job) return; - try { - const res = await fetch(`/api/print/${job.job_id}/cancel`, { method: 'POST' }); - if (res.ok) job = await res.json(); - } catch (e) { - console.error('cancel error:', e); - } - } + async function refreshJob() { + if (!job) return; + try { + const res = await fetch(`/api/print/${job.job_id}`); + if (!res.ok) return; + job = await res.json(); + + // Автопродолжение срабатывает ТОЛЬКО для awaiting_pickup + if (job.phase === 'awaiting_pickup' && autoContinue) { + advanceJob(); + } + } catch (e) { + console.error('refresh error:', e); + } + } - function resetJob() { - job = null; - autoContinue = false; - files.forEach((f) => f.previewUrl && URL.revokeObjectURL(f.previewUrl)); - files = []; - activeFile = null; - galleryThumbnails = []; - previewPagesCache = {}; - showPayment = false; - } + async function cancelJob() { + if (!job) return; + try { + const res = await fetch(`/api/print/${job.job_id}/cancel`, { method: 'POST' }); + if (res.ok) job = await res.json(); + } catch (e) { + console.error('cancel error:', e); + } + } - function setAutoContinue(v) { - autoContinue = !!v; - } + function resetJob() { + job = null; + autoContinue = false; + files.forEach((f) => f.previewUrl && URL.revokeObjectURL(f.previewUrl)); + files = []; + activeFile = null; + galleryThumbnails = []; + previewPagesCache = {}; + showPayment = false; + } - onDestroy(() => { - files.forEach((f) => f.previewUrl && URL.revokeObjectURL(f.previewUrl)); - }); + function setAutoContinue(v) { + autoContinue = !!v; + } - return { - get files() { return files; }, - get activeFile() { return activeFile; }, - get viewMode() { return viewMode; }, - get showPayment() { return showPayment; }, - get isPrinting() { return isPrinting; }, - get totalPrice() { return totalPrice; }, - get extraCopies() { return extraCopies; }, - get galleryThumbnails() { return galleryThumbnails; }, - get galleryLoading() { return galleryLoading; }, - get previewPagesCache() { return previewPagesCache; }, - get isPreviewRendering() { return isPreviewRendering; }, - get job() { return job; }, - get jobActive() { return jobActive; }, - get autoContinue() { return autoContinue; }, - addFiles, - removeFile, - toggleExpand, - updateFile, - openGallery, - openPreview, - closeOverlay: () => { activeFile = null; galleryThumbnails = []; previewPagesCache = {}; }, - openPayment: () => { if (files.length > 0 && !isPrinting) showPayment = true; }, - hidePayment: () => { showPayment = false; }, - submitPrint, - advanceJob, - refreshJob, - cancelJob, - resetJob, - setAutoContinue, - togglePageSelection, - selectAllPages, - deselectAllPages, - requestPreviewPages: (start, count) => { if (activeFile) preloadPreviewPages(activeFile, start, count); }, - pagesLabel: _pagesLabel - }; + onDestroy(() => { + files.forEach((f) => f.previewUrl && URL.revokeObjectURL(f.previewUrl)); + }); + + return { + get files() { return files; }, + get activeFile() { return activeFile; }, + get viewMode() { return viewMode; }, + get showPayment() { return showPayment; }, + get isPrinting() { return isPrinting; }, + get totalPrice() { return totalPrice; }, + get extraCopies() { return extraCopies; }, + get galleryThumbnails() { return galleryThumbnails; }, + get galleryLoading() { return galleryLoading; }, + get previewPagesCache() { return previewPagesCache; }, + get isPreviewRendering() { return isPreviewRendering; }, + get job() { return job; }, + get jobActive() { return jobActive; }, + get autoContinue() { return autoContinue; }, + addFiles, + removeFile, + toggleExpand, + updateFile, + openGallery, + openPreview, + closeOverlay: () => { activeFile = null; galleryThumbnails = []; previewPagesCache = {}; }, + openPayment: () => { if (files.length > 0 && !isPrinting) showPayment = true; }, + hidePayment: () => { showPayment = false; }, + submitPrint, + advanceJob, + refreshJob, + cancelJob, + resetJob, + setAutoContinue, + togglePageSelection, + selectAllPages, + deselectAllPages, + requestPreviewPages: (start, count) => { if (activeFile) preloadPreviewPages(activeFile, start, count); }, + pagesLabel: _pagesLabel + }; } diff --git a/vite.config.js b/vite.config.js index f54b4f5..3a844bd 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,8 +8,8 @@ export default defineConfig({ svelte({ onwarn(warning, handler) { if ( - warning.code.startsWith('a11y_') || - warning.code === 'slot_element_deprecated' || + warning.code.startsWith('a11y_') || + warning.code === 'slot_element_deprecated' || warning.code === 'svelte_self_deprecated' || warning.code === 'non_reactive_update' ) { @@ -22,9 +22,9 @@ export default defineConfig({ registerType: 'prompt', injectRegister: 'auto', workbox: { - clientsClaim: true, // Необходимо для PDF SW - globPatterns: ['**/*.{js,css,html,ico,png,svg,wasm}'], - // Важно: не кэшируем API endpoint версии + clientsClaim: true, + // Добавили mjs, чтобы pdf.worker.min.mjs попадал в precache Нужно для PDFJS + globPatterns: ['**/*.{js,mjs,css,html,ico,png,svg,wasm}'], navigateFallbackDenylist: [/^\/api\/version/], runtimeCaching: [ { @@ -34,12 +34,21 @@ export default defineConfig({ cacheName: 'google-fonts-cache', expiration: { maxEntries: 10, - maxAgeSeconds: 60 * 60 * 24 * 365 // 1 год + maxAgeSeconds: 60 * 60 * 24 * 365 }, cacheableResponse: { statuses: [0, 200] } } + }, + // Fallback кэширование для worker-файлов, если они не попали в precache + { + urlPattern: /\/assets\/.*\.(mjs|js)$/, + handler: 'CacheFirst', + options: { + cacheName: 'worker-cache', + expiration: { maxEntries: 20, maxAgeSeconds: 60 * 60 * 24 * 30 } + } } ] },