@pallasoft/ps-file-components 1.3.0 → 1.4.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 -0
- package/dist/components/ps-document-scanner-loader.js +40 -0
- package/dist/components/ps-document-scanner-sheet.d.ts +2 -0
- package/dist/components/ps-document-scanner-sheet.js +138 -0
- package/dist/components/ps-document-scanner.d.ts +2 -0
- package/dist/components/ps-document-scanner.js +17 -0
- package/dist/components/ps-file-viewer-pane.d.ts +2 -0
- package/dist/components/ps-file-viewer-pane.js +39 -0
- package/dist/components/ps-scan-camera-view.d.ts +17 -0
- package/dist/components/ps-scan-camera-view.js +27 -0
- package/dist/components/ps-scan-crop-editor.d.ts +13 -0
- package/dist/components/ps-scan-crop-editor.js +53 -0
- package/dist/components/ps-scan-icon-button.d.ts +10 -0
- package/dist/components/ps-scan-icon-button.js +7 -0
- package/dist/components/ps-scan-page-list.d.ts +12 -0
- package/dist/components/ps-scan-page-list.js +23 -0
- package/dist/components/ps-scan-review.d.ts +23 -0
- package/dist/components/ps-scan-review.js +20 -0
- package/dist/constants/ps-document-scanner-labels.d.ts +50 -0
- package/dist/constants/ps-document-scanner-labels.js +53 -0
- package/dist/constants/ps-scan-camera-status.d.ts +7 -0
- package/dist/constants/ps-scan-camera-status.js +9 -0
- package/dist/constants/ps-scan-config.d.ts +31 -0
- package/dist/constants/ps-scan-config.js +34 -0
- package/dist/constants/ps-scan-corner-order.d.ts +1 -0
- package/dist/constants/ps-scan-corner-order.js +4 -0
- package/dist/constants/ps-scan-filter.d.ts +7 -0
- package/dist/constants/ps-scan-filter.js +9 -0
- package/dist/constants/ps-scan-output-format.d.ts +5 -0
- package/dist/constants/ps-scan-output-format.js +7 -0
- package/dist/constants/ps-scan-step.d.ts +6 -0
- package/dist/constants/ps-scan-step.js +8 -0
- package/dist/esm/components/ps-document-scanner-loader.js +4 -0
- package/dist/esm/components/ps-document-scanner-sheet.js +134 -0
- package/dist/esm/components/ps-document-scanner.js +13 -0
- package/dist/esm/components/ps-file-viewer-pane.js +35 -0
- package/dist/esm/components/ps-scan-camera-view.js +23 -0
- package/dist/esm/components/ps-scan-crop-editor.js +49 -0
- package/dist/esm/components/ps-scan-icon-button.js +3 -0
- package/dist/esm/components/ps-scan-page-list.js +19 -0
- package/dist/esm/components/ps-scan-review.js +16 -0
- package/dist/esm/constants/ps-document-scanner-labels.js +50 -0
- package/dist/esm/constants/ps-scan-camera-status.js +6 -0
- package/dist/esm/constants/ps-scan-config.js +31 -0
- package/dist/esm/constants/ps-scan-corner-order.js +1 -0
- package/dist/esm/constants/ps-scan-filter.js +6 -0
- package/dist/esm/constants/ps-scan-output-format.js +4 -0
- package/dist/esm/constants/ps-scan-step.js +5 -0
- package/dist/esm/hooks/use-ps-focus-trap.js +45 -0
- package/dist/esm/hooks/use-ps-scan-camera.js +62 -0
- package/dist/esm/hooks/use-ps-scan-pages.js +62 -0
- package/dist/esm/hooks/use-ps-scan-previews.js +57 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/models/ps-document-scanner-props.js +1 -0
- package/dist/esm/models/ps-document-scanner-sheet-props.js +1 -0
- package/dist/esm/models/ps-file-viewer-pane-props.js +1 -0
- package/dist/esm/models/ps-scan-added-page.js +1 -0
- package/dist/esm/models/ps-scan-camera-session.js +1 -0
- package/dist/esm/models/ps-scan-decoded-image.js +1 -0
- package/dist/esm/models/ps-scan-levels.js +1 -0
- package/dist/esm/models/ps-scan-output-options.js +1 -0
- package/dist/esm/models/ps-scan-page.js +1 -0
- package/dist/esm/models/ps-scan-pdf-page.js +1 -0
- package/dist/esm/models/ps-scan-prepared.js +1 -0
- package/dist/esm/models/ps-scan-quad.js +1 -0
- package/dist/esm/models/ps-scan-raster.js +1 -0
- package/dist/esm/models/ps-scan-rendered-page.js +1 -0
- package/dist/esm/services/ps-scan-camera-service.js +50 -0
- package/dist/esm/services/ps-scan-canvas-service.js +46 -0
- package/dist/esm/services/ps-scan-edge-service.js +156 -0
- package/dist/esm/services/ps-scan-enhance-service.js +97 -0
- package/dist/esm/services/ps-scan-file-name-service.js +25 -0
- package/dist/esm/services/ps-scan-geometry-service.js +96 -0
- package/dist/esm/services/ps-scan-homography-service.js +46 -0
- package/dist/esm/services/ps-scan-hull-service.js +59 -0
- package/dist/esm/services/ps-scan-image-filter-service.js +52 -0
- package/dist/esm/services/ps-scan-order-service.js +13 -0
- package/dist/esm/services/ps-scan-output-service.js +24 -0
- package/dist/esm/services/ps-scan-pdf-service.js +56 -0
- package/dist/esm/services/ps-scan-pipeline-service.js +40 -0
- package/dist/esm/services/ps-scan-raster-service.js +67 -0
- package/dist/esm/services/ps-scan-template-service.js +5 -0
- package/dist/esm/services/ps-scan-warp-service.js +35 -0
- package/dist/hooks/use-ps-focus-trap.d.ts +2 -0
- package/dist/hooks/use-ps-focus-trap.js +49 -0
- package/dist/hooks/use-ps-scan-camera.d.ts +9 -0
- package/dist/hooks/use-ps-scan-camera.js +66 -0
- package/dist/hooks/use-ps-scan-pages.d.ts +12 -0
- package/dist/hooks/use-ps-scan-pages.js +66 -0
- package/dist/hooks/use-ps-scan-previews.d.ts +2 -0
- package/dist/hooks/use-ps-scan-previews.js +61 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/models/ps-document-scanner-props.d.ts +15 -0
- package/dist/models/ps-document-scanner-props.js +2 -0
- package/dist/models/ps-document-scanner-sheet-props.d.ts +13 -0
- package/dist/models/ps-document-scanner-sheet-props.js +2 -0
- package/dist/models/ps-file-viewer-pane-props.d.ts +12 -0
- package/dist/models/ps-file-viewer-pane-props.js +2 -0
- package/dist/models/ps-scan-added-page.d.ts +5 -0
- package/dist/models/ps-scan-added-page.js +2 -0
- package/dist/models/ps-scan-camera-session.d.ts +5 -0
- package/dist/models/ps-scan-camera-session.js +2 -0
- package/dist/models/ps-scan-decoded-image.d.ts +6 -0
- package/dist/models/ps-scan-decoded-image.js +2 -0
- package/dist/models/ps-scan-levels.d.ts +4 -0
- package/dist/models/ps-scan-levels.js +2 -0
- package/dist/models/ps-scan-output-options.d.ts +7 -0
- package/dist/models/ps-scan-output-options.js +2 -0
- package/dist/models/ps-scan-page.d.ts +12 -0
- package/dist/models/ps-scan-page.js +2 -0
- package/dist/models/ps-scan-pdf-page.d.ts +5 -0
- package/dist/models/ps-scan-pdf-page.js +2 -0
- package/dist/models/ps-scan-prepared.d.ts +7 -0
- package/dist/models/ps-scan-prepared.js +2 -0
- package/dist/models/ps-scan-quad.d.ts +2 -0
- package/dist/models/ps-scan-quad.js +2 -0
- package/dist/models/ps-scan-raster.d.ts +5 -0
- package/dist/models/ps-scan-raster.js +2 -0
- package/dist/models/ps-scan-rendered-page.d.ts +5 -0
- package/dist/models/ps-scan-rendered-page.js +2 -0
- package/dist/services/ps-scan-camera-service.d.ts +10 -0
- package/dist/services/ps-scan-camera-service.js +54 -0
- package/dist/services/ps-scan-canvas-service.d.ts +8 -0
- package/dist/services/ps-scan-canvas-service.js +50 -0
- package/dist/services/ps-scan-edge-service.d.ts +9 -0
- package/dist/services/ps-scan-edge-service.js +160 -0
- package/dist/services/ps-scan-enhance-service.d.ts +9 -0
- package/dist/services/ps-scan-enhance-service.js +101 -0
- package/dist/services/ps-scan-file-name-service.d.ts +6 -0
- package/dist/services/ps-scan-file-name-service.js +29 -0
- package/dist/services/ps-scan-geometry-service.d.ts +18 -0
- package/dist/services/ps-scan-geometry-service.js +100 -0
- package/dist/services/ps-scan-homography-service.d.ts +7 -0
- package/dist/services/ps-scan-homography-service.js +50 -0
- package/dist/services/ps-scan-hull-service.d.ts +6 -0
- package/dist/services/ps-scan-hull-service.js +63 -0
- package/dist/services/ps-scan-image-filter-service.d.ts +6 -0
- package/dist/services/ps-scan-image-filter-service.js +56 -0
- package/dist/services/ps-scan-order-service.d.ts +4 -0
- package/dist/services/ps-scan-order-service.js +17 -0
- package/dist/services/ps-scan-output-service.d.ts +5 -0
- package/dist/services/ps-scan-output-service.js +28 -0
- package/dist/services/ps-scan-pdf-service.d.ts +5 -0
- package/dist/services/ps-scan-pdf-service.js +60 -0
- package/dist/services/ps-scan-pipeline-service.d.ts +10 -0
- package/dist/services/ps-scan-pipeline-service.js +44 -0
- package/dist/services/ps-scan-raster-service.d.ts +6 -0
- package/dist/services/ps-scan-raster-service.js +71 -0
- package/dist/services/ps-scan-template-service.d.ts +3 -0
- package/dist/services/ps-scan-template-service.js +9 -0
- package/dist/services/ps-scan-warp-service.d.ts +6 -0
- package/dist/services/ps-scan-warp-service.js +39 -0
- package/package.json +9 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PsDocumentScannerLoader: import("react").LazyExoticComponent<({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: import("../models/ps-document-scanner-sheet-props").PsDocumentScannerSheetProps) => import("react").JSX.Element | null>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PsDocumentScannerLoader = void 0;
|
|
37
|
+
const react_1 = require("react");
|
|
38
|
+
exports.PsDocumentScannerLoader = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./ps-document-scanner-sheet'))).then((module) => ({
|
|
39
|
+
default: module.PsDocumentScannerSheet,
|
|
40
|
+
})));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { PsDocumentScannerSheetProps } from '../models/ps-document-scanner-sheet-props';
|
|
2
|
+
export declare const PsDocumentScannerSheet: ({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: PsDocumentScannerSheetProps) => import("react").JSX.Element | null;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsDocumentScannerSheet = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
7
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
8
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
9
|
+
const ps_popovers_1 = require("@pallasoft/ps-popovers");
|
|
10
|
+
const react_dom_1 = require("react-dom");
|
|
11
|
+
const ps_scan_step_1 = require("../constants/ps-scan-step");
|
|
12
|
+
const use_ps_focus_trap_1 = require("../hooks/use-ps-focus-trap");
|
|
13
|
+
const use_ps_scan_camera_1 = require("../hooks/use-ps-scan-camera");
|
|
14
|
+
const use_ps_scan_pages_1 = require("../hooks/use-ps-scan-pages");
|
|
15
|
+
const use_ps_scan_previews_1 = require("../hooks/use-ps-scan-previews");
|
|
16
|
+
const ps_scan_output_service_1 = require("../services/ps-scan-output-service");
|
|
17
|
+
const ps_scan_pipeline_service_1 = require("../services/ps-scan-pipeline-service");
|
|
18
|
+
const ps_scan_template_service_1 = require("../services/ps-scan-template-service");
|
|
19
|
+
const ps_scan_camera_view_1 = require("./ps-scan-camera-view");
|
|
20
|
+
const ps_scan_crop_editor_1 = require("./ps-scan-crop-editor");
|
|
21
|
+
const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
|
|
22
|
+
const ps_scan_review_1 = require("./ps-scan-review");
|
|
23
|
+
const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }) => {
|
|
24
|
+
const tier = (0, ps_core_components_1.usePsViewport)();
|
|
25
|
+
const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Modal);
|
|
26
|
+
const content = (0, react_1.useRef)(null);
|
|
27
|
+
const { add, move, pages, remove, replace, setCorners, update } = (0, use_ps_scan_pages_1.usePsScanPages)();
|
|
28
|
+
const [step, setStep] = (0, react_1.useState)(ps_scan_step_1.PsScanStep.Camera);
|
|
29
|
+
const [activeId, setActiveId] = (0, react_1.useState)('');
|
|
30
|
+
const [retakeId, setRetakeId] = (0, react_1.useState)('');
|
|
31
|
+
const [announcement, setAnnouncement] = (0, react_1.useState)('');
|
|
32
|
+
const [error, setError] = (0, react_1.useState)('');
|
|
33
|
+
const [isBusy, setIsBusy] = (0, react_1.useState)(false);
|
|
34
|
+
const camera = (0, use_ps_scan_camera_1.usePsScanCamera)(step === ps_scan_step_1.PsScanStep.Camera);
|
|
35
|
+
const previews = (0, use_ps_scan_previews_1.usePsScanPreviews)(pages);
|
|
36
|
+
const page = pages.find((item) => item.id === activeId);
|
|
37
|
+
const isDesktop = tier === ps_design_system_1.PallasoftViewportTier.Desktop;
|
|
38
|
+
(0, ps_core_components_1.usePsScrollLock)(!isDesktop);
|
|
39
|
+
(0, ps_core_components_1.usePsRestoreFocus)(true);
|
|
40
|
+
(0, use_ps_focus_trap_1.usePsFocusTrap)(content, true, onClose);
|
|
41
|
+
const edgeMessage = (isDetected) => isDetected ? labels.edgesFound : labels.edgesNotFound;
|
|
42
|
+
const addFiles = async (files) => {
|
|
43
|
+
setError('');
|
|
44
|
+
try {
|
|
45
|
+
const images = files.filter((file) => file.type === '' || file.type.startsWith('image/'));
|
|
46
|
+
if (retakeId !== '' && images.length > 0) {
|
|
47
|
+
const added = await replace(retakeId, images[0]);
|
|
48
|
+
setActiveId(retakeId);
|
|
49
|
+
setRetakeId('');
|
|
50
|
+
setStep(ps_scan_step_1.PsScanStep.Crop);
|
|
51
|
+
setAnnouncement(edgeMessage(added.isDetected));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const room = Math.max(0, maxPages - pages.length);
|
|
55
|
+
if (images.length > room)
|
|
56
|
+
setError(labels.maxPages);
|
|
57
|
+
const added = await add(images.slice(0, room));
|
|
58
|
+
if (added.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
setActiveId(added[0].page.id);
|
|
61
|
+
setStep(added.length === 1 ? ps_scan_step_1.PsScanStep.Crop : ps_scan_step_1.PsScanStep.Review);
|
|
62
|
+
setAnnouncement(added.length === 1
|
|
63
|
+
? edgeMessage(added[0].isDetected)
|
|
64
|
+
: ps_scan_template_service_1.PsScanTemplateService.fill(labels.pageAdded, { position: pages.length + added.length }));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
setError(labels.processFailed);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const capture = async () => {
|
|
71
|
+
const blob = await camera.capture();
|
|
72
|
+
if (blob !== null)
|
|
73
|
+
await addFiles([blob]);
|
|
74
|
+
};
|
|
75
|
+
const autoDetect = async () => {
|
|
76
|
+
if (page === undefined)
|
|
77
|
+
return;
|
|
78
|
+
const prepared = await ps_scan_pipeline_service_1.PsScanPipelineService.prepare(page.source);
|
|
79
|
+
setCorners(page.id, prepared.corners);
|
|
80
|
+
setAnnouncement(edgeMessage(prepared.isDetected));
|
|
81
|
+
};
|
|
82
|
+
const retake = () => {
|
|
83
|
+
if (page === undefined)
|
|
84
|
+
return;
|
|
85
|
+
setRetakeId(page.id);
|
|
86
|
+
setStep(ps_scan_step_1.PsScanStep.Camera);
|
|
87
|
+
};
|
|
88
|
+
const deletePage = () => {
|
|
89
|
+
if (page === undefined)
|
|
90
|
+
return;
|
|
91
|
+
const index = pages.findIndex((item) => item.id === page.id);
|
|
92
|
+
const rest = pages.filter((item) => item.id !== page.id);
|
|
93
|
+
remove(page.id);
|
|
94
|
+
setAnnouncement(labels.pageDeleted);
|
|
95
|
+
if (rest.length === 0) {
|
|
96
|
+
setActiveId('');
|
|
97
|
+
setStep(ps_scan_step_1.PsScanStep.Camera);
|
|
98
|
+
}
|
|
99
|
+
else
|
|
100
|
+
setActiveId(rest[Math.min(index, rest.length - 1)].id);
|
|
101
|
+
};
|
|
102
|
+
const finish = async () => {
|
|
103
|
+
setIsBusy(true);
|
|
104
|
+
setError('');
|
|
105
|
+
try {
|
|
106
|
+
const files = await ps_scan_output_service_1.PsScanOutputService.build(pages, {
|
|
107
|
+
fileName,
|
|
108
|
+
format: outputFormat,
|
|
109
|
+
jpegQuality,
|
|
110
|
+
maxDimension,
|
|
111
|
+
});
|
|
112
|
+
onComplete(files);
|
|
113
|
+
onClose();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
setError(labels.processFailed);
|
|
117
|
+
setIsBusy(false);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const moveTo = (0, react_1.useCallback)((from, to) => {
|
|
121
|
+
move(from, to);
|
|
122
|
+
setAnnouncement(ps_scan_template_service_1.PsScanTemplateService.fill(labels.pageMoved, {
|
|
123
|
+
count: pages.length,
|
|
124
|
+
page: `${labels.page} ${from + 1}`,
|
|
125
|
+
position: to + 1,
|
|
126
|
+
}));
|
|
127
|
+
}, [move, pages.length, labels]);
|
|
128
|
+
const body = ((0, jsx_runtime_1.jsxs)("div", { className: 'relative h-full min-h-0 bg-surface text-ink', ref: content, children: [(0, jsx_runtime_1.jsx)("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), step === ps_scan_step_1.PsScanStep.Camera ? ((0, jsx_runtime_1.jsx)(ps_scan_camera_view_1.PsScanCameraView, { canGoBack: pages.length > 0, canTorch: camera.canTorch, isTorchOn: camera.isTorchOn, labels: labels, onBack: () => {
|
|
129
|
+
setRetakeId('');
|
|
130
|
+
setStep(ps_scan_step_1.PsScanStep.Review);
|
|
131
|
+
}, onCapture: capture, onFiles: addFiles, onToggleTorch: camera.toggleTorch, status: camera.status, videoRef: camera.videoRef })) : null, step === ps_scan_step_1.PsScanStep.Crop && page !== undefined ? ((0, jsx_runtime_1.jsx)(ps_scan_crop_editor_1.PsScanCropEditor, { labels: labels, onApply: () => setStep(ps_scan_step_1.PsScanStep.Review), onAutoDetect: autoDetect, onChange: (corners) => setCorners(page.id, corners), onRetake: retake, page: page })) : null, step === ps_scan_step_1.PsScanStep.Review && page !== undefined ? ((0, jsx_runtime_1.jsx)(ps_scan_review_1.PsScanReview, { canAddPage: pages.length < maxPages, error: error, isBusy: isBusy, labels: labels, onAddPage: () => setStep(ps_scan_step_1.PsScanStep.Camera), onDelete: deletePage, onEditCrop: () => setStep(ps_scan_step_1.PsScanStep.Crop), onFilter: (filter) => update(page.id, { filter }), onFinish: finish, onMove: moveTo, onRetake: retake, onRotate: () => update(page.id, { rotation: (page.rotation + 90) % 360 }), onSelect: setActiveId, page: page, pages: pages, previews: previews })) : null, step === ps_scan_step_1.PsScanStep.Camera && error !== '' ? ((0, jsx_runtime_1.jsx)("p", { className: 'absolute inset-x-4 top-3 rounded-ps-control bg-danger px-3 py-2 text-sm text-white', role: 'alert', children: error })) : null] }));
|
|
132
|
+
if (isDesktop)
|
|
133
|
+
return ((0, jsx_runtime_1.jsx)(ps_popovers_1.PsModal, { isOpen: true, onClose: onClose, size: 'xl', title: labels.title, children: (0, jsx_runtime_1.jsx)("div", { className: '-m-4 h-[min(70dvh,38rem)] overflow-hidden sm:-m-5', children: body }) }));
|
|
134
|
+
if (typeof document === 'undefined')
|
|
135
|
+
return null;
|
|
136
|
+
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { "aria-label": labels.title, "aria-modal": 'true', className: 'fixed inset-0 flex flex-col bg-surface text-ink animate-ps-fade-in', role: 'dialog', style: { zIndex }, children: (0, jsx_runtime_1.jsxs)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: [(0, jsx_runtime_1.jsxs)("header", { className: 'flex shrink-0 items-center justify-between gap-3 border-b border-line px-4 pt-[max(0.5rem,env(safe-area-inset-top))] pb-2', children: [(0, jsx_runtime_1.jsx)("h2", { className: 'truncate text-base font-semibold', children: labels.title }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { label: labels.close, onClick: onClose, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Close }) })] }), (0, jsx_runtime_1.jsx)("div", { className: 'min-h-0 flex-1', children: body })] }) }), document.body);
|
|
137
|
+
};
|
|
138
|
+
exports.PsDocumentScannerSheet = PsDocumentScannerSheet;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { PsDocumentScannerProps } from '../models/ps-document-scanner-props';
|
|
2
|
+
export declare const PsDocumentScanner: ({ fileName, isOpen, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: PsDocumentScannerProps) => import("react").JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsDocumentScanner = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
7
|
+
const ps_document_scanner_labels_1 = require("../constants/ps-document-scanner-labels");
|
|
8
|
+
const ps_scan_config_1 = require("../constants/ps-scan-config");
|
|
9
|
+
const ps_scan_output_format_1 = require("../constants/ps-scan-output-format");
|
|
10
|
+
const ps_document_scanner_loader_1 = require("./ps-document-scanner-loader");
|
|
11
|
+
const PsDocumentScanner = ({ fileName = ps_scan_config_1.PsScanConfig.defaultFileName, isOpen, jpegQuality = ps_scan_config_1.PsScanConfig.defaultJpegQuality, labels, maxDimension = ps_scan_config_1.PsScanConfig.defaultMaxDimension, maxPages = ps_scan_config_1.PsScanConfig.defaultMaxPages, onClose, onComplete, outputFormat = ps_scan_output_format_1.PsScanOutputFormat.Pdf, }) => {
|
|
12
|
+
const isHydrated = (0, ps_core_components_1.usePsHydrated)();
|
|
13
|
+
if (!isOpen || !isHydrated)
|
|
14
|
+
return null;
|
|
15
|
+
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, maxPages: maxPages, onClose: onClose, onComplete: onComplete, outputFormat: outputFormat }) }));
|
|
16
|
+
};
|
|
17
|
+
exports.PsDocumentScanner = PsDocumentScanner;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsFileViewerPane = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
7
|
+
const ps_file_viewer_config_1 = require("../constants/ps-file-viewer-config");
|
|
8
|
+
const ps_file_viewer_kinds_1 = require("../constants/ps-file-viewer-kinds");
|
|
9
|
+
const ps_file_viewer_labels_1 = require("../constants/ps-file-viewer-labels");
|
|
10
|
+
const ps_file_type_service_1 = require("../services/ps-file-type-service");
|
|
11
|
+
const ps_file_viewer_button_1 = require("./ps-file-viewer-button");
|
|
12
|
+
const ps_file_viewer_content_1 = require("./ps-file-viewer-content");
|
|
13
|
+
const PsFileViewerPane = ({ contentType, headerActions, id, labels = ps_file_viewer_labels_1.PsFileViewerLabels, name, pdfWorkerSrc, url, }) => {
|
|
14
|
+
const [zoom, setZoom] = (0, react_1.useState)(1);
|
|
15
|
+
const [rotation, setRotation] = (0, react_1.useState)(0);
|
|
16
|
+
const kind = ps_file_type_service_1.PsFileTypeService.kindOf(contentType, name);
|
|
17
|
+
const canZoom = ps_file_viewer_kinds_1.PsFileViewerKinds.zoomable.includes(kind);
|
|
18
|
+
const canRotate = ps_file_viewer_kinds_1.PsFileViewerKinds.rotatable.includes(kind);
|
|
19
|
+
const changeZoom = (step) => setZoom((current) => Math.min(ps_file_viewer_config_1.PsFileViewerConfig.maxZoom, Math.max(ps_file_viewer_config_1.PsFileViewerConfig.minZoom, current + step)));
|
|
20
|
+
const fileKey = id === undefined ? url : id;
|
|
21
|
+
const rotate = () => setRotation((current) => (current + ps_file_viewer_config_1.PsFileViewerConfig.rotateStep) % 360);
|
|
22
|
+
const onKeyDown = (event) => {
|
|
23
|
+
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
24
|
+
return;
|
|
25
|
+
if (canZoom && (event.key === '+' || event.key === '='))
|
|
26
|
+
changeZoom(ps_file_viewer_config_1.PsFileViewerConfig.zoomStep);
|
|
27
|
+
else if (canZoom && (event.key === '-' || event.key === '_'))
|
|
28
|
+
changeZoom(-ps_file_viewer_config_1.PsFileViewerConfig.zoomStep);
|
|
29
|
+
else if (canZoom && event.key === '0')
|
|
30
|
+
setZoom(1);
|
|
31
|
+
else if (canRotate && event.key.toLowerCase() === 'r')
|
|
32
|
+
rotate();
|
|
33
|
+
else
|
|
34
|
+
return;
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
};
|
|
37
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden rounded-ps-panel bg-black/85', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex shrink-0 flex-wrap items-center justify-between gap-x-2 border-b border-white/10 px-2 py-1 text-white', children: [(0, jsx_runtime_1.jsx)("p", { className: 'min-w-0 flex-1 basis-32 truncate px-1 text-xs text-white/70', title: name, children: name }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-0.5', role: 'toolbar', children: [canZoom ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ps_file_viewer_button_1.PsFileViewerButton, { disabled: zoom <= ps_file_viewer_config_1.PsFileViewerConfig.minZoom, label: labels.zoomOut, onClick: () => changeZoom(-ps_file_viewer_config_1.PsFileViewerConfig.zoomStep), children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.ZoomOut }) }), (0, jsx_runtime_1.jsxs)(ps_file_viewer_button_1.PsFileViewerButton, { className: 'w-14 text-xs', label: labels.zoomReset, onClick: () => setZoom(1), children: [Math.round(zoom * 100), "%"] }), (0, jsx_runtime_1.jsx)(ps_file_viewer_button_1.PsFileViewerButton, { disabled: zoom >= ps_file_viewer_config_1.PsFileViewerConfig.maxZoom, label: labels.zoomIn, onClick: () => changeZoom(ps_file_viewer_config_1.PsFileViewerConfig.zoomStep), children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.ZoomIn }) })] })) : null, canRotate ? ((0, jsx_runtime_1.jsx)(ps_file_viewer_button_1.PsFileViewerButton, { label: labels.rotate, onClick: rotate, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Rotate }) })) : null, headerActions] })] }), (0, jsx_runtime_1.jsx)("div", { className: 'ps-focus-ring flex min-h-0 flex-1 p-2', onKeyDown: onKeyDown, tabIndex: 0, children: (0, jsx_runtime_1.jsx)(ps_file_viewer_content_1.PsFileViewerContent, { activeMatch: 0, file: { contentType, id: fileKey, name, url }, labels: labels, onMatchCount: () => undefined, pdfWorkerSrc: pdfWorkerSrc, query: '', rotation: rotation, zoom: zoom }, fileKey) })] }));
|
|
38
|
+
};
|
|
39
|
+
exports.PsFileViewerPane = PsFileViewerPane;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
3
|
+
import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
|
|
4
|
+
import type { PsLabelSet } from '../models/ps-label-set';
|
|
5
|
+
export interface PsScanCameraViewProps {
|
|
6
|
+
canGoBack: boolean;
|
|
7
|
+
canTorch: boolean;
|
|
8
|
+
isTorchOn: boolean;
|
|
9
|
+
labels: PsLabelSet<typeof PsDocumentScannerLabels>;
|
|
10
|
+
onBack: () => void;
|
|
11
|
+
onCapture: () => void;
|
|
12
|
+
onFiles: (files: File[]) => void;
|
|
13
|
+
onToggleTorch: () => void;
|
|
14
|
+
status: PsScanCameraStatus;
|
|
15
|
+
videoRef: RefObject<HTMLVideoElement>;
|
|
16
|
+
}
|
|
17
|
+
export declare const PsScanCameraView: ({ canGoBack, canTorch, isTorchOn, labels, onBack, onCapture, onFiles, onToggleTorch, status, videoRef, }: PsScanCameraViewProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsScanCameraView = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
7
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
8
|
+
const ps_scan_camera_status_1 = require("../constants/ps-scan-camera-status");
|
|
9
|
+
const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
|
|
10
|
+
const PsScanCameraView = ({ canGoBack, canTorch, isTorchOn, labels, onBack, onCapture, onFiles, onToggleTorch, status, videoRef, }) => {
|
|
11
|
+
const captureInput = (0, react_1.useRef)(null);
|
|
12
|
+
const libraryInput = (0, react_1.useRef)(null);
|
|
13
|
+
const isReady = status === ps_scan_camera_status_1.PsScanCameraStatus.Ready;
|
|
14
|
+
const message = status === ps_scan_camera_status_1.PsScanCameraStatus.Denied
|
|
15
|
+
? labels.cameraDenied
|
|
16
|
+
: status === ps_scan_camera_status_1.PsScanCameraStatus.Unavailable
|
|
17
|
+
? labels.cameraUnavailable
|
|
18
|
+
: '';
|
|
19
|
+
const onChange = (event) => {
|
|
20
|
+
const files = Array.from(event.target.files === null ? [] : event.target.files);
|
|
21
|
+
event.target.value = '';
|
|
22
|
+
if (files.length > 0)
|
|
23
|
+
onFiles(files);
|
|
24
|
+
};
|
|
25
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col bg-slate-950 text-white', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'relative min-h-0 flex-1', children: [(0, jsx_runtime_1.jsx)("video", { "aria-label": labels.title, autoPlay: true, className: `size-full object-contain ${isReady ? '' : 'invisible'}`, muted: true, playsInline: true, ref: videoRef }), status === ps_scan_camera_status_1.PsScanCameraStatus.Starting ? ((0, jsx_runtime_1.jsxs)("div", { className: 'absolute inset-0 flex flex-col items-center justify-center gap-3 text-sm text-white/80', role: 'status', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsSpinner, { color: 'current', label: labels.cameraStarting, size: 'small' }), (0, jsx_runtime_1.jsx)("span", { children: labels.cameraStarting })] })) : null, message === '' ? null : ((0, jsx_runtime_1.jsx)("div", { className: 'absolute inset-0 flex items-center justify-center p-6 text-center text-sm text-white/85', role: 'status', children: (0, jsx_runtime_1.jsx)("p", { className: 'max-w-sm', children: message }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex shrink-0 flex-wrap items-center justify-center gap-3 px-4 pt-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]', children: [canGoBack ? ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onBack, variant: 'surface', children: labels.backToPages })) : null, (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Image }), onClick: () => libraryInput.current?.click(), variant: 'surface', children: labels.choosePhotos }), isReady ? ((0, jsx_runtime_1.jsx)("button", { "aria-label": labels.capture, className: 'ps-focus-ring flex size-[4.5rem] shrink-0 items-center justify-center rounded-full border-4 border-white bg-white/20 transition active:scale-95', onClick: onCapture, title: labels.capture, type: 'button', children: (0, jsx_runtime_1.jsx)("span", { className: 'size-12 rounded-full bg-white' }) })) : ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: () => captureInput.current?.click(), variant: 'primary', children: labels.takePhoto })), canTorch && isReady ? ((0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { isPressed: isTorchOn, label: isTorchOn ? labels.torchOff : labels.torchOn, onClick: onToggleTorch, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Spark }) })) : null] }), (0, jsx_runtime_1.jsx)("input", { accept: 'image/*', capture: 'environment', className: 'sr-only', onChange: onChange, ref: captureInput, tabIndex: -1, type: 'file' }), (0, jsx_runtime_1.jsx)("input", { accept: 'image/*', className: 'sr-only', multiple: true, onChange: onChange, ref: libraryInput, tabIndex: -1, type: 'file' })] }));
|
|
26
|
+
};
|
|
27
|
+
exports.PsScanCameraView = PsScanCameraView;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
2
|
+
import type { PsLabelSet } from '../models/ps-label-set';
|
|
3
|
+
import type { PsScanPage } from '../models/ps-scan-page';
|
|
4
|
+
import type { PsScanQuad } from '../models/ps-scan-quad';
|
|
5
|
+
export interface PsScanCropEditorProps {
|
|
6
|
+
labels: PsLabelSet<typeof PsDocumentScannerLabels>;
|
|
7
|
+
onApply: () => void;
|
|
8
|
+
onAutoDetect: () => void;
|
|
9
|
+
onChange: (corners: PsScanQuad) => void;
|
|
10
|
+
onRetake: () => void;
|
|
11
|
+
page: PsScanPage;
|
|
12
|
+
}
|
|
13
|
+
export declare const PsScanCropEditor: ({ labels, onApply, onAutoDetect, onChange, onRetake, page, }: PsScanCropEditorProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsScanCropEditor = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
7
|
+
const ps_scan_config_1 = require("../constants/ps-scan-config");
|
|
8
|
+
const ps_scan_corner_order_1 = require("../constants/ps-scan-corner-order");
|
|
9
|
+
const ps_scan_geometry_service_1 = require("../services/ps-scan-geometry-service");
|
|
10
|
+
const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRetake, page, }) => {
|
|
11
|
+
const [areaRef, area] = (0, ps_core_components_1.usePsElementSize)();
|
|
12
|
+
const stageRef = (0, react_1.useRef)(null);
|
|
13
|
+
const hintId = 'ps-scan-crop-hint';
|
|
14
|
+
const size = ps_scan_geometry_service_1.PsScanGeometryService.fit(area, { height: page.height, width: page.width });
|
|
15
|
+
const cornerLabels = [
|
|
16
|
+
labels.cornerTopLeft,
|
|
17
|
+
labels.cornerTopRight,
|
|
18
|
+
labels.cornerBottomRight,
|
|
19
|
+
labels.cornerBottomLeft,
|
|
20
|
+
];
|
|
21
|
+
const move = (index, event) => {
|
|
22
|
+
const stage = stageRef.current;
|
|
23
|
+
if (stage === null || !event.currentTarget.hasPointerCapture(event.pointerId))
|
|
24
|
+
return;
|
|
25
|
+
const rect = stage.getBoundingClientRect();
|
|
26
|
+
const point = ps_scan_geometry_service_1.PsScanGeometryService.clamp({
|
|
27
|
+
x: (event.clientX - rect.left) / rect.width,
|
|
28
|
+
y: (event.clientY - rect.top) / rect.height,
|
|
29
|
+
});
|
|
30
|
+
const next = page.corners.map((corner, at) => (at === index ? point : corner));
|
|
31
|
+
if (ps_scan_geometry_service_1.PsScanGeometryService.isUsable(next))
|
|
32
|
+
onChange(next);
|
|
33
|
+
};
|
|
34
|
+
const nudge = (index, event) => {
|
|
35
|
+
const step = event.shiftKey ? ps_scan_config_1.PsScanConfig.cornerNudgeLarge : ps_scan_config_1.PsScanConfig.cornerNudge;
|
|
36
|
+
const deltas = {
|
|
37
|
+
ArrowDown: [0, step],
|
|
38
|
+
ArrowLeft: [-step, 0],
|
|
39
|
+
ArrowRight: [step, 0],
|
|
40
|
+
ArrowUp: [0, -step],
|
|
41
|
+
};
|
|
42
|
+
const delta = deltas[event.key];
|
|
43
|
+
if (delta === undefined)
|
|
44
|
+
return;
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
onChange(ps_scan_geometry_service_1.PsScanGeometryService.nudge(page.corners, index, delta[0], delta[1]));
|
|
47
|
+
};
|
|
48
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col bg-slate-950 text-white', children: [(0, jsx_runtime_1.jsx)("p", { className: 'shrink-0 px-4 pt-3 text-center text-sm text-white/80', id: hintId, children: labels.cropHint }), (0, jsx_runtime_1.jsx)("div", { className: 'flex min-h-0 flex-1 items-center justify-center p-6', ref: areaRef, children: (0, jsx_runtime_1.jsxs)("div", { className: 'relative touch-none select-none', ref: stageRef, style: { height: size.height, width: size.width }, children: [(0, jsx_runtime_1.jsx)("img", { alt: '', className: 'absolute inset-0 size-full', draggable: false, src: page.sourceUrl }), (0, jsx_runtime_1.jsx)("svg", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 size-full', preserveAspectRatio: 'none', viewBox: '0 0 1 1', children: (0, jsx_runtime_1.jsx)("polygon", { fill: 'rgba(255,255,255,0.12)', points: page.corners.map((point) => `${point.x},${point.y}`).join(' '), stroke: 'white', strokeWidth: 2, vectorEffect: 'non-scaling-stroke' }) }), ps_scan_corner_order_1.PsScanCornerOrder.map((key, index) => ((0, jsx_runtime_1.jsx)("button", { "aria-describedby": hintId, "aria-label": cornerLabels[index], className: 'ps-focus-ring absolute flex size-11 -translate-x-1/2 -translate-y-1/2 cursor-grab touch-none items-center justify-center rounded-full active:cursor-grabbing', onKeyDown: (event) => nudge(index, event), onPointerCancel: (event) => event.currentTarget.releasePointerCapture(event.pointerId), onPointerDown: (event) => event.currentTarget.setPointerCapture(event.pointerId), onPointerMove: (event) => move(index, event), onPointerUp: (event) => event.currentTarget.releasePointerCapture(event.pointerId), style: {
|
|
49
|
+
left: `${page.corners[index].x * 100}%`,
|
|
50
|
+
top: `${page.corners[index].y * 100}%`,
|
|
51
|
+
}, type: 'button', children: (0, jsx_runtime_1.jsx)("span", { className: 'size-5 rounded-full border-2 border-white bg-primary shadow' }) }, key)))] }) }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex shrink-0 flex-wrap items-center justify-center gap-3 px-4 pt-2 pb-[max(0.75rem,env(safe-area-inset-bottom))]', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onRetake, variant: 'surface', children: labels.retake }), (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onAutoDetect, variant: 'surface', children: labels.autoDetect }), (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onApply, variant: 'primary', children: labels.cropApply })] })] }));
|
|
52
|
+
};
|
|
53
|
+
exports.PsScanCropEditor = PsScanCropEditor;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface PsScanIconButtonProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
isPressed?: boolean;
|
|
7
|
+
label: string;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const PsScanIconButton: ({ children, className, disabled, isPressed, label, onClick, }: PsScanIconButtonProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsScanIconButton = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
6
|
+
const PsScanIconButton = ({ children, className, disabled, isPressed, label, onClick, }) => ((0, jsx_runtime_1.jsx)("button", { "aria-label": label, "aria-pressed": isPressed, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring inline-flex size-11 min-h-11 min-w-11 shrink-0 items-center justify-center rounded-ps-control border border-line bg-surface text-ink transition hover:border-primary/40 hover:text-primary disabled:pointer-events-none disabled:opacity-40', isPressed ? 'border-primary bg-primary-soft text-primary' : '', className), disabled: disabled, onClick: onClick, title: label, type: 'button', children: children }));
|
|
7
|
+
exports.PsScanIconButton = PsScanIconButton;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
2
|
+
import type { PsLabelSet } from '../models/ps-label-set';
|
|
3
|
+
import type { PsScanPage } from '../models/ps-scan-page';
|
|
4
|
+
export interface PsScanPageListProps {
|
|
5
|
+
activeId: string;
|
|
6
|
+
labels: PsLabelSet<typeof PsDocumentScannerLabels>;
|
|
7
|
+
onMove: (from: number, to: number) => void;
|
|
8
|
+
onSelect: (id: string) => void;
|
|
9
|
+
pages: PsScanPage[];
|
|
10
|
+
previews: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare const PsScanPageList: ({ activeId, labels, onMove, onSelect, pages, previews, }: PsScanPageListProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsScanPageList = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
6
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
7
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
8
|
+
const ps_scan_template_service_1 = require("../services/ps-scan-template-service");
|
|
9
|
+
const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
|
|
10
|
+
const PsScanPageList = ({ activeId, labels, onMove, onSelect, pages, previews, }) => {
|
|
11
|
+
const items = pages.map((page, index) => ({
|
|
12
|
+
id: page.id,
|
|
13
|
+
label: `${labels.page} ${index + 1}`,
|
|
14
|
+
}));
|
|
15
|
+
const say = (template, label, position) => ps_scan_template_service_1.PsScanTemplateService.fill(template, { count: pages.length, page: label, position });
|
|
16
|
+
return ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsSortableList, { announcements: {
|
|
17
|
+
cancelled: (label) => say(labels.reorderCancelled, label, 0),
|
|
18
|
+
dropped: (label, position) => say(labels.reorderDropped, label, position),
|
|
19
|
+
lifted: (label, position) => say(labels.reorderLifted, label, position),
|
|
20
|
+
moved: (label, position) => say(labels.reorderMoved, label, position),
|
|
21
|
+
}, className: 'flex flex-col gap-2', getHandleLabel: (label) => ps_scan_template_service_1.PsScanTemplateService.fill(labels.dragPage, { page: label }), instructions: labels.reorderInstructions, items: items, onMove: (_id, from, to) => onMove(from, to), renderItem: ({ handle, index, isDragging, item }) => ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('flex items-center gap-2 rounded-ps-control border bg-surface p-1.5', item.id === activeId ? 'border-primary' : 'border-line', isDragging ? 'opacity-80 shadow-lg' : ''), children: [handle, (0, jsx_runtime_1.jsxs)("button", { "aria-current": item.id === activeId, "aria-label": item.label, className: 'ps-focus-ring flex min-h-11 min-w-0 flex-1 items-center gap-3 rounded-ps-control text-left', onClick: () => onSelect(item.id), type: 'button', children: [previews[item.id] === undefined ? ((0, jsx_runtime_1.jsx)("span", { className: 'ps-skeleton h-14 w-10 shrink-0 rounded' })) : ((0, jsx_runtime_1.jsx)("img", { alt: '', className: 'h-14 w-10 shrink-0 rounded border border-line bg-white object-cover object-top', src: previews[item.id] })), (0, jsx_runtime_1.jsx)("span", { className: 'truncate text-sm font-medium text-ink', children: item.label })] }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { disabled: index === 0, label: ps_scan_template_service_1.PsScanTemplateService.fill(labels.moveUp, { page: item.label }), onClick: () => onMove(index, index - 1), children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5 -rotate-90', iconKey: ps_icons_1.PsIconKey.ChevronRight }) }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { disabled: index === pages.length - 1, label: ps_scan_template_service_1.PsScanTemplateService.fill(labels.moveDown, { page: item.label }), onClick: () => onMove(index, index + 1), children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5 rotate-90', iconKey: ps_icons_1.PsIconKey.ChevronRight }) })] })), role: 'list' }));
|
|
22
|
+
};
|
|
23
|
+
exports.PsScanPageList = PsScanPageList;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
|
|
2
|
+
import { PsScanFilter } from '../constants/ps-scan-filter';
|
|
3
|
+
import type { PsLabelSet } from '../models/ps-label-set';
|
|
4
|
+
import type { PsScanPage } from '../models/ps-scan-page';
|
|
5
|
+
export interface PsScanReviewProps {
|
|
6
|
+
canAddPage: boolean;
|
|
7
|
+
error: string;
|
|
8
|
+
isBusy: boolean;
|
|
9
|
+
labels: PsLabelSet<typeof PsDocumentScannerLabels>;
|
|
10
|
+
onAddPage: () => void;
|
|
11
|
+
onDelete: () => void;
|
|
12
|
+
onEditCrop: () => void;
|
|
13
|
+
onFilter: (filter: PsScanFilter) => void;
|
|
14
|
+
onFinish: () => void;
|
|
15
|
+
onMove: (from: number, to: number) => void;
|
|
16
|
+
onRetake: () => void;
|
|
17
|
+
onRotate: () => void;
|
|
18
|
+
onSelect: (id: string) => void;
|
|
19
|
+
page: PsScanPage;
|
|
20
|
+
pages: PsScanPage[];
|
|
21
|
+
previews: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
export declare const PsScanReview: ({ canAddPage, error, isBusy, labels, onAddPage, onDelete, onEditCrop, onFilter, onFinish, onMove, onRetake, onRotate, onSelect, page, pages, previews, }: PsScanReviewProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsScanReview = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
6
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
7
|
+
const ps_scan_filter_1 = require("../constants/ps-scan-filter");
|
|
8
|
+
const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
|
|
9
|
+
const ps_scan_page_list_1 = require("./ps-scan-page-list");
|
|
10
|
+
const PsScanReview = ({ canAddPage, error, isBusy, labels, onAddPage, onDelete, onEditCrop, onFilter, onFinish, onMove, onRetake, onRotate, onSelect, page, pages, previews, }) => {
|
|
11
|
+
const filters = [
|
|
12
|
+
{ key: ps_scan_filter_1.PsScanFilter.Original, label: labels.filterOriginal },
|
|
13
|
+
{ key: ps_scan_filter_1.PsScanFilter.Colour, label: labels.filterColour },
|
|
14
|
+
{ key: ps_scan_filter_1.PsScanFilter.Greyscale, label: labels.filterGreyscale },
|
|
15
|
+
{ key: ps_scan_filter_1.PsScanFilter.Document, label: labels.filterDocument },
|
|
16
|
+
];
|
|
17
|
+
const preview = previews[page.id];
|
|
18
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col lg:flex-row', children: [(0, jsx_runtime_1.jsx)("div", { className: 'flex min-h-0 flex-1 items-center justify-center bg-slate-950 p-3', children: preview === undefined ? ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true', className: 'ps-skeleton h-full w-2/3 rounded' })) : ((0, jsx_runtime_1.jsx)("img", { alt: labels.page, className: 'max-h-full max-w-full object-contain', src: preview })) }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex max-h-[55%] min-h-0 shrink-0 flex-col gap-3 overflow-y-auto bg-surface px-4 pt-3 lg:max-h-none lg:w-80 lg:border-l lg:border-line', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-wrap items-center gap-2', children: [(0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { label: labels.rotate, onClick: onRotate, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Rotate }) }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { label: labels.editCrop, onClick: onEditCrop, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Pencil }) }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { label: labels.retake, onClick: onRetake, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Refresh }) }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { label: labels.deletePage, onClick: onDelete, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Trash }) })] }), (0, jsx_runtime_1.jsx)("div", { "aria-label": labels.filters, className: 'flex flex-wrap gap-2', role: 'group', children: filters.map((filter) => ((0, jsx_runtime_1.jsx)("button", { "aria-pressed": page.filter === filter.key, className: `ps-focus-ring min-h-11 rounded-ps-control border px-3 text-sm font-medium transition ${page.filter === filter.key ? 'border-primary bg-primary-soft text-primary' : 'border-line bg-surface text-ink hover:border-primary/40'}`, onClick: () => onFilter(filter.key), type: 'button', children: filter.label }, filter.key))) }), (0, jsx_runtime_1.jsxs)("section", { "aria-label": labels.pages, className: 'min-h-0', children: [(0, jsx_runtime_1.jsxs)("h3", { className: 'mb-2 text-sm font-semibold text-ink', children: [labels.pages, " (", pages.length, ")"] }), (0, jsx_runtime_1.jsx)(ps_scan_page_list_1.PsScanPageList, { activeId: page.id, labels: labels, onMove: onMove, onSelect: onSelect, pages: pages, previews: previews })] }), error === '' ? null : ((0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-danger', role: 'alert', children: error })), (0, jsx_runtime_1.jsxs)("div", { className: 'sticky bottom-0 mt-auto flex flex-wrap gap-3 bg-surface pt-2 pb-[max(0.75rem,env(safe-area-inset-bottom))]', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { className: 'flex-1', disabled: !canAddPage || isBusy, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Plus }), onClick: onAddPage, variant: 'surface', children: labels.addPage }), (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { className: 'flex-1', isLoading: isBusy, loadingLabel: labels.finishing, onClick: onFinish, variant: 'primary', children: labels.finish })] })] })] }));
|
|
19
|
+
};
|
|
20
|
+
exports.PsScanReview = PsScanReview;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const PsDocumentScannerLabels: {
|
|
2
|
+
readonly addPage: "Add page";
|
|
3
|
+
readonly autoDetect: "Auto-detect edges";
|
|
4
|
+
readonly cameraDenied: "Camera access was blocked. Allow it in your browser settings, or take a photo or choose existing photos instead.";
|
|
5
|
+
readonly cameraStarting: "Starting camera";
|
|
6
|
+
readonly cameraUnavailable: "No camera is available here. Take a photo or choose existing photos instead.";
|
|
7
|
+
readonly backToPages: "Back to pages";
|
|
8
|
+
readonly capture: "Capture page";
|
|
9
|
+
readonly choosePhotos: "Choose photos";
|
|
10
|
+
readonly close: "Close scanner";
|
|
11
|
+
readonly cornerBottomLeft: "Bottom left corner";
|
|
12
|
+
readonly cornerBottomRight: "Bottom right corner";
|
|
13
|
+
readonly cornerTopLeft: "Top left corner";
|
|
14
|
+
readonly cornerTopRight: "Top right corner";
|
|
15
|
+
readonly cropApply: "Use crop";
|
|
16
|
+
readonly cropHint: "Drag the corners to fit the page, or use the arrow keys on a focused corner.";
|
|
17
|
+
readonly cropTitle: "Adjust edges";
|
|
18
|
+
readonly deletePage: "Delete page";
|
|
19
|
+
readonly dragPage: "Drag {page} to reorder";
|
|
20
|
+
readonly editCrop: "Edit edges";
|
|
21
|
+
readonly edgesFound: "Page edges found.";
|
|
22
|
+
readonly edgesNotFound: "Edges not found. Adjust the corners.";
|
|
23
|
+
readonly filterColour: "Colour";
|
|
24
|
+
readonly filterDocument: "Black and white";
|
|
25
|
+
readonly filterGreyscale: "Greyscale";
|
|
26
|
+
readonly filterOriginal: "Original";
|
|
27
|
+
readonly filters: "Enhance";
|
|
28
|
+
readonly finish: "Use scan";
|
|
29
|
+
readonly finishing: "Preparing your scan";
|
|
30
|
+
readonly maxPages: "You reached the page limit.";
|
|
31
|
+
readonly moveDown: "Move {page} down";
|
|
32
|
+
readonly moveUp: "Move {page} up";
|
|
33
|
+
readonly page: "Page";
|
|
34
|
+
readonly pageAdded: "Page {position} added.";
|
|
35
|
+
readonly pageDeleted: "Page deleted.";
|
|
36
|
+
readonly pageMoved: "{page} moved to position {position} of {count}.";
|
|
37
|
+
readonly pages: "Pages";
|
|
38
|
+
readonly processFailed: "The scan could not be prepared. Try again.";
|
|
39
|
+
readonly reorderCancelled: "{page} stays where it was.";
|
|
40
|
+
readonly reorderDropped: "{page} dropped at {position} of {count}.";
|
|
41
|
+
readonly reorderInstructions: "Press space to lift a page, the arrow keys to move it and space to drop it.";
|
|
42
|
+
readonly reorderLifted: "{page} lifted from {position} of {count}.";
|
|
43
|
+
readonly reorderMoved: "{page} is now {position} of {count}.";
|
|
44
|
+
readonly retake: "Retake";
|
|
45
|
+
readonly rotate: "Rotate";
|
|
46
|
+
readonly takePhoto: "Take photo";
|
|
47
|
+
readonly title: "Scan document";
|
|
48
|
+
readonly torchOff: "Turn light off";
|
|
49
|
+
readonly torchOn: "Turn light on";
|
|
50
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsDocumentScannerLabels = void 0;
|
|
4
|
+
exports.PsDocumentScannerLabels = {
|
|
5
|
+
addPage: 'Add page',
|
|
6
|
+
autoDetect: 'Auto-detect edges',
|
|
7
|
+
cameraDenied: 'Camera access was blocked. Allow it in your browser settings, or take a photo or choose existing photos instead.',
|
|
8
|
+
cameraStarting: 'Starting camera',
|
|
9
|
+
cameraUnavailable: 'No camera is available here. Take a photo or choose existing photos instead.',
|
|
10
|
+
backToPages: 'Back to pages',
|
|
11
|
+
capture: 'Capture page',
|
|
12
|
+
choosePhotos: 'Choose photos',
|
|
13
|
+
close: 'Close scanner',
|
|
14
|
+
cornerBottomLeft: 'Bottom left corner',
|
|
15
|
+
cornerBottomRight: 'Bottom right corner',
|
|
16
|
+
cornerTopLeft: 'Top left corner',
|
|
17
|
+
cornerTopRight: 'Top right corner',
|
|
18
|
+
cropApply: 'Use crop',
|
|
19
|
+
cropHint: 'Drag the corners to fit the page, or use the arrow keys on a focused corner.',
|
|
20
|
+
cropTitle: 'Adjust edges',
|
|
21
|
+
deletePage: 'Delete page',
|
|
22
|
+
dragPage: 'Drag {page} to reorder',
|
|
23
|
+
editCrop: 'Edit edges',
|
|
24
|
+
edgesFound: 'Page edges found.',
|
|
25
|
+
edgesNotFound: 'Edges not found. Adjust the corners.',
|
|
26
|
+
filterColour: 'Colour',
|
|
27
|
+
filterDocument: 'Black and white',
|
|
28
|
+
filterGreyscale: 'Greyscale',
|
|
29
|
+
filterOriginal: 'Original',
|
|
30
|
+
filters: 'Enhance',
|
|
31
|
+
finish: 'Use scan',
|
|
32
|
+
finishing: 'Preparing your scan',
|
|
33
|
+
maxPages: 'You reached the page limit.',
|
|
34
|
+
moveDown: 'Move {page} down',
|
|
35
|
+
moveUp: 'Move {page} up',
|
|
36
|
+
page: 'Page',
|
|
37
|
+
pageAdded: 'Page {position} added.',
|
|
38
|
+
pageDeleted: 'Page deleted.',
|
|
39
|
+
pageMoved: '{page} moved to position {position} of {count}.',
|
|
40
|
+
pages: 'Pages',
|
|
41
|
+
processFailed: 'The scan could not be prepared. Try again.',
|
|
42
|
+
reorderCancelled: '{page} stays where it was.',
|
|
43
|
+
reorderDropped: '{page} dropped at {position} of {count}.',
|
|
44
|
+
reorderInstructions: 'Press space to lift a page, the arrow keys to move it and space to drop it.',
|
|
45
|
+
reorderLifted: '{page} lifted from {position} of {count}.',
|
|
46
|
+
reorderMoved: '{page} is now {position} of {count}.',
|
|
47
|
+
retake: 'Retake',
|
|
48
|
+
rotate: 'Rotate',
|
|
49
|
+
takePhoto: 'Take photo',
|
|
50
|
+
title: 'Scan document',
|
|
51
|
+
torchOff: 'Turn light off',
|
|
52
|
+
torchOn: 'Turn light on',
|
|
53
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const PsScanCameraStatus: {
|
|
2
|
+
readonly Denied: "denied";
|
|
3
|
+
readonly Ready: "ready";
|
|
4
|
+
readonly Starting: "starting";
|
|
5
|
+
readonly Unavailable: "unavailable";
|
|
6
|
+
};
|
|
7
|
+
export type PsScanCameraStatus = (typeof PsScanCameraStatus)[keyof typeof PsScanCameraStatus];
|