@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.cjs
CHANGED
|
@@ -33,9 +33,11 @@ let _embedpdf_react_interaction = require("@embedpdf/react/interaction");
|
|
|
33
33
|
let _embedpdf_react_selection = require("@embedpdf/react/selection");
|
|
34
34
|
let _embedpdf_react_annotation = require("@embedpdf/react/annotation");
|
|
35
35
|
let _embedpdf_react_stamp = require("@embedpdf/react/stamp");
|
|
36
|
+
let _embedpdf_react_measurement = require("@embedpdf/react/measurement");
|
|
36
37
|
let _embedpdf_react_redaction = require("@embedpdf/react/redaction");
|
|
37
38
|
let _embedpdf_react_actions = require("@embedpdf/react/actions");
|
|
38
39
|
let _embedpdf_react_form = require("@embedpdf/react/form");
|
|
40
|
+
let _embedpdf_react_signature = require("@embedpdf/react/signature");
|
|
39
41
|
let _embedpdf_react_link = require("@embedpdf/react/link");
|
|
40
42
|
let _embedpdf_react_search = require("@embedpdf/react/search");
|
|
41
43
|
let _embedpdf_react_i18n = require("@embedpdf/react/i18n");
|
|
@@ -44,8 +46,10 @@ let _embedpdf_react_shell = require("@embedpdf/react/shell");
|
|
|
44
46
|
let _embedpdf_react_toolbar = require("@embedpdf/react/toolbar");
|
|
45
47
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
46
48
|
let _embedpdf_react_scrollbar = require("@embedpdf/react/scrollbar");
|
|
47
|
-
let _embedpdf_react_annotation_menu = require("@embedpdf/react/annotation-menu");
|
|
48
49
|
let _embedpdf_default_stamps_library = require("@embedpdf/default-stamps/library");
|
|
50
|
+
let _embedpdf_web = require("@embedpdf/web");
|
|
51
|
+
let _embedpdf_react_annotation_menu = require("@embedpdf/react/annotation-menu");
|
|
52
|
+
let _embedpdf_react_anchored = require("@embedpdf/react/anchored");
|
|
49
53
|
let _embedpdf_react_metadata = require("@embedpdf/react/metadata");
|
|
50
54
|
//#region src/config/stage.ts
|
|
51
55
|
/**
|
|
@@ -102,6 +106,32 @@ const en = {
|
|
|
102
106
|
code: "en",
|
|
103
107
|
name: "English",
|
|
104
108
|
translations: {
|
|
109
|
+
measurement: {
|
|
110
|
+
title: "Measurements",
|
|
111
|
+
distance: "Distance",
|
|
112
|
+
perimeter: "Perimeter",
|
|
113
|
+
area: "Area",
|
|
114
|
+
areaUnit: "Area unit",
|
|
115
|
+
resetLabel: "Reset label position",
|
|
116
|
+
calibrate: "Calibrate scale",
|
|
117
|
+
scale: "Scale",
|
|
118
|
+
custom: "Custom",
|
|
119
|
+
unavailable: "Unavailable",
|
|
120
|
+
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.",
|
|
121
|
+
sessionOnly: "This engine keeps page calibration for this session only.",
|
|
122
|
+
allPages: "Apply to all pages",
|
|
123
|
+
recalculate: "Recalculate existing measurements",
|
|
124
|
+
preset: "Scale preset",
|
|
125
|
+
choosePreset: "Choose a preset",
|
|
126
|
+
unit: "Unit",
|
|
127
|
+
precision: "Precision",
|
|
128
|
+
selection: "Selected measurements",
|
|
129
|
+
report: "{updated} updated · {skipped} skipped · {failed} failed",
|
|
130
|
+
knownLength: "Enter the real length represented by the line you just drew.",
|
|
131
|
+
length: "Known length",
|
|
132
|
+
apply: "Apply scale",
|
|
133
|
+
applying: "Applying…"
|
|
134
|
+
},
|
|
105
135
|
commands: {
|
|
106
136
|
zoom: {
|
|
107
137
|
in: "Zoom In",
|
|
@@ -145,6 +175,7 @@ const en = {
|
|
|
145
175
|
counterclockwise: "Rotate Counter-Clockwise"
|
|
146
176
|
},
|
|
147
177
|
mode: {
|
|
178
|
+
measure: "Measure",
|
|
148
179
|
group: "Mode",
|
|
149
180
|
view: "View",
|
|
150
181
|
annotate: "Annotate",
|
|
@@ -154,6 +185,7 @@ const en = {
|
|
|
154
185
|
redact: "Redact"
|
|
155
186
|
},
|
|
156
187
|
annotate: {
|
|
188
|
+
cancelCreation: "Cancel drawing",
|
|
157
189
|
highlight: "Highlight",
|
|
158
190
|
strikeout: "Strikeout",
|
|
159
191
|
underline: "Underline",
|
|
@@ -197,7 +229,8 @@ const en = {
|
|
|
197
229
|
checkbox: "Checkbox",
|
|
198
230
|
radio: "Radio Button",
|
|
199
231
|
select: "Dropdown",
|
|
200
|
-
listbox: "List Box"
|
|
232
|
+
listbox: "List Box",
|
|
233
|
+
signature: "Signature Field"
|
|
201
234
|
},
|
|
202
235
|
redact: {
|
|
203
236
|
mark: "Mark for Redaction",
|
|
@@ -294,6 +327,7 @@ const en = {
|
|
|
294
327
|
redactConfirmCollateral: "{count} other annotations will also be removed.",
|
|
295
328
|
redactApply: "Apply",
|
|
296
329
|
redactApplying: "Applying…",
|
|
330
|
+
formatLabel: "Format",
|
|
297
331
|
redactLabelTitle: "Redaction label",
|
|
298
332
|
redactLabelPlaceholder: "e.g. REDACTED",
|
|
299
333
|
redactLabelRepeat: "Repeat label across region",
|
|
@@ -312,7 +346,72 @@ const en = {
|
|
|
312
346
|
stampsCustomLabel: "Custom stamp",
|
|
313
347
|
stampsError: "The built-in stamps could not be loaded.",
|
|
314
348
|
stampsArmError: "That stamp could not be armed.",
|
|
315
|
-
stampsImportError: "That PDF could not be imported as a stamp library."
|
|
349
|
+
stampsImportError: "That PDF could not be imported as a stamp library.",
|
|
350
|
+
quickStampsLabel: "Quick stamps",
|
|
351
|
+
signaturesTitle: "Signatures",
|
|
352
|
+
signaturesEmpty: "No signatures yet. Create one to sign with.",
|
|
353
|
+
signaturesNew: "New signature…",
|
|
354
|
+
signaturesArmedHint: "Click a signature field to sign it, or anywhere on a page to place the mark.",
|
|
355
|
+
signaturesTargetHint: "Pick a mark for the selected field.",
|
|
356
|
+
signaturesInDocument: "In this document",
|
|
357
|
+
signaturesNone: "This document has no signature fields.",
|
|
358
|
+
signaturesValidate: "Validate",
|
|
359
|
+
signaturesValidating: "Validating…",
|
|
360
|
+
signaturesSigned: "Signed",
|
|
361
|
+
signaturesUnsigned: "Unsigned",
|
|
362
|
+
signaturesRename: "Rename",
|
|
363
|
+
signaturesRemove: "Remove",
|
|
364
|
+
signaturesExport: "Export as PDF",
|
|
365
|
+
signaturesAddInitials: "Add initials",
|
|
366
|
+
signatureLabel: "Signature",
|
|
367
|
+
initialsLabel: "Initials",
|
|
368
|
+
makerTitle: "New signature",
|
|
369
|
+
makerName: "Name",
|
|
370
|
+
makerNamePlaceholder: "Your name",
|
|
371
|
+
makerDraw: "Draw",
|
|
372
|
+
makerType: "Type",
|
|
373
|
+
makerUpload: "Upload",
|
|
374
|
+
makerTypePlaceholder: "Type your name",
|
|
375
|
+
makerFont: "Font",
|
|
376
|
+
makerUploadHint: "Choose a PNG, JPEG, or one-page PDF",
|
|
377
|
+
makerClear: "Clear",
|
|
378
|
+
makerOptional: "(optional)",
|
|
379
|
+
makerSave: "Save",
|
|
380
|
+
makerSaving: "Saving…",
|
|
381
|
+
makerError: "The signature could not be saved.",
|
|
382
|
+
signTitle: "Sign this field",
|
|
383
|
+
signReason: "Reason",
|
|
384
|
+
signLocation: "Location",
|
|
385
|
+
signKind: "What may change afterwards",
|
|
386
|
+
signKindApproval: "Approval signature — form filling and more signatures stay valid",
|
|
387
|
+
signKindCertifyAnnotate: "Certify — allow comments, form filling and signatures",
|
|
388
|
+
signKindCertifyFill: "Certify — allow form filling and signatures only",
|
|
389
|
+
signKindCertifyLocked: "Certify — allow no changes",
|
|
390
|
+
signButton: "Sign",
|
|
391
|
+
signSigning: "Signing…",
|
|
392
|
+
signFill: "Place without signing",
|
|
393
|
+
signError: "The field could not be signed.",
|
|
394
|
+
inspectTitle: "Signature",
|
|
395
|
+
inspectSigner: "Signed by",
|
|
396
|
+
inspectReason: "Reason",
|
|
397
|
+
inspectLocation: "Location",
|
|
398
|
+
inspectTime: "Time",
|
|
399
|
+
inspectIntegrity: "Document",
|
|
400
|
+
inspectCryptography: "Signature",
|
|
401
|
+
inspectTrust: "Signer",
|
|
402
|
+
inspectChanges: "Changes since",
|
|
403
|
+
inspectDownload: "Download signed version",
|
|
404
|
+
verdictValid: "Valid",
|
|
405
|
+
verdictValidUntrusted: "Valid, signer not trusted",
|
|
406
|
+
verdictInvalid: "Invalid — modified after signing",
|
|
407
|
+
verdictWillInvalidate: "Valid, invalid once saved",
|
|
408
|
+
signaturesInvalidating: "This change will invalidate {field} when the document is saved. Undo it to keep the signature.",
|
|
409
|
+
verdictIndeterminate: "Could not be verified",
|
|
410
|
+
verdictUnchanged: "None",
|
|
411
|
+
verdictRestored: "Changed, then restored",
|
|
412
|
+
inspectLaterRevisions: "Revisions after signing",
|
|
413
|
+
verdictPermitted: "Permitted",
|
|
414
|
+
verdictForbidden: "Not permitted"
|
|
316
415
|
}
|
|
317
416
|
}
|
|
318
417
|
};
|
|
@@ -330,7 +429,9 @@ const en = {
|
|
|
330
429
|
const ViewerConfigContext = (0, react.createContext)({
|
|
331
430
|
chrome: require_chrome.defaultChrome,
|
|
332
431
|
icons: {},
|
|
333
|
-
stamps: {}
|
|
432
|
+
stamps: {},
|
|
433
|
+
signatures: {},
|
|
434
|
+
annotations: {}
|
|
334
435
|
});
|
|
335
436
|
const ViewerConfigProvider = ViewerConfigContext.Provider;
|
|
336
437
|
function useChromeSchema() {
|
|
@@ -348,6 +449,12 @@ function useCustomIcons() {
|
|
|
348
449
|
function useStampsConfig() {
|
|
349
450
|
return (0, react.useContext)(ViewerConfigContext).stamps;
|
|
350
451
|
}
|
|
452
|
+
function useSignaturesConfig() {
|
|
453
|
+
return (0, react.useContext)(ViewerConfigContext).signatures;
|
|
454
|
+
}
|
|
455
|
+
function useAnnotationsConfig() {
|
|
456
|
+
return (0, react.useContext)(ViewerConfigContext).annotations;
|
|
457
|
+
}
|
|
351
458
|
//#endregion
|
|
352
459
|
//#region src/handle.ts
|
|
353
460
|
function createViewerHandle(kernel) {
|
|
@@ -394,7 +501,40 @@ function createViewerHandle(kernel) {
|
|
|
394
501
|
*
|
|
395
502
|
* GENERATED by scratchpad/extract-icons.mjs — 93 icons (slot tags by hand).
|
|
396
503
|
*/
|
|
504
|
+
const measurementOutline = [{
|
|
505
|
+
d: "M5 4H19M20 5V19M19 20H5M4 19V5",
|
|
506
|
+
stroke: "primary"
|
|
507
|
+
}, {
|
|
508
|
+
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",
|
|
509
|
+
stroke: "primary"
|
|
510
|
+
}];
|
|
397
511
|
const ICON_PATHS = {
|
|
512
|
+
ruler: ["M3 8h18v8H3z", "M7 8v4M11 8v2M15 8v4M19 8v2"],
|
|
513
|
+
distance: [{
|
|
514
|
+
d: "M5 12H19",
|
|
515
|
+
stroke: "primary",
|
|
516
|
+
strokeDasharray: "2 2"
|
|
517
|
+
}, {
|
|
518
|
+
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",
|
|
519
|
+
stroke: "primary",
|
|
520
|
+
fill: "primary",
|
|
521
|
+
fillOpacity: .25
|
|
522
|
+
}],
|
|
523
|
+
perimeter: measurementOutline,
|
|
524
|
+
area: [
|
|
525
|
+
{
|
|
526
|
+
d: "M5 4H19V5H20V19H19V20H5V19H4V5H5Z",
|
|
527
|
+
fill: "primary",
|
|
528
|
+
fillOpacity: .08
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
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",
|
|
532
|
+
stroke: "primary",
|
|
533
|
+
strokeWidth: .6,
|
|
534
|
+
strokeOpacity: .5
|
|
535
|
+
},
|
|
536
|
+
...measurementOutline
|
|
537
|
+
],
|
|
398
538
|
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"],
|
|
399
539
|
alignBottom: [
|
|
400
540
|
"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",
|
|
@@ -444,6 +584,11 @@ const ICON_PATHS = {
|
|
|
444
584
|
"M19 16h-12a2 2 0 0 0 -2 2",
|
|
445
585
|
"M9 8h6"
|
|
446
586
|
],
|
|
587
|
+
calibrate: [
|
|
588
|
+
"M12 12m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0 -15 0",
|
|
589
|
+
"M12 2.75V7M12 17V21.25M2.75 12H7M17 12H21.25",
|
|
590
|
+
"M12 12m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0"
|
|
591
|
+
],
|
|
447
592
|
callout: [
|
|
448
593
|
{
|
|
449
594
|
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",
|
|
@@ -866,6 +1011,11 @@ const ICON_PATHS = {
|
|
|
866
1011
|
],
|
|
867
1012
|
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"],
|
|
868
1013
|
unlock: ["M7 11V7a5 5 0 0 1 9.9-1"],
|
|
1014
|
+
updateScale: [
|
|
1015
|
+
"M3.5 13.5L18.5 7.5L21.25 14.5L6.25 20.5L3.5 13.5Z",
|
|
1016
|
+
"M8 12.8L9 15.2M11.5 11.4L12.2 13.2M15 10L16 12.4",
|
|
1017
|
+
"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"
|
|
1018
|
+
],
|
|
869
1019
|
vertical: [
|
|
870
1020
|
"M8 7l4 -4l4 4",
|
|
871
1021
|
"M8 17l4 4l4 -4",
|
|
@@ -935,7 +1085,11 @@ function Icon({ name, size = 20, strokeWidth = 2, className, title, accent }) {
|
|
|
935
1085
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
936
1086
|
d: p.d,
|
|
937
1087
|
fill: fill ?? "none",
|
|
938
|
-
stroke
|
|
1088
|
+
stroke,
|
|
1089
|
+
strokeWidth: p.strokeWidth,
|
|
1090
|
+
strokeDasharray: p.strokeDasharray,
|
|
1091
|
+
strokeOpacity: p.strokeOpacity,
|
|
1092
|
+
fillOpacity: p.fillOpacity
|
|
939
1093
|
}, i);
|
|
940
1094
|
})
|
|
941
1095
|
});
|
|
@@ -969,6 +1123,495 @@ function ThemeProvider({ children, preference = "system", target }) {
|
|
|
969
1123
|
});
|
|
970
1124
|
}
|
|
971
1125
|
//#endregion
|
|
1126
|
+
//#region src/ui/measurement.tsx
|
|
1127
|
+
const control = "border-border bg-surface text-fg w-full rounded-md border px-2 py-1.5 text-sm disabled:opacity-50";
|
|
1128
|
+
const button = "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm disabled:opacity-50";
|
|
1129
|
+
const useCurrentPon = () => (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_stage.StageToken, (c) => c.pages()[c.currentPage()]?.pon ?? -1);
|
|
1130
|
+
function MeasurementScaleButton() {
|
|
1131
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1132
|
+
const scale = (0, _embedpdf_react_measurement.usePageScale)(useCurrentPon());
|
|
1133
|
+
const surface = (0, _embedpdf_react_shell.useSurface)("measurement");
|
|
1134
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1135
|
+
className: `${button} inline-flex items-center gap-1.5 whitespace-nowrap`,
|
|
1136
|
+
onClick: () => surface.open({ exclusive: "right" }),
|
|
1137
|
+
title: t("measurement.title"),
|
|
1138
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1139
|
+
name: "updateScale",
|
|
1140
|
+
size: 20,
|
|
1141
|
+
className: "shrink-0"
|
|
1142
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1143
|
+
t("measurement.scale"),
|
|
1144
|
+
":",
|
|
1145
|
+
" ",
|
|
1146
|
+
scale.measure?.subtype === "RL" ? scale.measure.ratio ?? t("measurement.custom") : t("measurement.unavailable")
|
|
1147
|
+
] })]
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
function MeasurementSection() {
|
|
1151
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1152
|
+
const pon = useCurrentPon();
|
|
1153
|
+
const measurement = (0, _embedpdf_react_measurement.useMeasurement)();
|
|
1154
|
+
const scale = (0, _embedpdf_react_measurement.usePageScale)(pon);
|
|
1155
|
+
const anno = (0, _embedpdf_react_runtime.useCapability)(_embedpdf_react_annotation.AnnotationToken);
|
|
1156
|
+
const selected = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_annotation.AnnotationToken, (c) => c.selection());
|
|
1157
|
+
const resettable = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_annotation.AnnotationToken, (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]));
|
|
1158
|
+
const readouts = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_measurement.MeasurementToken, (c) => anno.getSelected().map((d) => c.readout(d.ref)).filter((r) => !("unavailable" in r)), (a, b) => JSON.stringify(a) === JSON.stringify(b));
|
|
1159
|
+
const reports = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_measurement.MeasurementToken, (c) => c.lastReports());
|
|
1160
|
+
const [allPages, setAllPages] = (0, react.useState)(false);
|
|
1161
|
+
const [recalculate, setRecalculate] = (0, react.useState)(true);
|
|
1162
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1163
|
+
const disabled = measurement.busy || !measurement.canCalibratePage || !scale.ready;
|
|
1164
|
+
const rectilinear = scale.measure?.subtype === "RL" ? scale.measure : null;
|
|
1165
|
+
const options = {
|
|
1166
|
+
allPages,
|
|
1167
|
+
recalculate
|
|
1168
|
+
};
|
|
1169
|
+
const run = async (work) => {
|
|
1170
|
+
setError(null);
|
|
1171
|
+
try {
|
|
1172
|
+
await work();
|
|
1173
|
+
} catch (e) {
|
|
1174
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
const precision = rectilinear?.distance[rectilinear.distance.length - 1]?.precision ?? 100;
|
|
1178
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1179
|
+
className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 text-sm",
|
|
1180
|
+
children: [
|
|
1181
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1182
|
+
className: "text-fg-muted",
|
|
1183
|
+
children: t("measurement.instructions")
|
|
1184
|
+
}),
|
|
1185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1186
|
+
className: "text-fg-muted",
|
|
1187
|
+
children: [t("measurement.scale"), ": "]
|
|
1188
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: rectilinear?.ratio ?? t("measurement.unavailable") })] }),
|
|
1189
|
+
scale.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1190
|
+
role: "alert",
|
|
1191
|
+
className: "text-red-600",
|
|
1192
|
+
children: scale.error.message
|
|
1193
|
+
}),
|
|
1194
|
+
!scale.persistent && scale.ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1195
|
+
className: "text-fg-muted",
|
|
1196
|
+
children: t("measurement.sessionOnly")
|
|
1197
|
+
}),
|
|
1198
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1199
|
+
className: "flex items-center gap-2",
|
|
1200
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1201
|
+
type: "checkbox",
|
|
1202
|
+
checked: allPages,
|
|
1203
|
+
disabled: measurement.busy,
|
|
1204
|
+
onChange: (e) => setAllPages(e.target.checked)
|
|
1205
|
+
}), t("measurement.allPages")]
|
|
1206
|
+
}),
|
|
1207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1208
|
+
className: "flex items-center gap-2",
|
|
1209
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1210
|
+
type: "checkbox",
|
|
1211
|
+
checked: recalculate,
|
|
1212
|
+
disabled: measurement.busy,
|
|
1213
|
+
onChange: (e) => setRecalculate(e.target.checked)
|
|
1214
|
+
}), t("measurement.recalculate")]
|
|
1215
|
+
}),
|
|
1216
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("measurement.preset"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1217
|
+
"aria-label": t("measurement.preset"),
|
|
1218
|
+
className: control,
|
|
1219
|
+
value: "",
|
|
1220
|
+
disabled,
|
|
1221
|
+
onChange: (e) => void run(() => measurement.setPreset(pon, e.target.value, options)),
|
|
1222
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1223
|
+
value: "",
|
|
1224
|
+
disabled: true,
|
|
1225
|
+
children: t("measurement.choosePreset")
|
|
1226
|
+
}), measurement.presets().map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1227
|
+
value: p.id,
|
|
1228
|
+
children: p.label
|
|
1229
|
+
}, p.id))]
|
|
1230
|
+
})] }),
|
|
1231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("measurement.unit"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1232
|
+
"aria-label": t("measurement.unit"),
|
|
1233
|
+
className: control,
|
|
1234
|
+
value: rectilinear?.distance[0]?.unit.trim() ?? "",
|
|
1235
|
+
disabled: disabled || !rectilinear,
|
|
1236
|
+
onChange: (e) => void run(() => measurement.setUnit(pon, e.target.value, void 0, options)),
|
|
1237
|
+
children: [!measurement.units().includes(rectilinear?.distance[0]?.unit.trim()) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1238
|
+
value: rectilinear?.distance[0]?.unit.trim() ?? "",
|
|
1239
|
+
children: t("measurement.custom")
|
|
1240
|
+
}), measurement.units().map((u) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1241
|
+
value: u,
|
|
1242
|
+
children: u
|
|
1243
|
+
}, u))]
|
|
1244
|
+
})] }),
|
|
1245
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("measurement.areaUnit"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1246
|
+
"aria-label": t("measurement.areaUnit"),
|
|
1247
|
+
className: control,
|
|
1248
|
+
value: rectilinear?.area[0]?.unit.trim().replace("²", "2") ?? "",
|
|
1249
|
+
disabled: disabled || !rectilinear,
|
|
1250
|
+
onChange: (e) => void run(() => measurement.setAreaUnit(pon, e.target.value, options)),
|
|
1251
|
+
children: [!measurement.areaUnits().includes(rectilinear?.area[0]?.unit.trim().replace("²", "2")) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1252
|
+
value: rectilinear?.area[0]?.unit.trim().replace("²", "2") ?? "",
|
|
1253
|
+
children: t("measurement.custom")
|
|
1254
|
+
}), measurement.areaUnits().map((unit) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1255
|
+
value: unit,
|
|
1256
|
+
children: unit.replace("2", "²")
|
|
1257
|
+
}, unit))]
|
|
1258
|
+
})] }),
|
|
1259
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("measurement.precision"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1260
|
+
"aria-label": t("measurement.precision"),
|
|
1261
|
+
className: control,
|
|
1262
|
+
value: precision,
|
|
1263
|
+
disabled: disabled || !rectilinear,
|
|
1264
|
+
onChange: (e) => void run(() => measurement.setPrecision(pon, Number(e.target.value), options)),
|
|
1265
|
+
children: [![
|
|
1266
|
+
1,
|
|
1267
|
+
10,
|
|
1268
|
+
100,
|
|
1269
|
+
1e3,
|
|
1270
|
+
1e4
|
|
1271
|
+
].includes(precision) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1272
|
+
value: precision,
|
|
1273
|
+
children: t("measurement.custom")
|
|
1274
|
+
}), [
|
|
1275
|
+
1,
|
|
1276
|
+
10,
|
|
1277
|
+
100,
|
|
1278
|
+
1e3,
|
|
1279
|
+
1e4
|
|
1280
|
+
].map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1281
|
+
value: p,
|
|
1282
|
+
children: 1 / p
|
|
1283
|
+
}, p))]
|
|
1284
|
+
})] }),
|
|
1285
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1286
|
+
className: button,
|
|
1287
|
+
disabled,
|
|
1288
|
+
onClick: () => measurement.startCalibration(),
|
|
1289
|
+
children: t("measurement.calibrate")
|
|
1290
|
+
}),
|
|
1291
|
+
selected.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1292
|
+
className: "border-border-subtle border-t pt-3",
|
|
1293
|
+
children: [
|
|
1294
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1295
|
+
className: "mb-1 font-medium",
|
|
1296
|
+
children: t("measurement.selection")
|
|
1297
|
+
}),
|
|
1298
|
+
readouts.length ? readouts.map((readout, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1299
|
+
className: "mb-2",
|
|
1300
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
1301
|
+
t(`measurement.${readout.kind}`),
|
|
1302
|
+
": ",
|
|
1303
|
+
readout.label
|
|
1304
|
+
] }), readout.perimeter && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
1305
|
+
t("measurement.perimeter"),
|
|
1306
|
+
": ",
|
|
1307
|
+
readout.perimeter
|
|
1308
|
+
] })]
|
|
1309
|
+
}, i)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1310
|
+
className: "text-fg-muted",
|
|
1311
|
+
children: t("measurement.unavailable")
|
|
1312
|
+
}),
|
|
1313
|
+
resettable.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1314
|
+
className: button,
|
|
1315
|
+
disabled: measurement.busy,
|
|
1316
|
+
onClick: () => void run(async () => {
|
|
1317
|
+
for (const annotation of resettable) if (annotation.subtype === "polygon" || annotation.subtype === "polyline") await anno.update(annotation.ref, {
|
|
1318
|
+
subtype: annotation.subtype,
|
|
1319
|
+
caption: { center: null }
|
|
1320
|
+
});
|
|
1321
|
+
}),
|
|
1322
|
+
children: t("measurement.resetLabel")
|
|
1323
|
+
})
|
|
1324
|
+
]
|
|
1325
|
+
}),
|
|
1326
|
+
reports.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1327
|
+
role: "status",
|
|
1328
|
+
children: t("measurement.report", { params: {
|
|
1329
|
+
updated: reports.reduce((n, r) => n + r.updated.length, 0),
|
|
1330
|
+
skipped: reports.reduce((n, r) => n + r.skipped.length, 0),
|
|
1331
|
+
failed: reports.reduce((n, r) => n + r.failed.length + (r.scaleError || r.error ? 1 : 0), 0)
|
|
1332
|
+
} })
|
|
1333
|
+
}),
|
|
1334
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1335
|
+
role: "alert",
|
|
1336
|
+
className: "text-red-600",
|
|
1337
|
+
children: error
|
|
1338
|
+
})
|
|
1339
|
+
]
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
function CalibrationDialog() {
|
|
1343
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1344
|
+
const measurement = (0, _embedpdf_react_measurement.useMeasurement)();
|
|
1345
|
+
const request = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_measurement.MeasurementToken, (c) => c.calibrationRequest());
|
|
1346
|
+
const input = (0, react.useRef)(null);
|
|
1347
|
+
const [value, setValue] = (0, react.useState)("");
|
|
1348
|
+
const [unit, setUnit] = (0, react.useState)("m");
|
|
1349
|
+
const [allPages, setAllPages] = (0, react.useState)(false);
|
|
1350
|
+
const [recalculate, setRecalculate] = (0, react.useState)(true);
|
|
1351
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1352
|
+
(0, react.useEffect)(() => {
|
|
1353
|
+
setValue("");
|
|
1354
|
+
setError(null);
|
|
1355
|
+
input.current?.focus();
|
|
1356
|
+
}, [request]);
|
|
1357
|
+
if (!request) return null;
|
|
1358
|
+
const submit = async () => {
|
|
1359
|
+
setError(null);
|
|
1360
|
+
try {
|
|
1361
|
+
await measurement.calibrate(request.pon, request.from, request.to, {
|
|
1362
|
+
value: Number(value),
|
|
1363
|
+
unit
|
|
1364
|
+
}, {
|
|
1365
|
+
allPages,
|
|
1366
|
+
recalculate
|
|
1367
|
+
});
|
|
1368
|
+
measurement.dismissCalibration();
|
|
1369
|
+
} catch (e) {
|
|
1370
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1374
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
1375
|
+
onKeyDown: (e) => {
|
|
1376
|
+
if (e.key === "Escape" && !measurement.busy) measurement.dismissCalibration();
|
|
1377
|
+
},
|
|
1378
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1379
|
+
role: "dialog",
|
|
1380
|
+
"aria-modal": "true",
|
|
1381
|
+
"aria-labelledby": "measurement-calibration-title",
|
|
1382
|
+
className: "border-border bg-surface flex w-96 flex-col gap-4 rounded-lg border p-5 shadow-xl",
|
|
1383
|
+
onSubmit: (e) => {
|
|
1384
|
+
e.preventDefault();
|
|
1385
|
+
submit();
|
|
1386
|
+
},
|
|
1387
|
+
children: [
|
|
1388
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1389
|
+
id: "measurement-calibration-title",
|
|
1390
|
+
className: "font-semibold",
|
|
1391
|
+
children: t("measurement.calibrate")
|
|
1392
|
+
}),
|
|
1393
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1394
|
+
className: "text-fg-muted text-sm",
|
|
1395
|
+
children: t("measurement.knownLength")
|
|
1396
|
+
}),
|
|
1397
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("measurement.length"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1398
|
+
ref: input,
|
|
1399
|
+
"aria-label": t("measurement.length"),
|
|
1400
|
+
className: control,
|
|
1401
|
+
type: "number",
|
|
1402
|
+
min: "0",
|
|
1403
|
+
step: "any",
|
|
1404
|
+
value,
|
|
1405
|
+
onChange: (e) => setValue(e.target.value)
|
|
1406
|
+
})] }),
|
|
1407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("measurement.unit"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1408
|
+
className: control,
|
|
1409
|
+
"aria-label": t("measurement.unit"),
|
|
1410
|
+
value: unit,
|
|
1411
|
+
onChange: (e) => setUnit(e.target.value),
|
|
1412
|
+
children: measurement.units().map((u) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { children: u }, u))
|
|
1413
|
+
})] }),
|
|
1414
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1415
|
+
className: "flex items-center gap-2 text-sm",
|
|
1416
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1417
|
+
type: "checkbox",
|
|
1418
|
+
checked: allPages,
|
|
1419
|
+
onChange: (e) => setAllPages(e.target.checked)
|
|
1420
|
+
}), t("measurement.allPages")]
|
|
1421
|
+
}),
|
|
1422
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1423
|
+
className: "flex items-center gap-2 text-sm",
|
|
1424
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1425
|
+
type: "checkbox",
|
|
1426
|
+
checked: recalculate,
|
|
1427
|
+
onChange: (e) => setRecalculate(e.target.checked)
|
|
1428
|
+
}), t("measurement.recalculate")]
|
|
1429
|
+
}),
|
|
1430
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1431
|
+
role: "alert",
|
|
1432
|
+
className: "text-sm text-red-600",
|
|
1433
|
+
children: error
|
|
1434
|
+
}),
|
|
1435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1436
|
+
className: "flex justify-end gap-2",
|
|
1437
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1438
|
+
type: "button",
|
|
1439
|
+
className: button,
|
|
1440
|
+
disabled: measurement.busy,
|
|
1441
|
+
onClick: () => measurement.dismissCalibration(),
|
|
1442
|
+
children: t("demo.cancel")
|
|
1443
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1444
|
+
className: button,
|
|
1445
|
+
type: "submit",
|
|
1446
|
+
disabled: measurement.busy || !measurement.canCalibratePage || !(Number(value) > 0) || !Number.isFinite(Number(value)),
|
|
1447
|
+
children: measurement.busy ? t("measurement.applying") : t("measurement.apply")
|
|
1448
|
+
})]
|
|
1449
|
+
})
|
|
1450
|
+
]
|
|
1451
|
+
})
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region src/config/default-stamps.ts
|
|
1456
|
+
/**
|
|
1457
|
+
* The viewer's BUILT-IN stamp library: `@embedpdf/default-stamps`, the
|
|
1458
|
+
* standard rubber stamps as one Acrobat-compatible PDF per locale.
|
|
1459
|
+
*
|
|
1460
|
+
* Delivery: the library ships INSIDE whatever ships this viewer. The package
|
|
1461
|
+
* exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
|
|
1462
|
+
* so it travels through the module graph like any other code — every bundler
|
|
1463
|
+
* splits it into a chunk served from the app's own origin, the CDN snippet
|
|
1464
|
+
* carries it as a sibling chunk in its folder, and nothing is fetched from a
|
|
1465
|
+
* third party. `stamps.defaultLibrary` overrides that:
|
|
1466
|
+
* - `false` → no built-in library, no request (air-gapped);
|
|
1467
|
+
* - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
|
|
1468
|
+
*
|
|
1469
|
+
* Loading is LAZY (first open of the stamps panel, never at boot) and
|
|
1470
|
+
* locale-aware: the chrome locale leads, the browser languages break ties
|
|
1471
|
+
* for the locales the chrome does not translate itself, and a locale change
|
|
1472
|
+
* while a default library is loaded swaps it for the new one. The file names
|
|
1473
|
+
* itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
|
|
1474
|
+
*/
|
|
1475
|
+
/** The built-in library's PieceInfo id — excluded from persistence (it is
|
|
1476
|
+
* loaded again on every first open, in the locale of that moment). */
|
|
1477
|
+
const DEFAULT_LIBRARY_ID = "embedpdf-standard";
|
|
1478
|
+
/** Which shipped locale to show for the chrome's locale + the browser's. */
|
|
1479
|
+
function resolveStampsLocale(chromeLocale) {
|
|
1480
|
+
return (0, _embedpdf_react_i18n.negotiateLocale)(_embedpdf_default_stamps_library.LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
|
|
1481
|
+
}
|
|
1482
|
+
const fetchBytes = async (url) => {
|
|
1483
|
+
const response = await fetch(url);
|
|
1484
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
1485
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1486
|
+
};
|
|
1487
|
+
/** The library bytes for `locale`: your URL when you gave one, otherwise the
|
|
1488
|
+
* copy that shipped with the viewer. Never anything else. */
|
|
1489
|
+
const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : (0, _embedpdf_default_stamps_library.loadDefaultLibrary)(locale);
|
|
1490
|
+
/** Per workspace: the locale the default library was last brought to, and
|
|
1491
|
+
* the in-flight work — the panel mounts and unmounts with the sidebar, so
|
|
1492
|
+
* this cannot live in component state. */
|
|
1493
|
+
const loads = /* @__PURE__ */ new WeakMap();
|
|
1494
|
+
/**
|
|
1495
|
+
* Bring the built-in library to `locale`. Idempotent per workspace and
|
|
1496
|
+
* locale; a locale change swaps the loaded default (remove, then import);
|
|
1497
|
+
* a default the user removed in this session stays removed — the locale is
|
|
1498
|
+
* recorded, nothing is re-added. An embedder that seeded a library under
|
|
1499
|
+
* the same id is left alone.
|
|
1500
|
+
*/
|
|
1501
|
+
function ensureDefaultLibrary(stamp, locale, source) {
|
|
1502
|
+
if (source === false) return Promise.resolve();
|
|
1503
|
+
const current = loads.get(stamp);
|
|
1504
|
+
if (current?.locale === locale) return current.promise;
|
|
1505
|
+
const run = async () => {
|
|
1506
|
+
await current?.promise.catch(() => void 0);
|
|
1507
|
+
const loaded = stamp.library(DEFAULT_LIBRARY_ID);
|
|
1508
|
+
if (loaded?.locale === locale) return;
|
|
1509
|
+
if (current && !loaded) return;
|
|
1510
|
+
if (loaded) await stamp.removeLibrary(DEFAULT_LIBRARY_ID);
|
|
1511
|
+
await stamp.importLibraryPdf(await defaultLibraryBytes(locale, source));
|
|
1512
|
+
};
|
|
1513
|
+
const promise = run();
|
|
1514
|
+
loads.set(stamp, {
|
|
1515
|
+
locale,
|
|
1516
|
+
promise
|
|
1517
|
+
});
|
|
1518
|
+
return promise;
|
|
1519
|
+
}
|
|
1520
|
+
//#endregion
|
|
1521
|
+
//#region src/ui/stamp-store.tsx
|
|
1522
|
+
/**
|
|
1523
|
+
* The user's stamp libraries live in the browser (IndexedDB): restored ONCE
|
|
1524
|
+
* per workspace, written on every change, for as long as the viewer lives —
|
|
1525
|
+
* a signature made while the stamps sidebar is closed is persisted exactly
|
|
1526
|
+
* like a stamp imported while it is open. The built-in library is never
|
|
1527
|
+
* stored (it is fetched again next time, in the locale of that moment); the
|
|
1528
|
+
* panels that need it bring it in lazily through `ensureDefaultLibrary`.
|
|
1529
|
+
*
|
|
1530
|
+
* Mounted once in the Shell, workspace-scoped (the stamp plugin is).
|
|
1531
|
+
*/
|
|
1532
|
+
const store = typeof indexedDB === "undefined" ? null : (0, _embedpdf_react_stamp.indexedDbByteStore)("embedpdf-stamps", { storeName: "libraries" });
|
|
1533
|
+
const restored = /* @__PURE__ */ new WeakMap();
|
|
1534
|
+
/** Bring the stored libraries back — once per workspace, whoever asks first. */
|
|
1535
|
+
const restoreStampLibrariesOnce = (stamp) => {
|
|
1536
|
+
let pending = restored.get(stamp);
|
|
1537
|
+
if (!pending) {
|
|
1538
|
+
pending = store ? (0, _embedpdf_react_stamp.restoreStampLibraries)(stamp, store) : Promise.resolve();
|
|
1539
|
+
restored.set(stamp, pending);
|
|
1540
|
+
}
|
|
1541
|
+
return pending;
|
|
1542
|
+
};
|
|
1543
|
+
function StampLibraryStore() {
|
|
1544
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
1545
|
+
(0, react.useEffect)(() => {
|
|
1546
|
+
restoreStampLibrariesOnce(stamp);
|
|
1547
|
+
return store ? (0, _embedpdf_react_stamp.persistStampLibraries)(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0;
|
|
1548
|
+
}, [stamp]);
|
|
1549
|
+
return null;
|
|
1550
|
+
}
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/ui/quick-stamps.tsx
|
|
1553
|
+
/**
|
|
1554
|
+
* Quick marks in the toolbar: a row of stamp thumbnails that ARM on click —
|
|
1555
|
+
* the checkmark, the cross, whatever the embedder configured
|
|
1556
|
+
* (`stamps.toolbar`: asset ids, or one library's assets). The hover ghost and
|
|
1557
|
+
* the click placement are the annotation plugin's, exactly as from the
|
|
1558
|
+
* sidebar; only the picker moved. Renders nothing until the assets exist.
|
|
1559
|
+
* Lives in the workspace toolbar, so it reads the ACTIVE document's tool.
|
|
1560
|
+
*/
|
|
1561
|
+
function QuickStamps() {
|
|
1562
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
1563
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
1564
|
+
const config = useStampsConfig();
|
|
1565
|
+
const { locale } = (0, _embedpdf_react_i18n.useLocale)();
|
|
1566
|
+
const assets = (0, _embedpdf_react_stamp.useStampAssets)();
|
|
1567
|
+
const documentId = (0, _embedpdf_react_runtime.useDocumentId)();
|
|
1568
|
+
const { armAsset } = (0, _embedpdf_react_stamp.useArmStampAsset)();
|
|
1569
|
+
const activeToolId = (0, _embedpdf_react_runtime.useOptionalSelector)(_embedpdf_react_interaction.InteractionToken, (c) => c.activeToolId(), null);
|
|
1570
|
+
const armedId = (0, _embedpdf_react_runtime.useOptionalSelector)(_embedpdf_react_stamp.StampToken, (c) => documentId ? c.armedAsset(documentId)?.id ?? null : null, null);
|
|
1571
|
+
const ids = Array.isArray(config.toolbar) ? config.toolbar : config.toolbar && "library" in config.toolbar ? stamp.library(config.toolbar.library)?.assetIds ?? [] : [];
|
|
1572
|
+
const wantsDefault = ids.some((id) => id.startsWith(`${DEFAULT_LIBRARY_ID}:`));
|
|
1573
|
+
(0, react.useEffect)(() => {
|
|
1574
|
+
if (!wantsDefault) return;
|
|
1575
|
+
restoreStampLibrariesOnce(stamp).then(() => ensureDefaultLibrary(stamp, resolveStampsLocale(locale), config.defaultLibrary)).catch((err) => console.error("[embedpdf] quick stamps failed:", err));
|
|
1576
|
+
}, [
|
|
1577
|
+
stamp,
|
|
1578
|
+
locale,
|
|
1579
|
+
wantsDefault
|
|
1580
|
+
]);
|
|
1581
|
+
const shown = ids.map((id) => assets.find((a) => a.id === id)).filter((a) => !!a);
|
|
1582
|
+
if (shown.length === 0 || !documentId) return null;
|
|
1583
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1584
|
+
className: "flex items-center gap-1",
|
|
1585
|
+
role: "group",
|
|
1586
|
+
"aria-label": t("demo.quickStampsLabel"),
|
|
1587
|
+
children: shown.map((asset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickStampButton, {
|
|
1588
|
+
asset,
|
|
1589
|
+
active: activeToolId === "stamp" && armedId === asset.id,
|
|
1590
|
+
onArm: () => void armAsset(asset.id).catch((e) => console.error("[embedpdf] arm failed", e))
|
|
1591
|
+
}, asset.id))
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
function QuickStampButton({ asset, active, onArm }) {
|
|
1595
|
+
const url = (0, _embedpdf_react_stamp.useStampAssetPreviewUrl)(asset.id);
|
|
1596
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1597
|
+
type: "button",
|
|
1598
|
+
onClick: onArm,
|
|
1599
|
+
title: asset.label,
|
|
1600
|
+
"aria-label": asset.label,
|
|
1601
|
+
"aria-pressed": active || void 0,
|
|
1602
|
+
className: buttonClass(active, true),
|
|
1603
|
+
children: url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
1604
|
+
src: url,
|
|
1605
|
+
alt: "",
|
|
1606
|
+
className: "h-5 w-5 object-contain",
|
|
1607
|
+
draggable: false
|
|
1608
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1609
|
+
className: "text-xs",
|
|
1610
|
+
children: asset.label
|
|
1611
|
+
})
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
//#endregion
|
|
972
1615
|
//#region src/ui/menu.tsx
|
|
973
1616
|
/**
|
|
974
1617
|
* Menu rendering — a MenuSchema (a tree of command ids) rendered as rows.
|
|
@@ -1377,7 +2020,11 @@ function AppToolbar({ bar, className }) {
|
|
|
1377
2020
|
variant,
|
|
1378
2021
|
run
|
|
1379
2022
|
}),
|
|
1380
|
-
renderCustom: {
|
|
2023
|
+
renderCustom: {
|
|
2024
|
+
"measurement-scale": () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MeasurementScaleButton, {}),
|
|
2025
|
+
"zoom-controls": (variant) => variant === "inline" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomControls, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomButton, {}),
|
|
2026
|
+
"quick-stamps": () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickStamps, {})
|
|
2027
|
+
},
|
|
1381
2028
|
renderCollapsed: (view) => view.id === "modes" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsedModes, { view }) : void 0,
|
|
1382
2029
|
renderGroupTrigger: (view) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupTrigger, { view }),
|
|
1383
2030
|
renderSeparator: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "bg-border h-6 w-px self-center" }),
|
|
@@ -1389,6 +2036,71 @@ function AppToolbar({ bar, className }) {
|
|
|
1389
2036
|
});
|
|
1390
2037
|
}
|
|
1391
2038
|
//#endregion
|
|
2039
|
+
//#region src/ui/annotation-fonts.tsx
|
|
2040
|
+
/**
|
|
2041
|
+
* The configured annotation fonts (`annotations.fonts`), loaded once per
|
|
2042
|
+
* viewer: fetch the bytes → register them on the viewer's engine under the
|
|
2043
|
+
* key (what a free-text `fontFamily` carries and what the engine embeds) →
|
|
2044
|
+
* mount the same bytes as a `@font-face` named by that key (what the plugin's
|
|
2045
|
+
* `cssFontFamily` emits for a registered font, so the live editor renders the
|
|
2046
|
+
* face the appearance stream will bake). The style panel lists a font only
|
|
2047
|
+
* once BOTH have happened — an offered key always resolves on write.
|
|
2048
|
+
*/
|
|
2049
|
+
const LoadedFontsContext = (0, react.createContext)([]);
|
|
2050
|
+
function AnnotationFontsProvider({ children }) {
|
|
2051
|
+
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
2052
|
+
const { fonts } = useAnnotationsConfig();
|
|
2053
|
+
const [loaded, setLoaded] = (0, react.useState)([]);
|
|
2054
|
+
(0, react.useEffect)(() => {
|
|
2055
|
+
const engineFonts = kernel.engine.fonts;
|
|
2056
|
+
if (!fonts?.length || !engineFonts) return;
|
|
2057
|
+
let cancelled = false;
|
|
2058
|
+
const unmounts = [];
|
|
2059
|
+
(async () => {
|
|
2060
|
+
for (const font of fonts) try {
|
|
2061
|
+
const response = await fetch(font.url);
|
|
2062
|
+
if (!response.ok) throw new Error(`${font.url}: HTTP ${response.status}`);
|
|
2063
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
2064
|
+
if (cancelled) return;
|
|
2065
|
+
await engineFonts.register({
|
|
2066
|
+
key: font.key,
|
|
2067
|
+
familyName: font.familyName,
|
|
2068
|
+
weight: font.weight,
|
|
2069
|
+
italic: font.italic,
|
|
2070
|
+
data: bytes
|
|
2071
|
+
});
|
|
2072
|
+
const unmount = await (0, _embedpdf_web.mountWebFont)(font.key, bytes, {
|
|
2073
|
+
weight: font.weight,
|
|
2074
|
+
style: font.italic ? "italic" : "normal"
|
|
2075
|
+
});
|
|
2076
|
+
if (cancelled) {
|
|
2077
|
+
unmount();
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
unmounts.push(unmount);
|
|
2081
|
+
setLoaded((prev) => prev.some((f) => f.key === font.key) ? prev : [...prev, {
|
|
2082
|
+
key: font.key,
|
|
2083
|
+
label: font.label
|
|
2084
|
+
}]);
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
console.warn(`[embedpdf] annotation font "${font.key}" was not loaded:`, error);
|
|
2087
|
+
}
|
|
2088
|
+
})();
|
|
2089
|
+
return () => {
|
|
2090
|
+
cancelled = true;
|
|
2091
|
+
for (const unmount of unmounts) unmount();
|
|
2092
|
+
};
|
|
2093
|
+
}, [kernel, fonts]);
|
|
2094
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoadedFontsContext.Provider, {
|
|
2095
|
+
value: loaded,
|
|
2096
|
+
children
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
/** The configured fonts that are registered AND mounted, in config order. */
|
|
2100
|
+
function useAnnotationFonts() {
|
|
2101
|
+
return (0, react.useContext)(LoadedFontsContext);
|
|
2102
|
+
}
|
|
2103
|
+
//#endregion
|
|
1392
2104
|
//#region src/ui/link-editor.tsx
|
|
1393
2105
|
/**
|
|
1394
2106
|
* The link editor POPOVER — v2's "add a link" popup, reborn. It rides the
|
|
@@ -1756,7 +2468,13 @@ function iconGlyph(name, accent) {
|
|
|
1756
2468
|
const fill = spec.fill === true ? INK : spec.fill ? accent?.[spec.fill] : void 0;
|
|
1757
2469
|
const stroke = spec.stroke ? accent?.[spec.stroke] ?? INK : spec.fill ? "none" : INK;
|
|
1758
2470
|
halo.push(`<path d="${spec.d}" fill="none" stroke="${HALO}" stroke-width="3.5"/>`);
|
|
1759
|
-
|
|
2471
|
+
const decoration = [
|
|
2472
|
+
["stroke-width", spec.strokeWidth],
|
|
2473
|
+
["stroke-dasharray", spec.strokeDasharray],
|
|
2474
|
+
["stroke-opacity", spec.strokeOpacity],
|
|
2475
|
+
["fill-opacity", spec.fillOpacity]
|
|
2476
|
+
].filter(([, value]) => value !== void 0).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
2477
|
+
draw.push(`<path d="${spec.d}" fill="${fill ?? "none"}" stroke="${stroke}" ${decoration}/>`);
|
|
1760
2478
|
}
|
|
1761
2479
|
return halo.join("") + draw.join("");
|
|
1762
2480
|
}
|
|
@@ -2374,12 +3092,17 @@ function LineEndingSelect({ side, value, onChange }) {
|
|
|
2374
3092
|
});
|
|
2375
3093
|
}
|
|
2376
3094
|
function FontFamilySelect({ value, onChange }) {
|
|
3095
|
+
const configured = useAnnotationFonts();
|
|
3096
|
+
const options = [...FONT_OPTIONS, ...configured.map((f) => ({
|
|
3097
|
+
v: f.key,
|
|
3098
|
+
label: f.label
|
|
3099
|
+
}))];
|
|
2377
3100
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownShell, {
|
|
2378
3101
|
trigger: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2379
3102
|
className: "text-fg text-sm",
|
|
2380
|
-
children:
|
|
3103
|
+
children: options.find((o) => o.v === value)?.label ?? value
|
|
2381
3104
|
}),
|
|
2382
|
-
children: (close) =>
|
|
3105
|
+
children: (close) => options.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionRow, {
|
|
2383
3106
|
selected: o.v === value,
|
|
2384
3107
|
onClick: () => {
|
|
2385
3108
|
onChange(o.v);
|
|
@@ -2391,17 +3114,33 @@ function FontFamilySelect({ value, onChange }) {
|
|
|
2391
3114
|
}
|
|
2392
3115
|
function FontSizeCombo({ value, onChange }) {
|
|
2393
3116
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3117
|
+
const rootRef = useOutsideClose(open, () => setOpen(false));
|
|
3118
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
3119
|
+
const commit = () => {
|
|
3120
|
+
if (draft === null) return;
|
|
3121
|
+
const n = parseInt(draft, 10);
|
|
3122
|
+
setDraft(null);
|
|
3123
|
+
if (Number.isFinite(n) && n > 0 && n !== value) onChange(n);
|
|
3124
|
+
};
|
|
2394
3125
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2395
|
-
ref:
|
|
3126
|
+
ref: rootRef,
|
|
2396
3127
|
className: "relative w-full",
|
|
2397
3128
|
children: [
|
|
2398
3129
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2399
3130
|
type: "number",
|
|
2400
3131
|
min: 1,
|
|
2401
|
-
value,
|
|
2402
|
-
onChange: (e) =>
|
|
2403
|
-
|
|
2404
|
-
|
|
3132
|
+
value: draft ?? value,
|
|
3133
|
+
onChange: (e) => setDraft(e.target.value),
|
|
3134
|
+
onBlur: commit,
|
|
3135
|
+
onKeyDown: (e) => {
|
|
3136
|
+
if (e.key === "Enter") {
|
|
3137
|
+
e.preventDefault();
|
|
3138
|
+
commit();
|
|
3139
|
+
setOpen(false);
|
|
3140
|
+
} else if (e.key === "Escape") {
|
|
3141
|
+
setDraft(null);
|
|
3142
|
+
setOpen(false);
|
|
3143
|
+
}
|
|
2405
3144
|
},
|
|
2406
3145
|
onClick: () => setOpen(true),
|
|
2407
3146
|
className: "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 pr-7 text-sm"
|
|
@@ -2422,6 +3161,7 @@ function FontSizeCombo({ value, onChange }) {
|
|
|
2422
3161
|
children: FONT_SIZES.map((sz) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionRow, {
|
|
2423
3162
|
selected: sz === value,
|
|
2424
3163
|
onClick: () => {
|
|
3164
|
+
setDraft(null);
|
|
2425
3165
|
onChange(sz);
|
|
2426
3166
|
setOpen(false);
|
|
2427
3167
|
},
|
|
@@ -2442,15 +3182,45 @@ function BlendModeSelect({ value, onChange }) {
|
|
|
2442
3182
|
}, mode.v))
|
|
2443
3183
|
});
|
|
2444
3184
|
}
|
|
2445
|
-
function Toggle({ active, title, onClick, children }) {
|
|
3185
|
+
function Toggle({ active, title, onClick, keepFocus, children }) {
|
|
2446
3186
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2447
3187
|
type: "button",
|
|
2448
3188
|
title,
|
|
2449
3189
|
onClick,
|
|
3190
|
+
onMouseDown: keepFocus ? (e) => e.preventDefault() : void 0,
|
|
2450
3191
|
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"}`,
|
|
2451
3192
|
children
|
|
2452
3193
|
});
|
|
2453
3194
|
}
|
|
3195
|
+
const isFormatSpec = (spec) => spec.key === "bold" || spec.key === "italic" || spec.key === "underline";
|
|
3196
|
+
/**
|
|
3197
|
+
* The format toggles a free-text kind declares, in one row. While the text
|
|
3198
|
+
* editor holds a range they read and write that range's runs (the plugin
|
|
3199
|
+
* routes `updateSelection`); otherwise the annotation's body. The buttons
|
|
3200
|
+
* keep focus in the editor so the range survives the click.
|
|
3201
|
+
*/
|
|
3202
|
+
function FormatToggles({ specs, values, mixed, onChange }) {
|
|
3203
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3204
|
+
label: (0, _embedpdf_react_i18n.useT)()("demo.formatLabel", { fallback: "Format" }),
|
|
3205
|
+
mixed: specs.some((s) => mixed.includes(s.key)),
|
|
3206
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3207
|
+
className: "flex gap-2",
|
|
3208
|
+
children: specs.map((spec) => {
|
|
3209
|
+
const active = values[spec.key] === true && !mixed.includes(spec.key);
|
|
3210
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
|
|
3211
|
+
title: spec.label,
|
|
3212
|
+
active,
|
|
3213
|
+
keepFocus: true,
|
|
3214
|
+
onClick: () => onChange({ [spec.key]: !active }),
|
|
3215
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3216
|
+
name: spec.key,
|
|
3217
|
+
size: 18
|
|
3218
|
+
})
|
|
3219
|
+
}, spec.key);
|
|
3220
|
+
})
|
|
3221
|
+
})
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
2454
3224
|
function PropControl({ spec, value, mixed, onChange }) {
|
|
2455
3225
|
switch (spec.key) {
|
|
2456
3226
|
case "color":
|
|
@@ -2585,6 +3355,9 @@ function PropControl({ spec, value, mixed, onChange }) {
|
|
|
2585
3355
|
})
|
|
2586
3356
|
});
|
|
2587
3357
|
}
|
|
3358
|
+
case "bold":
|
|
3359
|
+
case "italic":
|
|
3360
|
+
case "underline": return null;
|
|
2588
3361
|
case "blendMode": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2589
3362
|
label: spec.label,
|
|
2590
3363
|
mixed,
|
|
@@ -2632,7 +3405,12 @@ function AnnotationStylePanel() {
|
|
|
2632
3405
|
className: "text-fg-muted mb-4 text-[11px] font-semibold uppercase tracking-wide",
|
|
2633
3406
|
children: hasSel ? `${selected.length} selected` : `${activeToolId} defaults`
|
|
2634
3407
|
}),
|
|
2635
|
-
specs.map((spec) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3408
|
+
specs.map((spec) => spec.key === "bold" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormatToggles, {
|
|
3409
|
+
specs: specs.filter((s) => isFormatSpec(s)),
|
|
3410
|
+
values,
|
|
3411
|
+
mixed: hasSel ? sel.mixed : [],
|
|
3412
|
+
onChange: write
|
|
3413
|
+
}, "format") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PropControl, {
|
|
2636
3414
|
spec,
|
|
2637
3415
|
value: values[spec.key],
|
|
2638
3416
|
mixed: hasSel && sel.mixed.includes(spec.key),
|
|
@@ -3503,77 +4281,11 @@ function SearchPanel() {
|
|
|
3503
4281
|
});
|
|
3504
4282
|
}
|
|
3505
4283
|
//#endregion
|
|
3506
|
-
//#region src/
|
|
4284
|
+
//#region src/ui/stamps-panel.tsx
|
|
3507
4285
|
/**
|
|
3508
|
-
* The
|
|
3509
|
-
*
|
|
3510
|
-
*
|
|
3511
|
-
* Delivery: the library ships INSIDE whatever ships this viewer. The package
|
|
3512
|
-
* exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
|
|
3513
|
-
* so it travels through the module graph like any other code — every bundler
|
|
3514
|
-
* splits it into a chunk served from the app's own origin, the CDN snippet
|
|
3515
|
-
* carries it as a sibling chunk in its folder, and nothing is fetched from a
|
|
3516
|
-
* third party. `stamps.defaultLibrary` overrides that:
|
|
3517
|
-
* - `false` → no built-in library, no request (air-gapped);
|
|
3518
|
-
* - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
|
|
3519
|
-
*
|
|
3520
|
-
* Loading is LAZY (first open of the stamps panel, never at boot) and
|
|
3521
|
-
* locale-aware: the chrome locale leads, the browser languages break ties
|
|
3522
|
-
* for the locales the chrome does not translate itself, and a locale change
|
|
3523
|
-
* while a default library is loaded swaps it for the new one. The file names
|
|
3524
|
-
* itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
|
|
3525
|
-
*/
|
|
3526
|
-
/** The built-in library's PieceInfo id — excluded from persistence (it is
|
|
3527
|
-
* loaded again on every first open, in the locale of that moment). */
|
|
3528
|
-
const DEFAULT_LIBRARY_ID = "embedpdf-standard";
|
|
3529
|
-
/** Which shipped locale to show for the chrome's locale + the browser's. */
|
|
3530
|
-
function resolveStampsLocale(chromeLocale) {
|
|
3531
|
-
return (0, _embedpdf_react_i18n.negotiateLocale)(_embedpdf_default_stamps_library.LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
|
|
3532
|
-
}
|
|
3533
|
-
const fetchBytes = async (url) => {
|
|
3534
|
-
const response = await fetch(url);
|
|
3535
|
-
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
3536
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
3537
|
-
};
|
|
3538
|
-
/** The library bytes for `locale`: your URL when you gave one, otherwise the
|
|
3539
|
-
* copy that shipped with the viewer. Never anything else. */
|
|
3540
|
-
const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : (0, _embedpdf_default_stamps_library.loadDefaultLibrary)(locale);
|
|
3541
|
-
/** Per workspace: the locale the default library was last brought to, and
|
|
3542
|
-
* the in-flight work — the panel mounts and unmounts with the sidebar, so
|
|
3543
|
-
* this cannot live in component state. */
|
|
3544
|
-
const loads = /* @__PURE__ */ new WeakMap();
|
|
3545
|
-
/**
|
|
3546
|
-
* Bring the built-in library to `locale`. Idempotent per workspace and
|
|
3547
|
-
* locale; a locale change swaps the loaded default (remove, then import);
|
|
3548
|
-
* a default the user removed in this session stays removed — the locale is
|
|
3549
|
-
* recorded, nothing is re-added. An embedder that seeded a library under
|
|
3550
|
-
* the same id is left alone.
|
|
3551
|
-
*/
|
|
3552
|
-
function ensureDefaultLibrary(stamp, locale, source) {
|
|
3553
|
-
if (source === false) return Promise.resolve();
|
|
3554
|
-
const current = loads.get(stamp);
|
|
3555
|
-
if (current?.locale === locale) return current.promise;
|
|
3556
|
-
const run = async () => {
|
|
3557
|
-
await current?.promise.catch(() => void 0);
|
|
3558
|
-
const loaded = stamp.library(DEFAULT_LIBRARY_ID);
|
|
3559
|
-
if (loaded?.locale === locale) return;
|
|
3560
|
-
if (current && !loaded) return;
|
|
3561
|
-
if (loaded) await stamp.removeLibrary(DEFAULT_LIBRARY_ID);
|
|
3562
|
-
await stamp.importLibraryPdf(await defaultLibraryBytes(locale, source));
|
|
3563
|
-
};
|
|
3564
|
-
const promise = run();
|
|
3565
|
-
loads.set(stamp, {
|
|
3566
|
-
locale,
|
|
3567
|
-
promise
|
|
3568
|
-
});
|
|
3569
|
-
return promise;
|
|
3570
|
-
}
|
|
3571
|
-
//#endregion
|
|
3572
|
-
//#region src/ui/stamps-panel.tsx
|
|
3573
|
-
/**
|
|
3574
|
-
* The stamps sidebar (right panel) — v2's rubber-stamp sidebar on v3 parts.
|
|
3575
|
-
* The stamp plugin owns the libraries, the asset bytes and the cached
|
|
3576
|
-
* previews; this panel is the PICKER over them:
|
|
4286
|
+
* The stamps sidebar (right panel) — v2's rubber-stamp sidebar on v3 parts.
|
|
4287
|
+
* The stamp plugin owns the libraries, the asset bytes and the cached
|
|
4288
|
+
* previews; this panel is the PICKER over them:
|
|
3577
4289
|
*
|
|
3578
4290
|
* a library <select> ("All stamps" + one per library, shown once there
|
|
3579
4291
|
* are two) → a two-column grid of thumbnails → click a stamp → `armAsset`
|
|
@@ -3591,30 +4303,15 @@ function ensureDefaultLibrary(stamp, locale, source) {
|
|
|
3591
4303
|
* through the surface's open props.
|
|
3592
4304
|
*/
|
|
3593
4305
|
const ALL = "all";
|
|
3594
|
-
/** The user's libraries live in the browser (IndexedDB): restored on first
|
|
3595
|
-
* open, written on every change. The built-in set is never stored — it is
|
|
3596
|
-
* fetched again next time, in the locale of that moment. */
|
|
3597
|
-
const store = typeof indexedDB === "undefined" ? null : (0, _embedpdf_react_stamp.indexedDbByteStore)("embedpdf-stamps", { storeName: "libraries" });
|
|
3598
|
-
/** Restore runs ONCE per workspace — the panel mounts and unmounts with the
|
|
3599
|
-
* sidebar, so the guard cannot live in component state. */
|
|
3600
|
-
const restored = /* @__PURE__ */ new WeakMap();
|
|
3601
|
-
const restoreOnce = (stamp) => {
|
|
3602
|
-
let pending = restored.get(stamp);
|
|
3603
|
-
if (!pending) {
|
|
3604
|
-
pending = store ? (0, _embedpdf_react_stamp.restoreStampLibraries)(stamp, store) : Promise.resolve();
|
|
3605
|
-
restored.set(stamp, pending);
|
|
3606
|
-
}
|
|
3607
|
-
return pending;
|
|
3608
|
-
};
|
|
3609
4306
|
function StampsPanel() {
|
|
3610
4307
|
const t = (0, _embedpdf_react_i18n.useT)();
|
|
3611
4308
|
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
3612
|
-
const
|
|
4309
|
+
const { defaultLibrary, sidebar } = useStampsConfig();
|
|
4310
|
+
const libraries = (0, _embedpdf_react_stamp.useStampLibraries)({ kind: sidebar ?? ["stamps"] });
|
|
3613
4311
|
const assets = (0, _embedpdf_react_stamp.useStampAssets)();
|
|
3614
4312
|
const { armAsset } = (0, _embedpdf_react_stamp.useArmStampAsset)();
|
|
3615
4313
|
const { activeToolId } = (0, _embedpdf_react_interaction.useTool)();
|
|
3616
4314
|
const { locale } = (0, _embedpdf_react_i18n.useLocale)();
|
|
3617
|
-
const { defaultLibrary } = useStampsConfig();
|
|
3618
4315
|
const surface = (0, _embedpdf_react_shell.useSurface)("stamps");
|
|
3619
4316
|
const fileRef = (0, react.useRef)(null);
|
|
3620
4317
|
const [armedId, setArmedId] = (0, react.useState)(null);
|
|
@@ -3628,7 +4325,7 @@ function StampsPanel() {
|
|
|
3628
4325
|
(0, react.useEffect)(() => {
|
|
3629
4326
|
let live = true;
|
|
3630
4327
|
setBusy("loading");
|
|
3631
|
-
|
|
4328
|
+
restoreStampLibrariesOnce(stamp).then(() => ensureDefaultLibrary(stamp, resolveStampsLocale(locale), defaultLibrary)).catch((err) => {
|
|
3632
4329
|
console.error("[embedpdf] default stamps failed:", err);
|
|
3633
4330
|
if (live) setError(t("demo.stampsError"));
|
|
3634
4331
|
}).finally(() => live && setBusy(null));
|
|
@@ -3636,7 +4333,6 @@ function StampsPanel() {
|
|
|
3636
4333
|
live = false;
|
|
3637
4334
|
};
|
|
3638
4335
|
}, [stamp, locale]);
|
|
3639
|
-
(0, react.useEffect)(() => store ? (0, _embedpdf_react_stamp.persistStampLibraries)(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0, [stamp]);
|
|
3640
4336
|
(0, react.useEffect)(() => {
|
|
3641
4337
|
if (activeToolId !== "stamp") setArmedId(null);
|
|
3642
4338
|
}, [activeToolId]);
|
|
@@ -3807,6 +4503,293 @@ function StampAssetCell({ asset, armed, onArm }) {
|
|
|
3807
4503
|
});
|
|
3808
4504
|
}
|
|
3809
4505
|
//#endregion
|
|
4506
|
+
//#region src/ui/signatures-panel.tsx
|
|
4507
|
+
/**
|
|
4508
|
+
* The signatures sidebar (right panel) — Preview's signature sheet on v3
|
|
4509
|
+
* parts. One row per PERSON: a stamp library of kind `signatures` holding a
|
|
4510
|
+
* full signature and, optionally, initials. Nothing is stored beyond the
|
|
4511
|
+
* file; rename/export/delete are the library verbs.
|
|
4512
|
+
*
|
|
4513
|
+
* pick a mark → with a TARGET field ("sign here" was clicked, or a
|
|
4514
|
+
* signature widget selected) the mark goes into that field by the
|
|
4515
|
+
* plugin's mode; otherwise it ARMS — hover a page for the ghost, click a
|
|
4516
|
+
* signature field to sign it, click anywhere else to drop it as a stamp.
|
|
4517
|
+
*
|
|
4518
|
+
* Below the people: the signature fields of THIS document, signed or not,
|
|
4519
|
+
* with their verdicts — click an unsigned one to target it, a signed one to
|
|
4520
|
+
* inspect it.
|
|
4521
|
+
*/
|
|
4522
|
+
const fieldLabel = (field) => field.kind === "fqn" ? field.name : `#${field.fieldObjectNumber}`;
|
|
4523
|
+
function SignaturesPanel() {
|
|
4524
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
4525
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
4526
|
+
const signature = (0, _embedpdf_react_signature.useSignature)();
|
|
4527
|
+
const config = useSignaturesConfig();
|
|
4528
|
+
const rows = (0, _embedpdf_react_signature.useSignerRows)();
|
|
4529
|
+
const { target, busy } = (0, _embedpdf_react_signature.useSignatureTarget)();
|
|
4530
|
+
const { armAsset } = (0, _embedpdf_react_stamp.useArmStampAsset)();
|
|
4531
|
+
const { activeToolId } = (0, _embedpdf_react_interaction.useTool)();
|
|
4532
|
+
const maker = (0, _embedpdf_react_shell.useSurface)("signature-maker");
|
|
4533
|
+
const [armedId, setArmedId] = (0, react.useState)(null);
|
|
4534
|
+
const [error, setError] = (0, react.useState)(null);
|
|
4535
|
+
const [invalidating, setInvalidating] = (0, react.useState)(null);
|
|
4536
|
+
(0, _embedpdf_react_signature.useSignatureEvent)((event) => {
|
|
4537
|
+
if (event.type === "invalidating") setInvalidating(signature.signatureOf(event.field)?.fieldName ?? fieldLabel(event.field));
|
|
4538
|
+
else if (event.type === "validated" && !event.verdicts.some((v) => v.summary === "invalid" && v.modifications.basis === "working-copy")) setInvalidating(null);
|
|
4539
|
+
});
|
|
4540
|
+
(0, react.useEffect)(() => {
|
|
4541
|
+
restoreStampLibrariesOnce(stamp);
|
|
4542
|
+
}, [stamp]);
|
|
4543
|
+
(0, react.useEffect)(() => {
|
|
4544
|
+
if (activeToolId !== "stamp") setArmedId(null);
|
|
4545
|
+
}, [activeToolId]);
|
|
4546
|
+
const pick = (asset) => {
|
|
4547
|
+
setError(null);
|
|
4548
|
+
if (target) {
|
|
4549
|
+
signature.placeMark({ assetId: asset.id }, { field: target }).catch((err) => {
|
|
4550
|
+
console.error("[embedpdf] placing the mark failed:", err);
|
|
4551
|
+
setError(t("demo.signError"));
|
|
4552
|
+
});
|
|
4553
|
+
return;
|
|
4554
|
+
}
|
|
4555
|
+
setArmedId(asset.id);
|
|
4556
|
+
armAsset(asset.id).catch((err) => {
|
|
4557
|
+
console.error("[embedpdf] arm mark failed:", err);
|
|
4558
|
+
setArmedId(null);
|
|
4559
|
+
setError(t("demo.stampsArmError"));
|
|
4560
|
+
});
|
|
4561
|
+
};
|
|
4562
|
+
const canAddPerson = (config.libraries ?? "many") === "many" || rows.length === 0;
|
|
4563
|
+
const wantsInitials = (config.kinds ?? ["signature", "initials"]).includes("initials");
|
|
4564
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4565
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
4566
|
+
children: [
|
|
4567
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4568
|
+
className: "min-h-0 flex-1 overflow-y-auto p-3",
|
|
4569
|
+
children: [rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4570
|
+
className: "flex flex-col items-center gap-3 px-4 py-8 text-center",
|
|
4571
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4572
|
+
name: "signature",
|
|
4573
|
+
size: 32,
|
|
4574
|
+
className: "text-fg-muted"
|
|
4575
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4576
|
+
className: "text-fg-muted text-sm",
|
|
4577
|
+
children: t("demo.signaturesEmpty")
|
|
4578
|
+
})]
|
|
4579
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4580
|
+
className: "flex flex-col gap-3",
|
|
4581
|
+
children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignerRowView, {
|
|
4582
|
+
row,
|
|
4583
|
+
armedId,
|
|
4584
|
+
wantsInitials,
|
|
4585
|
+
onPick: pick,
|
|
4586
|
+
onAddInitials: () => maker.open({
|
|
4587
|
+
exclusive: "modal",
|
|
4588
|
+
props: { libraryId: row.libraryId }
|
|
4589
|
+
})
|
|
4590
|
+
}, row.libraryId))
|
|
4591
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentSignatures, {})]
|
|
4592
|
+
}),
|
|
4593
|
+
invalidating && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4594
|
+
className: "border-border-subtle shrink-0 border-t px-3 py-2 text-xs text-amber-700",
|
|
4595
|
+
children: t("demo.signaturesInvalidating", { params: { field: invalidating } })
|
|
4596
|
+
}),
|
|
4597
|
+
(error || target || armedId) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4598
|
+
className: `border-border-subtle shrink-0 border-t px-3 py-2 text-xs ${error ? "text-red-600" : "text-fg-muted"}`,
|
|
4599
|
+
children: error ?? (target ? busy ? t("demo.signSigning") : t("demo.signaturesTargetHint") : t("demo.signaturesArmedHint"))
|
|
4600
|
+
}),
|
|
4601
|
+
canAddPerson ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4602
|
+
className: "border-border-subtle shrink-0 border-t p-3",
|
|
4603
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4604
|
+
type: "button",
|
|
4605
|
+
onClick: () => maker.open({ exclusive: "modal" }),
|
|
4606
|
+
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",
|
|
4607
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4608
|
+
name: "plus",
|
|
4609
|
+
size: 16
|
|
4610
|
+
}), t("demo.signaturesNew")]
|
|
4611
|
+
})
|
|
4612
|
+
}) : null
|
|
4613
|
+
]
|
|
4614
|
+
});
|
|
4615
|
+
}
|
|
4616
|
+
function SignerRowView({ row, armedId, wantsInitials, onPick, onAddInitials }) {
|
|
4617
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
4618
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
4619
|
+
const [renaming, setRenaming] = (0, react.useState)(null);
|
|
4620
|
+
const exportPdf = () => {
|
|
4621
|
+
const bytes = stamp.exportLibrary(row.libraryId);
|
|
4622
|
+
if (!bytes) return;
|
|
4623
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
4624
|
+
const url = URL.createObjectURL(blob);
|
|
4625
|
+
const a = document.createElement("a");
|
|
4626
|
+
a.href = url;
|
|
4627
|
+
a.download = `${row.name || "signature"}.pdf`;
|
|
4628
|
+
a.click();
|
|
4629
|
+
URL.revokeObjectURL(url);
|
|
4630
|
+
};
|
|
4631
|
+
const commitRename = () => {
|
|
4632
|
+
const name = renaming?.trim();
|
|
4633
|
+
setRenaming(null);
|
|
4634
|
+
if (name && name !== row.name) stamp.updateLibrary(row.libraryId, { name });
|
|
4635
|
+
};
|
|
4636
|
+
const iconButton = "text-fg-muted hover:text-fg grid h-6 w-6 shrink-0 place-items-center rounded";
|
|
4637
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
4638
|
+
className: "border-border-subtle rounded-md border p-2",
|
|
4639
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4640
|
+
className: "flex items-center gap-1",
|
|
4641
|
+
children: [
|
|
4642
|
+
renaming !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4643
|
+
autoFocus: true,
|
|
4644
|
+
value: renaming,
|
|
4645
|
+
onChange: (e) => setRenaming(e.target.value),
|
|
4646
|
+
onBlur: commitRename,
|
|
4647
|
+
onKeyDown: (e) => {
|
|
4648
|
+
if (e.key === "Enter") commitRename();
|
|
4649
|
+
if (e.key === "Escape") setRenaming(null);
|
|
4650
|
+
},
|
|
4651
|
+
className: "border-border bg-surface text-fg min-w-0 flex-1 rounded border px-1.5 py-0.5 text-sm"
|
|
4652
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4653
|
+
className: "text-fg min-w-0 flex-1 truncate text-sm font-medium",
|
|
4654
|
+
children: row.name
|
|
4655
|
+
}),
|
|
4656
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4657
|
+
type: "button",
|
|
4658
|
+
onClick: () => setRenaming(row.name),
|
|
4659
|
+
title: t("demo.signaturesRename"),
|
|
4660
|
+
className: iconButton,
|
|
4661
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4662
|
+
name: "pencilMarker",
|
|
4663
|
+
size: 13
|
|
4664
|
+
})
|
|
4665
|
+
}),
|
|
4666
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4667
|
+
type: "button",
|
|
4668
|
+
onClick: exportPdf,
|
|
4669
|
+
title: t("demo.signaturesExport"),
|
|
4670
|
+
className: iconButton,
|
|
4671
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4672
|
+
name: "download",
|
|
4673
|
+
size: 13
|
|
4674
|
+
})
|
|
4675
|
+
}),
|
|
4676
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4677
|
+
type: "button",
|
|
4678
|
+
onClick: () => void stamp.removeLibrary(row.libraryId),
|
|
4679
|
+
title: t("demo.signaturesRemove"),
|
|
4680
|
+
className: iconButton,
|
|
4681
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4682
|
+
name: "trash",
|
|
4683
|
+
size: 13
|
|
4684
|
+
})
|
|
4685
|
+
})
|
|
4686
|
+
]
|
|
4687
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4688
|
+
className: "mt-2 grid grid-cols-[2fr_1fr] gap-2",
|
|
4689
|
+
children: [row.signatures.map((asset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkThumb, {
|
|
4690
|
+
asset,
|
|
4691
|
+
label: t("demo.signatureLabel"),
|
|
4692
|
+
armed: armedId === asset.id,
|
|
4693
|
+
onPick: () => onPick(asset)
|
|
4694
|
+
}, asset.id)), wantsInitials ? row.initials ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkThumb, {
|
|
4695
|
+
asset: row.initials,
|
|
4696
|
+
label: t("demo.initialsLabel"),
|
|
4697
|
+
armed: armedId === row.initials.id,
|
|
4698
|
+
onPick: () => onPick(row.initials)
|
|
4699
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4700
|
+
type: "button",
|
|
4701
|
+
onClick: onAddInitials,
|
|
4702
|
+
title: t("demo.signaturesAddInitials"),
|
|
4703
|
+
className: "border-border-subtle text-fg-muted hover:bg-hover flex h-16 items-center justify-center rounded-md border border-dashed text-xs",
|
|
4704
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4705
|
+
name: "plus",
|
|
4706
|
+
size: 14
|
|
4707
|
+
})
|
|
4708
|
+
}) : null]
|
|
4709
|
+
})]
|
|
4710
|
+
});
|
|
4711
|
+
}
|
|
4712
|
+
function MarkThumb({ asset, label, armed, onPick }) {
|
|
4713
|
+
const url = (0, _embedpdf_react_stamp.useStampAssetPreviewUrl)(asset.id);
|
|
4714
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4715
|
+
type: "button",
|
|
4716
|
+
onClick: onPick,
|
|
4717
|
+
title: label,
|
|
4718
|
+
"aria-pressed": armed,
|
|
4719
|
+
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"}`,
|
|
4720
|
+
children: url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
4721
|
+
src: url,
|
|
4722
|
+
alt: label,
|
|
4723
|
+
className: "max-h-full max-w-full object-contain"
|
|
4724
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4725
|
+
className: "text-fg-muted text-xs",
|
|
4726
|
+
children: label
|
|
4727
|
+
})
|
|
4728
|
+
});
|
|
4729
|
+
}
|
|
4730
|
+
/** The signature fields of this document, with their state. */
|
|
4731
|
+
function DocumentSignatures() {
|
|
4732
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
4733
|
+
const signature = (0, _embedpdf_react_signature.useSignature)();
|
|
4734
|
+
const snapshot = (0, _embedpdf_react_signature.useSignatureSnapshot)();
|
|
4735
|
+
const verdicts = (0, _embedpdf_react_signature.useSignatureVerdicts)();
|
|
4736
|
+
const { target } = (0, _embedpdf_react_signature.useSignatureTarget)();
|
|
4737
|
+
const [validating, setValidating] = (0, react.useState)(false);
|
|
4738
|
+
const fields = snapshot?.signatures ?? [];
|
|
4739
|
+
const verdictOf = (dto) => verdicts?.find((v) => v.signature.index === dto.index) ?? null;
|
|
4740
|
+
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");
|
|
4741
|
+
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";
|
|
4742
|
+
const isTarget = (dto) => !!target && (target.kind === "fqn" ? target.name === dto.fieldName : target.fieldObjectNumber === (dto.field.kind === "objectNumber" ? dto.field.fieldObjectNumber : -1));
|
|
4743
|
+
const validate = () => {
|
|
4744
|
+
setValidating(true);
|
|
4745
|
+
signature.validate().finally(() => setValidating(false));
|
|
4746
|
+
};
|
|
4747
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4748
|
+
className: "mt-4",
|
|
4749
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4750
|
+
className: "flex items-center justify-between",
|
|
4751
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4752
|
+
className: "text-fg-muted text-xs font-semibold uppercase tracking-wide",
|
|
4753
|
+
children: t("demo.signaturesInDocument")
|
|
4754
|
+
}), fields.some((f) => f.signed) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4755
|
+
type: "button",
|
|
4756
|
+
disabled: validating,
|
|
4757
|
+
onClick: validate,
|
|
4758
|
+
className: "text-accent text-xs hover:underline disabled:opacity-60",
|
|
4759
|
+
children: validating ? t("demo.signaturesValidating") : t("demo.signaturesValidate")
|
|
4760
|
+
}) : null]
|
|
4761
|
+
}), fields.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4762
|
+
className: "text-fg-muted mt-1 text-xs",
|
|
4763
|
+
children: t("demo.signaturesNone")
|
|
4764
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4765
|
+
className: "mt-1 flex flex-col gap-1",
|
|
4766
|
+
children: fields.map((dto) => {
|
|
4767
|
+
const verdict = verdictOf(dto);
|
|
4768
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4769
|
+
type: "button",
|
|
4770
|
+
onClick: () => dto.signed ? signature.inspect(dto.field) : signature.setTarget(dto.field),
|
|
4771
|
+
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" : ""}`,
|
|
4772
|
+
children: [
|
|
4773
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4774
|
+
name: dto.signed ? "check" : "signature",
|
|
4775
|
+
size: 14,
|
|
4776
|
+
className: dto.signed ? "text-green-600" : "text-fg-muted"
|
|
4777
|
+
}),
|
|
4778
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4779
|
+
className: "text-fg min-w-0 flex-1 truncate",
|
|
4780
|
+
children: dto.fieldName
|
|
4781
|
+
}),
|
|
4782
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4783
|
+
className: `shrink-0 text-right text-xs ${dto.signed ? verdictTone(verdict) : "text-fg-muted"}`,
|
|
4784
|
+
children: dto.signed ? verdictLabel(verdict) : t("demo.signaturesUnsigned")
|
|
4785
|
+
})
|
|
4786
|
+
]
|
|
4787
|
+
}) }, dto.index);
|
|
4788
|
+
})
|
|
4789
|
+
})]
|
|
4790
|
+
});
|
|
4791
|
+
}
|
|
4792
|
+
//#endregion
|
|
3810
4793
|
//#region src/ui/panels.tsx
|
|
3811
4794
|
/**
|
|
3812
4795
|
* The static chrome around the toolbars: the left/right sidebars (shell
|
|
@@ -3900,11 +4883,13 @@ function RightSidebar() {
|
|
|
3900
4883
|
const comment = (0, _embedpdf_react_shell.useSurface)("comment");
|
|
3901
4884
|
const style = (0, _embedpdf_react_shell.useSurface)("annotation-style");
|
|
3902
4885
|
const redaction = (0, _embedpdf_react_shell.useSurface)("redaction");
|
|
4886
|
+
const measurement = (0, _embedpdf_react_shell.useSurface)("measurement");
|
|
3903
4887
|
const stamps = (0, _embedpdf_react_shell.useSurface)("stamps");
|
|
3904
|
-
const
|
|
4888
|
+
const signatures = (0, _embedpdf_react_shell.useSurface)("signatures");
|
|
4889
|
+
const active = measurement.isOpen ? "measurement" : search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : stamps.isOpen ? "stamps" : signatures.isOpen ? "signatures" : null;
|
|
3905
4890
|
if (!active) return null;
|
|
3906
|
-
const titleKey = active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : active === "stamps" ? "demo.stampsTitle" : "demo.styleTitle";
|
|
3907
|
-
const close = active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : active === "stamps" ? stamps.close : style.close;
|
|
4891
|
+
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";
|
|
4892
|
+
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;
|
|
3908
4893
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
3909
4894
|
className: "border-border-subtle bg-surface flex w-72 shrink-0 flex-col border-l",
|
|
3910
4895
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -3921,7 +4906,7 @@ function RightSidebar() {
|
|
|
3921
4906
|
size: 16
|
|
3922
4907
|
})
|
|
3923
4908
|
})]
|
|
3924
|
-
}), active === "style" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StampsPanel, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommentsPanel, {}, documentId ?? "none")]
|
|
4909
|
+
}), active === "measurement" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MeasurementSection, {}, documentId ?? "none") : active === "style" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StampsPanel, {}) : active === "signatures" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignaturesPanel, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommentsPanel, {}, documentId ?? "none")]
|
|
3925
4910
|
});
|
|
3926
4911
|
}
|
|
3927
4912
|
function PageControls() {
|
|
@@ -4012,6 +4997,684 @@ function RedactConfirmModal() {
|
|
|
4012
4997
|
});
|
|
4013
4998
|
}
|
|
4014
4999
|
//#endregion
|
|
5000
|
+
//#region src/ui/sign-dialog.tsx
|
|
5001
|
+
/**
|
|
5002
|
+
* The sign dialog (`signature-sign` modal surface): a mark met a field and
|
|
5003
|
+
* the mode is `ask` — or a "sign here" flow wants the facts first. Shows
|
|
5004
|
+
* the mark as it will sit in the field, takes reason and location, offers
|
|
5005
|
+
* a certification when the config allows it, and either SEALS the field
|
|
5006
|
+
* (`signField`) or only draws the mark into it (`fillField`).
|
|
5007
|
+
*/
|
|
5008
|
+
function SignDialog() {
|
|
5009
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
5010
|
+
const surface = (0, _embedpdf_react_shell.useSurface)("signature-sign");
|
|
5011
|
+
const signature = (0, _embedpdf_react_signature.useSignature)();
|
|
5012
|
+
const [reason, setReason] = (0, react.useState)("");
|
|
5013
|
+
const [location, setLocation] = (0, react.useState)("");
|
|
5014
|
+
const [signKind, setSignKind] = (0, react.useState)("approval");
|
|
5015
|
+
const [busy, setBusy] = (0, react.useState)(null);
|
|
5016
|
+
const [error, setError] = (0, react.useState)(null);
|
|
5017
|
+
const field = surface.props?.field;
|
|
5018
|
+
const mark = surface.props?.mark;
|
|
5019
|
+
const preview = (0, _embedpdf_react_stamp.useStampAssetPreviewUrl)(mark && "assetId" in mark ? mark.assetId : null);
|
|
5020
|
+
if (!surface.isOpen || !field || !mark) return null;
|
|
5021
|
+
const attribution = {
|
|
5022
|
+
...reason.trim() ? { reason: reason.trim() } : {},
|
|
5023
|
+
...location.trim() ? { location: location.trim() } : {}
|
|
5024
|
+
};
|
|
5025
|
+
const run = async (kind) => {
|
|
5026
|
+
setBusy(kind);
|
|
5027
|
+
setError(null);
|
|
5028
|
+
try {
|
|
5029
|
+
if (kind === "sign") await signature.signField({
|
|
5030
|
+
field,
|
|
5031
|
+
mark,
|
|
5032
|
+
attribution,
|
|
5033
|
+
...signKind !== "approval" && signature.canCertify() ? { certify: { permission: Number(signKind.slice(-1)) } } : {}
|
|
5034
|
+
});
|
|
5035
|
+
else await signature.fillField(field, mark);
|
|
5036
|
+
surface.close();
|
|
5037
|
+
} catch (err) {
|
|
5038
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
5039
|
+
} finally {
|
|
5040
|
+
setBusy(null);
|
|
5041
|
+
}
|
|
5042
|
+
};
|
|
5043
|
+
const input = "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 text-sm";
|
|
5044
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5045
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
5046
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5047
|
+
className: "border-border bg-surface w-96 rounded-lg border p-4 shadow-xl",
|
|
5048
|
+
children: [
|
|
5049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
5050
|
+
className: "text-fg text-base font-semibold",
|
|
5051
|
+
children: t("demo.signTitle")
|
|
5052
|
+
}),
|
|
5053
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5054
|
+
className: "border-border-subtle mt-3 flex h-24 items-center justify-center rounded-md border p-2",
|
|
5055
|
+
children: preview ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
5056
|
+
src: preview,
|
|
5057
|
+
alt: "",
|
|
5058
|
+
className: "max-h-full max-w-full object-contain"
|
|
5059
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5060
|
+
className: "text-fg-muted text-sm",
|
|
5061
|
+
children: t("demo.signatureLabel")
|
|
5062
|
+
})
|
|
5063
|
+
}),
|
|
5064
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5065
|
+
className: "text-fg-muted mt-3 block text-xs",
|
|
5066
|
+
children: [t("demo.signReason"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5067
|
+
value: reason,
|
|
5068
|
+
onChange: (e) => setReason(e.target.value),
|
|
5069
|
+
className: input
|
|
5070
|
+
})]
|
|
5071
|
+
}),
|
|
5072
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5073
|
+
className: "text-fg-muted mt-2 block text-xs",
|
|
5074
|
+
children: [t("demo.signLocation"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5075
|
+
value: location,
|
|
5076
|
+
onChange: (e) => setLocation(e.target.value),
|
|
5077
|
+
className: input
|
|
5078
|
+
})]
|
|
5079
|
+
}),
|
|
5080
|
+
signature.canCertify() ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5081
|
+
className: "text-fg-muted mt-3 block text-xs",
|
|
5082
|
+
children: [t("demo.signKind"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
5083
|
+
value: signKind,
|
|
5084
|
+
onChange: (e) => setSignKind(e.target.value),
|
|
5085
|
+
className: input,
|
|
5086
|
+
children: [
|
|
5087
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5088
|
+
value: "approval",
|
|
5089
|
+
children: t("demo.signKindApproval")
|
|
5090
|
+
}),
|
|
5091
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5092
|
+
value: "certify-3",
|
|
5093
|
+
children: t("demo.signKindCertifyAnnotate")
|
|
5094
|
+
}),
|
|
5095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5096
|
+
value: "certify-2",
|
|
5097
|
+
children: t("demo.signKindCertifyFill")
|
|
5098
|
+
}),
|
|
5099
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5100
|
+
value: "certify-1",
|
|
5101
|
+
children: t("demo.signKindCertifyLocked")
|
|
5102
|
+
})
|
|
5103
|
+
]
|
|
5104
|
+
})]
|
|
5105
|
+
}) : null,
|
|
5106
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5107
|
+
className: "mt-2 text-sm text-red-600",
|
|
5108
|
+
children: error
|
|
5109
|
+
}) : null,
|
|
5110
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5111
|
+
className: "mt-4 flex justify-end gap-2",
|
|
5112
|
+
children: [
|
|
5113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5114
|
+
type: "button",
|
|
5115
|
+
disabled: busy != null,
|
|
5116
|
+
onClick: surface.close,
|
|
5117
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm",
|
|
5118
|
+
children: t("demo.cancel")
|
|
5119
|
+
}),
|
|
5120
|
+
signature.canFill() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5121
|
+
type: "button",
|
|
5122
|
+
disabled: busy != null,
|
|
5123
|
+
onClick: () => void run("fill"),
|
|
5124
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm disabled:opacity-60",
|
|
5125
|
+
children: t("demo.signFill")
|
|
5126
|
+
}) : null,
|
|
5127
|
+
signature.canSign() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5128
|
+
type: "button",
|
|
5129
|
+
disabled: busy != null,
|
|
5130
|
+
onClick: () => void run("sign"),
|
|
5131
|
+
className: "bg-accent text-on-accent rounded-md px-3 py-1.5 text-sm disabled:opacity-60",
|
|
5132
|
+
children: busy === "sign" ? t("demo.signSigning") : t("demo.signButton")
|
|
5133
|
+
}) : null
|
|
5134
|
+
]
|
|
5135
|
+
})
|
|
5136
|
+
]
|
|
5137
|
+
})
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
5140
|
+
//#endregion
|
|
5141
|
+
//#region src/ui/signature-bridge.tsx
|
|
5142
|
+
/**
|
|
5143
|
+
* The signature plugin's UI intents → shell surfaces. The plugin owns the
|
|
5144
|
+
* act and says WHAT happened; the chrome decides WHAT OPENS:
|
|
5145
|
+
*
|
|
5146
|
+
* target (a "sign here" click) → the signatures panel, right side
|
|
5147
|
+
* ask (mode 'ask' met a field) → the sign dialog (modal)
|
|
5148
|
+
* inspect (a signed field clicked) → the validation popover
|
|
5149
|
+
*
|
|
5150
|
+
* Mounted once per document view; renders nothing.
|
|
5151
|
+
*/
|
|
5152
|
+
function SignatureBridge() {
|
|
5153
|
+
const shell = (0, _embedpdf_react_shell.useShell)();
|
|
5154
|
+
(0, _embedpdf_react_signature.useSignatureEvent)((event) => {
|
|
5155
|
+
switch (event.type) {
|
|
5156
|
+
case "target":
|
|
5157
|
+
if (event.field) shell.open("signatures", { exclusive: "right" });
|
|
5158
|
+
return;
|
|
5159
|
+
case "ask":
|
|
5160
|
+
shell.open("signature-sign", {
|
|
5161
|
+
exclusive: "modal",
|
|
5162
|
+
props: {
|
|
5163
|
+
field: event.field,
|
|
5164
|
+
mark: event.mark
|
|
5165
|
+
}
|
|
5166
|
+
});
|
|
5167
|
+
return;
|
|
5168
|
+
case "inspect":
|
|
5169
|
+
shell.open("signature-inspector", { props: { field: event.field } });
|
|
5170
|
+
return;
|
|
5171
|
+
case "invalidating":
|
|
5172
|
+
shell.open("signatures", { exclusive: "right" });
|
|
5173
|
+
return;
|
|
5174
|
+
default: return;
|
|
5175
|
+
}
|
|
5176
|
+
});
|
|
5177
|
+
return null;
|
|
5178
|
+
}
|
|
5179
|
+
//#endregion
|
|
5180
|
+
//#region src/ui/signature-inspector.tsx
|
|
5181
|
+
/**
|
|
5182
|
+
* The validation popover (`signature-inspector` surface): what a signed
|
|
5183
|
+
* field's signature says and whether it holds — the four independent facts
|
|
5184
|
+
* (integrity, cryptography, trust, changes since) and the signer's claims,
|
|
5185
|
+
* anchored at the widget when its box is known, plus the signed revision as
|
|
5186
|
+
* a download (the exact bytes the signature covers). Mounted in the Stage
|
|
5187
|
+
* overlay so `<Anchored>` can project onto the page.
|
|
5188
|
+
*/
|
|
5189
|
+
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";
|
|
5190
|
+
/** A PDF date string (`D:YYYYMMDDHHmmSSZ` or with a zone offset) as a locale date; the raw text when it is not one. */
|
|
5191
|
+
const pdfDate = (raw) => {
|
|
5192
|
+
const m = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?(Z|[+-]\d{2}'?\d{2}'?)?/.exec(raw);
|
|
5193
|
+
if (!m) return raw;
|
|
5194
|
+
const [, y, mo = "01", d = "01", h = "00", mi = "00", s = "00", zone] = m;
|
|
5195
|
+
const offset = !zone || zone === "Z" ? "Z" : `${zone[0]}${zone.slice(1, 3)}:${zone.slice(3).replace(/'/g, "") || "00"}`;
|
|
5196
|
+
const date = /* @__PURE__ */ new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${offset}`);
|
|
5197
|
+
return Number.isNaN(date.getTime()) ? raw : date.toLocaleString();
|
|
5198
|
+
};
|
|
5199
|
+
const tone = (summary) => summary === "valid" ? "text-green-600" : summary === "invalid" ? "text-red-600" : summary === "valid-untrusted" ? "text-amber-600" : "text-fg-muted";
|
|
5200
|
+
function SignatureInspector() {
|
|
5201
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
5202
|
+
const surface = (0, _embedpdf_react_shell.useSurface)("signature-inspector");
|
|
5203
|
+
const signature = (0, _embedpdf_react_signature.useSignature)();
|
|
5204
|
+
const snapshot = (0, _embedpdf_react_signature.useSignatureSnapshot)();
|
|
5205
|
+
const verdicts = (0, _embedpdf_react_signature.useSignatureVerdicts)();
|
|
5206
|
+
const [validating, setValidating] = (0, react.useState)(false);
|
|
5207
|
+
const field = surface.props?.field;
|
|
5208
|
+
const dto = field ? signature.signatureOf(field) : null;
|
|
5209
|
+
const verdict = field ? signature.verdictOf(field) : null;
|
|
5210
|
+
const widget = dto?.widget ?? null;
|
|
5211
|
+
const box = (0, _embedpdf_react_runtime.useSelector)(_embedpdf_react_form.FormToken, (c) => widget && widget.annotObjectNumber > 0 ? c.fillItem(widget.annotObjectNumber)?.box ?? null : null);
|
|
5212
|
+
(0, react.useEffect)(() => {
|
|
5213
|
+
if (!surface.isOpen || !dto?.signed || verdicts) return;
|
|
5214
|
+
setValidating(true);
|
|
5215
|
+
signature.validate().finally(() => setValidating(false));
|
|
5216
|
+
}, [surface.isOpen, dto?.index]);
|
|
5217
|
+
if (!surface.isOpen || !field || !dto || !snapshot) return null;
|
|
5218
|
+
const revalidate = () => {
|
|
5219
|
+
setValidating(true);
|
|
5220
|
+
signature.validate().finally(() => setValidating(false));
|
|
5221
|
+
};
|
|
5222
|
+
const summary = verdict?.summary ?? null;
|
|
5223
|
+
const modifications = verdict?.modifications.verdict ?? null;
|
|
5224
|
+
const row = (label, value, className = "text-fg") => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5225
|
+
className: "flex items-baseline justify-between gap-3 text-xs",
|
|
5226
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5227
|
+
className: "text-fg-muted",
|
|
5228
|
+
children: label
|
|
5229
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5230
|
+
className: `text-right ${className}`,
|
|
5231
|
+
children: value
|
|
5232
|
+
})]
|
|
5233
|
+
});
|
|
5234
|
+
const card = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5235
|
+
className: "border-border bg-elevated w-72 rounded-lg border p-3 text-sm shadow-xl",
|
|
5236
|
+
children: [
|
|
5237
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5238
|
+
className: "flex items-center justify-between",
|
|
5239
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5240
|
+
className: "text-fg font-semibold",
|
|
5241
|
+
children: t("demo.inspectTitle")
|
|
5242
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5243
|
+
type: "button",
|
|
5244
|
+
onClick: surface.close,
|
|
5245
|
+
className: "text-fg-muted hover:bg-hover grid h-6 w-6 place-items-center rounded",
|
|
5246
|
+
"aria-label": t("demo.cancel"),
|
|
5247
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
5248
|
+
name: "x",
|
|
5249
|
+
size: 14
|
|
5250
|
+
})
|
|
5251
|
+
})]
|
|
5252
|
+
}),
|
|
5253
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5254
|
+
className: `mt-1 text-sm font-medium ${tone(summary)}`,
|
|
5255
|
+
children: validating ? t("demo.signaturesValidating") : t(verdictKey(verdict))
|
|
5256
|
+
}),
|
|
5257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5258
|
+
className: "mt-2 flex flex-col gap-1",
|
|
5259
|
+
children: [
|
|
5260
|
+
row(t("demo.inspectSigner"), dto.signer.name ?? "—"),
|
|
5261
|
+
dto.signer.reason ? row(t("demo.inspectReason"), dto.signer.reason) : null,
|
|
5262
|
+
dto.signer.location ? row(t("demo.inspectLocation"), dto.signer.location) : null,
|
|
5263
|
+
dto.signer.claimedTime ? row(t("demo.inspectTime"), pdfDate(dto.signer.claimedTime)) : null
|
|
5264
|
+
]
|
|
5265
|
+
}),
|
|
5266
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5267
|
+
className: "border-border-subtle mt-2 flex flex-col gap-1 border-t pt-2",
|
|
5268
|
+
children: [
|
|
5269
|
+
row(t("demo.inspectIntegrity"), verdict?.integrity ?? "—", tone(verdict?.integrity === "valid" ? "valid" : verdict?.integrity === "invalid" ? "invalid" : null)),
|
|
5270
|
+
row(t("demo.inspectCryptography"), verdict?.cryptography ?? "—"),
|
|
5271
|
+
row(t("demo.inspectTrust"), verdict?.trust ?? "—"),
|
|
5272
|
+
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"),
|
|
5273
|
+
verdict?.modifications.laterRevisions ? row(t("demo.inspectLaterRevisions"), String(verdict.modifications.laterRevisions)) : null
|
|
5274
|
+
]
|
|
5275
|
+
}),
|
|
5276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5277
|
+
className: "mt-3 flex justify-end gap-2",
|
|
5278
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5279
|
+
type: "button",
|
|
5280
|
+
disabled: validating,
|
|
5281
|
+
onClick: revalidate,
|
|
5282
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-2 py-1 text-xs disabled:opacity-60",
|
|
5283
|
+
children: t("demo.signaturesValidate")
|
|
5284
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DownloadRevision, { field })]
|
|
5285
|
+
})
|
|
5286
|
+
]
|
|
5287
|
+
});
|
|
5288
|
+
if (widget && widget.pageObjectNumber > 0 && box) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_anchored.Anchored, {
|
|
5289
|
+
anchor: {
|
|
5290
|
+
pon: widget.pageObjectNumber,
|
|
5291
|
+
bounds: box
|
|
5292
|
+
},
|
|
5293
|
+
placement: "bottom",
|
|
5294
|
+
gap: 8,
|
|
5295
|
+
children: card
|
|
5296
|
+
});
|
|
5297
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5298
|
+
className: "absolute right-4 top-4 z-40",
|
|
5299
|
+
children: card
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
/** The signed revision — the exact bytes the signature covers — as a file. */
|
|
5303
|
+
function DownloadRevision({ field }) {
|
|
5304
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
5305
|
+
const signature = (0, _embedpdf_react_signature.useSignature)();
|
|
5306
|
+
const dto = signature.signatureOf(field);
|
|
5307
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
5308
|
+
if (!dto || dto.revisionIndex === null) return null;
|
|
5309
|
+
const revisionIndex = dto.revisionIndex;
|
|
5310
|
+
const run = async () => {
|
|
5311
|
+
setBusy(true);
|
|
5312
|
+
try {
|
|
5313
|
+
const bytes = await signature.revisionBytes(revisionIndex);
|
|
5314
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
5315
|
+
const url = URL.createObjectURL(blob);
|
|
5316
|
+
const a = document.createElement("a");
|
|
5317
|
+
a.href = url;
|
|
5318
|
+
a.download = `${dto.fieldName}-signed.pdf`;
|
|
5319
|
+
a.click();
|
|
5320
|
+
URL.revokeObjectURL(url);
|
|
5321
|
+
} catch (err) {
|
|
5322
|
+
console.error("[embedpdf] signed revision download failed:", err);
|
|
5323
|
+
} finally {
|
|
5324
|
+
setBusy(false);
|
|
5325
|
+
}
|
|
5326
|
+
};
|
|
5327
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5328
|
+
type: "button",
|
|
5329
|
+
disabled: busy,
|
|
5330
|
+
onClick: () => void run(),
|
|
5331
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-2 py-1 text-xs disabled:opacity-60",
|
|
5332
|
+
children: t("demo.inspectDownload")
|
|
5333
|
+
});
|
|
5334
|
+
}
|
|
5335
|
+
//#endregion
|
|
5336
|
+
//#region src/ui/signature-maker.tsx
|
|
5337
|
+
/**
|
|
5338
|
+
* The mark maker (`signature-maker` modal surface): a person's signature and
|
|
5339
|
+
* initials, drawn, typed, or uploaded, saved as ONE library of kind
|
|
5340
|
+
* `signatures` — the file IS the person (Preview's model). Chrome code over
|
|
5341
|
+
* the stamp plugin's generic calls: `createLibrary` + `addAsset({ mark })`;
|
|
5342
|
+
* rename/export/delete are the library verbs. Opened on an existing library
|
|
5343
|
+
* (`props.libraryId`) it only adds the missing initials.
|
|
5344
|
+
*/
|
|
5345
|
+
const PAD = {
|
|
5346
|
+
width: 360,
|
|
5347
|
+
height: 120
|
|
5348
|
+
};
|
|
5349
|
+
const BUILTIN_FONTS = [{
|
|
5350
|
+
key: "helvetica-oblique",
|
|
5351
|
+
label: "Helvetica"
|
|
5352
|
+
}, {
|
|
5353
|
+
key: "times-italic",
|
|
5354
|
+
label: "Times"
|
|
5355
|
+
}];
|
|
5356
|
+
/** One pad: draw / type / upload, producing a `MarkSource` or null. */
|
|
5357
|
+
function MarkPad({ value, onChange, fonts, registerFont }) {
|
|
5358
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
5359
|
+
const [tab, setTab] = (0, react.useState)("draw");
|
|
5360
|
+
const [strokes, setStrokes] = (0, react.useState)([]);
|
|
5361
|
+
const [text, setText] = (0, react.useState)("");
|
|
5362
|
+
const [font, setFont] = (0, react.useState)(fonts[0]?.key ?? "helvetica-oblique");
|
|
5363
|
+
const [fileName, setFileName] = (0, react.useState)(null);
|
|
5364
|
+
const canvas = (0, react.useRef)(null);
|
|
5365
|
+
const drawing = (0, react.useRef)(null);
|
|
5366
|
+
(0, react.useEffect)(() => {
|
|
5367
|
+
const el = canvas.current;
|
|
5368
|
+
if (!el) return;
|
|
5369
|
+
const dpr = window.devicePixelRatio || 1;
|
|
5370
|
+
el.width = PAD.width * dpr;
|
|
5371
|
+
el.height = PAD.height * dpr;
|
|
5372
|
+
const ctx = el.getContext("2d");
|
|
5373
|
+
if (!ctx) return;
|
|
5374
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
5375
|
+
ctx.clearRect(0, 0, PAD.width, PAD.height);
|
|
5376
|
+
ctx.lineCap = "round";
|
|
5377
|
+
ctx.lineJoin = "round";
|
|
5378
|
+
ctx.lineWidth = 2.5;
|
|
5379
|
+
ctx.strokeStyle = getComputedStyle(el).color || "#000";
|
|
5380
|
+
for (const stroke of strokes) {
|
|
5381
|
+
ctx.beginPath();
|
|
5382
|
+
stroke.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
5383
|
+
ctx.stroke();
|
|
5384
|
+
}
|
|
5385
|
+
}, [strokes, tab]);
|
|
5386
|
+
const point = (e) => {
|
|
5387
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
5388
|
+
return {
|
|
5389
|
+
x: e.clientX - r.left,
|
|
5390
|
+
y: e.clientY - r.top
|
|
5391
|
+
};
|
|
5392
|
+
};
|
|
5393
|
+
const commitStrokes = (next) => {
|
|
5394
|
+
setStrokes(next);
|
|
5395
|
+
onChange(next.length ? {
|
|
5396
|
+
kind: "ink",
|
|
5397
|
+
strokes: next,
|
|
5398
|
+
color: "#000000",
|
|
5399
|
+
strokeWidth: 2.5
|
|
5400
|
+
} : null);
|
|
5401
|
+
};
|
|
5402
|
+
const commitText = async (next, key) => {
|
|
5403
|
+
setText(next);
|
|
5404
|
+
setFont(key);
|
|
5405
|
+
if (!next.trim()) {
|
|
5406
|
+
onChange(null);
|
|
5407
|
+
return;
|
|
5408
|
+
}
|
|
5409
|
+
await registerFont(key);
|
|
5410
|
+
onChange({
|
|
5411
|
+
kind: "text",
|
|
5412
|
+
text: next.trim(),
|
|
5413
|
+
fontFamily: key,
|
|
5414
|
+
color: "#000000",
|
|
5415
|
+
fontSize: 36
|
|
5416
|
+
});
|
|
5417
|
+
};
|
|
5418
|
+
const pickFile = (file) => {
|
|
5419
|
+
setFileName(file.name);
|
|
5420
|
+
onChange(file.type === "application/pdf" ? {
|
|
5421
|
+
kind: "pdf",
|
|
5422
|
+
source: file,
|
|
5423
|
+
pageIndex: 0
|
|
5424
|
+
} : {
|
|
5425
|
+
kind: "image",
|
|
5426
|
+
source: file
|
|
5427
|
+
});
|
|
5428
|
+
};
|
|
5429
|
+
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"}`;
|
|
5430
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5431
|
+
className: "flex flex-col gap-2",
|
|
5432
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5433
|
+
className: "flex items-center gap-1",
|
|
5434
|
+
children: [
|
|
5435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5436
|
+
type: "button",
|
|
5437
|
+
className: tabClass("draw"),
|
|
5438
|
+
onClick: () => setTab("draw"),
|
|
5439
|
+
children: t("demo.makerDraw")
|
|
5440
|
+
}),
|
|
5441
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5442
|
+
type: "button",
|
|
5443
|
+
className: tabClass("type"),
|
|
5444
|
+
onClick: () => setTab("type"),
|
|
5445
|
+
children: t("demo.makerType")
|
|
5446
|
+
}),
|
|
5447
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5448
|
+
type: "button",
|
|
5449
|
+
className: tabClass("upload"),
|
|
5450
|
+
onClick: () => setTab("upload"),
|
|
5451
|
+
children: t("demo.makerUpload")
|
|
5452
|
+
}),
|
|
5453
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex-1" }),
|
|
5454
|
+
value ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5455
|
+
type: "button",
|
|
5456
|
+
onClick: () => {
|
|
5457
|
+
commitStrokes([]);
|
|
5458
|
+
setText("");
|
|
5459
|
+
setFileName(null);
|
|
5460
|
+
onChange(null);
|
|
5461
|
+
},
|
|
5462
|
+
className: "text-fg-muted hover:text-fg text-xs",
|
|
5463
|
+
children: t("demo.makerClear")
|
|
5464
|
+
}) : null
|
|
5465
|
+
]
|
|
5466
|
+
}), tab === "draw" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
|
|
5467
|
+
ref: canvas,
|
|
5468
|
+
style: {
|
|
5469
|
+
width: PAD.width,
|
|
5470
|
+
height: PAD.height,
|
|
5471
|
+
touchAction: "none"
|
|
5472
|
+
},
|
|
5473
|
+
className: "border-border-subtle bg-surface text-fg rounded-md border",
|
|
5474
|
+
onPointerDown: (e) => {
|
|
5475
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
5476
|
+
drawing.current = [point(e)];
|
|
5477
|
+
setStrokes((s) => [...s, drawing.current]);
|
|
5478
|
+
},
|
|
5479
|
+
onPointerMove: (e) => {
|
|
5480
|
+
if (!drawing.current) return;
|
|
5481
|
+
drawing.current.push(point(e));
|
|
5482
|
+
setStrokes((s) => [...s.slice(0, -1), [...drawing.current]]);
|
|
5483
|
+
},
|
|
5484
|
+
onPointerUp: () => {
|
|
5485
|
+
if (!drawing.current) return;
|
|
5486
|
+
const done = drawing.current;
|
|
5487
|
+
drawing.current = null;
|
|
5488
|
+
setStrokes((s) => {
|
|
5489
|
+
const next = [...s.slice(0, -1), done];
|
|
5490
|
+
commitStrokes(next);
|
|
5491
|
+
return next;
|
|
5492
|
+
});
|
|
5493
|
+
}
|
|
5494
|
+
}) : tab === "type" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5495
|
+
className: "flex flex-col gap-2",
|
|
5496
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5497
|
+
value: text,
|
|
5498
|
+
placeholder: t("demo.makerTypePlaceholder"),
|
|
5499
|
+
onChange: (e) => void commitText(e.target.value, font),
|
|
5500
|
+
className: "border-border bg-surface text-fg w-full rounded border px-2 py-1.5 text-sm"
|
|
5501
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5502
|
+
className: "text-fg-muted flex items-center gap-2 text-xs",
|
|
5503
|
+
children: [t("demo.makerFont"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
5504
|
+
value: font,
|
|
5505
|
+
onChange: (e) => void commitText(text, e.target.value),
|
|
5506
|
+
className: "border-border bg-surface text-fg rounded border px-2 py-1 text-xs",
|
|
5507
|
+
children: fonts.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5508
|
+
value: f.key,
|
|
5509
|
+
children: f.label
|
|
5510
|
+
}, f.key))
|
|
5511
|
+
})]
|
|
5512
|
+
})]
|
|
5513
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5514
|
+
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",
|
|
5515
|
+
children: [fileName ?? t("demo.makerUploadHint"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5516
|
+
type: "file",
|
|
5517
|
+
accept: "image/png,image/jpeg,application/pdf",
|
|
5518
|
+
className: "hidden",
|
|
5519
|
+
onChange: (e) => {
|
|
5520
|
+
const file = e.currentTarget.files?.[0];
|
|
5521
|
+
e.currentTarget.value = "";
|
|
5522
|
+
if (file) pickFile(file);
|
|
5523
|
+
}
|
|
5524
|
+
})]
|
|
5525
|
+
})]
|
|
5526
|
+
});
|
|
5527
|
+
}
|
|
5528
|
+
function SignatureMakerModal() {
|
|
5529
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
5530
|
+
const surface = (0, _embedpdf_react_shell.useSurface)("signature-maker");
|
|
5531
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
5532
|
+
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
5533
|
+
const config = useSignaturesConfig();
|
|
5534
|
+
const [name, setName] = (0, react.useState)("");
|
|
5535
|
+
const [signatureMark, setSignatureMark] = (0, react.useState)(null);
|
|
5536
|
+
const [initialsMark, setInitialsMark] = (0, react.useState)(null);
|
|
5537
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
5538
|
+
const [error, setError] = (0, react.useState)(null);
|
|
5539
|
+
const existingId = surface.props?.libraryId;
|
|
5540
|
+
const existing = existingId ? stamp.library(existingId) : null;
|
|
5541
|
+
const wantsInitials = (config.kinds ?? ["signature", "initials"]).includes("initials");
|
|
5542
|
+
const fonts = [...(config.fonts ?? []).map((f) => ({
|
|
5543
|
+
key: f.key,
|
|
5544
|
+
label: f.label
|
|
5545
|
+
})), ...BUILTIN_FONTS];
|
|
5546
|
+
const registered = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
5547
|
+
const registerFont = async (key) => {
|
|
5548
|
+
const spec = config.fonts?.find((f) => f.key === key);
|
|
5549
|
+
if (!spec || registered.current.has(key) || !kernel.engine.fonts) return;
|
|
5550
|
+
const response = await fetch(spec.url);
|
|
5551
|
+
if (!response.ok) throw new Error(`${spec.url}: HTTP ${response.status}`);
|
|
5552
|
+
await kernel.engine.fonts.register({
|
|
5553
|
+
key,
|
|
5554
|
+
data: new Uint8Array(await response.arrayBuffer())
|
|
5555
|
+
});
|
|
5556
|
+
registered.current.add(key);
|
|
5557
|
+
};
|
|
5558
|
+
if (!surface.isOpen) return null;
|
|
5559
|
+
const canSave = existing ? initialsMark != null : name.trim().length > 0 && signatureMark != null;
|
|
5560
|
+
const save = async () => {
|
|
5561
|
+
setBusy(true);
|
|
5562
|
+
setError(null);
|
|
5563
|
+
try {
|
|
5564
|
+
if (existing) await stamp.addAsset({
|
|
5565
|
+
libraryId: existing.id,
|
|
5566
|
+
name: _embedpdf_react_signature.INITIALS_MARK_NAME,
|
|
5567
|
+
label: t("demo.initialsLabel"),
|
|
5568
|
+
mark: initialsMark
|
|
5569
|
+
});
|
|
5570
|
+
else {
|
|
5571
|
+
const libraryId = await stamp.createLibrary(name.trim(), { kind: _embedpdf_react_signature.SIGNATURES_LIBRARY_KIND });
|
|
5572
|
+
await stamp.addAsset({
|
|
5573
|
+
libraryId,
|
|
5574
|
+
name: _embedpdf_react_signature.SIGNATURE_MARK_NAME,
|
|
5575
|
+
label: t("demo.signatureLabel"),
|
|
5576
|
+
mark: signatureMark
|
|
5577
|
+
});
|
|
5578
|
+
if (initialsMark) await stamp.addAsset({
|
|
5579
|
+
libraryId,
|
|
5580
|
+
name: _embedpdf_react_signature.INITIALS_MARK_NAME,
|
|
5581
|
+
label: t("demo.initialsLabel"),
|
|
5582
|
+
mark: initialsMark
|
|
5583
|
+
});
|
|
5584
|
+
}
|
|
5585
|
+
setName("");
|
|
5586
|
+
setSignatureMark(null);
|
|
5587
|
+
setInitialsMark(null);
|
|
5588
|
+
surface.close();
|
|
5589
|
+
} catch (err) {
|
|
5590
|
+
console.error("[embedpdf] saving the signature failed:", err);
|
|
5591
|
+
setError(t("demo.makerError"));
|
|
5592
|
+
} finally {
|
|
5593
|
+
setBusy(false);
|
|
5594
|
+
}
|
|
5595
|
+
};
|
|
5596
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5597
|
+
className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
|
|
5598
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5599
|
+
className: "border-border bg-surface w-[26rem] rounded-lg border p-4 shadow-xl",
|
|
5600
|
+
children: [
|
|
5601
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5602
|
+
className: "flex items-center justify-between",
|
|
5603
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
5604
|
+
className: "text-fg text-base font-semibold",
|
|
5605
|
+
children: existing ? t("demo.signaturesAddInitials") : t("demo.makerTitle")
|
|
5606
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5607
|
+
type: "button",
|
|
5608
|
+
onClick: surface.close,
|
|
5609
|
+
className: "text-fg-muted hover:bg-hover grid h-7 w-7 place-items-center rounded-md",
|
|
5610
|
+
"aria-label": t("demo.cancel"),
|
|
5611
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
5612
|
+
name: "x",
|
|
5613
|
+
size: 16
|
|
5614
|
+
})
|
|
5615
|
+
})]
|
|
5616
|
+
}),
|
|
5617
|
+
existing ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5618
|
+
className: "text-fg-muted mt-3 block text-xs",
|
|
5619
|
+
children: [t("demo.makerName"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5620
|
+
value: name,
|
|
5621
|
+
placeholder: t("demo.makerNamePlaceholder"),
|
|
5622
|
+
onChange: (e) => setName(e.target.value),
|
|
5623
|
+
className: "border-border bg-surface text-fg mt-1 w-full rounded border px-2 py-1.5 text-sm"
|
|
5624
|
+
})]
|
|
5625
|
+
}),
|
|
5626
|
+
existing ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5627
|
+
className: "mt-3",
|
|
5628
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5629
|
+
className: "text-fg mb-1 text-xs font-semibold uppercase tracking-wide",
|
|
5630
|
+
children: t("demo.signatureLabel")
|
|
5631
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkPad, {
|
|
5632
|
+
value: signatureMark,
|
|
5633
|
+
onChange: setSignatureMark,
|
|
5634
|
+
fonts,
|
|
5635
|
+
registerFont
|
|
5636
|
+
})]
|
|
5637
|
+
}),
|
|
5638
|
+
wantsInitials ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5639
|
+
className: "mt-3",
|
|
5640
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
5641
|
+
className: "text-fg mb-1 text-xs font-semibold uppercase tracking-wide",
|
|
5642
|
+
children: [t("demo.initialsLabel"), existing ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5643
|
+
className: "text-fg-muted ml-1 font-normal normal-case tracking-normal",
|
|
5644
|
+
children: t("demo.makerOptional")
|
|
5645
|
+
})]
|
|
5646
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkPad, {
|
|
5647
|
+
value: initialsMark,
|
|
5648
|
+
onChange: setInitialsMark,
|
|
5649
|
+
fonts,
|
|
5650
|
+
registerFont
|
|
5651
|
+
})]
|
|
5652
|
+
}) : null,
|
|
5653
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5654
|
+
className: "mt-2 text-sm text-red-600",
|
|
5655
|
+
children: error
|
|
5656
|
+
}) : null,
|
|
5657
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5658
|
+
className: "mt-4 flex justify-end gap-2",
|
|
5659
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5660
|
+
type: "button",
|
|
5661
|
+
disabled: busy,
|
|
5662
|
+
onClick: surface.close,
|
|
5663
|
+
className: "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm",
|
|
5664
|
+
children: t("demo.cancel")
|
|
5665
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5666
|
+
type: "button",
|
|
5667
|
+
disabled: busy || !canSave,
|
|
5668
|
+
onClick: () => void save(),
|
|
5669
|
+
className: "bg-accent text-on-accent rounded-md px-3 py-1.5 text-sm disabled:opacity-60",
|
|
5670
|
+
children: busy ? t("demo.makerSaving") : t("demo.makerSave")
|
|
5671
|
+
})]
|
|
5672
|
+
})
|
|
5673
|
+
]
|
|
5674
|
+
})
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5677
|
+
//#endregion
|
|
4015
5678
|
//#region src/ui/document-boot.tsx
|
|
4016
5679
|
/**
|
|
4017
5680
|
* Boot-state panes for the document area — what renders INSTEAD of the Stage
|
|
@@ -4253,9 +5916,10 @@ function Shell() {
|
|
|
4253
5916
|
className: "w-full"
|
|
4254
5917
|
})
|
|
4255
5918
|
});
|
|
4256
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5919
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationFontsProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4257
5920
|
className: "bg-app text-fg flex h-full flex-col",
|
|
4258
5921
|
children: [
|
|
5922
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StampLibraryStore, {}),
|
|
4259
5923
|
(frame.header ?? true) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("slot", { name: "header" }),
|
|
4260
5924
|
(frame.tabs ?? "always") !== "never" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("slot", {
|
|
4261
5925
|
name: "tabs",
|
|
@@ -4275,6 +5939,7 @@ function Shell() {
|
|
|
4275
5939
|
overlay: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4276
5940
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationStrip, {}),
|
|
4277
5941
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectionStrip, {}),
|
|
5942
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignatureInspector, {}),
|
|
4278
5943
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_selection.SelectionHandles, {}),
|
|
4279
5944
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_scrollbar.Scrollbar, { axis: "y" }),
|
|
4280
5945
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_embedpdf_react_scrollbar.Scrollbar, { axis: "x" })
|
|
@@ -4293,12 +5958,16 @@ function Shell() {
|
|
|
4293
5958
|
]
|
|
4294
5959
|
}),
|
|
4295
5960
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RightSidebar, {}),
|
|
4296
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactConfirmModal, {})
|
|
5961
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedactConfirmModal, {}),
|
|
5962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalibrationDialog, {}),
|
|
5963
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignatureBridge, {}),
|
|
5964
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignDialog, {}),
|
|
5965
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignatureMakerModal, {})
|
|
4297
5966
|
] })
|
|
4298
5967
|
}),
|
|
4299
5968
|
toolbarEdge === "bottom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeBand, { edge: "bottom" }), toolbarBand] })
|
|
4300
5969
|
]
|
|
4301
|
-
});
|
|
5970
|
+
}) });
|
|
4302
5971
|
}
|
|
4303
5972
|
//#endregion
|
|
4304
5973
|
//#region src/viewer.tsx
|
|
@@ -4321,7 +5990,7 @@ function Shell() {
|
|
|
4321
5990
|
* changing these props on a mounted viewer does not rebuild the workspace.
|
|
4322
5991
|
*/
|
|
4323
5992
|
/** Lazy built-in packs — a strings override for one of these wraps its loader. */
|
|
4324
|
-
const BUILTIN_LOADERS = { es: () => Promise.resolve().then(() => require("./es-
|
|
5993
|
+
const BUILTIN_LOADERS = { es: () => Promise.resolve().then(() => require("./es-BXLlBo2b.cjs")).then((m) => m.es) };
|
|
4325
5994
|
const isDict = (v) => typeof v === "object" && v !== null;
|
|
4326
5995
|
/**
|
|
4327
5996
|
* Deep-merge `over` into `base` (immutably), expanding dotted keys —
|
|
@@ -4365,7 +6034,7 @@ function Booting() {
|
|
|
4365
6034
|
})
|
|
4366
6035
|
});
|
|
4367
6036
|
}
|
|
4368
|
-
function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, stamps, theme, themeTarget, onViewer }) {
|
|
6037
|
+
function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale = "auto", disabledCategories, chrome, stamps, signatures, annotations, theme, themeTarget, onViewer }) {
|
|
4369
6038
|
const [resolved] = (0, react.useState)(() => {
|
|
4370
6039
|
const byId = new Map(require_commands.defaultCommands.map((c) => [c.id, c]));
|
|
4371
6040
|
for (const c of commands ?? []) byId.set(c.id, c);
|
|
@@ -4397,6 +6066,8 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
4397
6066
|
chrome: resolvedChrome,
|
|
4398
6067
|
icons: icons ?? {},
|
|
4399
6068
|
stamps: stamps ?? {},
|
|
6069
|
+
signatures: signatures ?? {},
|
|
6070
|
+
annotations: annotations ?? {},
|
|
4400
6071
|
i18n: {
|
|
4401
6072
|
locales,
|
|
4402
6073
|
loaders,
|
|
@@ -4462,8 +6133,15 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
|
|
|
4462
6133
|
}
|
|
4463
6134
|
} }),
|
|
4464
6135
|
(0, _embedpdf_react_form.formPlugin)(),
|
|
6136
|
+
(0, _embedpdf_react_signature.signaturePlugin)({
|
|
6137
|
+
mode: resolved.signatures.mode,
|
|
6138
|
+
signer: resolved.signatures.signer,
|
|
6139
|
+
trust: resolved.signatures.trust,
|
|
6140
|
+
allowCertify: resolved.signatures.allowCertify
|
|
6141
|
+
}),
|
|
4465
6142
|
(0, _embedpdf_react_link.linkPlugin)(),
|
|
4466
6143
|
(0, _embedpdf_react_redaction.redactionPlugin)(),
|
|
6144
|
+
(0, _embedpdf_react_measurement.measurementPlugin)(),
|
|
4467
6145
|
(0, _embedpdf_react_search.searchPlugin)(),
|
|
4468
6146
|
demoToolsPlugin(),
|
|
4469
6147
|
(0, _embedpdf_react_i18n.i18nPlugin)({
|
|
@@ -4532,6 +6210,12 @@ Object.defineProperty(exports, "InteractionToken", {
|
|
|
4532
6210
|
return _embedpdf_react_interaction.InteractionToken;
|
|
4533
6211
|
}
|
|
4534
6212
|
});
|
|
6213
|
+
Object.defineProperty(exports, "MeasurementToken", {
|
|
6214
|
+
enumerable: true,
|
|
6215
|
+
get: function() {
|
|
6216
|
+
return _embedpdf_react_measurement.MeasurementToken;
|
|
6217
|
+
}
|
|
6218
|
+
});
|
|
4535
6219
|
Object.defineProperty(exports, "MetadataToken", {
|
|
4536
6220
|
enumerable: true,
|
|
4537
6221
|
get: function() {
|
|
@@ -4562,6 +6246,12 @@ Object.defineProperty(exports, "ShellToken", {
|
|
|
4562
6246
|
return _embedpdf_react_shell.ShellToken;
|
|
4563
6247
|
}
|
|
4564
6248
|
});
|
|
6249
|
+
Object.defineProperty(exports, "SignatureToken", {
|
|
6250
|
+
enumerable: true,
|
|
6251
|
+
get: function() {
|
|
6252
|
+
return _embedpdf_react_signature.SignatureToken;
|
|
6253
|
+
}
|
|
6254
|
+
});
|
|
4565
6255
|
Object.defineProperty(exports, "StageToken", {
|
|
4566
6256
|
enumerable: true,
|
|
4567
6257
|
get: function() {
|
|
@@ -4586,6 +6276,12 @@ Object.defineProperty(exports, "chromeHelpers", {
|
|
|
4586
6276
|
return _embedpdf_react_toolbar.chromeHelpers;
|
|
4587
6277
|
}
|
|
4588
6278
|
});
|
|
6279
|
+
Object.defineProperty(exports, "createTestSigner", {
|
|
6280
|
+
enumerable: true,
|
|
6281
|
+
get: function() {
|
|
6282
|
+
return _embedpdf_react_signature.createTestSigner;
|
|
6283
|
+
}
|
|
6284
|
+
});
|
|
4589
6285
|
exports.createViewerHandle = createViewerHandle;
|
|
4590
6286
|
Object.defineProperty(exports, "custom", {
|
|
4591
6287
|
enumerable: true,
|
|
@@ -4608,12 +6304,36 @@ Object.defineProperty(exports, "group", {
|
|
|
4608
6304
|
return _embedpdf_react_toolbar.group;
|
|
4609
6305
|
}
|
|
4610
6306
|
});
|
|
6307
|
+
Object.defineProperty(exports, "indexedDbKeyStore", {
|
|
6308
|
+
enumerable: true,
|
|
6309
|
+
get: function() {
|
|
6310
|
+
return _embedpdf_react_signature.indexedDbKeyStore;
|
|
6311
|
+
}
|
|
6312
|
+
});
|
|
4611
6313
|
Object.defineProperty(exports, "item", {
|
|
4612
6314
|
enumerable: true,
|
|
4613
6315
|
get: function() {
|
|
4614
6316
|
return _embedpdf_react_toolbar.item;
|
|
4615
6317
|
}
|
|
4616
6318
|
});
|
|
6319
|
+
Object.defineProperty(exports, "memoryKeyStore", {
|
|
6320
|
+
enumerable: true,
|
|
6321
|
+
get: function() {
|
|
6322
|
+
return _embedpdf_react_signature.memoryKeyStore;
|
|
6323
|
+
}
|
|
6324
|
+
});
|
|
6325
|
+
Object.defineProperty(exports, "personalSigner", {
|
|
6326
|
+
enumerable: true,
|
|
6327
|
+
get: function() {
|
|
6328
|
+
return _embedpdf_react_signature.personalSigner;
|
|
6329
|
+
}
|
|
6330
|
+
});
|
|
6331
|
+
Object.defineProperty(exports, "remoteSigner", {
|
|
6332
|
+
enumerable: true,
|
|
6333
|
+
get: function() {
|
|
6334
|
+
return _embedpdf_react_signature.remoteSigner;
|
|
6335
|
+
}
|
|
6336
|
+
});
|
|
4617
6337
|
Object.defineProperty(exports, "removeItems", {
|
|
4618
6338
|
enumerable: true,
|
|
4619
6339
|
get: function() {
|
|
@@ -4633,5 +6353,11 @@ Object.defineProperty(exports, "validateChrome", {
|
|
|
4633
6353
|
return _embedpdf_react_toolbar.validateChrome;
|
|
4634
6354
|
}
|
|
4635
6355
|
});
|
|
6356
|
+
Object.defineProperty(exports, "webCryptoSigner", {
|
|
6357
|
+
enumerable: true,
|
|
6358
|
+
get: function() {
|
|
6359
|
+
return _embedpdf_react_signature.webCryptoSigner;
|
|
6360
|
+
}
|
|
6361
|
+
});
|
|
4636
6362
|
|
|
4637
6363
|
//# sourceMappingURL=index.cjs.map
|