@embedpdf/viewer-chrome 3.0.0-next.12 → 3.0.0-next.14
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/README.md +47 -0
- package/dist/chrome.cjs +27 -1
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.d.cts +1 -0
- package/dist/chrome.d.ts +1 -0
- package/dist/chrome.js +27 -1
- package/dist/chrome.js.map +1 -1
- package/dist/commands.cjs +69 -2
- package/dist/commands.cjs.map +1 -1
- package/dist/commands.d.cts +1 -1
- package/dist/commands.d.ts +1 -1
- package/dist/commands.js +69 -2
- package/dist/commands.js.map +1 -1
- package/dist/{es-BRYSk4rY.cjs → es-BXLlBo2b.cjs} +98 -3
- package/dist/es-BXLlBo2b.cjs.map +1 -0
- package/dist/{es-DT5ZoB99.js → es-CLgTsFDS.js} +98 -3
- package/dist/es-CLgTsFDS.js.map +1 -0
- package/dist/index.cjs +1840 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +1796 -118
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/es-BRYSk4rY.cjs.map +0 -1
- package/dist/es-DT5ZoB99.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -8,20 +8,24 @@ import { pageEditPlugin } from "@embedpdf/react/page-edit";
|
|
|
8
8
|
import { InteractionToken, InteractionToken as InteractionToken$1, feedbackPlugin, interactionPlugin, useTool, useToolCursor, vibrationFeedback } from "@embedpdf/react/interaction";
|
|
9
9
|
import { SelectionClipboard, SelectionHandles, SelectionLayer, SelectionMenu, SelectionToken, selectionPlugin } from "@embedpdf/react/selection";
|
|
10
10
|
import { AnnotationLayer, AnnotationToken, AnnotationToken as AnnotationToken$1, annotationPlugin, refKey, useAnnotation, useAnnotationDefaults, useAnnotationSelected, useAnnotationSelection, useCommentThreads, useComments, useCommentsHydration, useFilePickerProvider, useSelectionFlags, useSelectionProps } from "@embedpdf/react/annotation";
|
|
11
|
-
import { StampToken, indexedDbByteStore, persistStampLibraries, restoreStampLibraries, stampPlugin, useArmStampAsset, useStamp, useStampAssetPreviewUrl, useStampAssets, useStampLibraries } from "@embedpdf/react/stamp";
|
|
11
|
+
import { StampToken, StampToken as StampToken$1, indexedDbByteStore, persistStampLibraries, restoreStampLibraries, stampPlugin, useArmStampAsset, useStamp, useStampAssetPreviewUrl, useStampAssets, useStampLibraries } from "@embedpdf/react/stamp";
|
|
12
|
+
import { MeasurementToken, MeasurementToken as MeasurementToken$1, measurementPlugin, useMeasurement, usePageScale } from "@embedpdf/react/measurement";
|
|
12
13
|
import { RedactionToken, RedactionToken as RedactionToken$1, redactionPlugin, usePendingRedactions, useRedaction } from "@embedpdf/react/redaction";
|
|
13
14
|
import { actionsPlugin, useActionsUiAdapter } from "@embedpdf/react/actions";
|
|
14
|
-
import { FormToken, formPlugin, formWidgetRenderer } from "@embedpdf/react/form";
|
|
15
|
+
import { FormToken, FormToken as FormToken$1, formPlugin, formWidgetRenderer } from "@embedpdf/react/form";
|
|
16
|
+
import { INITIALS_MARK_NAME, SIGNATURES_LIBRARY_KIND, SIGNATURE_MARK_NAME, SignatureToken, createTestSigner, indexedDbKeyStore, memoryKeyStore, personalSigner, remoteSigner, signaturePlugin, useSignature, useSignatureEvent, useSignatureSnapshot, useSignatureTarget, useSignatureVerdicts, useSignerRows, webCryptoSigner } from "@embedpdf/react/signature";
|
|
15
17
|
import { LinkLayer, linkPlugin } from "@embedpdf/react/link";
|
|
16
18
|
import { SearchLayer, SearchToken, SearchToken as SearchToken$1, searchPlugin, useSearch, useSearchState, validateSearchQuery } from "@embedpdf/react/search";
|
|
17
19
|
import { I18nToken, i18nPlugin, negotiateLocale, useLocale, useT } from "@embedpdf/react/i18n";
|
|
18
20
|
import { CommandsToken, CommandsToken as CommandsToken$1, commandsPlugin, resolvedCommandsEqual, useCommand, useCommandShortcuts, useCommands } from "@embedpdf/react/commands";
|
|
19
|
-
import { ShellToken, ShellToken as ShellToken$1, shellPlugin, useMenus, useSurface } from "@embedpdf/react/shell";
|
|
21
|
+
import { ShellToken, ShellToken as ShellToken$1, shellPlugin, useMenus, useShell, useSurface } from "@embedpdf/react/shell";
|
|
20
22
|
import { Toolbar, addItem, chromeHelpers, chromeHelpers as chromeHelpers$1, custom, defineChrome, group, item, removeItems, replaceItem, useStripView, validateChrome, validateChrome as validateChrome$1 } from "@embedpdf/react/toolbar";
|
|
21
23
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
22
24
|
import { Scrollbar } from "@embedpdf/react/scrollbar";
|
|
23
|
-
import { AnnotationMenu } from "@embedpdf/react/annotation-menu";
|
|
24
25
|
import { LOCALES, loadDefaultLibrary } from "@embedpdf/default-stamps/library";
|
|
26
|
+
import { mountWebFont } from "@embedpdf/web";
|
|
27
|
+
import { AnnotationMenu } from "@embedpdf/react/annotation-menu";
|
|
28
|
+
import { Anchored } from "@embedpdf/react/anchored";
|
|
25
29
|
import { MetadataToken } from "@embedpdf/react/metadata";
|
|
26
30
|
//#region src/config/stage.ts
|
|
27
31
|
/**
|
|
@@ -78,6 +82,32 @@ const en = {
|
|
|
78
82
|
code: "en",
|
|
79
83
|
name: "English",
|
|
80
84
|
translations: {
|
|
85
|
+
measurement: {
|
|
86
|
+
title: "Measurements",
|
|
87
|
+
distance: "Distance",
|
|
88
|
+
perimeter: "Perimeter",
|
|
89
|
+
area: "Area",
|
|
90
|
+
areaUnit: "Area unit",
|
|
91
|
+
resetLabel: "Reset label position",
|
|
92
|
+
calibrate: "Calibrate scale",
|
|
93
|
+
scale: "Scale",
|
|
94
|
+
custom: "Custom",
|
|
95
|
+
unavailable: "Unavailable",
|
|
96
|
+
instructions: "Drag to measure a distance, then click to place its dimension line. Click vertices for a perimeter or area and double-click to finish. Area boundaries must not cross; an unavailable preview cannot be finished. Press Escape to cancel. Drag the label itself to move it.",
|
|
97
|
+
sessionOnly: "This engine keeps page calibration for this session only.",
|
|
98
|
+
allPages: "Apply to all pages",
|
|
99
|
+
recalculate: "Recalculate existing measurements",
|
|
100
|
+
preset: "Scale preset",
|
|
101
|
+
choosePreset: "Choose a preset",
|
|
102
|
+
unit: "Unit",
|
|
103
|
+
precision: "Precision",
|
|
104
|
+
selection: "Selected measurements",
|
|
105
|
+
report: "{updated} updated · {skipped} skipped · {failed} failed",
|
|
106
|
+
knownLength: "Enter the real length represented by the line you just drew.",
|
|
107
|
+
length: "Known length",
|
|
108
|
+
apply: "Apply scale",
|
|
109
|
+
applying: "Applying…"
|
|
110
|
+
},
|
|
81
111
|
commands: {
|
|
82
112
|
zoom: {
|
|
83
113
|
in: "Zoom In",
|
|
@@ -121,6 +151,7 @@ const en = {
|
|
|
121
151
|
counterclockwise: "Rotate Counter-Clockwise"
|
|
122
152
|
},
|
|
123
153
|
mode: {
|
|
154
|
+
measure: "Measure",
|
|
124
155
|
group: "Mode",
|
|
125
156
|
view: "View",
|
|
126
157
|
annotate: "Annotate",
|
|
@@ -130,6 +161,7 @@ const en = {
|
|
|
130
161
|
redact: "Redact"
|
|
131
162
|
},
|
|
132
163
|
annotate: {
|
|
164
|
+
cancelCreation: "Cancel drawing",
|
|
133
165
|
highlight: "Highlight",
|
|
134
166
|
strikeout: "Strikeout",
|
|
135
167
|
underline: "Underline",
|
|
@@ -173,7 +205,8 @@ const en = {
|
|
|
173
205
|
checkbox: "Checkbox",
|
|
174
206
|
radio: "Radio Button",
|
|
175
207
|
select: "Dropdown",
|
|
176
|
-
listbox: "List Box"
|
|
208
|
+
listbox: "List Box",
|
|
209
|
+
signature: "Signature Field"
|
|
177
210
|
},
|
|
178
211
|
redact: {
|
|
179
212
|
mark: "Mark for Redaction",
|
|
@@ -270,6 +303,7 @@ const en = {
|
|
|
270
303
|
redactConfirmCollateral: "{count} other annotations will also be removed.",
|
|
271
304
|
redactApply: "Apply",
|
|
272
305
|
redactApplying: "Applying…",
|
|
306
|
+
formatLabel: "Format",
|
|
273
307
|
redactLabelTitle: "Redaction label",
|
|
274
308
|
redactLabelPlaceholder: "e.g. REDACTED",
|
|
275
309
|
redactLabelRepeat: "Repeat label across region",
|
|
@@ -288,7 +322,72 @@ const en = {
|
|
|
288
322
|
stampsCustomLabel: "Custom stamp",
|
|
289
323
|
stampsError: "The built-in stamps could not be loaded.",
|
|
290
324
|
stampsArmError: "That stamp could not be armed.",
|
|
291
|
-
stampsImportError: "That PDF could not be imported as a stamp library."
|
|
325
|
+
stampsImportError: "That PDF could not be imported as a stamp library.",
|
|
326
|
+
quickStampsLabel: "Quick stamps",
|
|
327
|
+
signaturesTitle: "Signatures",
|
|
328
|
+
signaturesEmpty: "No signatures yet. Create one to sign with.",
|
|
329
|
+
signaturesNew: "New signature…",
|
|
330
|
+
signaturesArmedHint: "Click a signature field to sign it, or anywhere on a page to place the mark.",
|
|
331
|
+
signaturesTargetHint: "Pick a mark for the selected field.",
|
|
332
|
+
signaturesInDocument: "In this document",
|
|
333
|
+
signaturesNone: "This document has no signature fields.",
|
|
334
|
+
signaturesValidate: "Validate",
|
|
335
|
+
signaturesValidating: "Validating…",
|
|
336
|
+
signaturesSigned: "Signed",
|
|
337
|
+
signaturesUnsigned: "Unsigned",
|
|
338
|
+
signaturesRename: "Rename",
|
|
339
|
+
signaturesRemove: "Remove",
|
|
340
|
+
signaturesExport: "Export as PDF",
|
|
341
|
+
signaturesAddInitials: "Add initials",
|
|
342
|
+
signatureLabel: "Signature",
|
|
343
|
+
initialsLabel: "Initials",
|
|
344
|
+
makerTitle: "New signature",
|
|
345
|
+
makerName: "Name",
|
|
346
|
+
makerNamePlaceholder: "Your name",
|
|
347
|
+
makerDraw: "Draw",
|
|
348
|
+
makerType: "Type",
|
|
349
|
+
makerUpload: "Upload",
|
|
350
|
+
makerTypePlaceholder: "Type your name",
|
|
351
|
+
makerFont: "Font",
|
|
352
|
+
makerUploadHint: "Choose a PNG, JPEG, or one-page PDF",
|
|
353
|
+
makerClear: "Clear",
|
|
354
|
+
makerOptional: "(optional)",
|
|
355
|
+
makerSave: "Save",
|
|
356
|
+
makerSaving: "Saving…",
|
|
357
|
+
makerError: "The signature could not be saved.",
|
|
358
|
+
signTitle: "Sign this field",
|
|
359
|
+
signReason: "Reason",
|
|
360
|
+
signLocation: "Location",
|
|
361
|
+
signKind: "What may change afterwards",
|
|
362
|
+
signKindApproval: "Approval signature — form filling and more signatures stay valid",
|
|
363
|
+
signKindCertifyAnnotate: "Certify — allow comments, form filling and signatures",
|
|
364
|
+
signKindCertifyFill: "Certify — allow form filling and signatures only",
|
|
365
|
+
signKindCertifyLocked: "Certify — allow no changes",
|
|
366
|
+
signButton: "Sign",
|
|
367
|
+
signSigning: "Signing…",
|
|
368
|
+
signFill: "Place without signing",
|
|
369
|
+
signError: "The field could not be signed.",
|
|
370
|
+
inspectTitle: "Signature",
|
|
371
|
+
inspectSigner: "Signed by",
|
|
372
|
+
inspectReason: "Reason",
|
|
373
|
+
inspectLocation: "Location",
|
|
374
|
+
inspectTime: "Time",
|
|
375
|
+
inspectIntegrity: "Document",
|
|
376
|
+
inspectCryptography: "Signature",
|
|
377
|
+
inspectTrust: "Signer",
|
|
378
|
+
inspectChanges: "Changes since",
|
|
379
|
+
inspectDownload: "Download signed version",
|
|
380
|
+
verdictValid: "Valid",
|
|
381
|
+
verdictValidUntrusted: "Valid, signer not trusted",
|
|
382
|
+
verdictInvalid: "Invalid — modified after signing",
|
|
383
|
+
verdictWillInvalidate: "Valid, invalid once saved",
|
|
384
|
+
signaturesInvalidating: "This change will invalidate {field} when the document is saved. Undo it to keep the signature.",
|
|
385
|
+
verdictIndeterminate: "Could not be verified",
|
|
386
|
+
verdictUnchanged: "None",
|
|
387
|
+
verdictRestored: "Changed, then restored",
|
|
388
|
+
inspectLaterRevisions: "Revisions after signing",
|
|
389
|
+
verdictPermitted: "Permitted",
|
|
390
|
+
verdictForbidden: "Not permitted"
|
|
292
391
|
}
|
|
293
392
|
}
|
|
294
393
|
};
|
|
@@ -306,7 +405,9 @@ const en = {
|
|
|
306
405
|
const ViewerConfigContext = createContext({
|
|
307
406
|
chrome: defaultChrome,
|
|
308
407
|
icons: {},
|
|
309
|
-
stamps: {}
|
|
408
|
+
stamps: {},
|
|
409
|
+
signatures: {},
|
|
410
|
+
annotations: {}
|
|
310
411
|
});
|
|
311
412
|
const ViewerConfigProvider = ViewerConfigContext.Provider;
|
|
312
413
|
function useChromeSchema() {
|
|
@@ -324,6 +425,12 @@ function useCustomIcons() {
|
|
|
324
425
|
function useStampsConfig() {
|
|
325
426
|
return useContext(ViewerConfigContext).stamps;
|
|
326
427
|
}
|
|
428
|
+
function useSignaturesConfig() {
|
|
429
|
+
return useContext(ViewerConfigContext).signatures;
|
|
430
|
+
}
|
|
431
|
+
function useAnnotationsConfig() {
|
|
432
|
+
return useContext(ViewerConfigContext).annotations;
|
|
433
|
+
}
|
|
327
434
|
//#endregion
|
|
328
435
|
//#region src/handle.ts
|
|
329
436
|
function createViewerHandle(kernel) {
|
|
@@ -354,7 +461,40 @@ function createViewerHandle(kernel) {
|
|
|
354
461
|
}
|
|
355
462
|
//#endregion
|
|
356
463
|
//#region src/ui/icons.tsx
|
|
464
|
+
const measurementOutline = [{
|
|
465
|
+
d: "M5 4H19M20 5V19M19 20H5M4 19V5",
|
|
466
|
+
stroke: "primary"
|
|
467
|
+
}, {
|
|
468
|
+
d: "M3.1 3.1h1.8v1.8H3.1zM19.1 3.1h1.8v1.8h-1.8zM3.1 19.1h1.8v1.8H3.1zM19.1 19.1h1.8v1.8h-1.8z",
|
|
469
|
+
stroke: "primary"
|
|
470
|
+
}];
|
|
357
471
|
const ICON_PATHS = {
|
|
472
|
+
ruler: ["M3 8h18v8H3z", "M7 8v4M11 8v2M15 8v4M19 8v2"],
|
|
473
|
+
distance: [{
|
|
474
|
+
d: "M5 12H19",
|
|
475
|
+
stroke: "primary",
|
|
476
|
+
strokeDasharray: "2 2"
|
|
477
|
+
}, {
|
|
478
|
+
d: "M3.5 10.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 1 0 0-3M20.5 10.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 1 0 0-3",
|
|
479
|
+
stroke: "primary",
|
|
480
|
+
fill: "primary",
|
|
481
|
+
fillOpacity: .25
|
|
482
|
+
}],
|
|
483
|
+
perimeter: measurementOutline,
|
|
484
|
+
area: [
|
|
485
|
+
{
|
|
486
|
+
d: "M5 4H19V5H20V19H19V20H5V19H4V5H5Z",
|
|
487
|
+
fill: "primary",
|
|
488
|
+
fillOpacity: .08
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
d: "M4.5 7.5L7.5 4.5M4.5 10.5L10.5 4.5M4.5 13.5L13.5 4.5M4.5 16.5L16.5 4.5M4.5 19.5L19.5 4.5M7.5 19.5L19.5 7.5M10.5 19.5L19.5 10.5M13.5 19.5L19.5 13.5M16.5 19.5L19.5 16.5",
|
|
492
|
+
stroke: "primary",
|
|
493
|
+
strokeWidth: .6,
|
|
494
|
+
strokeOpacity: .5
|
|
495
|
+
},
|
|
496
|
+
...measurementOutline
|
|
497
|
+
],
|
|
358
498
|
alertTriangle: ["M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"],
|
|
359
499
|
alignBottom: [
|
|
360
500
|
"M3 19v-14a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z",
|
|
@@ -404,6 +544,11 @@ const ICON_PATHS = {
|
|
|
404
544
|
"M19 16h-12a2 2 0 0 0 -2 2",
|
|
405
545
|
"M9 8h6"
|
|
406
546
|
],
|
|
547
|
+
calibrate: [
|
|
548
|
+
"M12 12m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0 -15 0",
|
|
549
|
+
"M12 2.75V7M12 17V21.25M2.75 12H7M17 12H21.25",
|
|
550
|
+
"M12 12m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0"
|
|
551
|
+
],
|
|
407
552
|
callout: [
|
|
408
553
|
{
|
|
409
554
|
d: "M20 6v4a1 1 0 0 1 -1 1h-14a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h14a1 1 0 0 1 1 1",
|
|
@@ -826,6 +971,11 @@ const ICON_PATHS = {
|
|
|
826
971
|
],
|
|
827
972
|
ungroup: ["M4 5a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1z", "M13 14a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1z"],
|
|
828
973
|
unlock: ["M7 11V7a5 5 0 0 1 9.9-1"],
|
|
974
|
+
updateScale: [
|
|
975
|
+
"M3.5 13.5L18.5 7.5L21.25 14.5L6.25 20.5L3.5 13.5Z",
|
|
976
|
+
"M8 12.8L9 15.2M11.5 11.4L12.2 13.2M15 10L16 12.4",
|
|
977
|
+
"M4 9.39849C4.10585 8.34804 4.487 7.34411 5.10497 6.48809C5.72294 5.63207 6.55584 4.95427 7.51957 4.52312C8.4833 4.09198 9.54373 3.92277 10.5938 4.03257C11.6438 4.14237 12.6463 4.5273 13.5 5.14849M11.5 6.00003L13.5 5.14849L12.7807 2.96999"
|
|
978
|
+
],
|
|
829
979
|
vertical: [
|
|
830
980
|
"M8 7l4 -4l4 4",
|
|
831
981
|
"M8 17l4 4l4 -4",
|
|
@@ -895,7 +1045,11 @@ function Icon({ name, size = 20, strokeWidth = 2, className, title, accent }) {
|
|
|
895
1045
|
return /* @__PURE__ */ jsx("path", {
|
|
896
1046
|
d: p.d,
|
|
897
1047
|
fill: fill ?? "none",
|
|
898
|
-
stroke
|
|
1048
|
+
stroke,
|
|
1049
|
+
strokeWidth: p.strokeWidth,
|
|
1050
|
+
strokeDasharray: p.strokeDasharray,
|
|
1051
|
+
strokeOpacity: p.strokeOpacity,
|
|
1052
|
+
fillOpacity: p.fillOpacity
|
|
899
1053
|
}, i);
|
|
900
1054
|
})
|
|
901
1055
|
});
|
|
@@ -929,6 +1083,495 @@ function ThemeProvider({ children, preference = "system", target }) {
|
|
|
929
1083
|
});
|
|
930
1084
|
}
|
|
931
1085
|
//#endregion
|
|
1086
|
+
//#region src/ui/measurement.tsx
|
|
1087
|
+
const control = "border-border bg-surface text-fg w-full rounded-md border px-2 py-1.5 text-sm disabled:opacity-50";
|
|
1088
|
+
const button = "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm disabled:opacity-50";
|
|
1089
|
+
const useCurrentPon = () => useSelector(StageToken$1, (c) => c.pages()[c.currentPage()]?.pon ?? -1);
|
|
1090
|
+
function MeasurementScaleButton() {
|
|
1091
|
+
const t = useT();
|
|
1092
|
+
const scale = usePageScale(useCurrentPon());
|
|
1093
|
+
const surface = useSurface("measurement");
|
|
1094
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1095
|
+
className: `${button} inline-flex items-center gap-1.5 whitespace-nowrap`,
|
|
1096
|
+
onClick: () => surface.open({ exclusive: "right" }),
|
|
1097
|
+
title: t("measurement.title"),
|
|
1098
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
1099
|
+
name: "updateScale",
|
|
1100
|
+
size: 20,
|
|
1101
|
+
className: "shrink-0"
|
|
1102
|
+
}), /* @__PURE__ */ jsxs("span", { children: [
|
|
1103
|
+
t("measurement.scale"),
|
|
1104
|
+
":",
|
|
1105
|
+
" ",
|
|
1106
|
+
scale.measure?.subtype === "RL" ? scale.measure.ratio ?? t("measurement.custom") : t("measurement.unavailable")
|
|
1107
|
+
] })]
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
function MeasurementSection() {
|
|
1111
|
+
const t = useT();
|
|
1112
|
+
const pon = useCurrentPon();
|
|
1113
|
+
const measurement = useMeasurement();
|
|
1114
|
+
const scale = usePageScale(pon);
|
|
1115
|
+
const anno = useCapability(AnnotationToken$1);
|
|
1116
|
+
const selected = useSelector(AnnotationToken$1, (c) => c.selection());
|
|
1117
|
+
const resettable = useSelector(AnnotationToken$1, (c) => c.getSelected().filter((annotation) => (annotation.subtype === "polygon" || annotation.subtype === "polyline") && annotation.caption?.center && c.canEdit(annotation.ref) && !annotation.flags.lockedContents), (a, b) => a.length === b.length && a.every((annotation, i) => annotation === b[i]));
|
|
1118
|
+
const readouts = useSelector(MeasurementToken$1, (c) => anno.getSelected().map((d) => c.readout(d.ref)).filter((r) => !("unavailable" in r)), (a, b) => JSON.stringify(a) === JSON.stringify(b));
|
|
1119
|
+
const reports = useSelector(MeasurementToken$1, (c) => c.lastReports());
|
|
1120
|
+
const [allPages, setAllPages] = useState(false);
|
|
1121
|
+
const [recalculate, setRecalculate] = useState(true);
|
|
1122
|
+
const [error, setError] = useState(null);
|
|
1123
|
+
const disabled = measurement.busy || !measurement.canCalibratePage || !scale.ready;
|
|
1124
|
+
const rectilinear = scale.measure?.subtype === "RL" ? scale.measure : null;
|
|
1125
|
+
const options = {
|
|
1126
|
+
allPages,
|
|
1127
|
+
recalculate
|
|
1128
|
+
};
|
|
1129
|
+
const run = async (work) => {
|
|
1130
|
+
setError(null);
|
|
1131
|
+
try {
|
|
1132
|
+
await work();
|
|
1133
|
+
} catch (e) {
|
|
1134
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
const precision = rectilinear?.distance[rectilinear.distance.length - 1]?.precision ?? 100;
|
|
1138
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1139
|
+
className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 text-sm",
|
|
1140
|
+
children: [
|
|
1141
|
+
/* @__PURE__ */ jsx("p", {
|
|
1142
|
+
className: "text-fg-muted",
|
|
1143
|
+
children: t("measurement.instructions")
|
|
1144
|
+
}),
|
|
1145
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("span", {
|
|
1146
|
+
className: "text-fg-muted",
|
|
1147
|
+
children: [t("measurement.scale"), ": "]
|
|
1148
|
+
}), /* @__PURE__ */ jsx("strong", { children: rectilinear?.ratio ?? t("measurement.unavailable") })] }),
|
|
1149
|
+
scale.error && /* @__PURE__ */ jsx("p", {
|
|
1150
|
+
role: "alert",
|
|
1151
|
+
className: "text-red-600",
|
|
1152
|
+
children: scale.error.message
|
|
1153
|
+
}),
|
|
1154
|
+
!scale.persistent && scale.ready && /* @__PURE__ */ jsx("p", {
|
|
1155
|
+
className: "text-fg-muted",
|
|
1156
|
+
children: t("measurement.sessionOnly")
|
|
1157
|
+
}),
|
|
1158
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1159
|
+
className: "flex items-center gap-2",
|
|
1160
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1161
|
+
type: "checkbox",
|
|
1162
|
+
checked: allPages,
|
|
1163
|
+
disabled: measurement.busy,
|
|
1164
|
+
onChange: (e) => setAllPages(e.target.checked)
|
|
1165
|
+
}), t("measurement.allPages")]
|
|
1166
|
+
}),
|
|
1167
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1168
|
+
className: "flex items-center gap-2",
|
|
1169
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1170
|
+
type: "checkbox",
|
|
1171
|
+
checked: recalculate,
|
|
1172
|
+
disabled: measurement.busy,
|
|
1173
|
+
onChange: (e) => setRecalculate(e.target.checked)
|
|
1174
|
+
}), t("measurement.recalculate")]
|
|
1175
|
+
}),
|
|
1176
|
+
/* @__PURE__ */ jsxs("label", { children: [t("measurement.preset"), /* @__PURE__ */ jsxs("select", {
|
|
1177
|
+
"aria-label": t("measurement.preset"),
|
|
1178
|
+
className: control,
|
|
1179
|
+
value: "",
|
|
1180
|
+
disabled,
|
|
1181
|
+
onChange: (e) => void run(() => measurement.setPreset(pon, e.target.value, options)),
|
|
1182
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
1183
|
+
value: "",
|
|
1184
|
+
disabled: true,
|
|
1185
|
+
children: t("measurement.choosePreset")
|
|
1186
|
+
}), measurement.presets().map((p) => /* @__PURE__ */ jsx("option", {
|
|
1187
|
+
value: p.id,
|
|
1188
|
+
children: p.label
|
|
1189
|
+
}, p.id))]
|
|
1190
|
+
})] }),
|
|
1191
|
+
/* @__PURE__ */ jsxs("label", { children: [t("measurement.unit"), /* @__PURE__ */ jsxs("select", {
|
|
1192
|
+
"aria-label": t("measurement.unit"),
|
|
1193
|
+
className: control,
|
|
1194
|
+
value: rectilinear?.distance[0]?.unit.trim() ?? "",
|
|
1195
|
+
disabled: disabled || !rectilinear,
|
|
1196
|
+
onChange: (e) => void run(() => measurement.setUnit(pon, e.target.value, void 0, options)),
|
|
1197
|
+
children: [!measurement.units().includes(rectilinear?.distance[0]?.unit.trim()) && /* @__PURE__ */ jsx("option", {
|
|
1198
|
+
value: rectilinear?.distance[0]?.unit.trim() ?? "",
|
|
1199
|
+
children: t("measurement.custom")
|
|
1200
|
+
}), measurement.units().map((u) => /* @__PURE__ */ jsx("option", {
|
|
1201
|
+
value: u,
|
|
1202
|
+
children: u
|
|
1203
|
+
}, u))]
|
|
1204
|
+
})] }),
|
|
1205
|
+
/* @__PURE__ */ jsxs("label", { children: [t("measurement.areaUnit"), /* @__PURE__ */ jsxs("select", {
|
|
1206
|
+
"aria-label": t("measurement.areaUnit"),
|
|
1207
|
+
className: control,
|
|
1208
|
+
value: rectilinear?.area[0]?.unit.trim().replace("²", "2") ?? "",
|
|
1209
|
+
disabled: disabled || !rectilinear,
|
|
1210
|
+
onChange: (e) => void run(() => measurement.setAreaUnit(pon, e.target.value, options)),
|
|
1211
|
+
children: [!measurement.areaUnits().includes(rectilinear?.area[0]?.unit.trim().replace("²", "2")) && /* @__PURE__ */ jsx("option", {
|
|
1212
|
+
value: rectilinear?.area[0]?.unit.trim().replace("²", "2") ?? "",
|
|
1213
|
+
children: t("measurement.custom")
|
|
1214
|
+
}), measurement.areaUnits().map((unit) => /* @__PURE__ */ jsx("option", {
|
|
1215
|
+
value: unit,
|
|
1216
|
+
children: unit.replace("2", "²")
|
|
1217
|
+
}, unit))]
|
|
1218
|
+
})] }),
|
|
1219
|
+
/* @__PURE__ */ jsxs("label", { children: [t("measurement.precision"), /* @__PURE__ */ jsxs("select", {
|
|
1220
|
+
"aria-label": t("measurement.precision"),
|
|
1221
|
+
className: control,
|
|
1222
|
+
value: precision,
|
|
1223
|
+
disabled: disabled || !rectilinear,
|
|
1224
|
+
onChange: (e) => void run(() => measurement.setPrecision(pon, Number(e.target.value), options)),
|
|
1225
|
+
children: [![
|
|
1226
|
+
1,
|
|
1227
|
+
10,
|
|
1228
|
+
100,
|
|
1229
|
+
1e3,
|
|
1230
|
+
1e4
|
|
1231
|
+
].includes(precision) && /* @__PURE__ */ jsx("option", {
|
|
1232
|
+
value: precision,
|
|
1233
|
+
children: t("measurement.custom")
|
|
1234
|
+
}), [
|
|
1235
|
+
1,
|
|
1236
|
+
10,
|
|
1237
|
+
100,
|
|
1238
|
+
1e3,
|
|
1239
|
+
1e4
|
|
1240
|
+
].map((p) => /* @__PURE__ */ jsx("option", {
|
|
1241
|
+
value: p,
|
|
1242
|
+
children: 1 / p
|
|
1243
|
+
}, p))]
|
|
1244
|
+
})] }),
|
|
1245
|
+
/* @__PURE__ */ jsx("button", {
|
|
1246
|
+
className: button,
|
|
1247
|
+
disabled,
|
|
1248
|
+
onClick: () => measurement.startCalibration(),
|
|
1249
|
+
children: t("measurement.calibrate")
|
|
1250
|
+
}),
|
|
1251
|
+
selected.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
1252
|
+
className: "border-border-subtle border-t pt-3",
|
|
1253
|
+
children: [
|
|
1254
|
+
/* @__PURE__ */ jsx("h3", {
|
|
1255
|
+
className: "mb-1 font-medium",
|
|
1256
|
+
children: t("measurement.selection")
|
|
1257
|
+
}),
|
|
1258
|
+
readouts.length ? readouts.map((readout, i) => /* @__PURE__ */ jsxs("div", {
|
|
1259
|
+
className: "mb-2",
|
|
1260
|
+
children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
1261
|
+
t(`measurement.${readout.kind}`),
|
|
1262
|
+
": ",
|
|
1263
|
+
readout.label
|
|
1264
|
+
] }), readout.perimeter && /* @__PURE__ */ jsxs("p", { children: [
|
|
1265
|
+
t("measurement.perimeter"),
|
|
1266
|
+
": ",
|
|
1267
|
+
readout.perimeter
|
|
1268
|
+
] })]
|
|
1269
|
+
}, i)) : /* @__PURE__ */ jsx("p", {
|
|
1270
|
+
className: "text-fg-muted",
|
|
1271
|
+
children: t("measurement.unavailable")
|
|
1272
|
+
}),
|
|
1273
|
+
resettable.length > 0 && /* @__PURE__ */ jsx("button", {
|
|
1274
|
+
className: button,
|
|
1275
|
+
disabled: measurement.busy,
|
|
1276
|
+
onClick: () => void run(async () => {
|
|
1277
|
+
for (const annotation of resettable) if (annotation.subtype === "polygon" || annotation.subtype === "polyline") await anno.update(annotation.ref, {
|
|
1278
|
+
subtype: annotation.subtype,
|
|
1279
|
+
caption: { center: null }
|
|
1280
|
+
});
|
|
1281
|
+
}),
|
|
1282
|
+
children: t("measurement.resetLabel")
|
|
1283
|
+
})
|
|
1284
|
+
]
|
|
1285
|
+
}),
|
|
1286
|
+
reports.length > 0 && /* @__PURE__ */ jsx("p", {
|
|
1287
|
+
role: "status",
|
|
1288
|
+
children: t("measurement.report", { params: {
|
|
1289
|
+
updated: reports.reduce((n, r) => n + r.updated.length, 0),
|
|
1290
|
+
skipped: reports.reduce((n, r) => n + r.skipped.length, 0),
|
|
1291
|
+
failed: reports.reduce((n, r) => n + r.failed.length + (r.scaleError || r.error ? 1 : 0), 0)
|
|
1292
|
+
} })
|
|
1293
|
+
}),
|
|
1294
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1295
|
+
role: "alert",
|
|
1296
|
+
className: "text-red-600",
|
|
1297
|
+
children: error
|
|
1298
|
+
})
|
|
1299
|
+
]
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
function CalibrationDialog() {
|
|
1303
|
+
const t = useT();
|
|
1304
|
+
const measurement = useMeasurement();
|
|
1305
|
+
const request = useSelector(MeasurementToken$1, (c) => c.calibrationRequest());
|
|
1306
|
+
const input = useRef(null);
|
|
1307
|
+
const [value, setValue] = useState("");
|
|
1308
|
+
const [unit, setUnit] = useState("m");
|
|
1309
|
+
const [allPages, setAllPages] = useState(false);
|
|
1310
|
+
const [recalculate, setRecalculate] = useState(true);
|
|
1311
|
+
const [error, setError] = useState(null);
|
|
1312
|
+
useEffect(() => {
|
|
1313
|
+
setValue("");
|
|
1314
|
+
setError(null);
|
|
1315
|
+
input.current?.focus();
|
|
1316
|
+
}, [request]);
|
|
1317
|
+
if (!request) return null;
|
|
1318
|
+
const submit = async () => {
|
|
1319
|
+
setError(null);
|
|
1320
|
+
try {
|
|
1321
|
+
await measurement.calibrate(request.pon, request.from, request.to, {
|
|
1322
|
+
value: Number(value),
|
|
1323
|
+
unit
|
|
1324
|
+
}, {
|
|
1325
|
+
allPages,
|
|
1326
|
+
recalculate
|
|
1327
|
+
});
|
|
1328
|
+
measurement.dismissCalibration();
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1334
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
1335
|
+
onKeyDown: (e) => {
|
|
1336
|
+
if (e.key === "Escape" && !measurement.busy) measurement.dismissCalibration();
|
|
1337
|
+
},
|
|
1338
|
+
children: /* @__PURE__ */ jsxs("form", {
|
|
1339
|
+
role: "dialog",
|
|
1340
|
+
"aria-modal": "true",
|
|
1341
|
+
"aria-labelledby": "measurement-calibration-title",
|
|
1342
|
+
className: "border-border bg-surface flex w-96 flex-col gap-4 rounded-lg border p-5 shadow-xl",
|
|
1343
|
+
onSubmit: (e) => {
|
|
1344
|
+
e.preventDefault();
|
|
1345
|
+
submit();
|
|
1346
|
+
},
|
|
1347
|
+
children: [
|
|
1348
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1349
|
+
id: "measurement-calibration-title",
|
|
1350
|
+
className: "font-semibold",
|
|
1351
|
+
children: t("measurement.calibrate")
|
|
1352
|
+
}),
|
|
1353
|
+
/* @__PURE__ */ jsx("p", {
|
|
1354
|
+
className: "text-fg-muted text-sm",
|
|
1355
|
+
children: t("measurement.knownLength")
|
|
1356
|
+
}),
|
|
1357
|
+
/* @__PURE__ */ jsxs("label", { children: [t("measurement.length"), /* @__PURE__ */ jsx("input", {
|
|
1358
|
+
ref: input,
|
|
1359
|
+
"aria-label": t("measurement.length"),
|
|
1360
|
+
className: control,
|
|
1361
|
+
type: "number",
|
|
1362
|
+
min: "0",
|
|
1363
|
+
step: "any",
|
|
1364
|
+
value,
|
|
1365
|
+
onChange: (e) => setValue(e.target.value)
|
|
1366
|
+
})] }),
|
|
1367
|
+
/* @__PURE__ */ jsxs("label", { children: [t("measurement.unit"), /* @__PURE__ */ jsx("select", {
|
|
1368
|
+
className: control,
|
|
1369
|
+
"aria-label": t("measurement.unit"),
|
|
1370
|
+
value: unit,
|
|
1371
|
+
onChange: (e) => setUnit(e.target.value),
|
|
1372
|
+
children: measurement.units().map((u) => /* @__PURE__ */ jsx("option", { children: u }, u))
|
|
1373
|
+
})] }),
|
|
1374
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1375
|
+
className: "flex items-center gap-2 text-sm",
|
|
1376
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1377
|
+
type: "checkbox",
|
|
1378
|
+
checked: allPages,
|
|
1379
|
+
onChange: (e) => setAllPages(e.target.checked)
|
|
1380
|
+
}), t("measurement.allPages")]
|
|
1381
|
+
}),
|
|
1382
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1383
|
+
className: "flex items-center gap-2 text-sm",
|
|
1384
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1385
|
+
type: "checkbox",
|
|
1386
|
+
checked: recalculate,
|
|
1387
|
+
onChange: (e) => setRecalculate(e.target.checked)
|
|
1388
|
+
}), t("measurement.recalculate")]
|
|
1389
|
+
}),
|
|
1390
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1391
|
+
role: "alert",
|
|
1392
|
+
className: "text-sm text-red-600",
|
|
1393
|
+
children: error
|
|
1394
|
+
}),
|
|
1395
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1396
|
+
className: "flex justify-end gap-2",
|
|
1397
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1398
|
+
type: "button",
|
|
1399
|
+
className: button,
|
|
1400
|
+
disabled: measurement.busy,
|
|
1401
|
+
onClick: () => measurement.dismissCalibration(),
|
|
1402
|
+
children: t("demo.cancel")
|
|
1403
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1404
|
+
className: button,
|
|
1405
|
+
type: "submit",
|
|
1406
|
+
disabled: measurement.busy || !measurement.canCalibratePage || !(Number(value) > 0) || !Number.isFinite(Number(value)),
|
|
1407
|
+
children: measurement.busy ? t("measurement.applying") : t("measurement.apply")
|
|
1408
|
+
})]
|
|
1409
|
+
})
|
|
1410
|
+
]
|
|
1411
|
+
})
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
//#endregion
|
|
1415
|
+
//#region src/config/default-stamps.ts
|
|
1416
|
+
/**
|
|
1417
|
+
* The viewer's BUILT-IN stamp library: `@embedpdf/default-stamps`, the
|
|
1418
|
+
* standard rubber stamps as one Acrobat-compatible PDF per locale.
|
|
1419
|
+
*
|
|
1420
|
+
* Delivery: the library ships INSIDE whatever ships this viewer. The package
|
|
1421
|
+
* exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
|
|
1422
|
+
* so it travels through the module graph like any other code — every bundler
|
|
1423
|
+
* splits it into a chunk served from the app's own origin, the CDN snippet
|
|
1424
|
+
* carries it as a sibling chunk in its folder, and nothing is fetched from a
|
|
1425
|
+
* third party. `stamps.defaultLibrary` overrides that:
|
|
1426
|
+
* - `false` → no built-in library, no request (air-gapped);
|
|
1427
|
+
* - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
|
|
1428
|
+
*
|
|
1429
|
+
* Loading is LAZY (first open of the stamps panel, never at boot) and
|
|
1430
|
+
* locale-aware: the chrome locale leads, the browser languages break ties
|
|
1431
|
+
* for the locales the chrome does not translate itself, and a locale change
|
|
1432
|
+
* while a default library is loaded swaps it for the new one. The file names
|
|
1433
|
+
* itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
|
|
1434
|
+
*/
|
|
1435
|
+
/** The built-in library's PieceInfo id — excluded from persistence (it is
|
|
1436
|
+
* loaded again on every first open, in the locale of that moment). */
|
|
1437
|
+
const DEFAULT_LIBRARY_ID = "embedpdf-standard";
|
|
1438
|
+
/** Which shipped locale to show for the chrome's locale + the browser's. */
|
|
1439
|
+
function resolveStampsLocale(chromeLocale) {
|
|
1440
|
+
return negotiateLocale(LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
|
|
1441
|
+
}
|
|
1442
|
+
const fetchBytes = async (url) => {
|
|
1443
|
+
const response = await fetch(url);
|
|
1444
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
1445
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1446
|
+
};
|
|
1447
|
+
/** The library bytes for `locale`: your URL when you gave one, otherwise the
|
|
1448
|
+
* copy that shipped with the viewer. Never anything else. */
|
|
1449
|
+
const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : loadDefaultLibrary(locale);
|
|
1450
|
+
/** Per workspace: the locale the default library was last brought to, and
|
|
1451
|
+
* the in-flight work — the panel mounts and unmounts with the sidebar, so
|
|
1452
|
+
* this cannot live in component state. */
|
|
1453
|
+
const loads = /* @__PURE__ */ new WeakMap();
|
|
1454
|
+
/**
|
|
1455
|
+
* Bring the built-in library to `locale`. Idempotent per workspace and
|
|
1456
|
+
* locale; a locale change swaps the loaded default (remove, then import);
|
|
1457
|
+
* a default the user removed in this session stays removed — the locale is
|
|
1458
|
+
* recorded, nothing is re-added. An embedder that seeded a library under
|
|
1459
|
+
* the same id is left alone.
|
|
1460
|
+
*/
|
|
1461
|
+
function ensureDefaultLibrary(stamp, locale, source) {
|
|
1462
|
+
if (source === false) return Promise.resolve();
|
|
1463
|
+
const current = loads.get(stamp);
|
|
1464
|
+
if (current?.locale === locale) return current.promise;
|
|
1465
|
+
const run = async () => {
|
|
1466
|
+
await current?.promise.catch(() => void 0);
|
|
1467
|
+
const loaded = stamp.library(DEFAULT_LIBRARY_ID);
|
|
1468
|
+
if (loaded?.locale === locale) return;
|
|
1469
|
+
if (current && !loaded) return;
|
|
1470
|
+
if (loaded) await stamp.removeLibrary(DEFAULT_LIBRARY_ID);
|
|
1471
|
+
await stamp.importLibraryPdf(await defaultLibraryBytes(locale, source));
|
|
1472
|
+
};
|
|
1473
|
+
const promise = run();
|
|
1474
|
+
loads.set(stamp, {
|
|
1475
|
+
locale,
|
|
1476
|
+
promise
|
|
1477
|
+
});
|
|
1478
|
+
return promise;
|
|
1479
|
+
}
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region src/ui/stamp-store.tsx
|
|
1482
|
+
/**
|
|
1483
|
+
* The user's stamp libraries live in the browser (IndexedDB): restored ONCE
|
|
1484
|
+
* per workspace, written on every change, for as long as the viewer lives —
|
|
1485
|
+
* a signature made while the stamps sidebar is closed is persisted exactly
|
|
1486
|
+
* like a stamp imported while it is open. The built-in library is never
|
|
1487
|
+
* stored (it is fetched again next time, in the locale of that moment); the
|
|
1488
|
+
* panels that need it bring it in lazily through `ensureDefaultLibrary`.
|
|
1489
|
+
*
|
|
1490
|
+
* Mounted once in the Shell, workspace-scoped (the stamp plugin is).
|
|
1491
|
+
*/
|
|
1492
|
+
const store = typeof indexedDB === "undefined" ? null : indexedDbByteStore("embedpdf-stamps", { storeName: "libraries" });
|
|
1493
|
+
const restored = /* @__PURE__ */ new WeakMap();
|
|
1494
|
+
/** Bring the stored libraries back — once per workspace, whoever asks first. */
|
|
1495
|
+
const restoreStampLibrariesOnce = (stamp) => {
|
|
1496
|
+
let pending = restored.get(stamp);
|
|
1497
|
+
if (!pending) {
|
|
1498
|
+
pending = store ? restoreStampLibraries(stamp, store) : Promise.resolve();
|
|
1499
|
+
restored.set(stamp, pending);
|
|
1500
|
+
}
|
|
1501
|
+
return pending;
|
|
1502
|
+
};
|
|
1503
|
+
function StampLibraryStore() {
|
|
1504
|
+
const stamp = useStamp();
|
|
1505
|
+
useEffect(() => {
|
|
1506
|
+
restoreStampLibrariesOnce(stamp);
|
|
1507
|
+
return store ? persistStampLibraries(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0;
|
|
1508
|
+
}, [stamp]);
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
//#endregion
|
|
1512
|
+
//#region src/ui/quick-stamps.tsx
|
|
1513
|
+
/**
|
|
1514
|
+
* Quick marks in the toolbar: a row of stamp thumbnails that ARM on click —
|
|
1515
|
+
* the checkmark, the cross, whatever the embedder configured
|
|
1516
|
+
* (`stamps.toolbar`: asset ids, or one library's assets). The hover ghost and
|
|
1517
|
+
* the click placement are the annotation plugin's, exactly as from the
|
|
1518
|
+
* sidebar; only the picker moved. Renders nothing until the assets exist.
|
|
1519
|
+
* Lives in the workspace toolbar, so it reads the ACTIVE document's tool.
|
|
1520
|
+
*/
|
|
1521
|
+
function QuickStamps() {
|
|
1522
|
+
const t = useT();
|
|
1523
|
+
const stamp = useStamp();
|
|
1524
|
+
const config = useStampsConfig();
|
|
1525
|
+
const { locale } = useLocale();
|
|
1526
|
+
const assets = useStampAssets();
|
|
1527
|
+
const documentId = useDocumentId();
|
|
1528
|
+
const { armAsset } = useArmStampAsset();
|
|
1529
|
+
const activeToolId = useOptionalSelector(InteractionToken$1, (c) => c.activeToolId(), null);
|
|
1530
|
+
const armedId = useOptionalSelector(StampToken$1, (c) => documentId ? c.armedAsset(documentId)?.id ?? null : null, null);
|
|
1531
|
+
const ids = Array.isArray(config.toolbar) ? config.toolbar : config.toolbar && "library" in config.toolbar ? stamp.library(config.toolbar.library)?.assetIds ?? [] : [];
|
|
1532
|
+
const wantsDefault = ids.some((id) => id.startsWith(`${DEFAULT_LIBRARY_ID}:`));
|
|
1533
|
+
useEffect(() => {
|
|
1534
|
+
if (!wantsDefault) return;
|
|
1535
|
+
restoreStampLibrariesOnce(stamp).then(() => ensureDefaultLibrary(stamp, resolveStampsLocale(locale), config.defaultLibrary)).catch((err) => console.error("[embedpdf] quick stamps failed:", err));
|
|
1536
|
+
}, [
|
|
1537
|
+
stamp,
|
|
1538
|
+
locale,
|
|
1539
|
+
wantsDefault
|
|
1540
|
+
]);
|
|
1541
|
+
const shown = ids.map((id) => assets.find((a) => a.id === id)).filter((a) => !!a);
|
|
1542
|
+
if (shown.length === 0 || !documentId) return null;
|
|
1543
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1544
|
+
className: "flex items-center gap-1",
|
|
1545
|
+
role: "group",
|
|
1546
|
+
"aria-label": t("demo.quickStampsLabel"),
|
|
1547
|
+
children: shown.map((asset) => /* @__PURE__ */ jsx(QuickStampButton, {
|
|
1548
|
+
asset,
|
|
1549
|
+
active: activeToolId === "stamp" && armedId === asset.id,
|
|
1550
|
+
onArm: () => void armAsset(asset.id).catch((e) => console.error("[embedpdf] arm failed", e))
|
|
1551
|
+
}, asset.id))
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
function QuickStampButton({ asset, active, onArm }) {
|
|
1555
|
+
const url = useStampAssetPreviewUrl(asset.id);
|
|
1556
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1557
|
+
type: "button",
|
|
1558
|
+
onClick: onArm,
|
|
1559
|
+
title: asset.label,
|
|
1560
|
+
"aria-label": asset.label,
|
|
1561
|
+
"aria-pressed": active || void 0,
|
|
1562
|
+
className: buttonClass(active, true),
|
|
1563
|
+
children: url ? /* @__PURE__ */ jsx("img", {
|
|
1564
|
+
src: url,
|
|
1565
|
+
alt: "",
|
|
1566
|
+
className: "h-5 w-5 object-contain",
|
|
1567
|
+
draggable: false
|
|
1568
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
1569
|
+
className: "text-xs",
|
|
1570
|
+
children: asset.label
|
|
1571
|
+
})
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
//#endregion
|
|
932
1575
|
//#region src/ui/menu.tsx
|
|
933
1576
|
/**
|
|
934
1577
|
* Menu rendering — a MenuSchema (a tree of command ids) rendered as rows.
|
|
@@ -1337,7 +1980,11 @@ function AppToolbar({ bar, className }) {
|
|
|
1337
1980
|
variant,
|
|
1338
1981
|
run
|
|
1339
1982
|
}),
|
|
1340
|
-
renderCustom: {
|
|
1983
|
+
renderCustom: {
|
|
1984
|
+
"measurement-scale": () => /* @__PURE__ */ jsx(MeasurementScaleButton, {}),
|
|
1985
|
+
"zoom-controls": (variant) => variant === "inline" ? /* @__PURE__ */ jsx(ZoomControls, {}) : /* @__PURE__ */ jsx(ZoomButton, {}),
|
|
1986
|
+
"quick-stamps": () => /* @__PURE__ */ jsx(QuickStamps, {})
|
|
1987
|
+
},
|
|
1341
1988
|
renderCollapsed: (view) => view.id === "modes" ? /* @__PURE__ */ jsx(CollapsedModes, { view }) : void 0,
|
|
1342
1989
|
renderGroupTrigger: (view) => /* @__PURE__ */ jsx(GroupTrigger, { view }),
|
|
1343
1990
|
renderSeparator: () => /* @__PURE__ */ jsx("span", { className: "bg-border h-6 w-px self-center" }),
|
|
@@ -1349,6 +1996,71 @@ function AppToolbar({ bar, className }) {
|
|
|
1349
1996
|
});
|
|
1350
1997
|
}
|
|
1351
1998
|
//#endregion
|
|
1999
|
+
//#region src/ui/annotation-fonts.tsx
|
|
2000
|
+
/**
|
|
2001
|
+
* The configured annotation fonts (`annotations.fonts`), loaded once per
|
|
2002
|
+
* viewer: fetch the bytes → register them on the viewer's engine under the
|
|
2003
|
+
* key (what a free-text `fontFamily` carries and what the engine embeds) →
|
|
2004
|
+
* mount the same bytes as a `@font-face` named by that key (what the plugin's
|
|
2005
|
+
* `cssFontFamily` emits for a registered font, so the live editor renders the
|
|
2006
|
+
* face the appearance stream will bake). The style panel lists a font only
|
|
2007
|
+
* once BOTH have happened — an offered key always resolves on write.
|
|
2008
|
+
*/
|
|
2009
|
+
const LoadedFontsContext = createContext([]);
|
|
2010
|
+
function AnnotationFontsProvider({ children }) {
|
|
2011
|
+
const kernel = useKernel();
|
|
2012
|
+
const { fonts } = useAnnotationsConfig();
|
|
2013
|
+
const [loaded, setLoaded] = useState([]);
|
|
2014
|
+
useEffect(() => {
|
|
2015
|
+
const engineFonts = kernel.engine.fonts;
|
|
2016
|
+
if (!fonts?.length || !engineFonts) return;
|
|
2017
|
+
let cancelled = false;
|
|
2018
|
+
const unmounts = [];
|
|
2019
|
+
(async () => {
|
|
2020
|
+
for (const font of fonts) try {
|
|
2021
|
+
const response = await fetch(font.url);
|
|
2022
|
+
if (!response.ok) throw new Error(`${font.url}: HTTP ${response.status}`);
|
|
2023
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
2024
|
+
if (cancelled) return;
|
|
2025
|
+
await engineFonts.register({
|
|
2026
|
+
key: font.key,
|
|
2027
|
+
familyName: font.familyName,
|
|
2028
|
+
weight: font.weight,
|
|
2029
|
+
italic: font.italic,
|
|
2030
|
+
data: bytes
|
|
2031
|
+
});
|
|
2032
|
+
const unmount = await mountWebFont(font.key, bytes, {
|
|
2033
|
+
weight: font.weight,
|
|
2034
|
+
style: font.italic ? "italic" : "normal"
|
|
2035
|
+
});
|
|
2036
|
+
if (cancelled) {
|
|
2037
|
+
unmount();
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
unmounts.push(unmount);
|
|
2041
|
+
setLoaded((prev) => prev.some((f) => f.key === font.key) ? prev : [...prev, {
|
|
2042
|
+
key: font.key,
|
|
2043
|
+
label: font.label
|
|
2044
|
+
}]);
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
console.warn(`[embedpdf] annotation font "${font.key}" was not loaded:`, error);
|
|
2047
|
+
}
|
|
2048
|
+
})();
|
|
2049
|
+
return () => {
|
|
2050
|
+
cancelled = true;
|
|
2051
|
+
for (const unmount of unmounts) unmount();
|
|
2052
|
+
};
|
|
2053
|
+
}, [kernel, fonts]);
|
|
2054
|
+
return /* @__PURE__ */ jsx(LoadedFontsContext.Provider, {
|
|
2055
|
+
value: loaded,
|
|
2056
|
+
children
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
/** The configured fonts that are registered AND mounted, in config order. */
|
|
2060
|
+
function useAnnotationFonts() {
|
|
2061
|
+
return useContext(LoadedFontsContext);
|
|
2062
|
+
}
|
|
2063
|
+
//#endregion
|
|
1352
2064
|
//#region src/ui/link-editor.tsx
|
|
1353
2065
|
/**
|
|
1354
2066
|
* The link editor POPOVER — v2's "add a link" popup, reborn. It rides the
|
|
@@ -1716,7 +2428,13 @@ function iconGlyph(name, accent) {
|
|
|
1716
2428
|
const fill = spec.fill === true ? INK : spec.fill ? accent?.[spec.fill] : void 0;
|
|
1717
2429
|
const stroke = spec.stroke ? accent?.[spec.stroke] ?? INK : spec.fill ? "none" : INK;
|
|
1718
2430
|
halo.push(`<path d="${spec.d}" fill="none" stroke="${HALO}" stroke-width="3.5"/>`);
|
|
1719
|
-
|
|
2431
|
+
const decoration = [
|
|
2432
|
+
["stroke-width", spec.strokeWidth],
|
|
2433
|
+
["stroke-dasharray", spec.strokeDasharray],
|
|
2434
|
+
["stroke-opacity", spec.strokeOpacity],
|
|
2435
|
+
["fill-opacity", spec.fillOpacity]
|
|
2436
|
+
].filter(([, value]) => value !== void 0).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
2437
|
+
draw.push(`<path d="${spec.d}" fill="${fill ?? "none"}" stroke="${stroke}" ${decoration}/>`);
|
|
1720
2438
|
}
|
|
1721
2439
|
return halo.join("") + draw.join("");
|
|
1722
2440
|
}
|
|
@@ -2334,12 +3052,17 @@ function LineEndingSelect({ side, value, onChange }) {
|
|
|
2334
3052
|
});
|
|
2335
3053
|
}
|
|
2336
3054
|
function FontFamilySelect({ value, onChange }) {
|
|
3055
|
+
const configured = useAnnotationFonts();
|
|
3056
|
+
const options = [...FONT_OPTIONS, ...configured.map((f) => ({
|
|
3057
|
+
v: f.key,
|
|
3058
|
+
label: f.label
|
|
3059
|
+
}))];
|
|
2337
3060
|
return /* @__PURE__ */ jsx(DropdownShell, {
|
|
2338
3061
|
trigger: /* @__PURE__ */ jsx("span", {
|
|
2339
3062
|
className: "text-fg text-sm",
|
|
2340
|
-
children:
|
|
3063
|
+
children: options.find((o) => o.v === value)?.label ?? value
|
|
2341
3064
|
}),
|
|
2342
|
-
children: (close) =>
|
|
3065
|
+
children: (close) => options.map((o) => /* @__PURE__ */ jsx(OptionRow, {
|
|
2343
3066
|
selected: o.v === value,
|
|
2344
3067
|
onClick: () => {
|
|
2345
3068
|
onChange(o.v);
|
|
@@ -2351,17 +3074,33 @@ function FontFamilySelect({ value, onChange }) {
|
|
|
2351
3074
|
}
|
|
2352
3075
|
function FontSizeCombo({ value, onChange }) {
|
|
2353
3076
|
const [open, setOpen] = useState(false);
|
|
3077
|
+
const rootRef = useOutsideClose(open, () => setOpen(false));
|
|
3078
|
+
const [draft, setDraft] = useState(null);
|
|
3079
|
+
const commit = () => {
|
|
3080
|
+
if (draft === null) return;
|
|
3081
|
+
const n = parseInt(draft, 10);
|
|
3082
|
+
setDraft(null);
|
|
3083
|
+
if (Number.isFinite(n) && n > 0 && n !== value) onChange(n);
|
|
3084
|
+
};
|
|
2354
3085
|
return /* @__PURE__ */ jsxs("div", {
|
|
2355
|
-
ref:
|
|
3086
|
+
ref: rootRef,
|
|
2356
3087
|
className: "relative w-full",
|
|
2357
3088
|
children: [
|
|
2358
3089
|
/* @__PURE__ */ jsx("input", {
|
|
2359
3090
|
type: "number",
|
|
2360
3091
|
min: 1,
|
|
2361
|
-
value,
|
|
2362
|
-
onChange: (e) =>
|
|
2363
|
-
|
|
2364
|
-
|
|
3092
|
+
value: draft ?? value,
|
|
3093
|
+
onChange: (e) => setDraft(e.target.value),
|
|
3094
|
+
onBlur: commit,
|
|
3095
|
+
onKeyDown: (e) => {
|
|
3096
|
+
if (e.key === "Enter") {
|
|
3097
|
+
e.preventDefault();
|
|
3098
|
+
commit();
|
|
3099
|
+
setOpen(false);
|
|
3100
|
+
} else if (e.key === "Escape") {
|
|
3101
|
+
setDraft(null);
|
|
3102
|
+
setOpen(false);
|
|
3103
|
+
}
|
|
2365
3104
|
},
|
|
2366
3105
|
onClick: () => setOpen(true),
|
|
2367
3106
|
className: "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 pr-7 text-sm"
|
|
@@ -2382,6 +3121,7 @@ function FontSizeCombo({ value, onChange }) {
|
|
|
2382
3121
|
children: FONT_SIZES.map((sz) => /* @__PURE__ */ jsx(OptionRow, {
|
|
2383
3122
|
selected: sz === value,
|
|
2384
3123
|
onClick: () => {
|
|
3124
|
+
setDraft(null);
|
|
2385
3125
|
onChange(sz);
|
|
2386
3126
|
setOpen(false);
|
|
2387
3127
|
},
|
|
@@ -2402,15 +3142,45 @@ function BlendModeSelect({ value, onChange }) {
|
|
|
2402
3142
|
}, mode.v))
|
|
2403
3143
|
});
|
|
2404
3144
|
}
|
|
2405
|
-
function Toggle({ active, title, onClick, children }) {
|
|
3145
|
+
function Toggle({ active, title, onClick, keepFocus, children }) {
|
|
2406
3146
|
return /* @__PURE__ */ jsx("button", {
|
|
2407
3147
|
type: "button",
|
|
2408
3148
|
title,
|
|
2409
3149
|
onClick,
|
|
3150
|
+
onMouseDown: keepFocus ? (e) => e.preventDefault() : void 0,
|
|
2410
3151
|
className: `flex h-9 w-9 items-center justify-center rounded border transition-colors ${active ? "border-accent bg-accent text-on-accent" : "border-border bg-surface text-fg hover:bg-hover"}`,
|
|
2411
3152
|
children
|
|
2412
3153
|
});
|
|
2413
3154
|
}
|
|
3155
|
+
const isFormatSpec = (spec) => spec.key === "bold" || spec.key === "italic" || spec.key === "underline";
|
|
3156
|
+
/**
|
|
3157
|
+
* The format toggles a free-text kind declares, in one row. While the text
|
|
3158
|
+
* editor holds a range they read and write that range's runs (the plugin
|
|
3159
|
+
* routes `updateSelection`); otherwise the annotation's body. The buttons
|
|
3160
|
+
* keep focus in the editor so the range survives the click.
|
|
3161
|
+
*/
|
|
3162
|
+
function FormatToggles({ specs, values, mixed, onChange }) {
|
|
3163
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
3164
|
+
label: useT()("demo.formatLabel", { fallback: "Format" }),
|
|
3165
|
+
mixed: specs.some((s) => mixed.includes(s.key)),
|
|
3166
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3167
|
+
className: "flex gap-2",
|
|
3168
|
+
children: specs.map((spec) => {
|
|
3169
|
+
const active = values[spec.key] === true && !mixed.includes(spec.key);
|
|
3170
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
3171
|
+
title: spec.label,
|
|
3172
|
+
active,
|
|
3173
|
+
keepFocus: true,
|
|
3174
|
+
onClick: () => onChange({ [spec.key]: !active }),
|
|
3175
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
3176
|
+
name: spec.key,
|
|
3177
|
+
size: 18
|
|
3178
|
+
})
|
|
3179
|
+
}, spec.key);
|
|
3180
|
+
})
|
|
3181
|
+
})
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
2414
3184
|
function PropControl({ spec, value, mixed, onChange }) {
|
|
2415
3185
|
switch (spec.key) {
|
|
2416
3186
|
case "color":
|
|
@@ -2545,6 +3315,9 @@ function PropControl({ spec, value, mixed, onChange }) {
|
|
|
2545
3315
|
})
|
|
2546
3316
|
});
|
|
2547
3317
|
}
|
|
3318
|
+
case "bold":
|
|
3319
|
+
case "italic":
|
|
3320
|
+
case "underline": return null;
|
|
2548
3321
|
case "blendMode": return /* @__PURE__ */ jsx(Field, {
|
|
2549
3322
|
label: spec.label,
|
|
2550
3323
|
mixed,
|
|
@@ -2592,7 +3365,12 @@ function AnnotationStylePanel() {
|
|
|
2592
3365
|
className: "text-fg-muted mb-4 text-[11px] font-semibold uppercase tracking-wide",
|
|
2593
3366
|
children: hasSel ? `${selected.length} selected` : `${activeToolId} defaults`
|
|
2594
3367
|
}),
|
|
2595
|
-
specs.map((spec) => /* @__PURE__ */ jsx(
|
|
3368
|
+
specs.map((spec) => spec.key === "bold" ? /* @__PURE__ */ jsx(FormatToggles, {
|
|
3369
|
+
specs: specs.filter((s) => isFormatSpec(s)),
|
|
3370
|
+
values,
|
|
3371
|
+
mixed: hasSel ? sel.mixed : [],
|
|
3372
|
+
onChange: write
|
|
3373
|
+
}, "format") : /* @__PURE__ */ jsx(PropControl, {
|
|
2596
3374
|
spec,
|
|
2597
3375
|
value: values[spec.key],
|
|
2598
3376
|
mixed: hasSel && sel.mixed.includes(spec.key),
|
|
@@ -3463,77 +4241,11 @@ function SearchPanel() {
|
|
|
3463
4241
|
});
|
|
3464
4242
|
}
|
|
3465
4243
|
//#endregion
|
|
3466
|
-
//#region src/
|
|
4244
|
+
//#region src/ui/stamps-panel.tsx
|
|
3467
4245
|
/**
|
|
3468
|
-
* The
|
|
3469
|
-
*
|
|
3470
|
-
*
|
|
3471
|
-
* Delivery: the library ships INSIDE whatever ships this viewer. The package
|
|
3472
|
-
* exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
|
|
3473
|
-
* so it travels through the module graph like any other code — every bundler
|
|
3474
|
-
* splits it into a chunk served from the app's own origin, the CDN snippet
|
|
3475
|
-
* carries it as a sibling chunk in its folder, and nothing is fetched from a
|
|
3476
|
-
* third party. `stamps.defaultLibrary` overrides that:
|
|
3477
|
-
* - `false` → no built-in library, no request (air-gapped);
|
|
3478
|
-
* - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
|
|
3479
|
-
*
|
|
3480
|
-
* Loading is LAZY (first open of the stamps panel, never at boot) and
|
|
3481
|
-
* locale-aware: the chrome locale leads, the browser languages break ties
|
|
3482
|
-
* for the locales the chrome does not translate itself, and a locale change
|
|
3483
|
-
* while a default library is loaded swaps it for the new one. The file names
|
|
3484
|
-
* itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
|
|
3485
|
-
*/
|
|
3486
|
-
/** The built-in library's PieceInfo id — excluded from persistence (it is
|
|
3487
|
-
* loaded again on every first open, in the locale of that moment). */
|
|
3488
|
-
const DEFAULT_LIBRARY_ID = "embedpdf-standard";
|
|
3489
|
-
/** Which shipped locale to show for the chrome's locale + the browser's. */
|
|
3490
|
-
function resolveStampsLocale(chromeLocale) {
|
|
3491
|
-
return negotiateLocale(LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
|
|
3492
|
-
}
|
|
3493
|
-
const fetchBytes = async (url) => {
|
|
3494
|
-
const response = await fetch(url);
|
|
3495
|
-
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
3496
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
3497
|
-
};
|
|
3498
|
-
/** The library bytes for `locale`: your URL when you gave one, otherwise the
|
|
3499
|
-
* copy that shipped with the viewer. Never anything else. */
|
|
3500
|
-
const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : loadDefaultLibrary(locale);
|
|
3501
|
-
/** Per workspace: the locale the default library was last brought to, and
|
|
3502
|
-
* the in-flight work — the panel mounts and unmounts with the sidebar, so
|
|
3503
|
-
* this cannot live in component state. */
|
|
3504
|
-
const loads = /* @__PURE__ */ new WeakMap();
|
|
3505
|
-
/**
|
|
3506
|
-
* Bring the built-in library to `locale`. Idempotent per workspace and
|
|
3507
|
-
* locale; a locale change swaps the loaded default (remove, then import);
|
|
3508
|
-
* a default the user removed in this session stays removed — the locale is
|
|
3509
|
-
* recorded, nothing is re-added. An embedder that seeded a library under
|
|
3510
|
-
* the same id is left alone.
|
|
3511
|
-
*/
|
|
3512
|
-
function ensureDefaultLibrary(stamp, locale, source) {
|
|
3513
|
-
if (source === false) return Promise.resolve();
|
|
3514
|
-
const current = loads.get(stamp);
|
|
3515
|
-
if (current?.locale === locale) return current.promise;
|
|
3516
|
-
const run = async () => {
|
|
3517
|
-
await current?.promise.catch(() => void 0);
|
|
3518
|
-
const loaded = stamp.library(DEFAULT_LIBRARY_ID);
|
|
3519
|
-
if (loaded?.locale === locale) return;
|
|
3520
|
-
if (current && !loaded) return;
|
|
3521
|
-
if (loaded) await stamp.removeLibrary(DEFAULT_LIBRARY_ID);
|
|
3522
|
-
await stamp.importLibraryPdf(await defaultLibraryBytes(locale, source));
|
|
3523
|
-
};
|
|
3524
|
-
const promise = run();
|
|
3525
|
-
loads.set(stamp, {
|
|
3526
|
-
locale,
|
|
3527
|
-
promise
|
|
3528
|
-
});
|
|
3529
|
-
return promise;
|
|
3530
|
-
}
|
|
3531
|
-
//#endregion
|
|
3532
|
-
//#region src/ui/stamps-panel.tsx
|
|
3533
|
-
/**
|
|
3534
|
-
* The stamps sidebar (right panel) — v2's rubber-stamp sidebar on v3 parts.
|
|
3535
|
-
* The stamp plugin owns the libraries, the asset bytes and the cached
|
|
3536
|
-
* previews; this panel is the PICKER over them:
|
|
4246
|
+
* The stamps sidebar (right panel) — v2's rubber-stamp sidebar on v3 parts.
|
|
4247
|
+
* The stamp plugin owns the libraries, the asset bytes and the cached
|
|
4248
|
+
* previews; this panel is the PICKER over them:
|
|
3537
4249
|
*
|
|
3538
4250
|
* a library <select> ("All stamps" + one per library, shown once there
|
|
3539
4251
|
* are two) → a two-column grid of thumbnails → click a stamp → `armAsset`
|
|
@@ -3551,30 +4263,15 @@ function ensureDefaultLibrary(stamp, locale, source) {
|
|
|
3551
4263
|
* through the surface's open props.
|
|
3552
4264
|
*/
|
|
3553
4265
|
const ALL = "all";
|
|
3554
|
-
/** The user's libraries live in the browser (IndexedDB): restored on first
|
|
3555
|
-
* open, written on every change. The built-in set is never stored — it is
|
|
3556
|
-
* fetched again next time, in the locale of that moment. */
|
|
3557
|
-
const store = typeof indexedDB === "undefined" ? null : indexedDbByteStore("embedpdf-stamps", { storeName: "libraries" });
|
|
3558
|
-
/** Restore runs ONCE per workspace — the panel mounts and unmounts with the
|
|
3559
|
-
* sidebar, so the guard cannot live in component state. */
|
|
3560
|
-
const restored = /* @__PURE__ */ new WeakMap();
|
|
3561
|
-
const restoreOnce = (stamp) => {
|
|
3562
|
-
let pending = restored.get(stamp);
|
|
3563
|
-
if (!pending) {
|
|
3564
|
-
pending = store ? restoreStampLibraries(stamp, store) : Promise.resolve();
|
|
3565
|
-
restored.set(stamp, pending);
|
|
3566
|
-
}
|
|
3567
|
-
return pending;
|
|
3568
|
-
};
|
|
3569
4266
|
function StampsPanel() {
|
|
3570
4267
|
const t = useT();
|
|
3571
4268
|
const stamp = useStamp();
|
|
3572
|
-
const
|
|
4269
|
+
const { defaultLibrary, sidebar } = useStampsConfig();
|
|
4270
|
+
const libraries = useStampLibraries({ kind: sidebar ?? ["stamps"] });
|
|
3573
4271
|
const assets = useStampAssets();
|
|
3574
4272
|
const { armAsset } = useArmStampAsset();
|
|
3575
4273
|
const { activeToolId } = useTool();
|
|
3576
4274
|
const { locale } = useLocale();
|
|
3577
|
-
const { defaultLibrary } = useStampsConfig();
|
|
3578
4275
|
const surface = useSurface("stamps");
|
|
3579
4276
|
const fileRef = useRef(null);
|
|
3580
4277
|
const [armedId, setArmedId] = useState(null);
|
|
@@ -3588,7 +4285,7 @@ function StampsPanel() {
|
|
|
3588
4285
|
useEffect(() => {
|
|
3589
4286
|
let live = true;
|
|
3590
4287
|
setBusy("loading");
|
|
3591
|
-
|
|
4288
|
+
restoreStampLibrariesOnce(stamp).then(() => ensureDefaultLibrary(stamp, resolveStampsLocale(locale), defaultLibrary)).catch((err) => {
|
|
3592
4289
|
console.error("[embedpdf] default stamps failed:", err);
|
|
3593
4290
|
if (live) setError(t("demo.stampsError"));
|
|
3594
4291
|
}).finally(() => live && setBusy(null));
|
|
@@ -3596,7 +4293,6 @@ function StampsPanel() {
|
|
|
3596
4293
|
live = false;
|
|
3597
4294
|
};
|
|
3598
4295
|
}, [stamp, locale]);
|
|
3599
|
-
useEffect(() => store ? persistStampLibraries(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0, [stamp]);
|
|
3600
4296
|
useEffect(() => {
|
|
3601
4297
|
if (activeToolId !== "stamp") setArmedId(null);
|
|
3602
4298
|
}, [activeToolId]);
|
|
@@ -3767,6 +4463,293 @@ function StampAssetCell({ asset, armed, onArm }) {
|
|
|
3767
4463
|
});
|
|
3768
4464
|
}
|
|
3769
4465
|
//#endregion
|
|
4466
|
+
//#region src/ui/signatures-panel.tsx
|
|
4467
|
+
/**
|
|
4468
|
+
* The signatures sidebar (right panel) — Preview's signature sheet on v3
|
|
4469
|
+
* parts. One row per PERSON: a stamp library of kind `signatures` holding a
|
|
4470
|
+
* full signature and, optionally, initials. Nothing is stored beyond the
|
|
4471
|
+
* file; rename/export/delete are the library verbs.
|
|
4472
|
+
*
|
|
4473
|
+
* pick a mark → with a TARGET field ("sign here" was clicked, or a
|
|
4474
|
+
* signature widget selected) the mark goes into that field by the
|
|
4475
|
+
* plugin's mode; otherwise it ARMS — hover a page for the ghost, click a
|
|
4476
|
+
* signature field to sign it, click anywhere else to drop it as a stamp.
|
|
4477
|
+
*
|
|
4478
|
+
* Below the people: the signature fields of THIS document, signed or not,
|
|
4479
|
+
* with their verdicts — click an unsigned one to target it, a signed one to
|
|
4480
|
+
* inspect it.
|
|
4481
|
+
*/
|
|
4482
|
+
const fieldLabel = (field) => field.kind === "fqn" ? field.name : `#${field.fieldObjectNumber}`;
|
|
4483
|
+
function SignaturesPanel() {
|
|
4484
|
+
const t = useT();
|
|
4485
|
+
const stamp = useStamp();
|
|
4486
|
+
const signature = useSignature();
|
|
4487
|
+
const config = useSignaturesConfig();
|
|
4488
|
+
const rows = useSignerRows();
|
|
4489
|
+
const { target, busy } = useSignatureTarget();
|
|
4490
|
+
const { armAsset } = useArmStampAsset();
|
|
4491
|
+
const { activeToolId } = useTool();
|
|
4492
|
+
const maker = useSurface("signature-maker");
|
|
4493
|
+
const [armedId, setArmedId] = useState(null);
|
|
4494
|
+
const [error, setError] = useState(null);
|
|
4495
|
+
const [invalidating, setInvalidating] = useState(null);
|
|
4496
|
+
useSignatureEvent((event) => {
|
|
4497
|
+
if (event.type === "invalidating") setInvalidating(signature.signatureOf(event.field)?.fieldName ?? fieldLabel(event.field));
|
|
4498
|
+
else if (event.type === "validated" && !event.verdicts.some((v) => v.summary === "invalid" && v.modifications.basis === "working-copy")) setInvalidating(null);
|
|
4499
|
+
});
|
|
4500
|
+
useEffect(() => {
|
|
4501
|
+
restoreStampLibrariesOnce(stamp);
|
|
4502
|
+
}, [stamp]);
|
|
4503
|
+
useEffect(() => {
|
|
4504
|
+
if (activeToolId !== "stamp") setArmedId(null);
|
|
4505
|
+
}, [activeToolId]);
|
|
4506
|
+
const pick = (asset) => {
|
|
4507
|
+
setError(null);
|
|
4508
|
+
if (target) {
|
|
4509
|
+
signature.placeMark({ assetId: asset.id }, { field: target }).catch((err) => {
|
|
4510
|
+
console.error("[embedpdf] placing the mark failed:", err);
|
|
4511
|
+
setError(t("demo.signError"));
|
|
4512
|
+
});
|
|
4513
|
+
return;
|
|
4514
|
+
}
|
|
4515
|
+
setArmedId(asset.id);
|
|
4516
|
+
armAsset(asset.id).catch((err) => {
|
|
4517
|
+
console.error("[embedpdf] arm mark failed:", err);
|
|
4518
|
+
setArmedId(null);
|
|
4519
|
+
setError(t("demo.stampsArmError"));
|
|
4520
|
+
});
|
|
4521
|
+
};
|
|
4522
|
+
const canAddPerson = (config.libraries ?? "many") === "many" || rows.length === 0;
|
|
4523
|
+
const wantsInitials = (config.kinds ?? ["signature", "initials"]).includes("initials");
|
|
4524
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4525
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
4526
|
+
children: [
|
|
4527
|
+
/* @__PURE__ */ jsxs("div", {
|
|
4528
|
+
className: "min-h-0 flex-1 overflow-y-auto p-3",
|
|
4529
|
+
children: [rows.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
4530
|
+
className: "flex flex-col items-center gap-3 px-4 py-8 text-center",
|
|
4531
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
4532
|
+
name: "signature",
|
|
4533
|
+
size: 32,
|
|
4534
|
+
className: "text-fg-muted"
|
|
4535
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
4536
|
+
className: "text-fg-muted text-sm",
|
|
4537
|
+
children: t("demo.signaturesEmpty")
|
|
4538
|
+
})]
|
|
4539
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
4540
|
+
className: "flex flex-col gap-3",
|
|
4541
|
+
children: rows.map((row) => /* @__PURE__ */ jsx(SignerRowView, {
|
|
4542
|
+
row,
|
|
4543
|
+
armedId,
|
|
4544
|
+
wantsInitials,
|
|
4545
|
+
onPick: pick,
|
|
4546
|
+
onAddInitials: () => maker.open({
|
|
4547
|
+
exclusive: "modal",
|
|
4548
|
+
props: { libraryId: row.libraryId }
|
|
4549
|
+
})
|
|
4550
|
+
}, row.libraryId))
|
|
4551
|
+
}), /* @__PURE__ */ jsx(DocumentSignatures, {})]
|
|
4552
|
+
}),
|
|
4553
|
+
invalidating && /* @__PURE__ */ jsx("div", {
|
|
4554
|
+
className: "border-border-subtle shrink-0 border-t px-3 py-2 text-xs text-amber-700",
|
|
4555
|
+
children: t("demo.signaturesInvalidating", { params: { field: invalidating } })
|
|
4556
|
+
}),
|
|
4557
|
+
(error || target || armedId) && /* @__PURE__ */ jsx("div", {
|
|
4558
|
+
className: `border-border-subtle shrink-0 border-t px-3 py-2 text-xs ${error ? "text-red-600" : "text-fg-muted"}`,
|
|
4559
|
+
children: error ?? (target ? busy ? t("demo.signSigning") : t("demo.signaturesTargetHint") : t("demo.signaturesArmedHint"))
|
|
4560
|
+
}),
|
|
4561
|
+
canAddPerson ? /* @__PURE__ */ jsx("div", {
|
|
4562
|
+
className: "border-border-subtle shrink-0 border-t p-3",
|
|
4563
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
4564
|
+
type: "button",
|
|
4565
|
+
onClick: () => maker.open({ exclusive: "modal" }),
|
|
4566
|
+
className: "border-border text-fg hover:bg-hover flex w-full items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm",
|
|
4567
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
4568
|
+
name: "plus",
|
|
4569
|
+
size: 16
|
|
4570
|
+
}), t("demo.signaturesNew")]
|
|
4571
|
+
})
|
|
4572
|
+
}) : null
|
|
4573
|
+
]
|
|
4574
|
+
});
|
|
4575
|
+
}
|
|
4576
|
+
function SignerRowView({ row, armedId, wantsInitials, onPick, onAddInitials }) {
|
|
4577
|
+
const t = useT();
|
|
4578
|
+
const stamp = useStamp();
|
|
4579
|
+
const [renaming, setRenaming] = useState(null);
|
|
4580
|
+
const exportPdf = () => {
|
|
4581
|
+
const bytes = stamp.exportLibrary(row.libraryId);
|
|
4582
|
+
if (!bytes) return;
|
|
4583
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
4584
|
+
const url = URL.createObjectURL(blob);
|
|
4585
|
+
const a = document.createElement("a");
|
|
4586
|
+
a.href = url;
|
|
4587
|
+
a.download = `${row.name || "signature"}.pdf`;
|
|
4588
|
+
a.click();
|
|
4589
|
+
URL.revokeObjectURL(url);
|
|
4590
|
+
};
|
|
4591
|
+
const commitRename = () => {
|
|
4592
|
+
const name = renaming?.trim();
|
|
4593
|
+
setRenaming(null);
|
|
4594
|
+
if (name && name !== row.name) stamp.updateLibrary(row.libraryId, { name });
|
|
4595
|
+
};
|
|
4596
|
+
const iconButton = "text-fg-muted hover:text-fg grid h-6 w-6 shrink-0 place-items-center rounded";
|
|
4597
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
4598
|
+
className: "border-border-subtle rounded-md border p-2",
|
|
4599
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4600
|
+
className: "flex items-center gap-1",
|
|
4601
|
+
children: [
|
|
4602
|
+
renaming !== null ? /* @__PURE__ */ jsx("input", {
|
|
4603
|
+
autoFocus: true,
|
|
4604
|
+
value: renaming,
|
|
4605
|
+
onChange: (e) => setRenaming(e.target.value),
|
|
4606
|
+
onBlur: commitRename,
|
|
4607
|
+
onKeyDown: (e) => {
|
|
4608
|
+
if (e.key === "Enter") commitRename();
|
|
4609
|
+
if (e.key === "Escape") setRenaming(null);
|
|
4610
|
+
},
|
|
4611
|
+
className: "border-border bg-surface text-fg min-w-0 flex-1 rounded border px-1.5 py-0.5 text-sm"
|
|
4612
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
4613
|
+
className: "text-fg min-w-0 flex-1 truncate text-sm font-medium",
|
|
4614
|
+
children: row.name
|
|
4615
|
+
}),
|
|
4616
|
+
/* @__PURE__ */ jsx("button", {
|
|
4617
|
+
type: "button",
|
|
4618
|
+
onClick: () => setRenaming(row.name),
|
|
4619
|
+
title: t("demo.signaturesRename"),
|
|
4620
|
+
className: iconButton,
|
|
4621
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
4622
|
+
name: "pencilMarker",
|
|
4623
|
+
size: 13
|
|
4624
|
+
})
|
|
4625
|
+
}),
|
|
4626
|
+
/* @__PURE__ */ jsx("button", {
|
|
4627
|
+
type: "button",
|
|
4628
|
+
onClick: exportPdf,
|
|
4629
|
+
title: t("demo.signaturesExport"),
|
|
4630
|
+
className: iconButton,
|
|
4631
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
4632
|
+
name: "download",
|
|
4633
|
+
size: 13
|
|
4634
|
+
})
|
|
4635
|
+
}),
|
|
4636
|
+
/* @__PURE__ */ jsx("button", {
|
|
4637
|
+
type: "button",
|
|
4638
|
+
onClick: () => void stamp.removeLibrary(row.libraryId),
|
|
4639
|
+
title: t("demo.signaturesRemove"),
|
|
4640
|
+
className: iconButton,
|
|
4641
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
4642
|
+
name: "trash",
|
|
4643
|
+
size: 13
|
|
4644
|
+
})
|
|
4645
|
+
})
|
|
4646
|
+
]
|
|
4647
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
4648
|
+
className: "mt-2 grid grid-cols-[2fr_1fr] gap-2",
|
|
4649
|
+
children: [row.signatures.map((asset) => /* @__PURE__ */ jsx(MarkThumb, {
|
|
4650
|
+
asset,
|
|
4651
|
+
label: t("demo.signatureLabel"),
|
|
4652
|
+
armed: armedId === asset.id,
|
|
4653
|
+
onPick: () => onPick(asset)
|
|
4654
|
+
}, asset.id)), wantsInitials ? row.initials ? /* @__PURE__ */ jsx(MarkThumb, {
|
|
4655
|
+
asset: row.initials,
|
|
4656
|
+
label: t("demo.initialsLabel"),
|
|
4657
|
+
armed: armedId === row.initials.id,
|
|
4658
|
+
onPick: () => onPick(row.initials)
|
|
4659
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
4660
|
+
type: "button",
|
|
4661
|
+
onClick: onAddInitials,
|
|
4662
|
+
title: t("demo.signaturesAddInitials"),
|
|
4663
|
+
className: "border-border-subtle text-fg-muted hover:bg-hover flex h-16 items-center justify-center rounded-md border border-dashed text-xs",
|
|
4664
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
4665
|
+
name: "plus",
|
|
4666
|
+
size: 14
|
|
4667
|
+
})
|
|
4668
|
+
}) : null]
|
|
4669
|
+
})]
|
|
4670
|
+
});
|
|
4671
|
+
}
|
|
4672
|
+
function MarkThumb({ asset, label, armed, onPick }) {
|
|
4673
|
+
const url = useStampAssetPreviewUrl(asset.id);
|
|
4674
|
+
return /* @__PURE__ */ jsx("button", {
|
|
4675
|
+
type: "button",
|
|
4676
|
+
onClick: onPick,
|
|
4677
|
+
title: label,
|
|
4678
|
+
"aria-pressed": armed,
|
|
4679
|
+
className: `flex h-16 items-center justify-center rounded-md border p-1 ${armed ? "border-accent bg-accent-light ring-accent ring-2" : "border-border-subtle hover:border-border hover:bg-hover"}`,
|
|
4680
|
+
children: url ? /* @__PURE__ */ jsx("img", {
|
|
4681
|
+
src: url,
|
|
4682
|
+
alt: label,
|
|
4683
|
+
className: "max-h-full max-w-full object-contain"
|
|
4684
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
4685
|
+
className: "text-fg-muted text-xs",
|
|
4686
|
+
children: label
|
|
4687
|
+
})
|
|
4688
|
+
});
|
|
4689
|
+
}
|
|
4690
|
+
/** The signature fields of this document, with their state. */
|
|
4691
|
+
function DocumentSignatures() {
|
|
4692
|
+
const t = useT();
|
|
4693
|
+
const signature = useSignature();
|
|
4694
|
+
const snapshot = useSignatureSnapshot();
|
|
4695
|
+
const verdicts = useSignatureVerdicts();
|
|
4696
|
+
const { target } = useSignatureTarget();
|
|
4697
|
+
const [validating, setValidating] = useState(false);
|
|
4698
|
+
const fields = snapshot?.signatures ?? [];
|
|
4699
|
+
const verdictOf = (dto) => verdicts?.find((v) => v.signature.index === dto.index) ?? null;
|
|
4700
|
+
const verdictLabel = (v) => !v ? t("demo.signaturesSigned") : v.summary === "valid" ? t("demo.verdictValid") : v.summary === "valid-untrusted" ? t("demo.verdictValidUntrusted") : v.summary === "indeterminate" ? t("demo.verdictIndeterminate") : v.modifications.basis === "working-copy" ? t("demo.verdictWillInvalidate") : t("demo.verdictInvalid");
|
|
4701
|
+
const verdictTone = (v) => !v || v.summary === "indeterminate" ? "text-fg-muted" : v.summary === "invalid" ? v.modifications.basis === "working-copy" ? "text-amber-600" : "text-red-600" : v.summary === "valid-untrusted" ? "text-amber-600" : "text-green-600";
|
|
4702
|
+
const isTarget = (dto) => !!target && (target.kind === "fqn" ? target.name === dto.fieldName : target.fieldObjectNumber === (dto.field.kind === "objectNumber" ? dto.field.fieldObjectNumber : -1));
|
|
4703
|
+
const validate = () => {
|
|
4704
|
+
setValidating(true);
|
|
4705
|
+
signature.validate().finally(() => setValidating(false));
|
|
4706
|
+
};
|
|
4707
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4708
|
+
className: "mt-4",
|
|
4709
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4710
|
+
className: "flex items-center justify-between",
|
|
4711
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4712
|
+
className: "text-fg-muted text-xs font-semibold uppercase tracking-wide",
|
|
4713
|
+
children: t("demo.signaturesInDocument")
|
|
4714
|
+
}), fields.some((f) => f.signed) ? /* @__PURE__ */ jsx("button", {
|
|
4715
|
+
type: "button",
|
|
4716
|
+
disabled: validating,
|
|
4717
|
+
onClick: validate,
|
|
4718
|
+
className: "text-accent text-xs hover:underline disabled:opacity-60",
|
|
4719
|
+
children: validating ? t("demo.signaturesValidating") : t("demo.signaturesValidate")
|
|
4720
|
+
}) : null]
|
|
4721
|
+
}), fields.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
4722
|
+
className: "text-fg-muted mt-1 text-xs",
|
|
4723
|
+
children: t("demo.signaturesNone")
|
|
4724
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
4725
|
+
className: "mt-1 flex flex-col gap-1",
|
|
4726
|
+
children: fields.map((dto) => {
|
|
4727
|
+
const verdict = verdictOf(dto);
|
|
4728
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
|
|
4729
|
+
type: "button",
|
|
4730
|
+
onClick: () => dto.signed ? signature.inspect(dto.field) : signature.setTarget(dto.field),
|
|
4731
|
+
className: `hover:bg-hover flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm ${isTarget(dto) ? "bg-accent-light" : ""}`,
|
|
4732
|
+
children: [
|
|
4733
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
4734
|
+
name: dto.signed ? "check" : "signature",
|
|
4735
|
+
size: 14,
|
|
4736
|
+
className: dto.signed ? "text-green-600" : "text-fg-muted"
|
|
4737
|
+
}),
|
|
4738
|
+
/* @__PURE__ */ jsx("span", {
|
|
4739
|
+
className: "text-fg min-w-0 flex-1 truncate",
|
|
4740
|
+
children: dto.fieldName
|
|
4741
|
+
}),
|
|
4742
|
+
/* @__PURE__ */ jsx("span", {
|
|
4743
|
+
className: `shrink-0 text-right text-xs ${dto.signed ? verdictTone(verdict) : "text-fg-muted"}`,
|
|
4744
|
+
children: dto.signed ? verdictLabel(verdict) : t("demo.signaturesUnsigned")
|
|
4745
|
+
})
|
|
4746
|
+
]
|
|
4747
|
+
}) }, dto.index);
|
|
4748
|
+
})
|
|
4749
|
+
})]
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
//#endregion
|
|
3770
4753
|
//#region src/ui/panels.tsx
|
|
3771
4754
|
/**
|
|
3772
4755
|
* The static chrome around the toolbars: the left/right sidebars (shell
|
|
@@ -3860,11 +4843,13 @@ function RightSidebar() {
|
|
|
3860
4843
|
const comment = useSurface("comment");
|
|
3861
4844
|
const style = useSurface("annotation-style");
|
|
3862
4845
|
const redaction = useSurface("redaction");
|
|
4846
|
+
const measurement = useSurface("measurement");
|
|
3863
4847
|
const stamps = useSurface("stamps");
|
|
3864
|
-
const
|
|
4848
|
+
const signatures = useSurface("signatures");
|
|
4849
|
+
const active = measurement.isOpen ? "measurement" : search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : stamps.isOpen ? "stamps" : signatures.isOpen ? "signatures" : null;
|
|
3865
4850
|
if (!active) return null;
|
|
3866
|
-
const titleKey = active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : active === "stamps" ? "demo.stampsTitle" : "demo.styleTitle";
|
|
3867
|
-
const close = active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : active === "stamps" ? stamps.close : style.close;
|
|
4851
|
+
const titleKey = active === "measurement" ? "measurement.title" : active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : active === "stamps" ? "demo.stampsTitle" : active === "signatures" ? "demo.signaturesTitle" : "demo.styleTitle";
|
|
4852
|
+
const close = active === "measurement" ? measurement.close : active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : active === "stamps" ? stamps.close : active === "signatures" ? signatures.close : style.close;
|
|
3868
4853
|
return /* @__PURE__ */ jsxs("aside", {
|
|
3869
4854
|
className: "border-border-subtle bg-surface flex w-72 shrink-0 flex-col border-l",
|
|
3870
4855
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -3881,7 +4866,7 @@ function RightSidebar() {
|
|
|
3881
4866
|
size: 16
|
|
3882
4867
|
})
|
|
3883
4868
|
})]
|
|
3884
|
-
}), active === "style" ? /* @__PURE__ */ jsx(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ jsx(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ jsx(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ jsx(StampsPanel, {}) : /* @__PURE__ */ jsx(CommentsPanel, {}, documentId ?? "none")]
|
|
4869
|
+
}), active === "measurement" ? /* @__PURE__ */ jsx(MeasurementSection, {}, documentId ?? "none") : active === "style" ? /* @__PURE__ */ jsx(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ jsx(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ jsx(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ jsx(StampsPanel, {}) : active === "signatures" ? /* @__PURE__ */ jsx(SignaturesPanel, {}) : /* @__PURE__ */ jsx(CommentsPanel, {}, documentId ?? "none")]
|
|
3885
4870
|
});
|
|
3886
4871
|
}
|
|
3887
4872
|
function PageControls() {
|
|
@@ -3972,6 +4957,684 @@ function RedactConfirmModal() {
|
|
|
3972
4957
|
});
|
|
3973
4958
|
}
|
|
3974
4959
|
//#endregion
|
|
4960
|
+
//#region src/ui/sign-dialog.tsx
|
|
4961
|
+
/**
|
|
4962
|
+
* The sign dialog (`signature-sign` modal surface): a mark met a field and
|
|
4963
|
+
* the mode is `ask` — or a "sign here" flow wants the facts first. Shows
|
|
4964
|
+
* the mark as it will sit in the field, takes reason and location, offers
|
|
4965
|
+
* a certification when the config allows it, and either SEALS the field
|
|
4966
|
+
* (`signField`) or only draws the mark into it (`fillField`).
|
|
4967
|
+
*/
|
|
4968
|
+
function SignDialog() {
|
|
4969
|
+
const t = useT();
|
|
4970
|
+
const surface = useSurface("signature-sign");
|
|
4971
|
+
const signature = useSignature();
|
|
4972
|
+
const [reason, setReason] = useState("");
|
|
4973
|
+
const [location, setLocation] = useState("");
|
|
4974
|
+
const [signKind, setSignKind] = useState("approval");
|
|
4975
|
+
const [busy, setBusy] = useState(null);
|
|
4976
|
+
const [error, setError] = useState(null);
|
|
4977
|
+
const field = surface.props?.field;
|
|
4978
|
+
const mark = surface.props?.mark;
|
|
4979
|
+
const preview = useStampAssetPreviewUrl(mark && "assetId" in mark ? mark.assetId : null);
|
|
4980
|
+
if (!surface.isOpen || !field || !mark) return null;
|
|
4981
|
+
const attribution = {
|
|
4982
|
+
...reason.trim() ? { reason: reason.trim() } : {},
|
|
4983
|
+
...location.trim() ? { location: location.trim() } : {}
|
|
4984
|
+
};
|
|
4985
|
+
const run = async (kind) => {
|
|
4986
|
+
setBusy(kind);
|
|
4987
|
+
setError(null);
|
|
4988
|
+
try {
|
|
4989
|
+
if (kind === "sign") await signature.signField({
|
|
4990
|
+
field,
|
|
4991
|
+
mark,
|
|
4992
|
+
attribution,
|
|
4993
|
+
...signKind !== "approval" && signature.canCertify() ? { certify: { permission: Number(signKind.slice(-1)) } } : {}
|
|
4994
|
+
});
|
|
4995
|
+
else await signature.fillField(field, mark);
|
|
4996
|
+
surface.close();
|
|
4997
|
+
} catch (err) {
|
|
4998
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4999
|
+
} finally {
|
|
5000
|
+
setBusy(null);
|
|
5001
|
+
}
|
|
5002
|
+
};
|
|
5003
|
+
const input = "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 text-sm";
|
|
5004
|
+
return /* @__PURE__ */ jsx("div", {
|
|
5005
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
5006
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
5007
|
+
className: "border-border bg-surface w-96 rounded-lg border p-4 shadow-xl",
|
|
5008
|
+
children: [
|
|
5009
|
+
/* @__PURE__ */ jsx("h2", {
|
|
5010
|
+
className: "text-fg text-base font-semibold",
|
|
5011
|
+
children: t("demo.signTitle")
|
|
5012
|
+
}),
|
|
5013
|
+
/* @__PURE__ */ jsx("div", {
|
|
5014
|
+
className: "border-border-subtle mt-3 flex h-24 items-center justify-center rounded-md border p-2",
|
|
5015
|
+
children: preview ? /* @__PURE__ */ jsx("img", {
|
|
5016
|
+
src: preview,
|
|
5017
|
+
alt: "",
|
|
5018
|
+
className: "max-h-full max-w-full object-contain"
|
|
5019
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
5020
|
+
className: "text-fg-muted text-sm",
|
|
5021
|
+
children: t("demo.signatureLabel")
|
|
5022
|
+
})
|
|
5023
|
+
}),
|
|
5024
|
+
/* @__PURE__ */ jsxs("label", {
|
|
5025
|
+
className: "text-fg-muted mt-3 block text-xs",
|
|
5026
|
+
children: [t("demo.signReason"), /* @__PURE__ */ jsx("input", {
|
|
5027
|
+
value: reason,
|
|
5028
|
+
onChange: (e) => setReason(e.target.value),
|
|
5029
|
+
className: input
|
|
5030
|
+
})]
|
|
5031
|
+
}),
|
|
5032
|
+
/* @__PURE__ */ jsxs("label", {
|
|
5033
|
+
className: "text-fg-muted mt-2 block text-xs",
|
|
5034
|
+
children: [t("demo.signLocation"), /* @__PURE__ */ jsx("input", {
|
|
5035
|
+
value: location,
|
|
5036
|
+
onChange: (e) => setLocation(e.target.value),
|
|
5037
|
+
className: input
|
|
5038
|
+
})]
|
|
5039
|
+
}),
|
|
5040
|
+
signature.canCertify() ? /* @__PURE__ */ jsxs("label", {
|
|
5041
|
+
className: "text-fg-muted mt-3 block text-xs",
|
|
5042
|
+
children: [t("demo.signKind"), /* @__PURE__ */ jsxs("select", {
|
|
5043
|
+
value: signKind,
|
|
5044
|
+
onChange: (e) => setSignKind(e.target.value),
|
|
5045
|
+
className: input,
|
|
5046
|
+
children: [
|
|
5047
|
+
/* @__PURE__ */ jsx("option", {
|
|
5048
|
+
value: "approval",
|
|
5049
|
+
children: t("demo.signKindApproval")
|
|
5050
|
+
}),
|
|
5051
|
+
/* @__PURE__ */ jsx("option", {
|
|
5052
|
+
value: "certify-3",
|
|
5053
|
+
children: t("demo.signKindCertifyAnnotate")
|
|
5054
|
+
}),
|
|
5055
|
+
/* @__PURE__ */ jsx("option", {
|
|
5056
|
+
value: "certify-2",
|
|
5057
|
+
children: t("demo.signKindCertifyFill")
|
|
5058
|
+
}),
|
|
5059
|
+
/* @__PURE__ */ jsx("option", {
|
|
5060
|
+
value: "certify-1",
|
|
5061
|
+
children: t("demo.signKindCertifyLocked")
|
|
5062
|
+
})
|
|
5063
|
+
]
|
|
5064
|
+
})]
|
|
5065
|
+
}) : null,
|
|
5066
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
5067
|
+
className: "mt-2 text-sm text-red-600",
|
|
5068
|
+
children: error
|
|
5069
|
+
}) : null,
|
|
5070
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5071
|
+
className: "mt-4 flex justify-end gap-2",
|
|
5072
|
+
children: [
|
|
5073
|
+
/* @__PURE__ */ jsx("button", {
|
|
5074
|
+
type: "button",
|
|
5075
|
+
disabled: busy != null,
|
|
5076
|
+
onClick: surface.close,
|
|
5077
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm",
|
|
5078
|
+
children: t("demo.cancel")
|
|
5079
|
+
}),
|
|
5080
|
+
signature.canFill() ? /* @__PURE__ */ jsx("button", {
|
|
5081
|
+
type: "button",
|
|
5082
|
+
disabled: busy != null,
|
|
5083
|
+
onClick: () => void run("fill"),
|
|
5084
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm disabled:opacity-60",
|
|
5085
|
+
children: t("demo.signFill")
|
|
5086
|
+
}) : null,
|
|
5087
|
+
signature.canSign() ? /* @__PURE__ */ jsx("button", {
|
|
5088
|
+
type: "button",
|
|
5089
|
+
disabled: busy != null,
|
|
5090
|
+
onClick: () => void run("sign"),
|
|
5091
|
+
className: "bg-accent text-on-accent rounded-md px-3 py-1.5 text-sm disabled:opacity-60",
|
|
5092
|
+
children: busy === "sign" ? t("demo.signSigning") : t("demo.signButton")
|
|
5093
|
+
}) : null
|
|
5094
|
+
]
|
|
5095
|
+
})
|
|
5096
|
+
]
|
|
5097
|
+
})
|
|
5098
|
+
});
|
|
5099
|
+
}
|
|
5100
|
+
//#endregion
|
|
5101
|
+
//#region src/ui/signature-bridge.tsx
|
|
5102
|
+
/**
|
|
5103
|
+
* The signature plugin's UI intents → shell surfaces. The plugin owns the
|
|
5104
|
+
* act and says WHAT happened; the chrome decides WHAT OPENS:
|
|
5105
|
+
*
|
|
5106
|
+
* target (a "sign here" click) → the signatures panel, right side
|
|
5107
|
+
* ask (mode 'ask' met a field) → the sign dialog (modal)
|
|
5108
|
+
* inspect (a signed field clicked) → the validation popover
|
|
5109
|
+
*
|
|
5110
|
+
* Mounted once per document view; renders nothing.
|
|
5111
|
+
*/
|
|
5112
|
+
function SignatureBridge() {
|
|
5113
|
+
const shell = useShell();
|
|
5114
|
+
useSignatureEvent((event) => {
|
|
5115
|
+
switch (event.type) {
|
|
5116
|
+
case "target":
|
|
5117
|
+
if (event.field) shell.open("signatures", { exclusive: "right" });
|
|
5118
|
+
return;
|
|
5119
|
+
case "ask":
|
|
5120
|
+
shell.open("signature-sign", {
|
|
5121
|
+
exclusive: "modal",
|
|
5122
|
+
props: {
|
|
5123
|
+
field: event.field,
|
|
5124
|
+
mark: event.mark
|
|
5125
|
+
}
|
|
5126
|
+
});
|
|
5127
|
+
return;
|
|
5128
|
+
case "inspect":
|
|
5129
|
+
shell.open("signature-inspector", { props: { field: event.field } });
|
|
5130
|
+
return;
|
|
5131
|
+
case "invalidating":
|
|
5132
|
+
shell.open("signatures", { exclusive: "right" });
|
|
5133
|
+
return;
|
|
5134
|
+
default: return;
|
|
5135
|
+
}
|
|
5136
|
+
});
|
|
5137
|
+
return null;
|
|
5138
|
+
}
|
|
5139
|
+
//#endregion
|
|
5140
|
+
//#region src/ui/signature-inspector.tsx
|
|
5141
|
+
/**
|
|
5142
|
+
* The validation popover (`signature-inspector` surface): what a signed
|
|
5143
|
+
* field's signature says and whether it holds — the four independent facts
|
|
5144
|
+
* (integrity, cryptography, trust, changes since) and the signer's claims,
|
|
5145
|
+
* anchored at the widget when its box is known, plus the signed revision as
|
|
5146
|
+
* a download (the exact bytes the signature covers). Mounted in the Stage
|
|
5147
|
+
* overlay so `<Anchored>` can project onto the page.
|
|
5148
|
+
*/
|
|
5149
|
+
const verdictKey = (v) => !v ? "demo.verdictIndeterminate" : v.summary === "valid" ? "demo.verdictValid" : v.summary === "valid-untrusted" ? "demo.verdictValidUntrusted" : v.summary === "invalid" ? v.modifications.basis === "working-copy" ? "demo.verdictWillInvalidate" : "demo.verdictInvalid" : "demo.verdictIndeterminate";
|
|
5150
|
+
/** A PDF date string (`D:YYYYMMDDHHmmSSZ` or with a zone offset) as a locale date; the raw text when it is not one. */
|
|
5151
|
+
const pdfDate = (raw) => {
|
|
5152
|
+
const m = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?(Z|[+-]\d{2}'?\d{2}'?)?/.exec(raw);
|
|
5153
|
+
if (!m) return raw;
|
|
5154
|
+
const [, y, mo = "01", d = "01", h = "00", mi = "00", s = "00", zone] = m;
|
|
5155
|
+
const offset = !zone || zone === "Z" ? "Z" : `${zone[0]}${zone.slice(1, 3)}:${zone.slice(3).replace(/'/g, "") || "00"}`;
|
|
5156
|
+
const date = /* @__PURE__ */ new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${offset}`);
|
|
5157
|
+
return Number.isNaN(date.getTime()) ? raw : date.toLocaleString();
|
|
5158
|
+
};
|
|
5159
|
+
const tone = (summary) => summary === "valid" ? "text-green-600" : summary === "invalid" ? "text-red-600" : summary === "valid-untrusted" ? "text-amber-600" : "text-fg-muted";
|
|
5160
|
+
function SignatureInspector() {
|
|
5161
|
+
const t = useT();
|
|
5162
|
+
const surface = useSurface("signature-inspector");
|
|
5163
|
+
const signature = useSignature();
|
|
5164
|
+
const snapshot = useSignatureSnapshot();
|
|
5165
|
+
const verdicts = useSignatureVerdicts();
|
|
5166
|
+
const [validating, setValidating] = useState(false);
|
|
5167
|
+
const field = surface.props?.field;
|
|
5168
|
+
const dto = field ? signature.signatureOf(field) : null;
|
|
5169
|
+
const verdict = field ? signature.verdictOf(field) : null;
|
|
5170
|
+
const widget = dto?.widget ?? null;
|
|
5171
|
+
const box = useSelector(FormToken$1, (c) => widget && widget.annotObjectNumber > 0 ? c.fillItem(widget.annotObjectNumber)?.box ?? null : null);
|
|
5172
|
+
useEffect(() => {
|
|
5173
|
+
if (!surface.isOpen || !dto?.signed || verdicts) return;
|
|
5174
|
+
setValidating(true);
|
|
5175
|
+
signature.validate().finally(() => setValidating(false));
|
|
5176
|
+
}, [surface.isOpen, dto?.index]);
|
|
5177
|
+
if (!surface.isOpen || !field || !dto || !snapshot) return null;
|
|
5178
|
+
const revalidate = () => {
|
|
5179
|
+
setValidating(true);
|
|
5180
|
+
signature.validate().finally(() => setValidating(false));
|
|
5181
|
+
};
|
|
5182
|
+
const summary = verdict?.summary ?? null;
|
|
5183
|
+
const modifications = verdict?.modifications.verdict ?? null;
|
|
5184
|
+
const row = (label, value, className = "text-fg") => /* @__PURE__ */ jsxs("div", {
|
|
5185
|
+
className: "flex items-baseline justify-between gap-3 text-xs",
|
|
5186
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5187
|
+
className: "text-fg-muted",
|
|
5188
|
+
children: label
|
|
5189
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
5190
|
+
className: `text-right ${className}`,
|
|
5191
|
+
children: value
|
|
5192
|
+
})]
|
|
5193
|
+
});
|
|
5194
|
+
const card = /* @__PURE__ */ jsxs("div", {
|
|
5195
|
+
className: "border-border bg-elevated w-72 rounded-lg border p-3 text-sm shadow-xl",
|
|
5196
|
+
children: [
|
|
5197
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5198
|
+
className: "flex items-center justify-between",
|
|
5199
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5200
|
+
className: "text-fg font-semibold",
|
|
5201
|
+
children: t("demo.inspectTitle")
|
|
5202
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5203
|
+
type: "button",
|
|
5204
|
+
onClick: surface.close,
|
|
5205
|
+
className: "text-fg-muted hover:bg-hover grid h-6 w-6 place-items-center rounded",
|
|
5206
|
+
"aria-label": t("demo.cancel"),
|
|
5207
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
5208
|
+
name: "x",
|
|
5209
|
+
size: 14
|
|
5210
|
+
})
|
|
5211
|
+
})]
|
|
5212
|
+
}),
|
|
5213
|
+
/* @__PURE__ */ jsx("p", {
|
|
5214
|
+
className: `mt-1 text-sm font-medium ${tone(summary)}`,
|
|
5215
|
+
children: validating ? t("demo.signaturesValidating") : t(verdictKey(verdict))
|
|
5216
|
+
}),
|
|
5217
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5218
|
+
className: "mt-2 flex flex-col gap-1",
|
|
5219
|
+
children: [
|
|
5220
|
+
row(t("demo.inspectSigner"), dto.signer.name ?? "—"),
|
|
5221
|
+
dto.signer.reason ? row(t("demo.inspectReason"), dto.signer.reason) : null,
|
|
5222
|
+
dto.signer.location ? row(t("demo.inspectLocation"), dto.signer.location) : null,
|
|
5223
|
+
dto.signer.claimedTime ? row(t("demo.inspectTime"), pdfDate(dto.signer.claimedTime)) : null
|
|
5224
|
+
]
|
|
5225
|
+
}),
|
|
5226
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5227
|
+
className: "border-border-subtle mt-2 flex flex-col gap-1 border-t pt-2",
|
|
5228
|
+
children: [
|
|
5229
|
+
row(t("demo.inspectIntegrity"), verdict?.integrity ?? "—", tone(verdict?.integrity === "valid" ? "valid" : verdict?.integrity === "invalid" ? "invalid" : null)),
|
|
5230
|
+
row(t("demo.inspectCryptography"), verdict?.cryptography ?? "—"),
|
|
5231
|
+
row(t("demo.inspectTrust"), verdict?.trust ?? "—"),
|
|
5232
|
+
row(t("demo.inspectChanges"), modifications === "unchanged" ? verdict?.modifications.undone ? t("demo.verdictRestored") : t("demo.verdictUnchanged") : modifications === "permitted" ? t("demo.verdictPermitted") : modifications === "forbidden" ? t("demo.verdictForbidden") : "—", modifications === "forbidden" ? "text-red-600" : "text-fg"),
|
|
5233
|
+
verdict?.modifications.laterRevisions ? row(t("demo.inspectLaterRevisions"), String(verdict.modifications.laterRevisions)) : null
|
|
5234
|
+
]
|
|
5235
|
+
}),
|
|
5236
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5237
|
+
className: "mt-3 flex justify-end gap-2",
|
|
5238
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5239
|
+
type: "button",
|
|
5240
|
+
disabled: validating,
|
|
5241
|
+
onClick: revalidate,
|
|
5242
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-2 py-1 text-xs disabled:opacity-60",
|
|
5243
|
+
children: t("demo.signaturesValidate")
|
|
5244
|
+
}), /* @__PURE__ */ jsx(DownloadRevision, { field })]
|
|
5245
|
+
})
|
|
5246
|
+
]
|
|
5247
|
+
});
|
|
5248
|
+
if (widget && widget.pageObjectNumber > 0 && box) return /* @__PURE__ */ jsx(Anchored, {
|
|
5249
|
+
anchor: {
|
|
5250
|
+
pon: widget.pageObjectNumber,
|
|
5251
|
+
bounds: box
|
|
5252
|
+
},
|
|
5253
|
+
placement: "bottom",
|
|
5254
|
+
gap: 8,
|
|
5255
|
+
children: card
|
|
5256
|
+
});
|
|
5257
|
+
return /* @__PURE__ */ jsx("div", {
|
|
5258
|
+
className: "absolute right-4 top-4 z-40",
|
|
5259
|
+
children: card
|
|
5260
|
+
});
|
|
5261
|
+
}
|
|
5262
|
+
/** The signed revision — the exact bytes the signature covers — as a file. */
|
|
5263
|
+
function DownloadRevision({ field }) {
|
|
5264
|
+
const t = useT();
|
|
5265
|
+
const signature = useSignature();
|
|
5266
|
+
const dto = signature.signatureOf(field);
|
|
5267
|
+
const [busy, setBusy] = useState(false);
|
|
5268
|
+
if (!dto || dto.revisionIndex === null) return null;
|
|
5269
|
+
const revisionIndex = dto.revisionIndex;
|
|
5270
|
+
const run = async () => {
|
|
5271
|
+
setBusy(true);
|
|
5272
|
+
try {
|
|
5273
|
+
const bytes = await signature.revisionBytes(revisionIndex);
|
|
5274
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
5275
|
+
const url = URL.createObjectURL(blob);
|
|
5276
|
+
const a = document.createElement("a");
|
|
5277
|
+
a.href = url;
|
|
5278
|
+
a.download = `${dto.fieldName}-signed.pdf`;
|
|
5279
|
+
a.click();
|
|
5280
|
+
URL.revokeObjectURL(url);
|
|
5281
|
+
} catch (err) {
|
|
5282
|
+
console.error("[embedpdf] signed revision download failed:", err);
|
|
5283
|
+
} finally {
|
|
5284
|
+
setBusy(false);
|
|
5285
|
+
}
|
|
5286
|
+
};
|
|
5287
|
+
return /* @__PURE__ */ jsx("button", {
|
|
5288
|
+
type: "button",
|
|
5289
|
+
disabled: busy,
|
|
5290
|
+
onClick: () => void run(),
|
|
5291
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-2 py-1 text-xs disabled:opacity-60",
|
|
5292
|
+
children: t("demo.inspectDownload")
|
|
5293
|
+
});
|
|
5294
|
+
}
|
|
5295
|
+
//#endregion
|
|
5296
|
+
//#region src/ui/signature-maker.tsx
|
|
5297
|
+
/**
|
|
5298
|
+
* The mark maker (`signature-maker` modal surface): a person's signature and
|
|
5299
|
+
* initials, drawn, typed, or uploaded, saved as ONE library of kind
|
|
5300
|
+
* `signatures` — the file IS the person (Preview's model). Chrome code over
|
|
5301
|
+
* the stamp plugin's generic calls: `createLibrary` + `addAsset({ mark })`;
|
|
5302
|
+
* rename/export/delete are the library verbs. Opened on an existing library
|
|
5303
|
+
* (`props.libraryId`) it only adds the missing initials.
|
|
5304
|
+
*/
|
|
5305
|
+
const PAD = {
|
|
5306
|
+
width: 360,
|
|
5307
|
+
height: 120
|
|
5308
|
+
};
|
|
5309
|
+
const BUILTIN_FONTS = [{
|
|
5310
|
+
key: "helvetica-oblique",
|
|
5311
|
+
label: "Helvetica"
|
|
5312
|
+
}, {
|
|
5313
|
+
key: "times-italic",
|
|
5314
|
+
label: "Times"
|
|
5315
|
+
}];
|
|
5316
|
+
/** One pad: draw / type / upload, producing a `MarkSource` or null. */
|
|
5317
|
+
function MarkPad({ value, onChange, fonts, registerFont }) {
|
|
5318
|
+
const t = useT();
|
|
5319
|
+
const [tab, setTab] = useState("draw");
|
|
5320
|
+
const [strokes, setStrokes] = useState([]);
|
|
5321
|
+
const [text, setText] = useState("");
|
|
5322
|
+
const [font, setFont] = useState(fonts[0]?.key ?? "helvetica-oblique");
|
|
5323
|
+
const [fileName, setFileName] = useState(null);
|
|
5324
|
+
const canvas = useRef(null);
|
|
5325
|
+
const drawing = useRef(null);
|
|
5326
|
+
useEffect(() => {
|
|
5327
|
+
const el = canvas.current;
|
|
5328
|
+
if (!el) return;
|
|
5329
|
+
const dpr = window.devicePixelRatio || 1;
|
|
5330
|
+
el.width = PAD.width * dpr;
|
|
5331
|
+
el.height = PAD.height * dpr;
|
|
5332
|
+
const ctx = el.getContext("2d");
|
|
5333
|
+
if (!ctx) return;
|
|
5334
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
5335
|
+
ctx.clearRect(0, 0, PAD.width, PAD.height);
|
|
5336
|
+
ctx.lineCap = "round";
|
|
5337
|
+
ctx.lineJoin = "round";
|
|
5338
|
+
ctx.lineWidth = 2.5;
|
|
5339
|
+
ctx.strokeStyle = getComputedStyle(el).color || "#000";
|
|
5340
|
+
for (const stroke of strokes) {
|
|
5341
|
+
ctx.beginPath();
|
|
5342
|
+
stroke.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
5343
|
+
ctx.stroke();
|
|
5344
|
+
}
|
|
5345
|
+
}, [strokes, tab]);
|
|
5346
|
+
const point = (e) => {
|
|
5347
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
5348
|
+
return {
|
|
5349
|
+
x: e.clientX - r.left,
|
|
5350
|
+
y: e.clientY - r.top
|
|
5351
|
+
};
|
|
5352
|
+
};
|
|
5353
|
+
const commitStrokes = (next) => {
|
|
5354
|
+
setStrokes(next);
|
|
5355
|
+
onChange(next.length ? {
|
|
5356
|
+
kind: "ink",
|
|
5357
|
+
strokes: next,
|
|
5358
|
+
color: "#000000",
|
|
5359
|
+
strokeWidth: 2.5
|
|
5360
|
+
} : null);
|
|
5361
|
+
};
|
|
5362
|
+
const commitText = async (next, key) => {
|
|
5363
|
+
setText(next);
|
|
5364
|
+
setFont(key);
|
|
5365
|
+
if (!next.trim()) {
|
|
5366
|
+
onChange(null);
|
|
5367
|
+
return;
|
|
5368
|
+
}
|
|
5369
|
+
await registerFont(key);
|
|
5370
|
+
onChange({
|
|
5371
|
+
kind: "text",
|
|
5372
|
+
text: next.trim(),
|
|
5373
|
+
fontFamily: key,
|
|
5374
|
+
color: "#000000",
|
|
5375
|
+
fontSize: 36
|
|
5376
|
+
});
|
|
5377
|
+
};
|
|
5378
|
+
const pickFile = (file) => {
|
|
5379
|
+
setFileName(file.name);
|
|
5380
|
+
onChange(file.type === "application/pdf" ? {
|
|
5381
|
+
kind: "pdf",
|
|
5382
|
+
source: file,
|
|
5383
|
+
pageIndex: 0
|
|
5384
|
+
} : {
|
|
5385
|
+
kind: "image",
|
|
5386
|
+
source: file
|
|
5387
|
+
});
|
|
5388
|
+
};
|
|
5389
|
+
const tabClass = (id) => `rounded px-2 py-1 text-xs font-medium ${tab === id ? "bg-accent text-on-accent" : "text-fg-muted hover:bg-hover"}`;
|
|
5390
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5391
|
+
className: "flex flex-col gap-2",
|
|
5392
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
5393
|
+
className: "flex items-center gap-1",
|
|
5394
|
+
children: [
|
|
5395
|
+
/* @__PURE__ */ jsx("button", {
|
|
5396
|
+
type: "button",
|
|
5397
|
+
className: tabClass("draw"),
|
|
5398
|
+
onClick: () => setTab("draw"),
|
|
5399
|
+
children: t("demo.makerDraw")
|
|
5400
|
+
}),
|
|
5401
|
+
/* @__PURE__ */ jsx("button", {
|
|
5402
|
+
type: "button",
|
|
5403
|
+
className: tabClass("type"),
|
|
5404
|
+
onClick: () => setTab("type"),
|
|
5405
|
+
children: t("demo.makerType")
|
|
5406
|
+
}),
|
|
5407
|
+
/* @__PURE__ */ jsx("button", {
|
|
5408
|
+
type: "button",
|
|
5409
|
+
className: tabClass("upload"),
|
|
5410
|
+
onClick: () => setTab("upload"),
|
|
5411
|
+
children: t("demo.makerUpload")
|
|
5412
|
+
}),
|
|
5413
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
5414
|
+
value ? /* @__PURE__ */ jsx("button", {
|
|
5415
|
+
type: "button",
|
|
5416
|
+
onClick: () => {
|
|
5417
|
+
commitStrokes([]);
|
|
5418
|
+
setText("");
|
|
5419
|
+
setFileName(null);
|
|
5420
|
+
onChange(null);
|
|
5421
|
+
},
|
|
5422
|
+
className: "text-fg-muted hover:text-fg text-xs",
|
|
5423
|
+
children: t("demo.makerClear")
|
|
5424
|
+
}) : null
|
|
5425
|
+
]
|
|
5426
|
+
}), tab === "draw" ? /* @__PURE__ */ jsx("canvas", {
|
|
5427
|
+
ref: canvas,
|
|
5428
|
+
style: {
|
|
5429
|
+
width: PAD.width,
|
|
5430
|
+
height: PAD.height,
|
|
5431
|
+
touchAction: "none"
|
|
5432
|
+
},
|
|
5433
|
+
className: "border-border-subtle bg-surface text-fg rounded-md border",
|
|
5434
|
+
onPointerDown: (e) => {
|
|
5435
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
5436
|
+
drawing.current = [point(e)];
|
|
5437
|
+
setStrokes((s) => [...s, drawing.current]);
|
|
5438
|
+
},
|
|
5439
|
+
onPointerMove: (e) => {
|
|
5440
|
+
if (!drawing.current) return;
|
|
5441
|
+
drawing.current.push(point(e));
|
|
5442
|
+
setStrokes((s) => [...s.slice(0, -1), [...drawing.current]]);
|
|
5443
|
+
},
|
|
5444
|
+
onPointerUp: () => {
|
|
5445
|
+
if (!drawing.current) return;
|
|
5446
|
+
const done = drawing.current;
|
|
5447
|
+
drawing.current = null;
|
|
5448
|
+
setStrokes((s) => {
|
|
5449
|
+
const next = [...s.slice(0, -1), done];
|
|
5450
|
+
commitStrokes(next);
|
|
5451
|
+
return next;
|
|
5452
|
+
});
|
|
5453
|
+
}
|
|
5454
|
+
}) : tab === "type" ? /* @__PURE__ */ jsxs("div", {
|
|
5455
|
+
className: "flex flex-col gap-2",
|
|
5456
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
5457
|
+
value: text,
|
|
5458
|
+
placeholder: t("demo.makerTypePlaceholder"),
|
|
5459
|
+
onChange: (e) => void commitText(e.target.value, font),
|
|
5460
|
+
className: "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 text-sm"
|
|
5461
|
+
}), /* @__PURE__ */ jsxs("label", {
|
|
5462
|
+
className: "text-fg-muted flex items-center gap-2 text-xs",
|
|
5463
|
+
children: [t("demo.makerFont"), /* @__PURE__ */ jsx("select", {
|
|
5464
|
+
value: font,
|
|
5465
|
+
onChange: (e) => void commitText(text, e.target.value),
|
|
5466
|
+
className: "border-border bg-surface text-fg rounded border px-2 py-1 text-xs",
|
|
5467
|
+
children: fonts.map((f) => /* @__PURE__ */ jsx("option", {
|
|
5468
|
+
value: f.key,
|
|
5469
|
+
children: f.label
|
|
5470
|
+
}, f.key))
|
|
5471
|
+
})]
|
|
5472
|
+
})]
|
|
5473
|
+
}) : /* @__PURE__ */ jsxs("label", {
|
|
5474
|
+
className: "border-border-subtle text-fg-muted hover:bg-hover flex h-24 cursor-pointer items-center justify-center rounded-md border border-dashed text-sm",
|
|
5475
|
+
children: [fileName ?? t("demo.makerUploadHint"), /* @__PURE__ */ jsx("input", {
|
|
5476
|
+
type: "file",
|
|
5477
|
+
accept: "image/png,image/jpeg,application/pdf",
|
|
5478
|
+
className: "hidden",
|
|
5479
|
+
onChange: (e) => {
|
|
5480
|
+
const file = e.currentTarget.files?.[0];
|
|
5481
|
+
e.currentTarget.value = "";
|
|
5482
|
+
if (file) pickFile(file);
|
|
5483
|
+
}
|
|
5484
|
+
})]
|
|
5485
|
+
})]
|
|
5486
|
+
});
|
|
5487
|
+
}
|
|
5488
|
+
function SignatureMakerModal() {
|
|
5489
|
+
const t = useT();
|
|
5490
|
+
const surface = useSurface("signature-maker");
|
|
5491
|
+
const stamp = useStamp();
|
|
5492
|
+
const kernel = useKernel();
|
|
5493
|
+
const config = useSignaturesConfig();
|
|
5494
|
+
const [name, setName] = useState("");
|
|
5495
|
+
const [signatureMark, setSignatureMark] = useState(null);
|
|
5496
|
+
const [initialsMark, setInitialsMark] = useState(null);
|
|
5497
|
+
const [busy, setBusy] = useState(false);
|
|
5498
|
+
const [error, setError] = useState(null);
|
|
5499
|
+
const existingId = surface.props?.libraryId;
|
|
5500
|
+
const existing = existingId ? stamp.library(existingId) : null;
|
|
5501
|
+
const wantsInitials = (config.kinds ?? ["signature", "initials"]).includes("initials");
|
|
5502
|
+
const fonts = [...(config.fonts ?? []).map((f) => ({
|
|
5503
|
+
key: f.key,
|
|
5504
|
+
label: f.label
|
|
5505
|
+
})), ...BUILTIN_FONTS];
|
|
5506
|
+
const registered = useRef(/* @__PURE__ */ new Set());
|
|
5507
|
+
const registerFont = async (key) => {
|
|
5508
|
+
const spec = config.fonts?.find((f) => f.key === key);
|
|
5509
|
+
if (!spec || registered.current.has(key) || !kernel.engine.fonts) return;
|
|
5510
|
+
const response = await fetch(spec.url);
|
|
5511
|
+
if (!response.ok) throw new Error(`${spec.url}: HTTP ${response.status}`);
|
|
5512
|
+
await kernel.engine.fonts.register({
|
|
5513
|
+
key,
|
|
5514
|
+
data: new Uint8Array(await response.arrayBuffer())
|
|
5515
|
+
});
|
|
5516
|
+
registered.current.add(key);
|
|
5517
|
+
};
|
|
5518
|
+
if (!surface.isOpen) return null;
|
|
5519
|
+
const canSave = existing ? initialsMark != null : name.trim().length > 0 && signatureMark != null;
|
|
5520
|
+
const save = async () => {
|
|
5521
|
+
setBusy(true);
|
|
5522
|
+
setError(null);
|
|
5523
|
+
try {
|
|
5524
|
+
if (existing) await stamp.addAsset({
|
|
5525
|
+
libraryId: existing.id,
|
|
5526
|
+
name: INITIALS_MARK_NAME,
|
|
5527
|
+
label: t("demo.initialsLabel"),
|
|
5528
|
+
mark: initialsMark
|
|
5529
|
+
});
|
|
5530
|
+
else {
|
|
5531
|
+
const libraryId = await stamp.createLibrary(name.trim(), { kind: SIGNATURES_LIBRARY_KIND });
|
|
5532
|
+
await stamp.addAsset({
|
|
5533
|
+
libraryId,
|
|
5534
|
+
name: SIGNATURE_MARK_NAME,
|
|
5535
|
+
label: t("demo.signatureLabel"),
|
|
5536
|
+
mark: signatureMark
|
|
5537
|
+
});
|
|
5538
|
+
if (initialsMark) await stamp.addAsset({
|
|
5539
|
+
libraryId,
|
|
5540
|
+
name: INITIALS_MARK_NAME,
|
|
5541
|
+
label: t("demo.initialsLabel"),
|
|
5542
|
+
mark: initialsMark
|
|
5543
|
+
});
|
|
5544
|
+
}
|
|
5545
|
+
setName("");
|
|
5546
|
+
setSignatureMark(null);
|
|
5547
|
+
setInitialsMark(null);
|
|
5548
|
+
surface.close();
|
|
5549
|
+
} catch (err) {
|
|
5550
|
+
console.error("[embedpdf] saving the signature failed:", err);
|
|
5551
|
+
setError(t("demo.makerError"));
|
|
5552
|
+
} finally {
|
|
5553
|
+
setBusy(false);
|
|
5554
|
+
}
|
|
5555
|
+
};
|
|
5556
|
+
return /* @__PURE__ */ jsx("div", {
|
|
5557
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
5558
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
5559
|
+
className: "border-border bg-surface w-[26rem] rounded-lg border p-4 shadow-xl",
|
|
5560
|
+
children: [
|
|
5561
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5562
|
+
className: "flex items-center justify-between",
|
|
5563
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
5564
|
+
className: "text-fg text-base font-semibold",
|
|
5565
|
+
children: existing ? t("demo.signaturesAddInitials") : t("demo.makerTitle")
|
|
5566
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5567
|
+
type: "button",
|
|
5568
|
+
onClick: surface.close,
|
|
5569
|
+
className: "text-fg-muted hover:bg-hover grid h-7 w-7 place-items-center rounded-md",
|
|
5570
|
+
"aria-label": t("demo.cancel"),
|
|
5571
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
5572
|
+
name: "x",
|
|
5573
|
+
size: 16
|
|
5574
|
+
})
|
|
5575
|
+
})]
|
|
5576
|
+
}),
|
|
5577
|
+
existing ? null : /* @__PURE__ */ jsxs("label", {
|
|
5578
|
+
className: "text-fg-muted mt-3 block text-xs",
|
|
5579
|
+
children: [t("demo.makerName"), /* @__PURE__ */ jsx("input", {
|
|
5580
|
+
value: name,
|
|
5581
|
+
placeholder: t("demo.makerNamePlaceholder"),
|
|
5582
|
+
onChange: (e) => setName(e.target.value),
|
|
5583
|
+
className: "border-border bg-surface text-fg mt-1 w-full rounded border px-2 py-1.5 text-sm"
|
|
5584
|
+
})]
|
|
5585
|
+
}),
|
|
5586
|
+
existing ? null : /* @__PURE__ */ jsxs("div", {
|
|
5587
|
+
className: "mt-3",
|
|
5588
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
5589
|
+
className: "text-fg mb-1 text-xs font-semibold uppercase tracking-wide",
|
|
5590
|
+
children: t("demo.signatureLabel")
|
|
5591
|
+
}), /* @__PURE__ */ jsx(MarkPad, {
|
|
5592
|
+
value: signatureMark,
|
|
5593
|
+
onChange: setSignatureMark,
|
|
5594
|
+
fonts,
|
|
5595
|
+
registerFont
|
|
5596
|
+
})]
|
|
5597
|
+
}),
|
|
5598
|
+
wantsInitials ? /* @__PURE__ */ jsxs("div", {
|
|
5599
|
+
className: "mt-3",
|
|
5600
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
5601
|
+
className: "text-fg mb-1 text-xs font-semibold uppercase tracking-wide",
|
|
5602
|
+
children: [t("demo.initialsLabel"), existing ? null : /* @__PURE__ */ jsx("span", {
|
|
5603
|
+
className: "text-fg-muted ml-1 font-normal normal-case tracking-normal",
|
|
5604
|
+
children: t("demo.makerOptional")
|
|
5605
|
+
})]
|
|
5606
|
+
}), /* @__PURE__ */ jsx(MarkPad, {
|
|
5607
|
+
value: initialsMark,
|
|
5608
|
+
onChange: setInitialsMark,
|
|
5609
|
+
fonts,
|
|
5610
|
+
registerFont
|
|
5611
|
+
})]
|
|
5612
|
+
}) : null,
|
|
5613
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
5614
|
+
className: "mt-2 text-sm text-red-600",
|
|
5615
|
+
children: error
|
|
5616
|
+
}) : null,
|
|
5617
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5618
|
+
className: "mt-4 flex justify-end gap-2",
|
|
5619
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5620
|
+
type: "button",
|
|
5621
|
+
disabled: busy,
|
|
5622
|
+
onClick: surface.close,
|
|
5623
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm",
|
|
5624
|
+
children: t("demo.cancel")
|
|
5625
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5626
|
+
type: "button",
|
|
5627
|
+
disabled: busy || !canSave,
|
|
5628
|
+
onClick: () => void save(),
|
|
5629
|
+
className: "bg-accent text-on-accent rounded-md px-3 py-1.5 text-sm disabled:opacity-60",
|
|
5630
|
+
children: busy ? t("demo.makerSaving") : t("demo.makerSave")
|
|
5631
|
+
})]
|
|
5632
|
+
})
|
|
5633
|
+
]
|
|
5634
|
+
})
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
//#endregion
|
|
3975
5638
|
//#region src/ui/document-boot.tsx
|
|
3976
5639
|
/**
|
|
3977
5640
|
* Boot-state panes for the document area — what renders INSTEAD of the Stage
|
|
@@ -4213,9 +5876,10 @@ function Shell() {
|
|
|
4213
5876
|
className: "w-full"
|
|
4214
5877
|
})
|
|
4215
5878
|
});
|
|
4216
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5879
|
+
return /* @__PURE__ */ jsx(AnnotationFontsProvider, { children: /* @__PURE__ */ jsxs("div", {
|
|
4217
5880
|
className: "bg-app text-fg flex h-full flex-col",
|
|
4218
5881
|
children: [
|
|
5882
|
+
/* @__PURE__ */ jsx(StampLibraryStore, {}),
|
|
4219
5883
|
(frame.header ?? true) && /* @__PURE__ */ jsx("slot", { name: "header" }),
|
|
4220
5884
|
(frame.tabs ?? "always") !== "never" && /* @__PURE__ */ jsx("slot", {
|
|
4221
5885
|
name: "tabs",
|
|
@@ -4235,6 +5899,7 @@ function Shell() {
|
|
|
4235
5899
|
overlay: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
4236
5900
|
/* @__PURE__ */ jsx(AnnotationStrip, {}),
|
|
4237
5901
|
/* @__PURE__ */ jsx(SelectionStrip, {}),
|
|
5902
|
+
/* @__PURE__ */ jsx(SignatureInspector, {}),
|
|
4238
5903
|
/* @__PURE__ */ jsx(SelectionHandles, {}),
|
|
4239
5904
|
/* @__PURE__ */ jsx(Scrollbar, { axis: "y" }),
|
|
4240
5905
|
/* @__PURE__ */ jsx(Scrollbar, { axis: "x" })
|
|
@@ -4253,12 +5918,16 @@ function Shell() {
|
|
|
4253
5918
|
]
|
|
4254
5919
|
}),
|
|
4255
5920
|
/* @__PURE__ */ jsx(RightSidebar, {}),
|
|
4256
|
-
/* @__PURE__ */ jsx(RedactConfirmModal, {})
|
|
5921
|
+
/* @__PURE__ */ jsx(RedactConfirmModal, {}),
|
|
5922
|
+
/* @__PURE__ */ jsx(CalibrationDialog, {}),
|
|
5923
|
+
/* @__PURE__ */ jsx(SignatureBridge, {}),
|
|
5924
|
+
/* @__PURE__ */ jsx(SignDialog, {}),
|
|
5925
|
+
/* @__PURE__ */ jsx(SignatureMakerModal, {})
|
|
4257
5926
|
] })
|
|
4258
5927
|
}),
|
|
4259
5928
|
toolbarEdge === "bottom" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ModeBand, { edge: "bottom" }), toolbarBand] })
|
|
4260
5929
|
]
|
|
4261
|
-
});
|
|
5930
|
+
}) });
|
|
4262
5931
|
}
|
|
4263
5932
|
//#endregion
|
|
4264
5933
|
//#region src/viewer.tsx
|
|
@@ -4281,7 +5950,7 @@ function Shell() {
|
|
|
4281
5950
|
* changing these props on a mounted viewer does not rebuild the workspace.
|
|
4282
5951
|
*/
|
|
4283
5952
|
/** Lazy built-in packs — a strings override for one of these wraps its loader. */
|
|
4284
|
-
const BUILTIN_LOADERS = { es: () => import("./es-
|
|
5953
|
+
const BUILTIN_LOADERS = { es: () => import("./es-CLgTsFDS.js").then((m) => m.es) };
|
|
4285
5954
|
const isDict = (v) => typeof v === "object" && v !== null;
|
|
4286
5955
|
/**
|
|
4287
5956
|
* Deep-merge `over` into `base` (immutably), expanding dotted keys —
|
|
@@ -4325,7 +5994,7 @@ function Booting() {
|
|
|
4325
5994
|
})
|
|
4326
5995
|
});
|
|
4327
5996
|
}
|
|
4328
|
-
function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, stamps, theme, themeTarget, onViewer }) {
|
|
5997
|
+
function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, stamps, signatures, annotations, theme, themeTarget, onViewer }) {
|
|
4329
5998
|
const [resolved] = useState(() => {
|
|
4330
5999
|
const byId = new Map(defaultCommands.map((c) => [c.id, c]));
|
|
4331
6000
|
for (const c of commands ?? []) byId.set(c.id, c);
|
|
@@ -4357,6 +6026,8 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
4357
6026
|
chrome: resolvedChrome,
|
|
4358
6027
|
icons: icons ?? {},
|
|
4359
6028
|
stamps: stamps ?? {},
|
|
6029
|
+
signatures: signatures ?? {},
|
|
6030
|
+
annotations: annotations ?? {},
|
|
4360
6031
|
i18n: {
|
|
4361
6032
|
locales,
|
|
4362
6033
|
loaders,
|
|
@@ -4422,8 +6093,15 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
4422
6093
|
}
|
|
4423
6094
|
} }),
|
|
4424
6095
|
formPlugin(),
|
|
6096
|
+
signaturePlugin({
|
|
6097
|
+
mode: resolved.signatures.mode,
|
|
6098
|
+
signer: resolved.signatures.signer,
|
|
6099
|
+
trust: resolved.signatures.trust,
|
|
6100
|
+
allowCertify: resolved.signatures.allowCertify
|
|
6101
|
+
}),
|
|
4425
6102
|
linkPlugin(),
|
|
4426
6103
|
redactionPlugin(),
|
|
6104
|
+
measurementPlugin(),
|
|
4427
6105
|
searchPlugin(),
|
|
4428
6106
|
demoToolsPlugin(),
|
|
4429
6107
|
i18nPlugin({
|
|
@@ -4455,6 +6133,6 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
4455
6133
|
});
|
|
4456
6134
|
}
|
|
4457
6135
|
//#endregion
|
|
4458
|
-
export { AnnotationToken, CommandsToken, DocumentsToken, FormToken, FullViewer, I18nToken, InteractionToken, MetadataToken, RedactionToken, SearchToken, SelectionToken, ShellToken, StageToken, StampToken, addItem, chromeHelpers, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, item, removeItems, replaceItem, themeConfigOf, validateChrome };
|
|
6136
|
+
export { AnnotationToken, CommandsToken, DocumentsToken, FormToken, FullViewer, I18nToken, InteractionToken, MeasurementToken, MetadataToken, RedactionToken, SearchToken, SelectionToken, ShellToken, SignatureToken, StageToken, StampToken, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
|
|
4459
6137
|
|
|
4460
6138
|
//# sourceMappingURL=index.js.map
|