@pallasoft/ps-file-components 1.4.0 → 1.5.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 +69 -21
- package/dist/components/ps-document-scanner.d.ts +1 -1
- package/dist/components/ps-document-scanner.js +2 -2
- package/dist/components/ps-file-picker-actions.d.ts +10 -0
- package/dist/components/ps-file-picker-actions.js +21 -0
- package/dist/components/ps-file-picker.d.ts +7 -1
- package/dist/components/ps-file-picker.js +30 -6
- package/dist/components/ps-scan-button.d.ts +13 -0
- package/dist/components/ps-scan-button.js +9 -0
- package/dist/components/ps-scan-camera-view.d.ts +17 -7
- package/dist/components/ps-scan-camera-view.js +23 -16
- package/dist/components/ps-scan-crop-editor.d.ts +2 -2
- package/dist/components/ps-scan-crop-editor.js +19 -3
- package/dist/components/ps-scan-denied-steps.d.ts +6 -0
- package/dist/components/ps-scan-denied-steps.js +33 -0
- package/dist/components/ps-scan-icon-button.d.ts +4 -3
- package/dist/components/ps-scan-icon-button.js +3 -1
- package/dist/components/ps-scan-illustration.d.ts +1 -0
- package/dist/components/ps-scan-illustration.js +6 -0
- package/dist/components/ps-scan-loupe.d.ts +10 -0
- package/dist/components/ps-scan-loupe.js +31 -0
- package/dist/components/ps-scan-page-list.d.ts +3 -1
- package/dist/components/ps-scan-page-list.js +7 -8
- package/dist/components/ps-scan-page-stack.d.ts +7 -0
- package/dist/components/ps-scan-page-stack.js +6 -0
- package/dist/components/ps-scan-permission-view.d.ts +11 -0
- package/dist/components/ps-scan-permission-view.js +48 -0
- package/dist/components/ps-scan-photo-button.d.ts +8 -0
- package/dist/components/ps-scan-photo-button.js +20 -0
- package/dist/components/ps-scan-review.d.ts +2 -1
- package/dist/components/ps-scan-review.js +21 -4
- package/dist/components/ps-scan-shutter.d.ts +7 -0
- package/dist/components/ps-scan-shutter.js +6 -0
- package/dist/components/ps-scan-viewfinder-overlay.d.ts +8 -0
- package/dist/components/ps-scan-viewfinder-overlay.js +28 -0
- package/dist/components/ps-upload-row.js +1 -1
- package/dist/constants/ps-document-scanner-labels.d.ts +49 -19
- package/dist/constants/ps-document-scanner-labels.js +49 -19
- package/dist/constants/ps-file-picker-labels.d.ts +5 -3
- package/dist/constants/ps-file-picker-labels.js +5 -3
- package/dist/constants/ps-scan-camera-status.d.ts +4 -0
- package/dist/constants/ps-scan-camera-status.js +4 -0
- package/dist/constants/ps-scan-chrome-classes.d.ts +12 -0
- package/dist/constants/ps-scan-chrome-classes.js +15 -0
- package/dist/constants/ps-scan-config.d.ts +18 -1
- package/dist/constants/ps-scan-config.js +18 -1
- package/dist/constants/ps-scan-platform.d.ts +6 -0
- package/dist/constants/ps-scan-platform.js +8 -0
- package/dist/esm/components/ps-document-scanner-sheet.js +71 -23
- package/dist/esm/components/ps-document-scanner.js +2 -2
- package/dist/esm/components/ps-file-picker-actions.js +17 -0
- package/dist/esm/components/ps-file-picker.js +31 -7
- package/dist/esm/components/ps-scan-button.js +5 -0
- package/dist/esm/components/ps-scan-camera-view.js +26 -19
- package/dist/esm/components/ps-scan-crop-editor.js +21 -5
- package/dist/esm/components/ps-scan-denied-steps.js +29 -0
- package/dist/esm/components/ps-scan-icon-button.js +3 -1
- package/dist/esm/components/ps-scan-illustration.js +2 -0
- package/dist/esm/components/ps-scan-loupe.js +27 -0
- package/dist/esm/components/ps-scan-page-list.js +7 -8
- package/dist/esm/components/ps-scan-page-stack.js +2 -0
- package/dist/esm/components/ps-scan-permission-view.js +44 -0
- package/dist/esm/components/ps-scan-photo-button.js +16 -0
- package/dist/esm/components/ps-scan-review.js +21 -4
- package/dist/esm/components/ps-scan-shutter.js +2 -0
- package/dist/esm/components/ps-scan-viewfinder-overlay.js +24 -0
- package/dist/esm/components/ps-upload-row.js +1 -1
- package/dist/esm/constants/ps-document-scanner-labels.js +49 -19
- package/dist/esm/constants/ps-file-picker-labels.js +5 -3
- package/dist/esm/constants/ps-scan-camera-status.js +4 -0
- package/dist/esm/constants/ps-scan-chrome-classes.js +12 -0
- package/dist/esm/constants/ps-scan-config.js +18 -1
- package/dist/esm/constants/ps-scan-platform.js +5 -0
- package/dist/esm/hooks/use-ps-scan-camera.js +52 -15
- package/dist/esm/hooks/use-ps-scan-live-detection.js +45 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/models/ps-file-picker-scan.js +1 -0
- package/dist/esm/models/ps-scan-live-detection.js +1 -0
- package/dist/esm/models/ps-scan-stability.js +1 -0
- package/dist/esm/services/ps-scan-camera-service.js +50 -5
- package/dist/esm/services/ps-scan-canvas-service.js +6 -0
- package/dist/esm/services/ps-scan-edge-service.js +1 -1
- package/dist/esm/services/ps-scan-geometry-service.js +20 -0
- package/dist/esm/services/ps-scan-platform-service.js +15 -0
- package/dist/esm/services/ps-scan-stability-service.js +41 -0
- package/dist/hooks/use-ps-scan-camera.d.ts +3 -1
- package/dist/hooks/use-ps-scan-camera.js +52 -15
- package/dist/hooks/use-ps-scan-live-detection.d.ts +3 -0
- package/dist/hooks/use-ps-scan-live-detection.js +49 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/ps-document-scanner-props.d.ts +1 -0
- package/dist/models/ps-document-scanner-sheet-props.d.ts +1 -0
- package/dist/models/ps-file-picker-scan.d.ts +4 -0
- package/dist/models/ps-file-picker-scan.js +2 -0
- package/dist/models/ps-scan-live-detection.d.ts +5 -0
- package/dist/models/ps-scan-live-detection.js +2 -0
- package/dist/models/ps-scan-stability.d.ts +8 -0
- package/dist/models/ps-scan-stability.js +2 -0
- package/dist/services/ps-scan-camera-service.d.ts +4 -0
- package/dist/services/ps-scan-camera-service.js +50 -5
- package/dist/services/ps-scan-canvas-service.d.ts +1 -0
- package/dist/services/ps-scan-canvas-service.js +6 -0
- package/dist/services/ps-scan-edge-service.js +1 -1
- package/dist/services/ps-scan-geometry-service.d.ts +2 -0
- package/dist/services/ps-scan-geometry-service.js +20 -0
- package/dist/services/ps-scan-platform-service.d.ts +4 -0
- package/dist/services/ps-scan-platform-service.js +19 -0
- package/dist/services/ps-scan-stability-service.d.ts +9 -0
- package/dist/services/ps-scan-stability-service.js +45 -0
- package/package.json +3 -3
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRef } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { usePsElementSize } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIconKey } from '@pallasoft/ps-icons';
|
|
4
6
|
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
5
7
|
import { PsScanCornerOrder } from '../constants/ps-scan-corner-order';
|
|
6
8
|
import { PsScanGeometryService } from '../services/ps-scan-geometry-service';
|
|
7
|
-
|
|
9
|
+
import { PsScanButton } from './ps-scan-button';
|
|
10
|
+
import { PsScanIconButton } from './ps-scan-icon-button';
|
|
11
|
+
import { PsScanLoupe } from './ps-scan-loupe';
|
|
12
|
+
export const PsScanCropEditor = ({ labels, onAutoDetect, onChange, onKeep, onRetake, page, }) => {
|
|
8
13
|
const [areaRef, area] = usePsElementSize();
|
|
9
14
|
const stageRef = useRef(null);
|
|
15
|
+
const [activeIndex, setActiveIndex] = useState(null);
|
|
10
16
|
const hintId = 'ps-scan-crop-hint';
|
|
11
17
|
const size = PsScanGeometryService.fit(area, { height: page.height, width: page.width });
|
|
12
18
|
const cornerLabels = [
|
|
@@ -15,6 +21,9 @@ export const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRe
|
|
|
15
21
|
labels.cornerBottomRight,
|
|
16
22
|
labels.cornerBottomLeft,
|
|
17
23
|
];
|
|
24
|
+
const outline = page.corners
|
|
25
|
+
.map((point) => `${point.x * size.width},${point.y * size.height}`)
|
|
26
|
+
.join(' L');
|
|
18
27
|
const move = (index, event) => {
|
|
19
28
|
const stage = stageRef.current;
|
|
20
29
|
if (stage === null || !event.currentTarget.hasPointerCapture(event.pointerId))
|
|
@@ -28,6 +37,10 @@ export const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRe
|
|
|
28
37
|
if (PsScanGeometryService.isUsable(next))
|
|
29
38
|
onChange(next);
|
|
30
39
|
};
|
|
40
|
+
const release = (event) => {
|
|
41
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
42
|
+
setActiveIndex(null);
|
|
43
|
+
};
|
|
31
44
|
const nudge = (index, event) => {
|
|
32
45
|
const step = event.shiftKey ? PsScanConfig.cornerNudgeLarge : PsScanConfig.cornerNudge;
|
|
33
46
|
const deltas = {
|
|
@@ -42,8 +55,11 @@ export const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRe
|
|
|
42
55
|
event.preventDefault();
|
|
43
56
|
onChange(PsScanGeometryService.nudge(page.corners, index, delta[0], delta[1]));
|
|
44
57
|
};
|
|
45
|
-
return (_jsxs("div", { className: 'flex h-full min-h-0 flex-col bg-
|
|
58
|
+
return (_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("span", { "aria-hidden": 'true' }), _jsx("h2", { className: 'truncate text-center text-base font-semibold', children: labels.cropTitle }), _jsx(PsScanIconButton, { iconKey: PsIconKey.Spark, label: labels.autoDetect, onClick: onAutoDetect })] }), _jsx("p", { className: 'shrink-0 px-6 text-center text-sm text-white/65', id: hintId, children: labels.cropHint }), _jsx("div", { className: 'flex min-h-0 flex-1 items-center justify-center px-8 py-6', ref: areaRef, children: _jsxs("div", { className: 'relative touch-none select-none', ref: stageRef, style: { height: size.height, width: size.width }, children: [_jsx("img", { alt: '', className: 'absolute inset-0 size-full rounded-sm', draggable: false, src: page.sourceUrl }), _jsxs("svg", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 size-full overflow-visible', viewBox: `0 0 ${Math.max(1, size.width)} ${Math.max(1, size.height)}`, children: [_jsx("path", { className: 'fill-black/55', d: `M0 0H${size.width}V${size.height}H0Z M${outline}Z`, fillRule: 'evenodd' }), _jsx("path", { className: 'fill-none stroke-primary', d: `M${outline}Z`, strokeLinejoin: 'round', strokeWidth: '2.5' })] }), PsScanCornerOrder.map((key, index) => (_jsx("button", { "aria-describedby": hintId, "aria-label": cornerLabels[index], className: 'ps-focus-ring absolute flex size-12 -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: release, onPointerDown: (event) => {
|
|
59
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
60
|
+
setActiveIndex(index);
|
|
61
|
+
}, onPointerMove: (event) => move(index, event), onPointerUp: release, style: {
|
|
46
62
|
left: `${page.corners[index].x * 100}%`,
|
|
47
63
|
top: `${page.corners[index].y * 100}%`,
|
|
48
|
-
}, type: 'button', children: _jsx("span", { className: 'size-
|
|
64
|
+
}, type: 'button', children: _jsx("span", { className: PallasoftClassNameService.merge('size-6 rounded-full border-[3px] border-white bg-primary shadow-lg transition-transform duration-150', activeIndex === index ? 'scale-125' : '') }) }, key))), activeIndex === null ? null : (_jsx(PsScanLoupe, { corners: page.corners, point: page.corners[activeIndex], source: page.sourceUrl, stage: size }))] }) }), _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: [_jsx(PsScanButton, { isLarge: true, onClick: onRetake, children: labels.retake }), _jsx(PsScanButton, { isLarge: true, isPrimary: true, onClick: onKeep, children: labels.keepScan })] })] }));
|
|
49
65
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
+
import { PsScanPlatform } from '../constants/ps-scan-platform';
|
|
5
|
+
import { PsScanPlatformService } from '../services/ps-scan-platform-service';
|
|
6
|
+
export const PsScanDeniedSteps = ({ labels }) => {
|
|
7
|
+
const [platform, setPlatform] = useState(() => PsScanPlatformService.detect());
|
|
8
|
+
const platforms = [
|
|
9
|
+
{
|
|
10
|
+
key: PsScanPlatform.Ios,
|
|
11
|
+
label: labels.platformIos,
|
|
12
|
+
steps: [labels.deniedIos1, labels.deniedIos2, labels.deniedIos3],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: PsScanPlatform.Android,
|
|
16
|
+
label: labels.platformAndroid,
|
|
17
|
+
steps: [labels.deniedAndroid1, labels.deniedAndroid2, labels.deniedAndroid3],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: PsScanPlatform.Desktop,
|
|
21
|
+
label: labels.platformDesktop,
|
|
22
|
+
steps: [labels.deniedDesktop1, labels.deniedDesktop2, labels.deniedDesktop3],
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
const active = platforms.find((item) => item.key === platform);
|
|
26
|
+
return (_jsxs("div", { className: 'flex w-full flex-col gap-4 rounded-ps-control border border-white/10 bg-white/5 p-4 text-left', children: [_jsx("div", { className: 'grid grid-cols-3 gap-1 rounded-ps-control bg-black/40 p-1', role: 'group', children: platforms.map((item) => (_jsx("button", { "aria-pressed": item.key === platform, className: PallasoftClassNameService.merge('ps-focus-ring ps-control min-w-0 truncate rounded-ps-control px-2 text-xs font-semibold transition', item.key === platform
|
|
27
|
+
? 'bg-white text-black'
|
|
28
|
+
: 'text-white/70 hover:bg-white/10 hover:text-white'), onClick: () => setPlatform(item.key), type: 'button', children: item.label }, item.key))) }), _jsx("ol", { className: 'flex flex-col gap-3', children: (active === undefined ? [] : active.steps).map((step, index) => (_jsxs("li", { className: 'flex items-start gap-3 text-sm leading-5 text-white/85', children: [_jsx("span", { className: 'flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-[color:var(--ps-color-primary-contrast)]', children: index + 1 }), _jsx("span", { className: 'pt-0.5', children: step })] }, step))) })] }));
|
|
29
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
-
|
|
3
|
+
import { PsIcon } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsScanChromeClasses } from '../constants/ps-scan-chrome-classes';
|
|
5
|
+
export const PsScanIconButton = ({ className, disabled, iconClassName, iconKey, isPressed, label, onClick, }) => (_jsx("button", { "aria-label": label, "aria-pressed": isPressed, className: PallasoftClassNameService.merge(PsScanChromeClasses.icon, isPressed ? PsScanChromeClasses.iconOn : PsScanChromeClasses.iconOff, className), disabled: disabled, onClick: onClick, title: label, type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('size-ps-control-icon-standalone', iconClassName), iconKey: iconKey }) }));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export const PsScanIllustration = () => (_jsxs("svg", { "aria-hidden": 'true', className: 'h-auto w-40', fill: 'none', viewBox: '0 0 160 160', children: [_jsx("rect", { className: 'fill-white/5 stroke-white/15', height: '148', rx: '28', width: '148', x: '6', y: '6' }), _jsx("path", { className: 'fill-white', d: 'M52 34h56a4 4 0 0 1 4 4v84l-8-5-8 5-8-5-8 5-8-5-8 5-8-5-8 5V38a4 4 0 0 1 4-4Z' }), _jsx("path", { className: 'stroke-black/25', d: 'M62 52h36M62 64h28M62 76h36M62 88h20M88 100h10', strokeLinecap: 'round', strokeWidth: '4' }), _jsx("path", { className: 'stroke-primary', d: 'M38 46V30a6 6 0 0 1 6-6h16M100 24h16a6 6 0 0 1 6 6v16M122 114v16a6 6 0 0 1-6 6h-16M60 136H44a6 6 0 0 1-6-6v-16', strokeLinecap: 'round', strokeWidth: '5' }), _jsx("rect", { className: 'fill-primary motion-safe:animate-pulse', height: '4', rx: '2', width: '96', x: '32', y: '78' })] }));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
3
|
+
export const PsScanLoupe = ({ corners, point, source, stage }) => {
|
|
4
|
+
const size = PsScanConfig.loupeSize;
|
|
5
|
+
const zoom = PsScanConfig.loupeZoom;
|
|
6
|
+
const centreX = point.x * stage.width * zoom;
|
|
7
|
+
const centreY = point.y * stage.height * zoom;
|
|
8
|
+
const pointX = point.x * stage.width;
|
|
9
|
+
const pointY = point.y * stage.height;
|
|
10
|
+
const isBelow = pointY - PsScanConfig.loupeOffset - size < 0;
|
|
11
|
+
const left = Math.min(Math.max(pointX - size / 2, -size / 4), stage.width - (size * 3) / 4);
|
|
12
|
+
const top = isBelow
|
|
13
|
+
? pointY + PsScanConfig.loupeOffset
|
|
14
|
+
: pointY - PsScanConfig.loupeOffset - size;
|
|
15
|
+
return (_jsx("div", { "aria-hidden": 'true', className: 'pointer-events-none absolute z-10 overflow-hidden rounded-full border-4 border-white bg-black shadow-2xl animate-ps-scale-in', style: {
|
|
16
|
+
backgroundImage: `url("${source}")`,
|
|
17
|
+
backgroundPosition: `${size / 2 - centreX}px ${size / 2 - centreY}px`,
|
|
18
|
+
backgroundRepeat: 'no-repeat',
|
|
19
|
+
backgroundSize: `${stage.width * zoom}px ${stage.height * zoom}px`,
|
|
20
|
+
height: size,
|
|
21
|
+
left,
|
|
22
|
+
top,
|
|
23
|
+
width: size,
|
|
24
|
+
}, children: _jsxs("svg", { className: 'absolute inset-0 size-full', viewBox: `0 0 ${size} ${size}`, children: [_jsx("polygon", { className: 'fill-none stroke-primary', points: corners
|
|
25
|
+
.map((corner) => `${corner.x * stage.width * zoom - centreX + size / 2},${corner.y * stage.height * zoom - centreY + size / 2}`)
|
|
26
|
+
.join(' '), strokeWidth: '2' }), _jsx("path", { className: 'stroke-white', d: `M${size / 2 - 10} ${size / 2}H${size / 2 + 10}M${size / 2} ${size / 2 - 10}V${size / 2 + 10}`, strokeLinecap: 'round', strokeWidth: '2' })] }) }));
|
|
27
|
+
};
|
|
@@ -3,17 +3,16 @@ import { PsSortableList } from '@pallasoft/ps-core-components';
|
|
|
3
3
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
4
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
5
5
|
import { PsScanTemplateService } from '../services/ps-scan-template-service';
|
|
6
|
-
|
|
7
|
-
export const PsScanPageList = ({ activeId, labels, onMove, onSelect, pages, previews, }) => {
|
|
6
|
+
export const PsScanPageList = ({ activeId, canAddPage, labels, onAddPage, onMove, onSelect, pages, previews, }) => {
|
|
8
7
|
const items = pages.map((page, index) => ({
|
|
9
8
|
id: page.id,
|
|
10
9
|
label: `${labels.page} ${index + 1}`,
|
|
11
10
|
}));
|
|
12
11
|
const say = (template, label, position) => PsScanTemplateService.fill(template, { count: pages.length, page: label, position });
|
|
13
|
-
return (_jsx(PsSortableList, { announcements: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
return (_jsxs("div", { className: 'ps-scrollbar-hidden -mx-4 flex items-center gap-3 overflow-x-auto px-4 py-2 sm:mx-0 sm:px-0 lg:flex-wrap lg:overflow-visible', children: [_jsx(PsSortableList, { announcements: {
|
|
13
|
+
cancelled: (label) => say(labels.reorderCancelled, label, 0),
|
|
14
|
+
dropped: (label, position) => say(labels.reorderDropped, label, position),
|
|
15
|
+
lifted: (label, position) => say(labels.reorderLifted, label, position),
|
|
16
|
+
moved: (label, position) => say(labels.reorderMoved, label, position),
|
|
17
|
+
}, className: 'flex items-center gap-3 lg:flex-wrap', getHandleLabel: (label) => PsScanTemplateService.fill(labels.dragPage, { page: label }), handleClassName: 'ps-focus-ring absolute inset-0 cursor-grab touch-none rounded-md active:cursor-grabbing', handleIcon: _jsx("span", { "aria-hidden": 'true' }), instructions: labels.reorderInstructions, isHorizontal: true, items: items, onMove: (_id, from, to) => onMove(from, to), renderItem: ({ handle, index, isOverlay, item }) => (_jsxs("div", { "aria-current": item.id === activeId, className: 'relative flex flex-col items-center gap-1.5', onClick: () => onSelect(item.id), role: 'listitem', children: [_jsxs("span", { className: PallasoftClassNameService.merge('relative block h-[4.5rem] w-14 overflow-hidden rounded-md bg-white/10 ring-offset-2 ring-offset-black transition', item.id === activeId ? 'ring-2 ring-primary' : 'opacity-75', isOverlay ? 'shadow-2xl' : ''), children: [previews[item.id] === undefined ? (_jsx("span", { className: 'ps-skeleton block size-full' })) : (_jsx("img", { alt: '', className: 'size-full object-cover', src: previews[item.id] })), handle] }), _jsx("span", { className: PallasoftClassNameService.merge('text-xs font-semibold tabular-nums', item.id === activeId ? 'text-white' : 'text-white/55'), children: index + 1 })] })), role: 'list' }), canAddPage ? (_jsxs("div", { className: 'flex shrink-0 flex-col items-center gap-1.5', children: [_jsx("button", { "aria-label": labels.addPage, className: 'ps-focus-ring flex h-[4.5rem] w-14 items-center justify-center rounded-md border-2 border-dashed border-white/25 text-white/70 transition hover:border-white/50 hover:text-white', onClick: onAddPage, title: labels.addPage, type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: PsIconKey.Plus }) }), _jsx("span", { "aria-hidden": 'true', className: 'text-xs text-transparent', children: "0" })] })) : null] }));
|
|
19
18
|
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export const PsScanPageStack = ({ count, label, onOpen, preview }) => (_jsxs("button", { "aria-label": label, className: 'ps-focus-ring relative flex h-16 w-12 shrink-0 rounded-md animate-ps-scale-in', onClick: onOpen, title: label, type: 'button', children: [count > 1 ? (_jsx("span", { "aria-hidden": 'true', className: 'absolute inset-0 rotate-6 rounded-md border-2 border-white/60 bg-white/30' })) : null, _jsx("span", { className: 'relative block size-full overflow-hidden rounded-md border-2 border-white bg-white/20', children: preview === undefined ? null : (_jsx("img", { alt: '', className: 'size-full object-cover', src: preview })) }), _jsx("span", { className: 'absolute -right-2 -top-2 flex h-6 min-w-6 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-bold tabular-nums text-[color:var(--ps-color-primary-contrast)]', children: count })] }));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
3
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
|
|
5
|
+
import { PsScanButton } from './ps-scan-button';
|
|
6
|
+
import { PsScanDeniedSteps } from './ps-scan-denied-steps';
|
|
7
|
+
import { PsScanIllustration } from './ps-scan-illustration';
|
|
8
|
+
import { PsScanPhotoButton } from './ps-scan-photo-button';
|
|
9
|
+
export const PsScanPermissionView = ({ isMultiple, labels, onAllow, onFiles, status, }) => {
|
|
10
|
+
if (status === PsScanCameraStatus.Checking || status === PsScanCameraStatus.Starting)
|
|
11
|
+
return (_jsxs("div", { className: 'flex h-full flex-col items-center justify-center gap-3 text-sm text-white/75', role: 'status', children: [_jsx(PsSpinner, { color: 'current', label: labels.cameraStarting, size: 'small' }), _jsx("span", { children: labels.cameraStarting })] }));
|
|
12
|
+
const screens = {
|
|
13
|
+
[PsScanCameraStatus.Busy]: {
|
|
14
|
+
body: labels.busyBody,
|
|
15
|
+
iconKey: PsIconKey.Camera,
|
|
16
|
+
title: labels.busyTitle,
|
|
17
|
+
},
|
|
18
|
+
[PsScanCameraStatus.Denied]: {
|
|
19
|
+
body: labels.deniedBody,
|
|
20
|
+
iconKey: PsIconKey.CameraOff,
|
|
21
|
+
title: labels.deniedTitle,
|
|
22
|
+
},
|
|
23
|
+
[PsScanCameraStatus.Insecure]: {
|
|
24
|
+
body: labels.insecureBody,
|
|
25
|
+
iconKey: PsIconKey.Shield,
|
|
26
|
+
title: labels.insecureTitle,
|
|
27
|
+
},
|
|
28
|
+
[PsScanCameraStatus.Prompt]: {
|
|
29
|
+
body: labels.primerBody,
|
|
30
|
+
iconKey: PsIconKey.Camera,
|
|
31
|
+
title: labels.primerTitle,
|
|
32
|
+
},
|
|
33
|
+
[PsScanCameraStatus.Ready]: { body: '', iconKey: PsIconKey.Camera, title: '' },
|
|
34
|
+
[PsScanCameraStatus.Unavailable]: {
|
|
35
|
+
body: labels.unavailableBody,
|
|
36
|
+
iconKey: PsIconKey.CameraOff,
|
|
37
|
+
title: labels.unavailableTitle,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const screen = screens[status];
|
|
41
|
+
const isPrimer = status === PsScanCameraStatus.Prompt;
|
|
42
|
+
const canRetry = status === PsScanCameraStatus.Denied || status === PsScanCameraStatus.Busy;
|
|
43
|
+
return (_jsx("div", { className: 'flex h-full min-h-0 flex-col overflow-y-auto', children: _jsxs("div", { className: 'm-auto flex w-full max-w-sm flex-col items-center gap-6 px-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] pt-20 text-center animate-ps-slide-up', children: [isPrimer ? (_jsx(PsScanIllustration, {})) : (_jsx("span", { className: 'flex size-20 items-center justify-center rounded-full bg-white/10 text-white', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-9', iconKey: screen.iconKey }) })), _jsxs("div", { className: 'flex flex-col gap-2', children: [_jsx("h2", { className: 'text-xl font-semibold leading-tight text-white sm:text-2xl', children: screen.title }), _jsx("p", { className: 'text-sm leading-6 text-white/70', children: screen.body })] }), status === PsScanCameraStatus.Denied ? _jsx(PsScanDeniedSteps, { labels: labels }) : null, _jsxs("div", { className: 'flex w-full flex-col gap-3', children: [isPrimer || canRetry ? (_jsx(PsScanButton, { className: 'w-full', isLarge: true, isPrimary: true, leadingIcon: isPrimer ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Camera })) : undefined, onClick: onAllow, children: isPrimer ? labels.allowCamera : labels.tryAgain })) : null, _jsx(PsScanPhotoButton, { className: 'w-full', isMultiple: isMultiple, label: labels.choosePhoto, onFiles: onFiles })] })] }) }));
|
|
44
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsScanButton } from './ps-scan-button';
|
|
5
|
+
import { PsScanIconButton } from './ps-scan-icon-button';
|
|
6
|
+
export const PsScanPhotoButton = ({ className, isIconOnly = false, isMultiple, label, onFiles, }) => {
|
|
7
|
+
const input = useRef(null);
|
|
8
|
+
const open = () => input.current?.click();
|
|
9
|
+
const onChange = (event) => {
|
|
10
|
+
const files = Array.from(event.target.files === null ? [] : event.target.files);
|
|
11
|
+
event.target.value = '';
|
|
12
|
+
if (files.length > 0)
|
|
13
|
+
onFiles(files);
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs(_Fragment, { children: [isIconOnly ? (_jsx(PsScanIconButton, { className: className, iconKey: PsIconKey.Image, label: label, onClick: open })) : (_jsx(PsScanButton, { className: className, isLarge: true, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Image }), onClick: open, children: label })), _jsx("input", { accept: 'image/*', "aria-hidden": 'true', className: 'sr-only', multiple: isMultiple, onChange: onChange, ref: input, tabIndex: -1, type: 'file' })] }));
|
|
16
|
+
};
|
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
3
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsScanChromeClasses } from '../constants/ps-scan-chrome-classes';
|
|
4
5
|
import { PsScanFilter } from '../constants/ps-scan-filter';
|
|
6
|
+
import { PsScanTemplateService } from '../services/ps-scan-template-service';
|
|
7
|
+
import { PsScanButton } from './ps-scan-button';
|
|
5
8
|
import { PsScanIconButton } from './ps-scan-icon-button';
|
|
6
9
|
import { PsScanPageList } from './ps-scan-page-list';
|
|
7
|
-
export const PsScanReview = ({ canAddPage, error, isBusy, labels, onAddPage, onDelete, onEditCrop, onFilter, onFinish, onMove, onRetake, onRotate, onSelect, page, pages, previews, }) => {
|
|
10
|
+
export const PsScanReview = ({ canAddPage, error, isBusy, labels, onAddPage, onClose, onDelete, onEditCrop, onFilter, onFinish, onMove, onRetake, onRotate, onSelect, page, pages, previews, }) => {
|
|
11
|
+
const index = pages.findIndex((item) => item.id === page.id);
|
|
12
|
+
const preview = previews[page.id];
|
|
8
13
|
const filters = [
|
|
9
14
|
{ key: PsScanFilter.Original, label: labels.filterOriginal },
|
|
10
15
|
{ key: PsScanFilter.Colour, label: labels.filterColour },
|
|
11
16
|
{ key: PsScanFilter.Greyscale, label: labels.filterGreyscale },
|
|
12
17
|
{ key: PsScanFilter.Document, label: labels.filterDocument },
|
|
13
18
|
];
|
|
14
|
-
const
|
|
15
|
-
|
|
19
|
+
const tools = [
|
|
20
|
+
{ iconKey: PsIconKey.Crop, label: labels.editCrop, onClick: onEditCrop },
|
|
21
|
+
{ iconKey: PsIconKey.Rotate, label: labels.rotate, onClick: onRotate },
|
|
22
|
+
{ iconKey: PsIconKey.Refresh, label: labels.retake, onClick: onRetake },
|
|
23
|
+
{ iconKey: PsIconKey.Trash, label: labels.deletePage, onClick: onDelete },
|
|
24
|
+
];
|
|
25
|
+
return (_jsxs("div", { className: 'flex h-full min-h-0 flex-col bg-black text-white animate-ps-fade-in lg:flex-row', children: [_jsxs("section", { className: 'flex min-h-0 flex-1 flex-col', 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.Close, label: labels.close, onClick: onClose }), _jsxs("div", { className: 'flex items-center justify-center gap-1', children: [_jsx(PsScanIconButton, { disabled: index <= 0, iconClassName: 'rotate-180', iconKey: PsIconKey.ChevronRight, label: labels.previousPage, onClick: () => onSelect(pages[index - 1].id) }), _jsx("h2", { className: 'min-w-16 text-center text-sm font-semibold tabular-nums', children: PsScanTemplateService.fill(labels.pageOf, {
|
|
26
|
+
count: pages.length,
|
|
27
|
+
position: index + 1,
|
|
28
|
+
}) }), _jsx(PsScanIconButton, { disabled: index >= pages.length - 1, iconKey: PsIconKey.ChevronRight, label: labels.nextPage, onClick: () => onSelect(pages[index + 1].id) })] }), _jsx(PsScanIconButton, { disabled: !canAddPage, iconKey: PsIconKey.Camera, label: labels.addPage, onClick: onAddPage })] }), _jsx("div", { className: 'flex min-h-0 flex-1 items-center justify-center px-6 py-4', children: preview === undefined ? (_jsx("span", { "aria-hidden": 'true', className: 'ps-skeleton block h-4/5 w-3/5 rounded-sm' })) : (_jsx("img", { alt: `${labels.page} ${index + 1}`, className: 'max-h-full max-w-full rounded-sm object-contain shadow-[0_24px_60px_-20px_rgb(0_0_0/0.9)] animate-ps-fade-in', src: preview }, preview)) })] }), _jsxs("aside", { className: 'flex shrink-0 flex-col gap-3 border-white/10 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] lg:w-[22rem] lg:gap-5 lg:overflow-y-auto lg:border-l lg:pt-[max(1rem,env(safe-area-inset-top))]', children: [_jsx("div", { "aria-label": labels.filters, className: 'grid grid-cols-4 gap-2 sm:mx-auto sm:w-full sm:max-w-md lg:max-w-none lg:grid-cols-2', role: 'group', children: filters.map((filter) => (_jsx("button", { "aria-pressed": page.filter === filter.key, className: PallasoftClassNameService.merge(PsScanChromeClasses.chip, page.filter === filter.key
|
|
29
|
+
? PsScanChromeClasses.iconOn
|
|
30
|
+
: 'bg-white/10 text-white/85 hover:bg-white/20'), onClick: () => onFilter(filter.key), type: 'button', children: filter.label }, filter.key))) }), _jsx("div", { className: 'grid grid-cols-4 gap-1 sm:mx-auto sm:w-full sm:max-w-md lg:max-w-none', children: tools.map((tool) => (_jsxs("button", { className: PsScanChromeClasses.tool, onClick: tool.onClick, type: 'button', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: tool.iconKey }), _jsx("span", { children: tool.label })] }, tool.label))) }), _jsxs("section", { "aria-label": labels.pages, className: 'min-w-0 sm:mx-auto sm:w-full sm:max-w-md lg:max-w-none', children: [_jsx("h3", { className: 'hidden text-xs font-semibold uppercase tracking-wide text-white/50 lg:block', children: labels.pages }), _jsx(PsScanPageList, { activeId: page.id, canAddPage: canAddPage, labels: labels, onAddPage: onAddPage, onMove: onMove, onSelect: onSelect, pages: pages, previews: previews })] }), error === '' ? null : (_jsx("p", { className: 'rounded-ps-control bg-danger px-3 py-2 text-sm text-white', role: 'alert', children: error })), _jsx(PsScanButton, { className: 'w-full sm:mx-auto sm:max-w-md lg:mt-auto lg:max-w-none', isLarge: true, isLoading: isBusy, isPrimary: true, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Upload }), loadingLabel: labels.finishing, onClick: onFinish, children: pages.length === 1
|
|
31
|
+
? labels.finishOne
|
|
32
|
+
: PsScanTemplateService.fill(labels.finish, { count: pages.length }) })] })] }));
|
|
16
33
|
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export const PsScanShutter = ({ disabled, label, onCapture, progress }) => (_jsxs("button", { "aria-label": label, className: 'ps-focus-ring group relative flex size-[4.75rem] shrink-0 items-center justify-center rounded-full disabled:opacity-40', disabled: disabled, onClick: onCapture, title: label, type: 'button', children: [_jsxs("svg", { "aria-hidden": 'true', className: 'absolute inset-0 size-full -rotate-90', viewBox: '0 0 76 76', children: [_jsx("circle", { className: 'stroke-white', cx: '38', cy: '38', fill: 'none', r: '35', strokeWidth: '4' }), _jsx("circle", { className: 'stroke-primary transition-[stroke-dashoffset] duration-150 ease-linear', cx: '38', cy: '38', fill: 'none', pathLength: '100', r: '35', strokeDasharray: '100', strokeDashoffset: 100 - Math.round(progress * 100), strokeLinecap: 'round', strokeWidth: '4' })] }), _jsx("span", { className: 'size-[3.75rem] rounded-full bg-white transition-transform duration-150 group-active:scale-90' })] }));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
3
|
+
import { PsScanGeometryService } from '../services/ps-scan-geometry-service';
|
|
4
|
+
export const PsScanViewfinderOverlay = ({ area, quad, video }) => {
|
|
5
|
+
if (area.width <= 0 || area.height <= 0)
|
|
6
|
+
return null;
|
|
7
|
+
const box = PsScanGeometryService.cover(area, video);
|
|
8
|
+
const left = (area.width - box.width) / 2;
|
|
9
|
+
const top = (area.height - box.height) / 2;
|
|
10
|
+
const frameHeight = Math.min(area.height * PsScanConfig.guideHeightRatio, (area.width * PsScanConfig.guideWidthRatio) / PsScanConfig.guideAspect);
|
|
11
|
+
const frameWidth = frameHeight * PsScanConfig.guideAspect;
|
|
12
|
+
const frameLeft = (area.width - frameWidth) / 2;
|
|
13
|
+
const frameTop = (area.height - frameHeight) / 2 - area.height * PsScanConfig.guideLift;
|
|
14
|
+
const arm = Math.min(PsScanConfig.guideArm, frameWidth * PsScanConfig.guideArmRatio);
|
|
15
|
+
const corners = [
|
|
16
|
+
`M${frameLeft} ${frameTop + arm}V${frameTop}H${frameLeft + arm}`,
|
|
17
|
+
`M${frameLeft + frameWidth - arm} ${frameTop}H${frameLeft + frameWidth}V${frameTop + arm}`,
|
|
18
|
+
`M${frameLeft + frameWidth} ${frameTop + frameHeight - arm}V${frameTop + frameHeight}H${frameLeft + frameWidth - arm}`,
|
|
19
|
+
`M${frameLeft + arm} ${frameTop + frameHeight}H${frameLeft}V${frameTop + frameHeight - arm}`,
|
|
20
|
+
].join('');
|
|
21
|
+
return (_jsx("svg", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 size-full', viewBox: `0 0 ${area.width} ${area.height}`, children: quad === null || box.width === 0 ? (_jsx("path", { className: 'stroke-white/75 animate-ps-fade-in', d: corners, fill: 'none', strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: '4' })) : (_jsx("polygon", { className: 'fill-primary/10 stroke-primary', points: quad
|
|
22
|
+
.map((point) => `${left + point.x * box.width},${top + point.y * box.height}`)
|
|
23
|
+
.join(' '), strokeLinejoin: 'round', strokeWidth: '3' })) }));
|
|
24
|
+
};
|
|
@@ -16,5 +16,5 @@ export const PsUploadRow = ({ item, labels, onCancel, onRemove, onRetry }) => {
|
|
|
16
16
|
: item.status === PsUploadStatus.failed && item.error
|
|
17
17
|
? item.error
|
|
18
18
|
: labels[item.status];
|
|
19
|
-
return (_jsxs("li", { className: 'flex items-center gap-3 rounded-ps-control border border-line px-3 py-2.5', children: [_jsx(PsFileKindIcon, { contentType: item.file.type, name: item.file.name }), _jsxs("div", { className: 'flex min-w-0 flex-1 flex-col gap-1.5', children: [_jsx("p", { className: 'truncate text-sm font-medium text-ink', children: item.file.name }), _jsx(PsProgressBar, { label: item.file.name, tone: PsUploadStatusTones[item.status], value: isProblem ? 1 : item.progress }), _jsxs("p", { className: `truncate text-xs ${isProblem ? 'text-danger' : item.status === PsUploadStatus.done ? 'text-success' : 'text-ink/55'}`, role: item.status === PsUploadStatus.failed ? 'alert' : undefined, children: [size, " \u00B7 ", status] })] }), _jsxs("div", { className: 'flex w-
|
|
19
|
+
return (_jsxs("li", { className: 'flex items-center gap-3 rounded-ps-control border border-line px-3 py-2.5', children: [_jsx(PsFileKindIcon, { contentType: item.file.type, name: item.file.name }), _jsxs("div", { className: 'flex min-w-0 flex-1 flex-col gap-1.5', children: [_jsx("p", { className: 'truncate text-sm font-medium text-ink', children: item.file.name }), _jsx(PsProgressBar, { label: item.file.name, tone: PsUploadStatusTones[item.status], value: isProblem ? 1 : item.progress }), _jsxs("p", { className: `truncate text-xs ${isProblem ? 'text-danger' : item.status === PsUploadStatus.done ? 'text-success' : 'text-ink/55'}`, role: item.status === PsUploadStatus.failed ? 'alert' : undefined, children: [size, " \u00B7 ", status] })] }), _jsxs("div", { className: 'flex w-20 shrink-0 items-center justify-end gap-1', children: [isProblem ? (_jsx(PsFileIconButton, { iconKey: PsIconKey.Refresh, label: `${labels.retry} ${item.file.name}`, onClick: onRetry })) : null, isActive ? (_jsx(PsFileIconButton, { iconKey: PsIconKey.Close, label: `${labels.cancel} ${item.file.name}`, onClick: onCancel, tone: 'danger' })) : item.status === PsUploadStatus.done ? (_jsx("span", { className: 'ps-control-square ps-control-compact inline-flex items-center justify-center text-success', children: _jsx(PsIcon, { "aria-label": labels.done, className: 'h-4 w-4', iconKey: PsIconKey.Check }) })) : (_jsx(PsFileIconButton, { iconKey: PsIconKey.Close, label: `${labels.remove} ${item.file.name}`, onClick: onRemove }))] })] }));
|
|
20
20
|
};
|
|
@@ -1,40 +1,67 @@
|
|
|
1
1
|
export const PsDocumentScannerLabels = {
|
|
2
2
|
addPage: 'Add page',
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
allowCamera: 'Allow camera',
|
|
4
|
+
auto: 'Auto',
|
|
5
|
+
autoCapture: 'Capture automatically when the page is steady',
|
|
6
|
+
autoDetect: 'Detect edges',
|
|
7
|
+
busyBody: 'Another app is using the camera. Close it, then try again.',
|
|
8
|
+
busyTitle: 'Your camera is busy',
|
|
5
9
|
cameraStarting: 'Starting camera',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
capture: 'Capture page',
|
|
10
|
+
capture: 'Take photo',
|
|
11
|
+
choosePhoto: 'Choose a photo instead',
|
|
9
12
|
choosePhotos: 'Choose photos',
|
|
10
13
|
close: 'Close scanner',
|
|
11
14
|
cornerBottomLeft: 'Bottom left corner',
|
|
12
15
|
cornerBottomRight: 'Bottom right corner',
|
|
13
16
|
cornerTopLeft: 'Top left corner',
|
|
14
17
|
cornerTopRight: 'Top right corner',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
cropHint: 'Drag the corners to the edges of the page.',
|
|
19
|
+
cropTitle: 'Adjust corners',
|
|
20
|
+
deletePage: 'Delete',
|
|
21
|
+
deniedAndroid1: 'Tap the icon to the left of the address bar.',
|
|
22
|
+
deniedAndroid2: 'Open Permissions, then Camera.',
|
|
23
|
+
deniedAndroid3: 'Choose Allow, then come back and tap Try again.',
|
|
24
|
+
deniedBody: 'You can turn it back on in a few taps.',
|
|
25
|
+
deniedDesktop1: 'Click the camera or site icon in the address bar.',
|
|
26
|
+
deniedDesktop2: 'Set Camera to Allow for this site.',
|
|
27
|
+
deniedDesktop3: 'Close the menu, then click Try again.',
|
|
28
|
+
deniedIos1: 'Tap aA in the address bar, then Website Settings.',
|
|
29
|
+
deniedIos2: 'Set Camera to Allow.',
|
|
30
|
+
deniedIos3: 'Come back and tap Try again.',
|
|
31
|
+
deniedTitle: 'Camera access is off',
|
|
32
|
+
done: 'Done',
|
|
19
33
|
dragPage: 'Drag {page} to reorder',
|
|
20
|
-
editCrop: '
|
|
34
|
+
editCrop: 'Crop',
|
|
21
35
|
edgesFound: 'Page edges found.',
|
|
22
|
-
edgesNotFound: 'Edges not found.
|
|
36
|
+
edgesNotFound: 'Edges not found. Drag the corners to the page.',
|
|
23
37
|
filterColour: 'Colour',
|
|
24
|
-
filterDocument: '
|
|
38
|
+
filterDocument: 'Document',
|
|
25
39
|
filterGreyscale: 'Greyscale',
|
|
26
40
|
filterOriginal: 'Original',
|
|
27
|
-
filters: '
|
|
28
|
-
finish: '
|
|
41
|
+
filters: 'Filter',
|
|
42
|
+
finish: 'Upload {count} pages',
|
|
43
|
+
finishOne: 'Upload 1 page',
|
|
29
44
|
finishing: 'Preparing your scan',
|
|
45
|
+
hintFind: 'Point your camera at a document',
|
|
46
|
+
hintFound: 'Document found',
|
|
47
|
+
hintHold: 'Hold steady',
|
|
48
|
+
insecureBody: 'The camera only works on a secure (https) page. You can still choose a photo.',
|
|
49
|
+
insecureTitle: 'Camera not available here',
|
|
50
|
+
keepScan: 'Keep scan',
|
|
30
51
|
maxPages: 'You reached the page limit.',
|
|
31
|
-
|
|
32
|
-
moveUp: 'Move {page} up',
|
|
52
|
+
nextPage: 'Next page',
|
|
33
53
|
page: 'Page',
|
|
34
54
|
pageAdded: 'Page {position} added.',
|
|
35
55
|
pageDeleted: 'Page deleted.',
|
|
36
56
|
pageMoved: '{page} moved to position {position} of {count}.',
|
|
57
|
+
pageOf: '{position} of {count}',
|
|
37
58
|
pages: 'Pages',
|
|
59
|
+
platformAndroid: 'Android',
|
|
60
|
+
platformDesktop: 'Computer',
|
|
61
|
+
platformIos: 'iPhone',
|
|
62
|
+
previousPage: 'Previous page',
|
|
63
|
+
primerBody: 'The camera is only used while the scanner is open. Nothing is saved until you upload.',
|
|
64
|
+
primerTitle: 'Allow camera access to scan your documents',
|
|
38
65
|
processFailed: 'The scan could not be prepared. Try again.',
|
|
39
66
|
reorderCancelled: '{page} stays where it was.',
|
|
40
67
|
reorderDropped: '{page} dropped at {position} of {count}.',
|
|
@@ -43,8 +70,11 @@ export const PsDocumentScannerLabels = {
|
|
|
43
70
|
reorderMoved: '{page} is now {position} of {count}.',
|
|
44
71
|
retake: 'Retake',
|
|
45
72
|
rotate: 'Rotate',
|
|
46
|
-
takePhoto: 'Take photo',
|
|
47
73
|
title: 'Scan document',
|
|
48
|
-
torchOff: 'Turn
|
|
49
|
-
torchOn: 'Turn
|
|
74
|
+
torchOff: 'Turn flash off',
|
|
75
|
+
torchOn: 'Turn flash on',
|
|
76
|
+
tryAgain: 'Try again',
|
|
77
|
+
unavailableBody: 'This device has no camera the browser can use. Choose a photo of the page instead.',
|
|
78
|
+
unavailableTitle: 'No camera found',
|
|
79
|
+
viewPages: 'Review {count} pages',
|
|
50
80
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export const PsFilePickerLabels = {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
choose: 'Choose files',
|
|
3
|
+
chooseOne: 'Choose a file',
|
|
4
|
+
dragHint: 'Drag and drop files here',
|
|
5
|
+
dragHintOne: 'Drag and drop a file here',
|
|
4
6
|
dropActive: 'Drop to upload',
|
|
5
|
-
formats: 'Accepted formats:',
|
|
6
7
|
invalidType: 'is not a supported file type.',
|
|
8
|
+
scan: 'Scan with camera',
|
|
7
9
|
tooLarge: 'is too large.',
|
|
8
10
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const PsScanChromeClasses = {
|
|
2
|
+
button: 'ps-focus-ring ps-control ps-control-padding inline-flex max-w-full items-center justify-center gap-2 rounded-ps-control border text-sm font-semibold leading-5 transition disabled:pointer-events-none disabled:opacity-40',
|
|
3
|
+
buttonLarge: 'ps-control-large ps-control-padding-large',
|
|
4
|
+
buttonPrimary: 'border-primary bg-primary text-[color:var(--ps-color-primary-contrast)] hover:border-primary-dark hover:bg-primary-dark',
|
|
5
|
+
buttonQuiet: 'border-white/15 bg-white/10 text-white hover:border-white/25 hover:bg-white/20',
|
|
6
|
+
chip: 'ps-focus-ring ps-control inline-flex min-w-0 items-center justify-center rounded-ps-control px-2 text-sm font-semibold transition',
|
|
7
|
+
icon: 'ps-focus-ring ps-control ps-control-square inline-flex shrink-0 items-center justify-center rounded-ps-control text-white transition disabled:pointer-events-none disabled:opacity-40',
|
|
8
|
+
iconOff: 'bg-black/45 backdrop-blur-md hover:bg-white/20',
|
|
9
|
+
iconOn: 'bg-white text-black hover:bg-white/90',
|
|
10
|
+
pill: 'pointer-events-none rounded-ps-pill bg-black/55 px-3.5 py-1.5 text-sm font-medium text-white backdrop-blur-md',
|
|
11
|
+
tool: 'ps-focus-ring flex min-h-14 min-w-16 flex-col items-center justify-center gap-1 rounded-ps-control px-2 text-xs font-medium text-white/80 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-40',
|
|
12
|
+
};
|
|
@@ -2,6 +2,8 @@ export const PsScanConfig = {
|
|
|
2
2
|
adaptiveRatio: 0.92,
|
|
3
3
|
adaptiveWindowDivisor: 16,
|
|
4
4
|
adaptiveWindowMin: 15,
|
|
5
|
+
autoCaptureMs: 1000,
|
|
6
|
+
cameraPermission: 'camera',
|
|
5
7
|
capturedJpegQuality: 0.92,
|
|
6
8
|
cornerNudge: 0.005,
|
|
7
9
|
cornerNudgeLarge: 0.025,
|
|
@@ -11,6 +13,7 @@ export const PsScanConfig = {
|
|
|
11
13
|
defaultMaxDimension: 2000,
|
|
12
14
|
defaultMaxPages: 30,
|
|
13
15
|
detectDilations: 2,
|
|
16
|
+
detectInset: 3,
|
|
14
17
|
detectMaxDimension: 320,
|
|
15
18
|
detectMinAreaRatio: 0.12,
|
|
16
19
|
detectMaxAreaRatio: 0.995,
|
|
@@ -18,14 +21,28 @@ export const PsScanConfig = {
|
|
|
18
21
|
detectMinHullFill: 0.85,
|
|
19
22
|
detectPercentile: 0.98,
|
|
20
23
|
detectRelativeStrength: 0.3,
|
|
24
|
+
guideArm: 36,
|
|
25
|
+
guideArmRatio: 0.18,
|
|
26
|
+
guideAspect: 0.72,
|
|
27
|
+
guideHeightRatio: 0.58,
|
|
28
|
+
guideLift: 0.03,
|
|
29
|
+
guideWidthRatio: 0.76,
|
|
21
30
|
hullCandidateLimit: 18,
|
|
22
31
|
levelHighPercentile: 0.99,
|
|
23
32
|
levelLowPercentile: 0.01,
|
|
33
|
+
liveDetectIntervalMs: 160,
|
|
34
|
+
liveDetectMaxDimension: 240,
|
|
35
|
+
liveMissLimit: 2,
|
|
36
|
+
liveRearmMovement: 0.08,
|
|
37
|
+
liveSmoothing: 0.5,
|
|
38
|
+
liveStillMovement: 0.02,
|
|
39
|
+
loupeOffset: 72,
|
|
40
|
+
loupeSize: 112,
|
|
41
|
+
loupeZoom: 2.5,
|
|
24
42
|
minCornerGap: 0.02,
|
|
25
43
|
pdfDpi: 150,
|
|
26
44
|
pdfPointsPerInch: 72,
|
|
27
45
|
previewDebounceMs: 120,
|
|
28
46
|
previewMaxDimension: 1400,
|
|
29
|
-
thumbnailHeight: 56,
|
|
30
47
|
torchConstraint: 'torch',
|
|
31
48
|
};
|