@pallasoft/ps-file-components 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-document-scanner-loader.d.ts +1 -1
- package/dist/components/ps-document-scanner-sheet.d.ts +1 -1
- package/dist/components/ps-document-scanner-sheet.js +10 -24
- package/dist/components/ps-document-scanner.d.ts +1 -1
- package/dist/components/ps-document-scanner.js +2 -2
- package/dist/components/ps-scan-preview.d.ts +6 -2
- package/dist/components/ps-scan-preview.js +3 -2
- package/dist/constants/ps-document-scanner-labels.d.ts +2 -1
- package/dist/constants/ps-document-scanner-labels.js +2 -1
- package/dist/constants/ps-scan-config.d.ts +4 -1
- package/dist/constants/ps-scan-config.js +5 -1
- package/dist/constants/ps-scan-format.d.ts +5 -0
- package/dist/constants/ps-scan-format.js +7 -0
- package/dist/esm/components/ps-document-scanner-sheet.js +10 -24
- package/dist/esm/components/ps-document-scanner.js +2 -2
- package/dist/esm/components/ps-scan-preview.js +3 -2
- package/dist/esm/constants/ps-document-scanner-labels.js +2 -1
- package/dist/esm/constants/ps-scan-config.js +5 -1
- package/dist/esm/constants/ps-scan-format.js +4 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/services/ps-scan-canvas-service.js +10 -0
- package/dist/esm/services/ps-scan-file-name-service.js +5 -1
- package/dist/esm/services/ps-scan-output-service.js +23 -0
- package/dist/esm/services/ps-scan-pdf-service.js +3 -52
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/ps-document-scanner-props.d.ts +2 -0
- package/dist/models/ps-document-scanner-sheet-props.d.ts +2 -0
- package/dist/services/ps-scan-canvas-service.d.ts +1 -0
- package/dist/services/ps-scan-canvas-service.js +10 -0
- package/dist/services/ps-scan-file-name-service.d.ts +1 -0
- package/dist/services/ps-scan-file-name-service.js +5 -1
- package/dist/services/ps-scan-output-service.d.ts +6 -0
- package/dist/services/ps-scan-output-service.js +27 -0
- package/dist/services/ps-scan-pdf-service.d.ts +0 -2
- package/dist/services/ps-scan-pdf-service.js +3 -52
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PsDocumentScannerLoader: import("react").LazyExoticComponent<({ fileName, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }: import("../models/ps-document-scanner-sheet-props").PsDocumentScannerSheetProps) => import("react").ReactPortal | null>;
|
|
1
|
+
export declare const PsDocumentScannerLoader: import("react").LazyExoticComponent<({ fileName, formats, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }: import("../models/ps-document-scanner-sheet-props").PsDocumentScannerSheetProps) => import("react").ReactPortal | null>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsDocumentScannerSheetProps } from '../models/ps-document-scanner-sheet-props';
|
|
2
|
-
export declare const PsDocumentScannerSheet: ({ fileName, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }: PsDocumentScannerSheetProps) => import("react").ReactPortal | null;
|
|
2
|
+
export declare const PsDocumentScannerSheet: ({ fileName, formats, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }: PsDocumentScannerSheetProps) => import("react").ReactPortal | null;
|
|
@@ -14,8 +14,7 @@ const use_ps_focus_trap_1 = require("../hooks/use-ps-focus-trap");
|
|
|
14
14
|
const use_ps_scan_camera_1 = require("../hooks/use-ps-scan-camera");
|
|
15
15
|
const use_ps_scan_live_detection_1 = require("../hooks/use-ps-scan-live-detection");
|
|
16
16
|
const use_ps_scan_photo_1 = require("../hooks/use-ps-scan-photo");
|
|
17
|
-
const
|
|
18
|
-
const ps_scan_pdf_service_1 = require("../services/ps-scan-pdf-service");
|
|
17
|
+
const ps_scan_output_service_1 = require("../services/ps-scan-output-service");
|
|
19
18
|
const ps_scan_pipeline_service_1 = require("../services/ps-scan-pipeline-service");
|
|
20
19
|
const ps_scan_camera_view_1 = require("./ps-scan-camera-view");
|
|
21
20
|
const ps_scan_crop_editor_1 = require("./ps-scan-crop-editor");
|
|
@@ -23,7 +22,7 @@ const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
|
|
|
23
22
|
const ps_scan_page_strip_1 = require("./ps-scan-page-strip");
|
|
24
23
|
const ps_scan_permission_view_1 = require("./ps-scan-permission-view");
|
|
25
24
|
const ps_scan_preview_1 = require("./ps-scan-preview");
|
|
26
|
-
const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }) => {
|
|
25
|
+
const PsDocumentScannerSheet = ({ fileName, formats, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }) => {
|
|
27
26
|
const tier = (0, ps_core_components_1.usePsViewport)();
|
|
28
27
|
const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Modal);
|
|
29
28
|
const content = (0, react_1.useRef)(null);
|
|
@@ -146,20 +145,19 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, o
|
|
|
146
145
|
URL.revokeObjectURL(removed.url);
|
|
147
146
|
setPages((current) => current.filter((page) => page.id !== id));
|
|
148
147
|
};
|
|
149
|
-
const
|
|
150
|
-
if (isAddingPage)
|
|
148
|
+
const save = async (format) => {
|
|
149
|
+
if (result === null || isAddingPage)
|
|
151
150
|
return;
|
|
152
151
|
setIsAddingPage(true);
|
|
153
152
|
setError('');
|
|
154
153
|
try {
|
|
155
|
-
const last = await renderPage();
|
|
154
|
+
const last = pages.length === 0 ? { ...result, id: 0 } : await renderPage();
|
|
156
155
|
if (last === null)
|
|
157
156
|
return;
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]);
|
|
157
|
+
const files = await ps_scan_output_service_1.PsScanOutputService.build([...pages, last], format, fileName);
|
|
158
|
+
if (pages.length > 0)
|
|
159
|
+
URL.revokeObjectURL(last.url);
|
|
160
|
+
onComplete(files);
|
|
163
161
|
onClose();
|
|
164
162
|
}
|
|
165
163
|
catch {
|
|
@@ -169,18 +167,6 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, o
|
|
|
169
167
|
setIsAddingPage(false);
|
|
170
168
|
}
|
|
171
169
|
};
|
|
172
|
-
const use = () => {
|
|
173
|
-
if (result === null)
|
|
174
|
-
return;
|
|
175
|
-
if (pages.length > 0) {
|
|
176
|
-
void useAsPdf();
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
onComplete([
|
|
180
|
-
new File([result.blob], ps_scan_file_name_service_1.PsScanFileNameService.build(fileName), { type: 'image/jpeg' }),
|
|
181
|
-
]);
|
|
182
|
-
onClose();
|
|
183
|
-
};
|
|
184
170
|
const multiPage = pageLabels === null
|
|
185
171
|
? undefined
|
|
186
172
|
: {
|
|
@@ -196,6 +182,6 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, o
|
|
|
196
182
|
? 'fixed inset-0 flex items-center justify-center bg-black/70 p-6 backdrop-blur-sm animate-ps-fade-in'
|
|
197
183
|
: 'fixed inset-0 bg-black animate-ps-fade-in', role: 'dialog', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: (0, jsx_runtime_1.jsxs)("div", { className: isDesktop
|
|
198
184
|
? 'relative h-[min(52rem,calc(100dvh-3rem))] w-[min(76rem,calc(100vw-3rem))] overflow-hidden rounded-2xl bg-black text-white shadow-2xl ring-1 ring-white/10 animate-ps-scale-in'
|
|
199
|
-
: 'relative h-full overflow-hidden bg-black text-white', ref: content, children: [(0, jsx_runtime_1.jsx)("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), isCameraStep && showsCamera ? ((0, jsx_runtime_1.jsx)(ps_scan_camera_view_1.PsScanCameraView, { canTorch: camera.canTorch, detection: detection, flashKey: flashKey, isBusy: isCapturing, isReady: isReady, isTorchOn: camera.isTorchOn, labels: labels, onCapture: capture, onClose: onClose, onFile: accept, onToggleTorch: camera.toggleTorch, videoRef: camera.videoRef, videoTarget: camera.attachVideo })) : null, isCameraStep && !showsCamera ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ps_scan_permission_view_1.PsScanPermissionView, { labels: labels, onAllow: camera.allow, onFile: accept, status: camera.status }), (0, jsx_runtime_1.jsx)("div", { className: 'absolute left-4 top-[max(1rem,env(safe-area-inset-top))] flex gap-2', children: (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Close, label: labels.close, onClick: onClose }) })] })) : null, step === ps_scan_step_1.PsScanStep.Crop && photo !== null ? ((0, jsx_runtime_1.jsx)(ps_scan_crop_editor_1.PsScanCropEditor, { labels: labels, onAutoDetect: autoDetect, onChange: setCorners, onContinue: flatten, onRetake: retake, photo: photo })) : null, step === ps_scan_step_1.PsScanStep.Preview ? ((0, jsx_runtime_1.jsx)(ps_scan_preview_1.PsScanPreview, { error: error, labels: labels, multiPage: multiPage, onAdjust: adjust, onRetake: retake,
|
|
185
|
+
: 'relative h-full overflow-hidden bg-black text-white', ref: content, children: [(0, jsx_runtime_1.jsx)("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), isCameraStep && showsCamera ? ((0, jsx_runtime_1.jsx)(ps_scan_camera_view_1.PsScanCameraView, { canTorch: camera.canTorch, detection: detection, flashKey: flashKey, isBusy: isCapturing, isReady: isReady, isTorchOn: camera.isTorchOn, labels: labels, onCapture: capture, onClose: onClose, onFile: accept, onToggleTorch: camera.toggleTorch, videoRef: camera.videoRef, videoTarget: camera.attachVideo })) : null, isCameraStep && !showsCamera ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ps_scan_permission_view_1.PsScanPermissionView, { labels: labels, onAllow: camera.allow, onFile: accept, status: camera.status }), (0, jsx_runtime_1.jsx)("div", { className: 'absolute left-4 top-[max(1rem,env(safe-area-inset-top))] flex gap-2', children: (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Close, label: labels.close, onClick: onClose }) })] })) : null, step === ps_scan_step_1.PsScanStep.Crop && photo !== null ? ((0, jsx_runtime_1.jsx)(ps_scan_crop_editor_1.PsScanCropEditor, { labels: labels, onAutoDetect: autoDetect, onChange: setCorners, onContinue: flatten, onRetake: retake, photo: photo })) : null, step === ps_scan_step_1.PsScanStep.Preview ? ((0, jsx_runtime_1.jsx)(ps_scan_preview_1.PsScanPreview, { error: error, isSaving: isAddingPage, labels: labels, multiPage: multiPage, onAdjust: adjust, onRetake: retake, onSave: save, formats: formats, preferredFormat: ps_scan_output_service_1.PsScanOutputService.preferred(pages.length + 1, formats), result: result })) : null, isCameraStep && pageLabels !== null && pages.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: 'absolute inset-x-0 bottom-32 flex justify-center', children: (0, jsx_runtime_1.jsx)(ps_scan_page_strip_1.PsScanPageStrip, { labels: pageLabels, onRemove: removePage, pages: pages }) })) : null, isCameraStep && error !== '' ? ((0, jsx_runtime_1.jsx)("p", { className: 'absolute inset-x-4 bottom-36 mx-auto max-w-sm rounded-ps-control bg-danger px-3 py-2 text-center text-sm text-white', role: 'alert', children: error })) : null] }) }) }), document.body);
|
|
200
186
|
};
|
|
201
187
|
exports.PsDocumentScannerSheet = PsDocumentScannerSheet;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsDocumentScannerProps } from '../models/ps-document-scanner-props';
|
|
2
|
-
export declare const PsDocumentScanner: ({ fileName, isMultiPage, isOpen, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }: PsDocumentScannerProps) => import("react").JSX.Element | null;
|
|
2
|
+
export declare const PsDocumentScanner: ({ fileName, formats, isMultiPage, isOpen, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }: PsDocumentScannerProps) => import("react").JSX.Element | null;
|
|
@@ -7,10 +7,10 @@ const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
|
7
7
|
const ps_document_scanner_labels_1 = require("../constants/ps-document-scanner-labels");
|
|
8
8
|
const ps_scan_config_1 = require("../constants/ps-scan-config");
|
|
9
9
|
const ps_document_scanner_loader_1 = require("./ps-document-scanner-loader");
|
|
10
|
-
const PsDocumentScanner = ({ fileName = ps_scan_config_1.PsScanConfig.defaultFileName, isMultiPage = false, isOpen, jpegQuality = ps_scan_config_1.PsScanConfig.defaultJpegQuality, labels, maxDimension = ps_scan_config_1.PsScanConfig.defaultMaxDimension, onClose, onComplete, pageLabels, }) => {
|
|
10
|
+
const PsDocumentScanner = ({ fileName = ps_scan_config_1.PsScanConfig.defaultFileName, formats = ps_scan_config_1.PsScanConfig.formats, isMultiPage = false, isOpen, jpegQuality = ps_scan_config_1.PsScanConfig.defaultJpegQuality, labels, maxDimension = ps_scan_config_1.PsScanConfig.defaultMaxDimension, onClose, onComplete, pageLabels, }) => {
|
|
11
11
|
const isHydrated = (0, ps_core_components_1.usePsHydrated)();
|
|
12
12
|
if (!isOpen || !isHydrated)
|
|
13
13
|
return null;
|
|
14
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(ps_document_scanner_loader_1.PsDocumentScannerLoader, { fileName: fileName, jpegQuality: jpegQuality, labels: { ...ps_document_scanner_labels_1.PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, onClose: onClose, onComplete: onComplete, pageLabels: isMultiPage && pageLabels !== undefined ? pageLabels : null }) }));
|
|
14
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(ps_document_scanner_loader_1.PsDocumentScannerLoader, { fileName: fileName, formats: formats, jpegQuality: jpegQuality, labels: { ...ps_document_scanner_labels_1.PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, onClose: onClose, onComplete: onComplete, pageLabels: isMultiPage && pageLabels !== undefined ? pageLabels : null }) }));
|
|
15
15
|
};
|
|
16
16
|
exports.PsDocumentScanner = PsDocumentScanner;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
2
|
+
import { PsScanFormat } from '../constants/ps-scan-format';
|
|
2
3
|
import type { PsLabelSet } from '../models/ps-label-set';
|
|
3
4
|
import type { PsScanMultiPage } from '../models/ps-scan-multi-page';
|
|
4
5
|
import type { PsScanResult } from '../models/ps-scan-result';
|
|
5
6
|
export interface PsScanPreviewProps {
|
|
6
7
|
error: string;
|
|
7
8
|
labels: PsLabelSet<typeof PsDocumentScannerLabels>;
|
|
9
|
+
formats: readonly PsScanFormat[];
|
|
10
|
+
isSaving: boolean;
|
|
8
11
|
multiPage?: PsScanMultiPage;
|
|
9
12
|
onAdjust: () => void;
|
|
10
13
|
onRetake: () => void;
|
|
11
|
-
|
|
14
|
+
onSave: (format: PsScanFormat) => void;
|
|
15
|
+
preferredFormat: PsScanFormat;
|
|
12
16
|
result: PsScanResult | null;
|
|
13
17
|
}
|
|
14
|
-
export declare const PsScanPreview: ({ error, labels, multiPage, onAdjust, onRetake,
|
|
18
|
+
export declare const PsScanPreview: ({ error, formats, isSaving, labels, multiPage, onAdjust, onRetake, onSave, preferredFormat, result, }: PsScanPreviewProps) => import("react").JSX.Element;
|
|
@@ -4,11 +4,12 @@ exports.PsScanPreview = void 0;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
6
6
|
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
7
|
+
const ps_scan_format_1 = require("../constants/ps-scan-format");
|
|
7
8
|
const ps_scan_template_service_1 = require("../services/ps-scan-template-service");
|
|
8
9
|
const ps_scan_button_1 = require("./ps-scan-button");
|
|
9
10
|
const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
|
|
10
11
|
const ps_scan_page_strip_1 = require("./ps-scan-page-strip");
|
|
11
|
-
const PsScanPreview = ({ error, labels, multiPage, onAdjust, onRetake,
|
|
12
|
+
const PsScanPreview = ({ error, formats, isSaving, labels, multiPage, onAdjust, onRetake, onSave, preferredFormat, result, }) => ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col bg-black text-white animate-ps-fade-in', children: [(0, jsx_runtime_1.jsxs)("header", { className: 'grid shrink-0 grid-cols-[2.75rem_1fr_2.75rem] items-center gap-3 px-4 pb-1 pt-[max(1rem,env(safe-area-inset-top))]', children: [(0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Crop, label: labels.cropTitle, onClick: onAdjust }), (0, jsx_runtime_1.jsx)("h2", { className: 'truncate text-center text-base font-semibold', children: labels.previewTitle }), (0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true' })] }), (0, jsx_runtime_1.jsx)("div", { className: 'flex min-h-0 flex-1 items-center justify-center px-6 py-4', children: result === null ? ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col items-center gap-3 text-sm text-white/75', role: 'status', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsSpinner, { color: 'current', label: labels.preparing, size: 'small' }), (0, jsx_runtime_1.jsx)("span", { children: labels.preparing })] })) : ((0, jsx_runtime_1.jsx)("img", { alt: labels.previewAlt, className: 'max-h-full max-w-full rounded-sm bg-white object-contain shadow-[0_24px_60px_-20px_rgb(0_0_0/0.9)] animate-ps-fade-in', src: result.url })) }), error === '' ? null : ((0, jsx_runtime_1.jsx)("p", { className: 'mx-auto mb-2 max-w-sm rounded-ps-control bg-danger px-3 py-2 text-center text-sm text-white', role: 'alert', children: error })), multiPage === undefined || multiPage.pages.length === 0 ? null : ((0, jsx_runtime_1.jsxs)("div", { className: 'shrink-0', children: [(0, jsx_runtime_1.jsx)(ps_scan_page_strip_1.PsScanPageStrip, { labels: multiPage.labels, onRemove: multiPage.onRemovePage, pages: multiPage.pages }), (0, jsx_runtime_1.jsx)("p", { className: 'text-center text-sm text-white/75', children: ps_scan_template_service_1.PsScanTemplateService.fill(multiPage.labels.pageCount, {
|
|
12
13
|
count: multiPage.pages.length + 1,
|
|
13
|
-
}) })] })), (0, jsx_runtime_1.jsxs)("footer", { className: 'mx-auto grid w-full max-w-md shrink-0 grid-cols-2 gap-3 px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-2', children: [multiPage === undefined ? null : ((0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { className: 'col-span-2', disabled: result === null, isLoading: multiPage.isBusy, isLarge: true, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Plus }), onClick: multiPage.onAddPage, children: multiPage.labels.addPage })), (0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { isLarge: true, onClick:
|
|
14
|
+
}) })] })), (0, jsx_runtime_1.jsxs)("footer", { className: 'mx-auto grid w-full max-w-md shrink-0 grid-cols-2 gap-3 px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-2', children: [multiPage === undefined ? null : ((0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { className: 'col-span-2', disabled: result === null, isLoading: multiPage.isBusy, isLarge: true, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Plus }), onClick: multiPage.onAddPage, children: multiPage.labels.addPage })), formats.includes(ps_scan_format_1.PsScanFormat.Pdf) ? ((0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { className: formats.length === 1 ? 'col-span-2' : undefined, disabled: result === null, isLarge: true, isLoading: isSaving, isPrimary: preferredFormat === ps_scan_format_1.PsScanFormat.Pdf, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.File }), onClick: () => onSave(ps_scan_format_1.PsScanFormat.Pdf), children: labels.saveAsPdf })) : null, formats.includes(ps_scan_format_1.PsScanFormat.Png) ? ((0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { className: formats.length === 1 ? 'col-span-2' : undefined, disabled: result === null, isLarge: true, isLoading: isSaving, isPrimary: preferredFormat === ps_scan_format_1.PsScanFormat.Png, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Image }), onClick: () => onSave(ps_scan_format_1.PsScanFormat.Png), children: labels.saveAsImages })) : null, (0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { className: 'col-span-2', disabled: isSaving, isLarge: true, onClick: onRetake, children: labels.retake })] })] }));
|
|
14
15
|
exports.PsScanPreview = PsScanPreview;
|
|
@@ -41,11 +41,12 @@ export declare const PsDocumentScannerLabels: {
|
|
|
41
41
|
readonly primerTitle: "Allow camera access to scan your documents";
|
|
42
42
|
readonly processFailed: "The scan could not be prepared. Try again.";
|
|
43
43
|
readonly retake: "Retake";
|
|
44
|
+
readonly saveAsImages: "Save as images";
|
|
45
|
+
readonly saveAsPdf: "Save as PDF";
|
|
44
46
|
readonly title: "Scan document";
|
|
45
47
|
readonly torchOff: "Turn flash off";
|
|
46
48
|
readonly torchOn: "Turn flash on";
|
|
47
49
|
readonly tryAgain: "Try again";
|
|
48
50
|
readonly unavailableBody: "This device has no camera the browser can use. Choose a photo of the page instead.";
|
|
49
51
|
readonly unavailableTitle: "No camera found";
|
|
50
|
-
readonly useScan: "Use scan";
|
|
51
52
|
};
|
|
@@ -44,11 +44,12 @@ exports.PsDocumentScannerLabels = {
|
|
|
44
44
|
primerTitle: 'Allow camera access to scan your documents',
|
|
45
45
|
processFailed: 'The scan could not be prepared. Try again.',
|
|
46
46
|
retake: 'Retake',
|
|
47
|
+
saveAsImages: 'Save as images',
|
|
48
|
+
saveAsPdf: 'Save as PDF',
|
|
47
49
|
title: 'Scan document',
|
|
48
50
|
torchOff: 'Turn flash off',
|
|
49
51
|
torchOn: 'Turn flash on',
|
|
50
52
|
tryAgain: 'Try again',
|
|
51
53
|
unavailableBody: 'This device has no camera the browser can use. Choose a photo of the page instead.',
|
|
52
54
|
unavailableTitle: 'No camera found',
|
|
53
|
-
useScan: 'Use scan',
|
|
54
55
|
};
|
|
@@ -20,6 +20,7 @@ export declare const PsScanConfig: {
|
|
|
20
20
|
readonly detectMinAreaRatio: 0.1;
|
|
21
21
|
readonly detectMinQuadFill: 0.88;
|
|
22
22
|
readonly detectMinSideRatio: 0.06;
|
|
23
|
+
readonly formats: readonly ["pdf", "png"];
|
|
23
24
|
readonly guideArm: 36;
|
|
24
25
|
readonly guideArmRatio: 0.18;
|
|
25
26
|
readonly guideAspect: 0.72;
|
|
@@ -38,7 +39,9 @@ export declare const PsScanConfig: {
|
|
|
38
39
|
readonly loupeZoom: 2.5;
|
|
39
40
|
readonly minCornerGap: 0.02;
|
|
40
41
|
readonly multiPageMaxDimension: 2400;
|
|
41
|
-
readonly
|
|
42
|
+
readonly jpegType: "image/jpeg";
|
|
43
|
+
readonly pdfType: "application/pdf";
|
|
44
|
+
readonly pngType: "image/png";
|
|
42
45
|
readonly polygonEpsilons: readonly [0.008, 0.012, 0.018, 0.026, 0.036, 0.05, 0.07];
|
|
43
46
|
readonly torchConstraint: "torch";
|
|
44
47
|
readonly warpChunkPixels: 400000;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsScanConfig = void 0;
|
|
4
|
+
const ps_scan_format_1 = require("./ps-scan-format");
|
|
4
5
|
exports.PsScanConfig = {
|
|
5
6
|
cameraPermission: 'camera',
|
|
6
7
|
cannyHighPercentile: 0.9,
|
|
@@ -23,6 +24,7 @@ exports.PsScanConfig = {
|
|
|
23
24
|
detectMinAreaRatio: 0.1,
|
|
24
25
|
detectMinQuadFill: 0.88,
|
|
25
26
|
detectMinSideRatio: 0.06,
|
|
27
|
+
formats: [ps_scan_format_1.PsScanFormat.Pdf, ps_scan_format_1.PsScanFormat.Png],
|
|
26
28
|
guideArm: 36,
|
|
27
29
|
guideArmRatio: 0.18,
|
|
28
30
|
guideAspect: 0.72,
|
|
@@ -41,7 +43,9 @@ exports.PsScanConfig = {
|
|
|
41
43
|
loupeZoom: 2.5,
|
|
42
44
|
minCornerGap: 0.02,
|
|
43
45
|
multiPageMaxDimension: 2400,
|
|
44
|
-
|
|
46
|
+
jpegType: 'image/jpeg',
|
|
47
|
+
pdfType: 'application/pdf',
|
|
48
|
+
pngType: 'image/png',
|
|
45
49
|
polygonEpsilons: [0.008, 0.012, 0.018, 0.026, 0.036, 0.05, 0.07],
|
|
46
50
|
torchConstraint: 'torch',
|
|
47
51
|
warpChunkPixels: 400000,
|
|
@@ -11,8 +11,7 @@ import { usePsFocusTrap } from '../hooks/use-ps-focus-trap';
|
|
|
11
11
|
import { usePsScanCamera } from '../hooks/use-ps-scan-camera';
|
|
12
12
|
import { usePsScanLiveDetection } from '../hooks/use-ps-scan-live-detection';
|
|
13
13
|
import { usePsScanPhoto } from '../hooks/use-ps-scan-photo';
|
|
14
|
-
import {
|
|
15
|
-
import { PsScanPdfService } from '../services/ps-scan-pdf-service';
|
|
14
|
+
import { PsScanOutputService } from '../services/ps-scan-output-service';
|
|
16
15
|
import { PsScanPipelineService } from '../services/ps-scan-pipeline-service';
|
|
17
16
|
import { PsScanCameraView } from './ps-scan-camera-view';
|
|
18
17
|
import { PsScanCropEditor } from './ps-scan-crop-editor';
|
|
@@ -20,7 +19,7 @@ import { PsScanIconButton } from './ps-scan-icon-button';
|
|
|
20
19
|
import { PsScanPageStrip } from './ps-scan-page-strip';
|
|
21
20
|
import { PsScanPermissionView } from './ps-scan-permission-view';
|
|
22
21
|
import { PsScanPreview } from './ps-scan-preview';
|
|
23
|
-
export const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }) => {
|
|
22
|
+
export const PsDocumentScannerSheet = ({ fileName, formats, jpegQuality, labels, maxDimension, onClose, onComplete, pageLabels, }) => {
|
|
24
23
|
const tier = usePsViewport();
|
|
25
24
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Modal);
|
|
26
25
|
const content = useRef(null);
|
|
@@ -143,20 +142,19 @@ export const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimen
|
|
|
143
142
|
URL.revokeObjectURL(removed.url);
|
|
144
143
|
setPages((current) => current.filter((page) => page.id !== id));
|
|
145
144
|
};
|
|
146
|
-
const
|
|
147
|
-
if (isAddingPage)
|
|
145
|
+
const save = async (format) => {
|
|
146
|
+
if (result === null || isAddingPage)
|
|
148
147
|
return;
|
|
149
148
|
setIsAddingPage(true);
|
|
150
149
|
setError('');
|
|
151
150
|
try {
|
|
152
|
-
const last = await renderPage();
|
|
151
|
+
const last = pages.length === 0 ? { ...result, id: 0 } : await renderPage();
|
|
153
152
|
if (last === null)
|
|
154
153
|
return;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
]);
|
|
154
|
+
const files = await PsScanOutputService.build([...pages, last], format, fileName);
|
|
155
|
+
if (pages.length > 0)
|
|
156
|
+
URL.revokeObjectURL(last.url);
|
|
157
|
+
onComplete(files);
|
|
160
158
|
onClose();
|
|
161
159
|
}
|
|
162
160
|
catch {
|
|
@@ -166,18 +164,6 @@ export const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimen
|
|
|
166
164
|
setIsAddingPage(false);
|
|
167
165
|
}
|
|
168
166
|
};
|
|
169
|
-
const use = () => {
|
|
170
|
-
if (result === null)
|
|
171
|
-
return;
|
|
172
|
-
if (pages.length > 0) {
|
|
173
|
-
void useAsPdf();
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
onComplete([
|
|
177
|
-
new File([result.blob], PsScanFileNameService.build(fileName), { type: 'image/jpeg' }),
|
|
178
|
-
]);
|
|
179
|
-
onClose();
|
|
180
|
-
};
|
|
181
167
|
const multiPage = pageLabels === null
|
|
182
168
|
? undefined
|
|
183
169
|
: {
|
|
@@ -193,5 +179,5 @@ export const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimen
|
|
|
193
179
|
? 'fixed inset-0 flex items-center justify-center bg-black/70 p-6 backdrop-blur-sm animate-ps-fade-in'
|
|
194
180
|
: 'fixed inset-0 bg-black animate-ps-fade-in', role: 'dialog', style: { zIndex }, children: _jsx(PsLayer, { zIndex: zIndex, children: _jsxs("div", { className: isDesktop
|
|
195
181
|
? 'relative h-[min(52rem,calc(100dvh-3rem))] w-[min(76rem,calc(100vw-3rem))] overflow-hidden rounded-2xl bg-black text-white shadow-2xl ring-1 ring-white/10 animate-ps-scale-in'
|
|
196
|
-
: 'relative h-full overflow-hidden bg-black text-white', ref: content, children: [_jsx("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), isCameraStep && showsCamera ? (_jsx(PsScanCameraView, { canTorch: camera.canTorch, detection: detection, flashKey: flashKey, isBusy: isCapturing, isReady: isReady, isTorchOn: camera.isTorchOn, labels: labels, onCapture: capture, onClose: onClose, onFile: accept, onToggleTorch: camera.toggleTorch, videoRef: camera.videoRef, videoTarget: camera.attachVideo })) : null, isCameraStep && !showsCamera ? (_jsxs(_Fragment, { children: [_jsx(PsScanPermissionView, { labels: labels, onAllow: camera.allow, onFile: accept, status: camera.status }), _jsx("div", { className: 'absolute left-4 top-[max(1rem,env(safe-area-inset-top))] flex gap-2', children: _jsx(PsScanIconButton, { iconKey: PsIconKey.Close, label: labels.close, onClick: onClose }) })] })) : null, step === PsScanStep.Crop && photo !== null ? (_jsx(PsScanCropEditor, { labels: labels, onAutoDetect: autoDetect, onChange: setCorners, onContinue: flatten, onRetake: retake, photo: photo })) : null, step === PsScanStep.Preview ? (_jsx(PsScanPreview, { error: error, labels: labels, multiPage: multiPage, onAdjust: adjust, onRetake: retake,
|
|
182
|
+
: 'relative h-full overflow-hidden bg-black text-white', ref: content, children: [_jsx("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), isCameraStep && showsCamera ? (_jsx(PsScanCameraView, { canTorch: camera.canTorch, detection: detection, flashKey: flashKey, isBusy: isCapturing, isReady: isReady, isTorchOn: camera.isTorchOn, labels: labels, onCapture: capture, onClose: onClose, onFile: accept, onToggleTorch: camera.toggleTorch, videoRef: camera.videoRef, videoTarget: camera.attachVideo })) : null, isCameraStep && !showsCamera ? (_jsxs(_Fragment, { children: [_jsx(PsScanPermissionView, { labels: labels, onAllow: camera.allow, onFile: accept, status: camera.status }), _jsx("div", { className: 'absolute left-4 top-[max(1rem,env(safe-area-inset-top))] flex gap-2', children: _jsx(PsScanIconButton, { iconKey: PsIconKey.Close, label: labels.close, onClick: onClose }) })] })) : null, step === PsScanStep.Crop && photo !== null ? (_jsx(PsScanCropEditor, { labels: labels, onAutoDetect: autoDetect, onChange: setCorners, onContinue: flatten, onRetake: retake, photo: photo })) : null, step === PsScanStep.Preview ? (_jsx(PsScanPreview, { error: error, isSaving: isAddingPage, labels: labels, multiPage: multiPage, onAdjust: adjust, onRetake: retake, onSave: save, formats: formats, preferredFormat: PsScanOutputService.preferred(pages.length + 1, formats), result: result })) : null, isCameraStep && pageLabels !== null && pages.length > 0 ? (_jsx("div", { className: 'absolute inset-x-0 bottom-32 flex justify-center', children: _jsx(PsScanPageStrip, { labels: pageLabels, onRemove: removePage, pages: pages }) })) : null, isCameraStep && error !== '' ? (_jsx("p", { className: 'absolute inset-x-4 bottom-36 mx-auto max-w-sm rounded-ps-control bg-danger px-3 py-2 text-center text-sm text-white', role: 'alert', children: error })) : null] }) }) }), document.body);
|
|
197
183
|
};
|
|
@@ -4,9 +4,9 @@ import { usePsHydrated } from '@pallasoft/ps-core-components';
|
|
|
4
4
|
import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
5
5
|
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
6
6
|
import { PsDocumentScannerLoader } from './ps-document-scanner-loader';
|
|
7
|
-
export const PsDocumentScanner = ({ fileName = PsScanConfig.defaultFileName, isMultiPage = false, isOpen, jpegQuality = PsScanConfig.defaultJpegQuality, labels, maxDimension = PsScanConfig.defaultMaxDimension, onClose, onComplete, pageLabels, }) => {
|
|
7
|
+
export const PsDocumentScanner = ({ fileName = PsScanConfig.defaultFileName, formats = PsScanConfig.formats, isMultiPage = false, isOpen, jpegQuality = PsScanConfig.defaultJpegQuality, labels, maxDimension = PsScanConfig.defaultMaxDimension, onClose, onComplete, pageLabels, }) => {
|
|
8
8
|
const isHydrated = usePsHydrated();
|
|
9
9
|
if (!isOpen || !isHydrated)
|
|
10
10
|
return null;
|
|
11
|
-
return (_jsx(Suspense, { fallback: null, children: _jsx(PsDocumentScannerLoader, { fileName: fileName, jpegQuality: jpegQuality, labels: { ...PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, onClose: onClose, onComplete: onComplete, pageLabels: isMultiPage && pageLabels !== undefined ? pageLabels : null }) }));
|
|
11
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(PsDocumentScannerLoader, { fileName: fileName, formats: formats, jpegQuality: jpegQuality, labels: { ...PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, onClose: onClose, onComplete: onComplete, pageLabels: isMultiPage && pageLabels !== undefined ? pageLabels : null }) }));
|
|
12
12
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
3
3
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsScanFormat } from '../constants/ps-scan-format';
|
|
4
5
|
import { PsScanTemplateService } from '../services/ps-scan-template-service';
|
|
5
6
|
import { PsScanButton } from './ps-scan-button';
|
|
6
7
|
import { PsScanIconButton } from './ps-scan-icon-button';
|
|
7
8
|
import { PsScanPageStrip } from './ps-scan-page-strip';
|
|
8
|
-
export const PsScanPreview = ({ error, labels, multiPage, onAdjust, onRetake,
|
|
9
|
+
export const PsScanPreview = ({ error, formats, isSaving, labels, multiPage, onAdjust, onRetake, onSave, preferredFormat, result, }) => (_jsxs("div", { className: 'flex h-full min-h-0 flex-col bg-black text-white animate-ps-fade-in', children: [_jsxs("header", { className: 'grid shrink-0 grid-cols-[2.75rem_1fr_2.75rem] items-center gap-3 px-4 pb-1 pt-[max(1rem,env(safe-area-inset-top))]', children: [_jsx(PsScanIconButton, { iconKey: PsIconKey.Crop, label: labels.cropTitle, onClick: onAdjust }), _jsx("h2", { className: 'truncate text-center text-base font-semibold', children: labels.previewTitle }), _jsx("span", { "aria-hidden": 'true' })] }), _jsx("div", { className: 'flex min-h-0 flex-1 items-center justify-center px-6 py-4', children: result === null ? (_jsxs("div", { className: 'flex flex-col items-center gap-3 text-sm text-white/75', role: 'status', children: [_jsx(PsSpinner, { color: 'current', label: labels.preparing, size: 'small' }), _jsx("span", { children: labels.preparing })] })) : (_jsx("img", { alt: labels.previewAlt, className: 'max-h-full max-w-full rounded-sm bg-white object-contain shadow-[0_24px_60px_-20px_rgb(0_0_0/0.9)] animate-ps-fade-in', src: result.url })) }), error === '' ? null : (_jsx("p", { className: 'mx-auto mb-2 max-w-sm rounded-ps-control bg-danger px-3 py-2 text-center text-sm text-white', role: 'alert', children: error })), multiPage === undefined || multiPage.pages.length === 0 ? null : (_jsxs("div", { className: 'shrink-0', children: [_jsx(PsScanPageStrip, { labels: multiPage.labels, onRemove: multiPage.onRemovePage, pages: multiPage.pages }), _jsx("p", { className: 'text-center text-sm text-white/75', children: PsScanTemplateService.fill(multiPage.labels.pageCount, {
|
|
9
10
|
count: multiPage.pages.length + 1,
|
|
10
|
-
}) })] })), _jsxs("footer", { className: 'mx-auto grid w-full max-w-md shrink-0 grid-cols-2 gap-3 px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-2', children: [multiPage === undefined ? null : (_jsx(PsScanButton, { className: 'col-span-2', disabled: result === null, isLoading: multiPage.isBusy, isLarge: true, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Plus }), onClick: multiPage.onAddPage, children: multiPage.labels.addPage })), _jsx(PsScanButton, { isLarge: true, onClick:
|
|
11
|
+
}) })] })), _jsxs("footer", { className: 'mx-auto grid w-full max-w-md shrink-0 grid-cols-2 gap-3 px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-2', children: [multiPage === undefined ? null : (_jsx(PsScanButton, { className: 'col-span-2', disabled: result === null, isLoading: multiPage.isBusy, isLarge: true, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Plus }), onClick: multiPage.onAddPage, children: multiPage.labels.addPage })), formats.includes(PsScanFormat.Pdf) ? (_jsx(PsScanButton, { className: formats.length === 1 ? 'col-span-2' : undefined, disabled: result === null, isLarge: true, isLoading: isSaving, isPrimary: preferredFormat === PsScanFormat.Pdf, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.File }), onClick: () => onSave(PsScanFormat.Pdf), children: labels.saveAsPdf })) : null, formats.includes(PsScanFormat.Png) ? (_jsx(PsScanButton, { className: formats.length === 1 ? 'col-span-2' : undefined, disabled: result === null, isLarge: true, isLoading: isSaving, isPrimary: preferredFormat === PsScanFormat.Png, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Image }), onClick: () => onSave(PsScanFormat.Png), children: labels.saveAsImages })) : null, _jsx(PsScanButton, { className: 'col-span-2', disabled: isSaving, isLarge: true, onClick: onRetake, children: labels.retake })] })] }));
|
|
@@ -41,11 +41,12 @@ export const PsDocumentScannerLabels = {
|
|
|
41
41
|
primerTitle: 'Allow camera access to scan your documents',
|
|
42
42
|
processFailed: 'The scan could not be prepared. Try again.',
|
|
43
43
|
retake: 'Retake',
|
|
44
|
+
saveAsImages: 'Save as images',
|
|
45
|
+
saveAsPdf: 'Save as PDF',
|
|
44
46
|
title: 'Scan document',
|
|
45
47
|
torchOff: 'Turn flash off',
|
|
46
48
|
torchOn: 'Turn flash on',
|
|
47
49
|
tryAgain: 'Try again',
|
|
48
50
|
unavailableBody: 'This device has no camera the browser can use. Choose a photo of the page instead.',
|
|
49
51
|
unavailableTitle: 'No camera found',
|
|
50
|
-
useScan: 'Use scan',
|
|
51
52
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PsScanFormat } from './ps-scan-format';
|
|
1
2
|
export const PsScanConfig = {
|
|
2
3
|
cameraPermission: 'camera',
|
|
3
4
|
cannyHighPercentile: 0.9,
|
|
@@ -20,6 +21,7 @@ export const PsScanConfig = {
|
|
|
20
21
|
detectMinAreaRatio: 0.1,
|
|
21
22
|
detectMinQuadFill: 0.88,
|
|
22
23
|
detectMinSideRatio: 0.06,
|
|
24
|
+
formats: [PsScanFormat.Pdf, PsScanFormat.Png],
|
|
23
25
|
guideArm: 36,
|
|
24
26
|
guideArmRatio: 0.18,
|
|
25
27
|
guideAspect: 0.72,
|
|
@@ -38,7 +40,9 @@ export const PsScanConfig = {
|
|
|
38
40
|
loupeZoom: 2.5,
|
|
39
41
|
minCornerGap: 0.02,
|
|
40
42
|
multiPageMaxDimension: 2400,
|
|
41
|
-
|
|
43
|
+
jpegType: 'image/jpeg',
|
|
44
|
+
pdfType: 'application/pdf',
|
|
45
|
+
pngType: 'image/png',
|
|
42
46
|
polygonEpsilons: [0.008, 0.012, 0.018, 0.026, 0.036, 0.05, 0.07],
|
|
43
47
|
torchConstraint: 'torch',
|
|
44
48
|
warpChunkPixels: 400000,
|
package/dist/esm/index.js
CHANGED
|
@@ -74,6 +74,7 @@ export * from './hooks/use-ps-in-view';
|
|
|
74
74
|
export * from './hooks/use-ps-upload-queue';
|
|
75
75
|
export * from './components/ps-document-scanner';
|
|
76
76
|
export * from './constants/ps-document-scanner-labels';
|
|
77
|
+
export * from './constants/ps-scan-format';
|
|
77
78
|
export * from './models/ps-document-scanner-page-labels';
|
|
78
79
|
export * from './models/ps-document-scanner-props';
|
|
79
80
|
export * from './models/ps-file-picker-scan';
|
|
@@ -28,6 +28,16 @@ export class PsScanCanvasService {
|
|
|
28
28
|
return await new Promise((resolve, reject) => canvas.toBlob((blob) => (blob === null ? reject(new Error('encode')) : resolve(blob)), 'image/jpeg', quality));
|
|
29
29
|
return await canvas.convertToBlob({ quality, type: 'image/jpeg' });
|
|
30
30
|
}
|
|
31
|
+
static async toPng(blob) {
|
|
32
|
+
const bitmap = await createImageBitmap(blob);
|
|
33
|
+
const context = PsScanCanvasService.context(bitmap.width, bitmap.height);
|
|
34
|
+
context.drawImage(bitmap, 0, 0);
|
|
35
|
+
bitmap.close();
|
|
36
|
+
const canvas = context.canvas;
|
|
37
|
+
if (canvas instanceof HTMLCanvasElement)
|
|
38
|
+
return await new Promise((resolve, reject) => canvas.toBlob((png) => (png === null ? reject(new Error('encode')) : resolve(png)), PsScanConfig.pngType));
|
|
39
|
+
return await canvas.convertToBlob({ type: PsScanConfig.pngType });
|
|
40
|
+
}
|
|
31
41
|
static async fromVideo(video, quality) {
|
|
32
42
|
const context = PsScanCanvasService.context(video.videoWidth, video.videoHeight);
|
|
33
43
|
context.drawImage(video, 0, 0);
|
|
@@ -3,6 +3,10 @@ export class PsScanFileNameService {
|
|
|
3
3
|
static build(base) {
|
|
4
4
|
return `${PsScanFileNameService.sanitize(base)}.jpg`;
|
|
5
5
|
}
|
|
6
|
+
static buildPng(base, index, count) {
|
|
7
|
+
const name = PsScanFileNameService.sanitize(base);
|
|
8
|
+
return count > 1 ? `${name}-${index + 1}.png` : `${name}.png`;
|
|
9
|
+
}
|
|
6
10
|
static buildPdf(base) {
|
|
7
11
|
return `${PsScanFileNameService.sanitize(base)}.pdf`;
|
|
8
12
|
}
|
|
@@ -10,7 +14,7 @@ export class PsScanFileNameService {
|
|
|
10
14
|
const cleaned = Array.from(base)
|
|
11
15
|
.filter((char) => char.charCodeAt(0) >= 32)
|
|
12
16
|
.join('')
|
|
13
|
-
.replace(/\.(pdf|jpe?g)$/i, '')
|
|
17
|
+
.replace(/\.(pdf|jpe?g|png)$/i, '')
|
|
14
18
|
.replace(/[\\/:*?"<>|]+/g, '-')
|
|
15
19
|
.replace(/\s+/g, ' ')
|
|
16
20
|
.replace(/^[\s.-]+|[\s.-]+$/g, '');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
2
|
+
import { PsScanFormat } from '../constants/ps-scan-format';
|
|
3
|
+
import { PsScanCanvasService } from './ps-scan-canvas-service';
|
|
4
|
+
import { PsScanFileNameService } from './ps-scan-file-name-service';
|
|
5
|
+
import { PsScanPdfService } from './ps-scan-pdf-service';
|
|
6
|
+
export class PsScanOutputService {
|
|
7
|
+
static async build(pages, format, fileName) {
|
|
8
|
+
if (format === PsScanFormat.Pdf)
|
|
9
|
+
return [
|
|
10
|
+
new File([await PsScanPdfService.build(pages)], PsScanFileNameService.buildPdf(fileName), {
|
|
11
|
+
type: PsScanConfig.pdfType,
|
|
12
|
+
}),
|
|
13
|
+
];
|
|
14
|
+
const files = [];
|
|
15
|
+
for (const [index, page] of pages.entries())
|
|
16
|
+
files.push(new File([await PsScanCanvasService.toPng(page.blob)], PsScanFileNameService.buildPng(fileName, index, pages.length), { type: PsScanConfig.pngType }));
|
|
17
|
+
return files;
|
|
18
|
+
}
|
|
19
|
+
static preferred(pageCount, formats) {
|
|
20
|
+
const wanted = pageCount > 1 ? PsScanFormat.Pdf : PsScanFormat.Png;
|
|
21
|
+
return formats.includes(wanted) ? wanted : formats[0];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Common } from '@pallasoft/ps-utilities';
|
|
1
2
|
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
2
3
|
export class PsScanPdfService {
|
|
3
4
|
static async build(pages) {
|
|
@@ -11,59 +12,9 @@ export class PsScanPdfService {
|
|
|
11
12
|
return PsScanPdfService.fromJpegs(loaded);
|
|
12
13
|
}
|
|
13
14
|
static fromJpegs(pages) {
|
|
14
|
-
|
|
15
|
-
const offsets = [];
|
|
16
|
-
let length = 0;
|
|
17
|
-
const push = (chunk) => {
|
|
18
|
-
chunks.push(chunk);
|
|
19
|
-
length += chunk.length;
|
|
20
|
-
};
|
|
21
|
-
const begin = (objectNumber) => {
|
|
22
|
-
offsets[objectNumber] = length;
|
|
23
|
-
push(PsScanPdfService.text(`${objectNumber} 0 obj\n`));
|
|
24
|
-
};
|
|
25
|
-
push(PsScanPdfService.text('%PDF-1.4\n'));
|
|
26
|
-
push(new Uint8Array([0x25, 0xe2, 0xe3, 0xcf, 0xd3, 0x0a]));
|
|
27
|
-
begin(1);
|
|
28
|
-
push(PsScanPdfService.text('<< /Type /Catalog /Pages 2 0 R >>\nendobj\n'));
|
|
29
|
-
const kids = pages.map((_, index) => `${PsScanPdfService.pageObject(index)} 0 R`).join(' ');
|
|
30
|
-
begin(2);
|
|
31
|
-
push(PsScanPdfService.text(`<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>\nendobj\n`));
|
|
32
|
-
pages.forEach((page, index) => {
|
|
33
|
-
const size = PsScanPdfService.pageSize(page);
|
|
34
|
-
const pageNumber = PsScanPdfService.pageObject(index);
|
|
35
|
-
const content = `q ${size.width} 0 0 ${size.height} 0 0 cm /Im0 Do Q`;
|
|
36
|
-
begin(pageNumber);
|
|
37
|
-
push(PsScanPdfService.text(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${size.width} ${size.height}] /Resources << /XObject << /Im0 ${pageNumber + 1} 0 R >> >> /Contents ${pageNumber + 2} 0 R >>\nendobj\n`));
|
|
38
|
-
begin(pageNumber + 1);
|
|
39
|
-
push(PsScanPdfService.text(`<< /Type /XObject /Subtype /Image /Width ${page.width} /Height ${page.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${page.bytes.length} >>\nstream\n`));
|
|
40
|
-
push(page.bytes);
|
|
41
|
-
push(PsScanPdfService.text('\nendstream\nendobj\n'));
|
|
42
|
-
begin(pageNumber + 2);
|
|
43
|
-
push(PsScanPdfService.text(`<< /Length ${content.length} >>\nstream\n${content}\nendstream\nendobj\n`));
|
|
44
|
-
});
|
|
45
|
-
const count = 3 + pages.length * 3;
|
|
46
|
-
const entries = Array.from({ length: count - 1 }, (_, index) => `${String(offsets[index + 1]).padStart(10, '0')} 00000 n \n`).join('');
|
|
47
|
-
push(PsScanPdfService.text(`xref\n0 ${count}\n0000000000 65535 f \n${entries}trailer\n<< /Size ${count} /Root 1 0 R >>\nstartxref\n${length}\n%%EOF\n`));
|
|
48
|
-
const merged = new Uint8Array(length);
|
|
49
|
-
let cursor = 0;
|
|
50
|
-
for (const chunk of chunks) {
|
|
51
|
-
merged.set(chunk, cursor);
|
|
52
|
-
cursor += chunk.length;
|
|
53
|
-
}
|
|
54
|
-
return new Blob([merged], { type: 'application/pdf' });
|
|
15
|
+
return new Blob([Common.JpegPdfService.fromJpegs(pages)], { type: PsScanConfig.pdfType });
|
|
55
16
|
}
|
|
56
17
|
static pageSize(page) {
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
height: Math.round(page.height * scale * 100) / 100,
|
|
60
|
-
width: Math.round(page.width * scale * 100) / 100,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
static pageObject(index) {
|
|
64
|
-
return 3 + index * 3;
|
|
65
|
-
}
|
|
66
|
-
static text(value) {
|
|
67
|
-
return new TextEncoder().encode(value);
|
|
18
|
+
return Common.JpegPdfService.pageSize(page);
|
|
68
19
|
}
|
|
69
20
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ export * from './hooks/use-ps-in-view';
|
|
|
74
74
|
export * from './hooks/use-ps-upload-queue';
|
|
75
75
|
export * from './components/ps-document-scanner';
|
|
76
76
|
export * from './constants/ps-document-scanner-labels';
|
|
77
|
+
export * from './constants/ps-scan-format';
|
|
77
78
|
export * from './models/ps-document-scanner-page-labels';
|
|
78
79
|
export * from './models/ps-document-scanner-props';
|
|
79
80
|
export * from './models/ps-file-picker-scan';
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ __exportStar(require("./hooks/use-ps-in-view"), exports);
|
|
|
90
90
|
__exportStar(require("./hooks/use-ps-upload-queue"), exports);
|
|
91
91
|
__exportStar(require("./components/ps-document-scanner"), exports);
|
|
92
92
|
__exportStar(require("./constants/ps-document-scanner-labels"), exports);
|
|
93
|
+
__exportStar(require("./constants/ps-scan-format"), exports);
|
|
93
94
|
__exportStar(require("./models/ps-document-scanner-page-labels"), exports);
|
|
94
95
|
__exportStar(require("./models/ps-document-scanner-props"), exports);
|
|
95
96
|
__exportStar(require("./models/ps-file-picker-scan"), exports);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
2
|
+
import type { PsScanFormat } from '../constants/ps-scan-format';
|
|
2
3
|
import type { PsDocumentScannerPageLabels } from './ps-document-scanner-page-labels';
|
|
3
4
|
import type { PsLabelSet } from './ps-label-set';
|
|
4
5
|
export interface PsDocumentScannerProps {
|
|
5
6
|
className?: string;
|
|
6
7
|
fileName?: string;
|
|
8
|
+
formats?: readonly PsScanFormat[];
|
|
7
9
|
isMultiPage?: boolean;
|
|
8
10
|
isOpen: boolean;
|
|
9
11
|
jpegQuality?: number;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
2
|
+
import type { PsScanFormat } from '../constants/ps-scan-format';
|
|
2
3
|
import type { PsDocumentScannerPageLabels } from './ps-document-scanner-page-labels';
|
|
3
4
|
import type { PsLabelSet } from './ps-label-set';
|
|
4
5
|
export interface PsDocumentScannerSheetProps {
|
|
5
6
|
fileName: string;
|
|
7
|
+
formats: readonly PsScanFormat[];
|
|
6
8
|
jpegQuality: number;
|
|
7
9
|
labels: PsLabelSet<typeof PsDocumentScannerLabels>;
|
|
8
10
|
maxDimension: number;
|
|
@@ -3,6 +3,7 @@ import type { PsScanRaster } from '../models/ps-scan-raster';
|
|
|
3
3
|
export declare class PsScanCanvasService {
|
|
4
4
|
static decode(blob: Blob, maxDimension: number): Promise<PsScanDecodedImage>;
|
|
5
5
|
static encode(raster: PsScanRaster, quality: number): Promise<Blob>;
|
|
6
|
+
static toPng(blob: Blob): Promise<Blob>;
|
|
6
7
|
static fromVideo(video: HTMLVideoElement, quality: number): Promise<Blob>;
|
|
7
8
|
static sample(video: HTMLVideoElement, maxDimension: number): PsScanRaster;
|
|
8
9
|
private static context;
|
|
@@ -31,6 +31,16 @@ class PsScanCanvasService {
|
|
|
31
31
|
return await new Promise((resolve, reject) => canvas.toBlob((blob) => (blob === null ? reject(new Error('encode')) : resolve(blob)), 'image/jpeg', quality));
|
|
32
32
|
return await canvas.convertToBlob({ quality, type: 'image/jpeg' });
|
|
33
33
|
}
|
|
34
|
+
static async toPng(blob) {
|
|
35
|
+
const bitmap = await createImageBitmap(blob);
|
|
36
|
+
const context = PsScanCanvasService.context(bitmap.width, bitmap.height);
|
|
37
|
+
context.drawImage(bitmap, 0, 0);
|
|
38
|
+
bitmap.close();
|
|
39
|
+
const canvas = context.canvas;
|
|
40
|
+
if (canvas instanceof HTMLCanvasElement)
|
|
41
|
+
return await new Promise((resolve, reject) => canvas.toBlob((png) => (png === null ? reject(new Error('encode')) : resolve(png)), ps_scan_config_1.PsScanConfig.pngType));
|
|
42
|
+
return await canvas.convertToBlob({ type: ps_scan_config_1.PsScanConfig.pngType });
|
|
43
|
+
}
|
|
34
44
|
static async fromVideo(video, quality) {
|
|
35
45
|
const context = PsScanCanvasService.context(video.videoWidth, video.videoHeight);
|
|
36
46
|
context.drawImage(video, 0, 0);
|
|
@@ -6,6 +6,10 @@ class PsScanFileNameService {
|
|
|
6
6
|
static build(base) {
|
|
7
7
|
return `${PsScanFileNameService.sanitize(base)}.jpg`;
|
|
8
8
|
}
|
|
9
|
+
static buildPng(base, index, count) {
|
|
10
|
+
const name = PsScanFileNameService.sanitize(base);
|
|
11
|
+
return count > 1 ? `${name}-${index + 1}.png` : `${name}.png`;
|
|
12
|
+
}
|
|
9
13
|
static buildPdf(base) {
|
|
10
14
|
return `${PsScanFileNameService.sanitize(base)}.pdf`;
|
|
11
15
|
}
|
|
@@ -13,7 +17,7 @@ class PsScanFileNameService {
|
|
|
13
17
|
const cleaned = Array.from(base)
|
|
14
18
|
.filter((char) => char.charCodeAt(0) >= 32)
|
|
15
19
|
.join('')
|
|
16
|
-
.replace(/\.(pdf|jpe?g)$/i, '')
|
|
20
|
+
.replace(/\.(pdf|jpe?g|png)$/i, '')
|
|
17
21
|
.replace(/[\\/:*?"<>|]+/g, '-')
|
|
18
22
|
.replace(/\s+/g, ' ')
|
|
19
23
|
.replace(/^[\s.-]+|[\s.-]+$/g, '');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { PsScanFormat } from '../constants/ps-scan-format';
|
|
2
|
+
import type { PsScanPage } from '../models/ps-scan-page';
|
|
3
|
+
export declare class PsScanOutputService {
|
|
4
|
+
static build(pages: PsScanPage[], format: PsScanFormat, fileName: string): Promise<File[]>;
|
|
5
|
+
static preferred(pageCount: number, formats: readonly PsScanFormat[]): PsScanFormat;
|
|
6
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsScanOutputService = void 0;
|
|
4
|
+
const ps_scan_config_1 = require("../constants/ps-scan-config");
|
|
5
|
+
const ps_scan_format_1 = require("../constants/ps-scan-format");
|
|
6
|
+
const ps_scan_canvas_service_1 = require("./ps-scan-canvas-service");
|
|
7
|
+
const ps_scan_file_name_service_1 = require("./ps-scan-file-name-service");
|
|
8
|
+
const ps_scan_pdf_service_1 = require("./ps-scan-pdf-service");
|
|
9
|
+
class PsScanOutputService {
|
|
10
|
+
static async build(pages, format, fileName) {
|
|
11
|
+
if (format === ps_scan_format_1.PsScanFormat.Pdf)
|
|
12
|
+
return [
|
|
13
|
+
new File([await ps_scan_pdf_service_1.PsScanPdfService.build(pages)], ps_scan_file_name_service_1.PsScanFileNameService.buildPdf(fileName), {
|
|
14
|
+
type: ps_scan_config_1.PsScanConfig.pdfType,
|
|
15
|
+
}),
|
|
16
|
+
];
|
|
17
|
+
const files = [];
|
|
18
|
+
for (const [index, page] of pages.entries())
|
|
19
|
+
files.push(new File([await ps_scan_canvas_service_1.PsScanCanvasService.toPng(page.blob)], ps_scan_file_name_service_1.PsScanFileNameService.buildPng(fileName, index, pages.length), { type: ps_scan_config_1.PsScanConfig.pngType }));
|
|
20
|
+
return files;
|
|
21
|
+
}
|
|
22
|
+
static preferred(pageCount, formats) {
|
|
23
|
+
const wanted = pageCount > 1 ? ps_scan_format_1.PsScanFormat.Pdf : ps_scan_format_1.PsScanFormat.Png;
|
|
24
|
+
return formats.includes(wanted) ? wanted : formats[0];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.PsScanOutputService = PsScanOutputService;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsScanPdfService = void 0;
|
|
4
|
+
const ps_utilities_1 = require("@pallasoft/ps-utilities");
|
|
4
5
|
const ps_scan_config_1 = require("../constants/ps-scan-config");
|
|
5
6
|
class PsScanPdfService {
|
|
6
7
|
static async build(pages) {
|
|
@@ -14,60 +15,10 @@ class PsScanPdfService {
|
|
|
14
15
|
return PsScanPdfService.fromJpegs(loaded);
|
|
15
16
|
}
|
|
16
17
|
static fromJpegs(pages) {
|
|
17
|
-
|
|
18
|
-
const offsets = [];
|
|
19
|
-
let length = 0;
|
|
20
|
-
const push = (chunk) => {
|
|
21
|
-
chunks.push(chunk);
|
|
22
|
-
length += chunk.length;
|
|
23
|
-
};
|
|
24
|
-
const begin = (objectNumber) => {
|
|
25
|
-
offsets[objectNumber] = length;
|
|
26
|
-
push(PsScanPdfService.text(`${objectNumber} 0 obj\n`));
|
|
27
|
-
};
|
|
28
|
-
push(PsScanPdfService.text('%PDF-1.4\n'));
|
|
29
|
-
push(new Uint8Array([0x25, 0xe2, 0xe3, 0xcf, 0xd3, 0x0a]));
|
|
30
|
-
begin(1);
|
|
31
|
-
push(PsScanPdfService.text('<< /Type /Catalog /Pages 2 0 R >>\nendobj\n'));
|
|
32
|
-
const kids = pages.map((_, index) => `${PsScanPdfService.pageObject(index)} 0 R`).join(' ');
|
|
33
|
-
begin(2);
|
|
34
|
-
push(PsScanPdfService.text(`<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>\nendobj\n`));
|
|
35
|
-
pages.forEach((page, index) => {
|
|
36
|
-
const size = PsScanPdfService.pageSize(page);
|
|
37
|
-
const pageNumber = PsScanPdfService.pageObject(index);
|
|
38
|
-
const content = `q ${size.width} 0 0 ${size.height} 0 0 cm /Im0 Do Q`;
|
|
39
|
-
begin(pageNumber);
|
|
40
|
-
push(PsScanPdfService.text(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${size.width} ${size.height}] /Resources << /XObject << /Im0 ${pageNumber + 1} 0 R >> >> /Contents ${pageNumber + 2} 0 R >>\nendobj\n`));
|
|
41
|
-
begin(pageNumber + 1);
|
|
42
|
-
push(PsScanPdfService.text(`<< /Type /XObject /Subtype /Image /Width ${page.width} /Height ${page.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${page.bytes.length} >>\nstream\n`));
|
|
43
|
-
push(page.bytes);
|
|
44
|
-
push(PsScanPdfService.text('\nendstream\nendobj\n'));
|
|
45
|
-
begin(pageNumber + 2);
|
|
46
|
-
push(PsScanPdfService.text(`<< /Length ${content.length} >>\nstream\n${content}\nendstream\nendobj\n`));
|
|
47
|
-
});
|
|
48
|
-
const count = 3 + pages.length * 3;
|
|
49
|
-
const entries = Array.from({ length: count - 1 }, (_, index) => `${String(offsets[index + 1]).padStart(10, '0')} 00000 n \n`).join('');
|
|
50
|
-
push(PsScanPdfService.text(`xref\n0 ${count}\n0000000000 65535 f \n${entries}trailer\n<< /Size ${count} /Root 1 0 R >>\nstartxref\n${length}\n%%EOF\n`));
|
|
51
|
-
const merged = new Uint8Array(length);
|
|
52
|
-
let cursor = 0;
|
|
53
|
-
for (const chunk of chunks) {
|
|
54
|
-
merged.set(chunk, cursor);
|
|
55
|
-
cursor += chunk.length;
|
|
56
|
-
}
|
|
57
|
-
return new Blob([merged], { type: 'application/pdf' });
|
|
18
|
+
return new Blob([ps_utilities_1.Common.JpegPdfService.fromJpegs(pages)], { type: ps_scan_config_1.PsScanConfig.pdfType });
|
|
58
19
|
}
|
|
59
20
|
static pageSize(page) {
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
height: Math.round(page.height * scale * 100) / 100,
|
|
63
|
-
width: Math.round(page.width * scale * 100) / 100,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
static pageObject(index) {
|
|
67
|
-
return 3 + index * 3;
|
|
68
|
-
}
|
|
69
|
-
static text(value) {
|
|
70
|
-
return new TextEncoder().encode(value);
|
|
21
|
+
return ps_utilities_1.Common.JpegPdfService.pageSize(page);
|
|
71
22
|
}
|
|
72
23
|
}
|
|
73
24
|
exports.PsScanPdfService = PsScanPdfService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pallasoft/ps-file-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@pallasoft/ps-core-components": "^2.37.0",
|
|
34
34
|
"@pallasoft/ps-icons": "^1.9.0",
|
|
35
35
|
"@pallasoft/ps-popovers": "^1.13.0",
|
|
36
|
-
"@pallasoft/ps-utilities": "^2.
|
|
36
|
+
"@pallasoft/ps-utilities": "^2.28.0",
|
|
37
37
|
"pdfjs-dist": "^6.3.289"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|