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'}
+
+ - Уберите распечатанные листы из выходного лотка
+ - Нажмите кнопку ниже для запуска двусторонней печати
+
+ {:else if f.status === 'awaiting_flip'}
+
+ - Не переворачивая, положите остальные листы в слот ручной подачи
+
+ {:else if isLastDone && hasNextQueued && !store.autoContinue}
+
+ - Возьмите готовый документ
+ - Нажмите далее
+
+ {/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 }
+ }
}
]
},