diff --git a/src/lib/common/utils/file.util.js b/src/lib/common/utils/file.util.js
index b45c86e..1c25421 100644
--- a/src/lib/common/utils/file.util.js
+++ b/src/lib/common/utils/file.util.js
@@ -3,13 +3,24 @@
/**
* Определение типа файла
* @param {File} file
- * @returns {'image' | 'pdf' | 'other'}
+ * @returns {'image' | 'pdf' | 'office' | 'other'}
*/
export function detectFileType(file) {
- if (!file) return 'other';
- if (file.type.startsWith('image/')) return 'image';
- if (file.type === 'application/pdf') return 'pdf';
- return 'other';
+ if (!file) return 'other';
+ if (file.type.startsWith('image/')) return 'image';
+ if (file.type === 'application/pdf') return 'pdf';
+
+ // Офисные документы — определяем по расширению,
+ // т.к. MIME может быть пустым или некорректным
+ const ext = (file.name || '').toLowerCase().split('.').pop() || '';
+ const officeExts = [
+ 'doc', 'docx', 'odt', // текст
+ 'xls', 'xlsx', 'ods', // таблицы
+ 'ppt', 'pptx', 'odp', // презентации (на всякий случай)
+ ];
+ if (officeExts.includes(ext)) return 'office';
+
+ return 'other';
}
/**
@@ -17,9 +28,9 @@ export function detectFileType(file) {
* @param {import('$lib/pages/print/stores/print.store.svelte').FileEntry} entry
*/
export function pagesLabel(entry) {
- if (!entry.totalPages || entry.totalPages === 0) return '...';
- if (!entry.selectedPages || entry.selectedPages.size === 0) return 'Нет';
- if (entry.selectedPages.size === entry.totalPages) return 'Все';
- if (entry.selectedPages.size === 1) return `стр. ${[...entry.selectedPages][0]}`;
- return `${entry.selectedPages.size}/${entry.totalPages} стр.`;
+ if (!entry.totalPages || entry.totalPages === 0) return '...';
+ if (!entry.selectedPages || entry.selectedPages.size === 0) return 'Нет';
+ if (entry.selectedPages.size === entry.totalPages) return 'Все';
+ if (entry.selectedPages.size === 1) return `стр. ${[...entry.selectedPages][0]}`;
+ return `${entry.selectedPages.size}/${entry.totalPages} стр.`;
}
diff --git a/src/lib/pages/print/PrintMenu.svelte b/src/lib/pages/print/PrintMenu.svelte
index 343b05a..92f0954 100644
--- a/src/lib/pages/print/PrintMenu.svelte
+++ b/src/lib/pages/print/PrintMenu.svelte
@@ -1,89 +1,97 @@
{#if store.showPayment}
-
+
{/if}
{#if !store.activeFile && !store.showPayment}
-
-
+
+
-
- {#if store.files.length === 0}
- fileInput.click()} />
- {:else}
- {#each store.files as file (file.id)}
- store.openPreview(f)}
- onPageSelect={(f) => (f.fileType === 'pdf' ? store.openGallery(f) : store.openPreview(f))}
- pagesLabel={store.pagesLabel}
- />
- {/each}
-
- {/if}
-
+
+ {#if store.files.length === 0}
+ fileInput.click()} />
+ {:else}
+ {#each store.files as file (file.id)}
+ store.openPreview(f)}
+ onPageSelect={(f) => (f.fileType === 'pdf' ? store.openGallery(f) : store.openPreview(f))}
+ pagesLabel={store.pagesLabel}
+ />
+ {/each}
+
+ {/if}
+
-
+
-
-
+
+
+
{/if}
diff --git a/src/lib/pages/print/PrintUI.svelte b/src/lib/pages/print/PrintUI.svelte
index 50f95ee..4284a73 100644
--- a/src/lib/pages/print/PrintUI.svelte
+++ b/src/lib/pages/print/PrintUI.svelte
@@ -1,230 +1,308 @@
{#if as === 'button'}
-
+
{:else if as === 'icon-button'}
-
+
{:else if as === 'counter'}
-
-
- {props.value} шт.
-
-
+
+
+ {props.value} шт.
+
+
{:else if as === 'toggle-group'}
-
- {#if props.label}
{/if}
-
- {#each props.options as opt}
- {@const val = props.valueKey ? opt[props.valueKey] : opt}
- {@const lbl = props.labelKey ? opt[props.labelKey] : opt}
-
- {/each}
-
-
+
+ {#if props.label}
{/if}
+
+ {#each props.options as opt}
+ {@const val = props.valueKey ? opt[props.valueKey] : opt}
+ {@const lbl = props.labelKey ? opt[props.labelKey] : opt}
+
+ {/each}
+
+
{:else if as === 'file-card'}
- {@const file = props.file}
-
- props.onToggle?.(file.id)}>
- {file.expanded ? '▼' : '►'}
- {file.file.name}
- e.stopPropagation()}>
- {#if file.previewUrl}
- props.onPreview?.(file)} title="Предпросмотр">🔍
- {/if}
- props.onRemove?.(file.id)} title="Удалить">✕
-
-
- {#if file.expanded}
-
-
-
-
-
- props.onPageSelect?.(file)} />
- (formatOpen = !formatOpen)} />
-
-
- props.onUpdate?.(file.id, { sides: v })}
- />
- c.id === file.colorMode))]}
- onchange={(v, i) => props.onUpdate?.(file.id, { colorMode: COLOR_MODES[i]?.id })}
- />
-
-
+ {@const file = props.file}
+
+ props.onToggle?.(file.id)}>
+
{file.expanded ? '▼' : '►'}
-
-
- props.onUpdate?.(file.id, { qualityIndex: QUALITY_LABELS.indexOf(v) })}
- />
- props.onUpdate?.(file.id, { dpi: v })}
- />
-
+
+ {#if isOffice(file)}
+
+ {file.file.name.split('.').pop()?.toUpperCase() ?? 'DOC'}
+
+ {/if}
-
-
-
- props.onUpdate?.(file.id, { copies: file.copies + 1 })}
- onDecrement={() => props.onUpdate?.(file.id, { copies: Math.max(1, file.copies - 1) })}
- />
-
-
+ {file.file.name}
- props.onUpdate?.(file.id, { format: v })}
- />
- {/if}
-
+
e.stopPropagation()}>
+
+ {#if file.previewUrl && !isOffice(file)}
+ props.onPreview?.(file)} title="Предпросмотр">🔍
+ {/if}
+ props.onRemove?.(file.id)} title="Удалить">✕
+
+
+
+ {#if file.expanded}
+
+
+ {#if isOffice(file)}
+
+
+ Документ будет распечатан как есть — конвертация выполняется на сервере.
+
+
+
+
+ props.onUpdate?.(file.id, { sides: v })}
+ />
+ c.id === file.colorMode))]}
+ onchange={(v, i) => props.onUpdate?.(file.id, { colorMode: COLOR_MODES[i]?.id })}
+ />
+
+
+
+
+ props.onUpdate?.(file.id, { qualityIndex: QUALITY_LABELS.indexOf(v) })}
+ />
+
+
+ {:else}
+
+
+
+ props.onPageSelect?.(file)} />
+ (formatOpen = !formatOpen)} />
+
+
+ props.onUpdate?.(file.id, { sides: v })}
+ />
+ c.id === file.colorMode))]}
+ onchange={(v, i) => props.onUpdate?.(file.id, { colorMode: COLOR_MODES[i]?.id })}
+ />
+
+
+
+
+ props.onUpdate?.(file.id, { qualityIndex: QUALITY_LABELS.indexOf(v) })}
+ />
+ props.onUpdate?.(file.id, { dpi: v })}
+ />
+
+ {/if}
+
+
+
+
+ props.onUpdate?.(file.id, { copies: file.copies + 1 })}
+ onDecrement={() => props.onUpdate?.(file.id, { copies: Math.max(1, file.copies - 1) })}
+ />
+
+
+
+
+ {#if !isOffice(file)}
+ props.onUpdate?.(file.id, { format: v })}
+ />
+ {/if}
+ {/if}
+
{:else if as === 'upload-zone'}
-
-
Загрузите файлы для печати
-
Загрузить файл(ы)
-
+
+
Загрузите файлы для печати
+
Загрузить файл(ы)
+
{:else if as === 'footer'}
-
+
{/if}
diff --git a/src/lib/pages/print/store.svelte.js b/src/lib/pages/print/store.svelte.js
index ca5e646..b74e134 100644
--- a/src/lib/pages/print/store.svelte.js
+++ b/src/lib/pages/print/store.svelte.js
@@ -1,243 +1,274 @@
// lib/pages/print/store.svelte.js
+
import { onDestroy, tick } from 'svelte';
import { getPricePerPage } from '$lib/common/utils/pricing.util.js';
import { detectFileType, pagesLabel as _pagesLabel } from '$lib/common/utils/file.util.js';
import { loadPdfDocument, renderPageToImage } from '$lib/common/services/pdf.service.js';
/**
- * @typedef {{ id: number, file: File, previewUrl: string | null, fileType: 'image' | 'pdf' | 'other', expanded: boolean, format: 'A4' | 'A5', colorMode: 'bw' | 'color', qualityIndex: number, copies: number, totalPages: number, selectedPages: Set, pdfDoc: any }} FileEntry
+ * @typedef {{
+ * id: number, file: File, previewUrl: string | null,
+ * fileType: 'image' | 'pdf' | 'office' | 'other',
+ * expanded: boolean, format: string, colorMode: string,
+ * qualityIndex: number, copies: number, sides: string,
+ * dpi: string, totalPages: number,
+ * selectedPages: Set, pdfDoc: any
+ * }} FileEntry
*/
-
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 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 uid = 0;
- let galleryThumbnails = $state([]);
- let galleryLoading = $state(false);
- let previewPagesCache = $state({});
- let isPreviewRendering = $state(false);
+ const totalPrice = $derived(
+ files.reduce((sum, f) => {
+ const pages = f.selectedPages?.size || 0;
+ return sum + pages * f.copies * getPricePerPage(pages);
+ }, 0)
+ );
- let uid = 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)
- );
+ 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);
+ }
+ }
- const extraCopies = $derived(files.reduce((sum, f) => sum + (f.copies - 1), 0));
+ 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;
+ }
- 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 addFiles(fileList) {
+ if (!fileList || fileList.length === 0) return;
+ const newFiles = [];
- 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;
- }
+ for (let i = 0; i < fileList.length; i++) {
+ const file = fileList[i];
+ const fType = detectFileType(file);
+ const isOffice = fType === 'office';
- 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);
- newFiles.push({
- id: ++uid,
- file,
- previewUrl: URL.createObjectURL(file),
- fileType: fType,
- expanded: false,
- format: 'A4',
- colorMode: 'bw',
- qualityIndex: 1,
- copies: 1,
- totalPages: fType === 'image' ? 1 : 0,
- selectedPages: fType === 'image' ? new Set([1]) : new Set(),
- pdfDoc: null
- });
- }
- files = [...files, ...newFiles];
- for (const entry of newFiles) {
- if (entry.fileType === 'pdf') ensurePdfLoaded(entry);
- }
- }
+ newFiles.push({
+ id: ++uid,
+ file,
+ // Для офисных файлов превью не создаём
+ previewUrl: isOffice ? null : URL.createObjectURL(file),
+ fileType: fType,
+ expanded: false,
+ format: 'Авто',
+ colorMode: 'bw',
+ qualityIndex: 1,
+ copies: 1,
+ sides: 'Односторонняя',
+ dpi: 'Авто',
+ // Для офисных файлов: 1 «страница» для расчёта цены,
+ // реальное кол-во определит CUPS
+ totalPages: (fType === 'image' || isOffice) ? 1 : 0,
+ selectedPages: (fType === 'image' || isOffice) ? new Set([1]) : new Set(),
+ pdfDoc: null
+ });
+ }
- 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);
- }
+ files = [...files, ...newFiles];
- function toggleExpand(id) {
- updateFile(id, { expanded: !files.find((f) => f.id === id)?.expanded });
- }
+ for (const entry of newFiles) {
+ if (entry.fileType === 'pdf') ensurePdfLoaded(entry);
+ // office и image — ничего дополнительно не загружаем
+ }
+ }
- async function openGallery(entry) {
- activeFile = entry;
- viewMode = 'gallery';
- galleryThumbnails = [];
- galleryLoading = true;
- previewPagesCache = {};
- await tick();
+ 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);
+ }
- const pdf = await ensurePdfLoaded(entry);
- if (!pdf || entry.totalPages === 0) {
- galleryLoading = false;
- return;
- }
+ function toggleExpand(id) {
+ updateFile(id, { expanded: !files.find((f) => f.id === id)?.expanded });
+ }
- 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();
- 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);
- }
- }
- }
+ const pdf = await ensurePdfLoaded(entry);
+ if (!pdf || entry.totalPages === 0) {
+ galleryLoading = false;
+ return;
+ }
- 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;
+ 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;
+ }
- isPreviewRendering = true;
- const endPage = Math.min(startPage + count - 1, entry.totalPages);
- const newCache = {};
+ async function openPreview(entry) {
+ activeFile = entry;
+ viewMode = 'preview';
+ previewPagesCache = {};
+ galleryThumbnails = [];
+ await tick();
- 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 (entry.fileType === 'pdf') {
+ const pdf = await ensurePdfLoaded(entry);
+ if (pdf && entry.totalPages > 0) {
+ preloadPreviewPages(entry, 1, 5);
+ }
+ }
+ }
- 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;
- 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 });
- }
+ isPreviewRendering = true;
+ const endPage = Math.min(startPage + count - 1, entry.totalPages);
+ const newCache = {};
- function selectAllPages() {
- if (!activeFile?.totalPages) return;
- updateFile(activeFile.id, {
- selectedPages: new Set(Array.from({ length: activeFile.totalPages }, (_, i) => i + 1))
- });
- }
+ 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);
+ }
+ }
- function deselectAllPages() {
- if (!activeFile) return;
- updateFile(activeFile.id, { selectedPages: new Set([1]) });
- }
+ if (Object.keys(newCache).length > 0) {
+ previewPagesCache = { ...previewPagesCache, ...newCache };
+ }
+ isPreviewRendering = 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);
- formData.append(
- 'settings',
- JSON.stringify({
- filename: f.file.name,
- pages: f.selectedPages ? Array.from(f.selectedPages).sort((a, b) => a - b).join(',') : 'all',
- copies: f.copies,
- colorMode: f.colorMode,
- format: f.format
- })
- );
- }
- try {
- const res = await fetch('/api/print', { method: 'POST', body: formData });
- const text = await res.text();
- alert(res.ok ? `✅ Успешно!\n${text}` : `❌ Ошибка (${res.status}):\n${text}`);
- } catch {
- alert('⚠️ Не удалось связаться с сервером печати.');
- } finally {
- isPrinting = false;
- showPayment = 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 });
+ }
- onDestroy(() => {
- files.forEach((f) => f.previewUrl && URL.revokeObjectURL(f.previewUrl));
- });
+ function selectAllPages() {
+ if (!activeFile?.totalPages) return;
+ updateFile(activeFile.id, {
+ selectedPages: new Set(Array.from({ length: activeFile.totalPages }, (_, i) => i + 1))
+ });
+ }
- 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; },
+ function deselectAllPages() {
+ if (!activeFile) return;
+ updateFile(activeFile.id, { selectedPages: new Set([1]) });
+ }
- addFiles,
- removeFile,
- toggleExpand,
- updateFile,
- openGallery,
- openPreview,
- closeOverlay: () => { activeFile = null; galleryThumbnails = []; previewPagesCache = {}; },
- openPayment: () => { if (files.length > 0 && !isPrinting) showPayment = true; },
- hidePayment: () => { showPayment = false; },
- submitPrint,
- togglePageSelection,
- selectAllPages,
- deselectAllPages,
- requestPreviewPages: (start, count) => { if (activeFile) preloadPreviewPages(activeFile, start, count); },
- pagesLabel: _pagesLabel
- };
+ 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);
+
+ // Для офисных файлов всегда отправляем все страницы —
+ // CUPS сам разберётся
+ 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();
+ alert(res.ok ? `✅ Успешно!\n${text}` : `❌ Ошибка (${res.status}):\n${text}`);
+ } catch {
+ alert('⚠️ Не удалось связаться с сервером печати.');
+ } finally {
+ isPrinting = false;
+ showPayment = false;
+ }
+ }
+
+ 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; },
+ addFiles,
+ removeFile,
+ toggleExpand,
+ updateFile,
+ openGallery,
+ openPreview,
+ closeOverlay: () => { activeFile = null; galleryThumbnails = []; previewPagesCache = {}; },
+ openPayment: () => { if (files.length > 0 && !isPrinting) showPayment = true; },
+ hidePayment: () => { showPayment = false; },
+ submitPrint,
+ togglePageSelection,
+ selectAllPages,
+ deselectAllPages,
+ requestPreviewPages: (start, count) => { if (activeFile) preloadPreviewPages(activeFile, start, count); },
+ pagesLabel: _pagesLabel
+ };
}