@unlev/exeq 0.1.2 → 0.1.3
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 +74 -88
- package/dist/index.css +289 -5
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +22 -7
- package/dist/index.d.ts +22 -7
- package/dist/index.js +626 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +598 -312
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -4
- package/dist/embed.css +0 -1
- package/dist/embed.global.js +0 -419
- package/dist/styles.css +0 -566
package/dist/index.js
CHANGED
|
@@ -45,12 +45,14 @@ __export(lib_exports, {
|
|
|
45
45
|
generateFilledPdf: () => generateFilledPdf,
|
|
46
46
|
getSignerColor: () => getSignerColor,
|
|
47
47
|
postPdfToCallback: () => postPdfToCallback,
|
|
48
|
-
renderPdfPages: () => renderPdfPages
|
|
48
|
+
renderPdfPages: () => renderPdfPages,
|
|
49
|
+
uniqueLabel: () => uniqueLabel
|
|
49
50
|
});
|
|
50
51
|
module.exports = __toCommonJS(lib_exports);
|
|
51
52
|
|
|
52
53
|
// src/components/pdf-builder/DesignerView.tsx
|
|
53
|
-
var
|
|
54
|
+
var import_react3 = require("react");
|
|
55
|
+
var import_react_dom = require("react-dom");
|
|
54
56
|
|
|
55
57
|
// src/types/pdf-builder.ts
|
|
56
58
|
function generateId() {
|
|
@@ -106,14 +108,44 @@ var FIELD_DEFAULTS = {
|
|
|
106
108
|
height: 5,
|
|
107
109
|
fontSize: 12,
|
|
108
110
|
placeholder: "Initials"
|
|
111
|
+
},
|
|
112
|
+
blackout: {
|
|
113
|
+
width: 20,
|
|
114
|
+
height: 3,
|
|
115
|
+
fontSize: 12,
|
|
116
|
+
placeholder: ""
|
|
117
|
+
},
|
|
118
|
+
whiteout: {
|
|
119
|
+
width: 20,
|
|
120
|
+
height: 3,
|
|
121
|
+
fontSize: 12,
|
|
122
|
+
placeholder: ""
|
|
109
123
|
}
|
|
110
124
|
};
|
|
111
|
-
|
|
125
|
+
var TYPE_LABELS = {
|
|
126
|
+
text: "Text Field",
|
|
127
|
+
signature: "Signature",
|
|
128
|
+
"signed-date": "Signed Date",
|
|
129
|
+
checkbox: "Checkbox",
|
|
130
|
+
initials: "Initials",
|
|
131
|
+
blackout: "Blackout",
|
|
132
|
+
whiteout: "Whiteout"
|
|
133
|
+
};
|
|
134
|
+
function uniqueLabel(desired, existingLabels) {
|
|
135
|
+
const lower = desired.toLowerCase();
|
|
136
|
+
if (!existingLabels.some((l) => l.toLowerCase() === lower)) return desired;
|
|
137
|
+
let i = 2;
|
|
138
|
+
while (existingLabels.some((l) => l.toLowerCase() === `${lower} ${i}`)) i++;
|
|
139
|
+
return `${desired} ${i}`;
|
|
140
|
+
}
|
|
141
|
+
function createField(type, assignee, page, x, y, existingFields) {
|
|
112
142
|
const defaults = FIELD_DEFAULTS[type];
|
|
143
|
+
const baseLabel = TYPE_LABELS[type];
|
|
144
|
+
const existingLabels = existingFields?.map((f) => f.label) ?? [];
|
|
113
145
|
return {
|
|
114
146
|
id: generateId(),
|
|
115
147
|
type,
|
|
116
|
-
label:
|
|
148
|
+
label: uniqueLabel(baseLabel, existingLabels),
|
|
117
149
|
placeholder: defaults.placeholder || "",
|
|
118
150
|
required: true,
|
|
119
151
|
assignee,
|
|
@@ -168,6 +200,7 @@ function PdfViewer({
|
|
|
168
200
|
onFieldMove,
|
|
169
201
|
onFieldResize,
|
|
170
202
|
onPageClick,
|
|
203
|
+
onDropField,
|
|
171
204
|
mode,
|
|
172
205
|
currentSigner,
|
|
173
206
|
renderFieldContent
|
|
@@ -185,6 +218,21 @@ function PdfViewer({
|
|
|
185
218
|
onSelectField(null);
|
|
186
219
|
}
|
|
187
220
|
}, [onPageClick, onSelectField]);
|
|
221
|
+
const handleDragOver = (0, import_react.useCallback)((e) => {
|
|
222
|
+
if (e.dataTransfer.types.includes("application/exeq-field-type")) {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
e.dataTransfer.dropEffect = "copy";
|
|
225
|
+
}
|
|
226
|
+
}, []);
|
|
227
|
+
const handleDrop = (0, import_react.useCallback)((e, pageIndex) => {
|
|
228
|
+
const fieldType = e.dataTransfer.getData("application/exeq-field-type");
|
|
229
|
+
if (!fieldType || !onDropField) return;
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
232
|
+
const x = (e.clientX - rect.left) / rect.width * 100;
|
|
233
|
+
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
234
|
+
onDropField(pageIndex, x, y, fieldType);
|
|
235
|
+
}, [onDropField]);
|
|
188
236
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
|
|
189
237
|
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
190
238
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
@@ -193,6 +241,8 @@ function PdfViewer({
|
|
|
193
241
|
className: "pdf-page",
|
|
194
242
|
style: { aspectRatio: `${page.width} / ${page.height}` },
|
|
195
243
|
onClick: (e) => handlePageClick(e, pageIndex),
|
|
244
|
+
onDragOver: handleDragOver,
|
|
245
|
+
onDrop: (e) => handleDrop(e, pageIndex),
|
|
196
246
|
"data-page": pageIndex,
|
|
197
247
|
children: [
|
|
198
248
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -238,6 +288,7 @@ function FieldOverlayItem({
|
|
|
238
288
|
const dragStartRef = (0, import_react.useRef)(null);
|
|
239
289
|
const resizeStartRef = (0, import_react.useRef)(null);
|
|
240
290
|
const color = getSignerColor(field.assignee);
|
|
291
|
+
const isRedact = field.type === "blackout" || field.type === "whiteout";
|
|
241
292
|
const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
|
|
242
293
|
const isPreFilled = !isEditable && !!field.value;
|
|
243
294
|
const handleMouseDown = (0, import_react.useCallback)((e) => {
|
|
@@ -303,14 +354,15 @@ function FieldOverlayItem({
|
|
|
303
354
|
"div",
|
|
304
355
|
{
|
|
305
356
|
ref: overlayRef,
|
|
306
|
-
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""}`,
|
|
357
|
+
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""}`,
|
|
307
358
|
style: {
|
|
308
359
|
left: `${field.x}%`,
|
|
309
360
|
top: `${field.y}%`,
|
|
310
361
|
width: `${field.width}%`,
|
|
311
362
|
height: `${field.height}%`,
|
|
312
|
-
borderColor: color,
|
|
313
|
-
|
|
363
|
+
borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
|
|
364
|
+
borderStyle: isRedact ? "dashed" : "solid",
|
|
365
|
+
backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isSelected ? `${color}22` : `${color}11`,
|
|
314
366
|
cursor: mode === "designer" ? "move" : "default"
|
|
315
367
|
},
|
|
316
368
|
onClick: (e) => {
|
|
@@ -319,7 +371,7 @@ function FieldOverlayItem({
|
|
|
319
371
|
},
|
|
320
372
|
onMouseDown: handleMouseDown,
|
|
321
373
|
children: [
|
|
322
|
-
mode === "designer" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
374
|
+
mode === "designer" && !isRedact && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
323
375
|
renderContent ? renderContent(field) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
|
|
324
376
|
mode === "designer" && isSelected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
325
377
|
"div",
|
|
@@ -335,8 +387,15 @@ function FieldOverlayItem({
|
|
|
335
387
|
|
|
336
388
|
// src/components/pdf-builder/FieldPropertyPanel.tsx
|
|
337
389
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
390
|
+
var INK_COLORS = [
|
|
391
|
+
{ value: "#000000", label: "Black" },
|
|
392
|
+
{ value: "#1a56db", label: "Blue" }
|
|
393
|
+
];
|
|
338
394
|
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
339
395
|
const color = getSignerColor(field.assignee);
|
|
396
|
+
const isRedactField = field.type === "blackout" || field.type === "whiteout";
|
|
397
|
+
const showFontSize = field.type === "text" || field.type === "signed-date";
|
|
398
|
+
const showInkColor = field.type === "text" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
|
|
340
399
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
|
|
341
400
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
|
|
342
401
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: { color }, children: field.label }),
|
|
@@ -353,7 +412,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
353
412
|
}
|
|
354
413
|
)
|
|
355
414
|
] }),
|
|
356
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
415
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
357
416
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Field Type" }),
|
|
358
417
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
359
418
|
"select",
|
|
@@ -387,7 +446,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
387
446
|
}
|
|
388
447
|
)
|
|
389
448
|
] }),
|
|
390
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
449
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
391
450
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Assigned To" }),
|
|
392
451
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
393
452
|
"select",
|
|
@@ -398,7 +457,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
398
457
|
}
|
|
399
458
|
)
|
|
400
459
|
] }),
|
|
401
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
460
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
402
461
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Placeholder" }),
|
|
403
462
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
404
463
|
"input",
|
|
@@ -409,7 +468,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
409
468
|
}
|
|
410
469
|
)
|
|
411
470
|
] }),
|
|
412
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-field", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
471
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-field", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
413
472
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
414
473
|
"input",
|
|
415
474
|
{
|
|
@@ -420,7 +479,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
420
479
|
),
|
|
421
480
|
"Required"
|
|
422
481
|
] }) }),
|
|
423
|
-
|
|
482
|
+
showFontSize && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
424
483
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
|
|
425
484
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
426
485
|
"input",
|
|
@@ -433,118 +492,26 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
433
492
|
}
|
|
434
493
|
)
|
|
435
494
|
] }),
|
|
436
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field
|
|
437
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
438
|
-
|
|
439
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
440
|
-
"input",
|
|
441
|
-
{
|
|
442
|
-
type: "number",
|
|
443
|
-
min: "1",
|
|
444
|
-
max: "100",
|
|
445
|
-
step: "0.5",
|
|
446
|
-
value: field.width,
|
|
447
|
-
onChange: (e) => onUpdate(field.id, { width: Number(e.target.value) })
|
|
448
|
-
}
|
|
449
|
-
)
|
|
450
|
-
] }),
|
|
451
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
452
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Height (%)" }),
|
|
453
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
454
|
-
"input",
|
|
455
|
-
{
|
|
456
|
-
type: "number",
|
|
457
|
-
min: "1",
|
|
458
|
-
max: "100",
|
|
459
|
-
step: "0.5",
|
|
460
|
-
value: field.height,
|
|
461
|
-
onChange: (e) => onUpdate(field.id, { height: Number(e.target.value) })
|
|
462
|
-
}
|
|
463
|
-
)
|
|
464
|
-
] })
|
|
465
|
-
] })
|
|
466
|
-
] });
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
470
|
-
var import_react2 = require("react");
|
|
471
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
472
|
-
function SignerRoleSelector({
|
|
473
|
-
roles,
|
|
474
|
-
activeRole,
|
|
475
|
-
onSetActiveRole,
|
|
476
|
-
onAddRole,
|
|
477
|
-
onRemoveRole
|
|
478
|
-
}) {
|
|
479
|
-
const [isAdding, setIsAdding] = (0, import_react2.useState)(false);
|
|
480
|
-
const [newRoleName, setNewRoleName] = (0, import_react2.useState)("");
|
|
481
|
-
const handleAdd = () => {
|
|
482
|
-
if (newRoleName.trim()) {
|
|
483
|
-
onAddRole(newRoleName.trim());
|
|
484
|
-
setNewRoleName("");
|
|
485
|
-
setIsAdding(false);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signer-role-selector", children: [
|
|
489
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "signer-role-label", children: "Signer Roles" }),
|
|
490
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signer-role-list", children: [
|
|
491
|
-
roles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
495
|
+
showInkColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Ink Color" }),
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
492
498
|
"button",
|
|
493
499
|
{
|
|
494
|
-
className: `
|
|
495
|
-
style: {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
color: role === activeRole ? "#fff" : getSignerColor(role)
|
|
499
|
-
},
|
|
500
|
-
onClick: () => onSetActiveRole(role),
|
|
501
|
-
children: [
|
|
502
|
-
role,
|
|
503
|
-
roles.length > 1 && role !== "Sender" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
504
|
-
"span",
|
|
505
|
-
{
|
|
506
|
-
className: "signer-role-remove",
|
|
507
|
-
onClick: (e) => {
|
|
508
|
-
e.stopPropagation();
|
|
509
|
-
onRemoveRole(role);
|
|
510
|
-
},
|
|
511
|
-
children: "\xD7"
|
|
512
|
-
}
|
|
513
|
-
)
|
|
514
|
-
]
|
|
500
|
+
className: `ink-color-swatch ${(field.inkColor || "#000000") === c.value ? "active" : ""}`,
|
|
501
|
+
style: { backgroundColor: c.value },
|
|
502
|
+
onClick: () => onUpdate(field.id, { inkColor: c.value }),
|
|
503
|
+
title: c.label
|
|
515
504
|
},
|
|
516
|
-
|
|
517
|
-
))
|
|
518
|
-
isAdding ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signer-role-add-input", children: [
|
|
519
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
520
|
-
"input",
|
|
521
|
-
{
|
|
522
|
-
type: "text",
|
|
523
|
-
value: newRoleName,
|
|
524
|
-
onChange: (e) => setNewRoleName(e.target.value),
|
|
525
|
-
onKeyDown: (e) => e.key === "Enter" && handleAdd(),
|
|
526
|
-
placeholder: "Role name",
|
|
527
|
-
autoFocus: true
|
|
528
|
-
}
|
|
529
|
-
),
|
|
530
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleAdd, children: "Add" }),
|
|
531
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => setIsAdding(false), children: "Cancel" })
|
|
532
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
533
|
-
"button",
|
|
534
|
-
{
|
|
535
|
-
className: "signer-role-add-btn",
|
|
536
|
-
onClick: () => setIsAdding(true),
|
|
537
|
-
children: "+ Add Role"
|
|
538
|
-
}
|
|
539
|
-
)
|
|
505
|
+
c.value
|
|
506
|
+
)) })
|
|
540
507
|
] })
|
|
541
508
|
] });
|
|
542
509
|
}
|
|
543
510
|
|
|
544
511
|
// src/components/pdf-builder/SignatureCanvas.tsx
|
|
545
|
-
var
|
|
512
|
+
var import_react2 = require("react");
|
|
546
513
|
var import_perfect_freehand = require("perfect-freehand");
|
|
547
|
-
var
|
|
514
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
548
515
|
function getSvgPathFromStroke(stroke) {
|
|
549
516
|
if (!stroke.length) return "";
|
|
550
517
|
const d = stroke.reduce(
|
|
@@ -563,13 +530,14 @@ function SignatureCanvas({
|
|
|
563
530
|
height = 150,
|
|
564
531
|
onSign,
|
|
565
532
|
initialValue,
|
|
566
|
-
className
|
|
533
|
+
className,
|
|
534
|
+
inkColor = "#000000"
|
|
567
535
|
}) {
|
|
568
|
-
const canvasRef = (0,
|
|
569
|
-
const [paths, setPaths] = (0,
|
|
570
|
-
const [currentPath, setCurrentPath] = (0,
|
|
571
|
-
const [isEmpty, setIsEmpty] = (0,
|
|
572
|
-
(0,
|
|
536
|
+
const canvasRef = (0, import_react2.useRef)(null);
|
|
537
|
+
const [paths, setPaths] = (0, import_react2.useState)([]);
|
|
538
|
+
const [currentPath, setCurrentPath] = (0, import_react2.useState)(null);
|
|
539
|
+
const [isEmpty, setIsEmpty] = (0, import_react2.useState)(!initialValue);
|
|
540
|
+
(0, import_react2.useEffect)(() => {
|
|
573
541
|
if (initialValue && canvasRef.current) {
|
|
574
542
|
const ctx = canvasRef.current.getContext("2d");
|
|
575
543
|
const img = new Image();
|
|
@@ -580,7 +548,7 @@ function SignatureCanvas({
|
|
|
580
548
|
img.src = initialValue;
|
|
581
549
|
}
|
|
582
550
|
}, [initialValue, width, height]);
|
|
583
|
-
(0,
|
|
551
|
+
(0, import_react2.useEffect)(() => {
|
|
584
552
|
const canvas = canvasRef.current;
|
|
585
553
|
if (!canvas) return;
|
|
586
554
|
const ctx = canvas.getContext("2d");
|
|
@@ -595,11 +563,11 @@ function SignatureCanvas({
|
|
|
595
563
|
});
|
|
596
564
|
const pathStr = getSvgPathFromStroke(stroke);
|
|
597
565
|
const path2d = new Path2D(pathStr);
|
|
598
|
-
ctx.fillStyle =
|
|
566
|
+
ctx.fillStyle = inkColor;
|
|
599
567
|
ctx.fill(path2d);
|
|
600
568
|
}
|
|
601
569
|
}, [paths, currentPath, width, height]);
|
|
602
|
-
const getPoint = (0,
|
|
570
|
+
const getPoint = (0, import_react2.useCallback)((e) => {
|
|
603
571
|
const rect = canvasRef.current.getBoundingClientRect();
|
|
604
572
|
return [
|
|
605
573
|
e.clientX - rect.left,
|
|
@@ -607,17 +575,17 @@ function SignatureCanvas({
|
|
|
607
575
|
e.pressure
|
|
608
576
|
];
|
|
609
577
|
}, []);
|
|
610
|
-
const handlePointerDown = (0,
|
|
578
|
+
const handlePointerDown = (0, import_react2.useCallback)((e) => {
|
|
611
579
|
e.preventDefault();
|
|
612
580
|
e.target.setPointerCapture(e.pointerId);
|
|
613
581
|
setCurrentPath([getPoint(e)]);
|
|
614
582
|
}, [getPoint]);
|
|
615
|
-
const handlePointerMove = (0,
|
|
583
|
+
const handlePointerMove = (0, import_react2.useCallback)((e) => {
|
|
616
584
|
if (!currentPath) return;
|
|
617
585
|
e.preventDefault();
|
|
618
586
|
setCurrentPath([...currentPath, getPoint(e)]);
|
|
619
587
|
}, [currentPath, getPoint]);
|
|
620
|
-
const handlePointerUp = (0,
|
|
588
|
+
const handlePointerUp = (0, import_react2.useCallback)(() => {
|
|
621
589
|
if (!currentPath) return;
|
|
622
590
|
setPaths((prev) => [...prev, currentPath]);
|
|
623
591
|
setCurrentPath(null);
|
|
@@ -628,7 +596,7 @@ function SignatureCanvas({
|
|
|
628
596
|
}
|
|
629
597
|
});
|
|
630
598
|
}, [currentPath, onSign]);
|
|
631
|
-
const handleClear = (0,
|
|
599
|
+
const handleClear = (0, import_react2.useCallback)(() => {
|
|
632
600
|
setPaths([]);
|
|
633
601
|
setCurrentPath(null);
|
|
634
602
|
setIsEmpty(true);
|
|
@@ -639,8 +607,8 @@ function SignatureCanvas({
|
|
|
639
607
|
onSign("");
|
|
640
608
|
}
|
|
641
609
|
}, [width, height, onSign]);
|
|
642
|
-
return /* @__PURE__ */ (0,
|
|
643
|
-
/* @__PURE__ */ (0,
|
|
610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `signature-canvas-wrapper ${className || ""}`, children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
644
612
|
"canvas",
|
|
645
613
|
{
|
|
646
614
|
ref: canvasRef,
|
|
@@ -653,29 +621,67 @@ function SignatureCanvas({
|
|
|
653
621
|
style: { touchAction: "none" }
|
|
654
622
|
}
|
|
655
623
|
),
|
|
656
|
-
/* @__PURE__ */ (0,
|
|
657
|
-
!isEmpty && /* @__PURE__ */ (0,
|
|
658
|
-
isEmpty && /* @__PURE__ */ (0,
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signature-canvas-actions", children: [
|
|
625
|
+
!isEmpty && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: handleClear, className: "signature-clear-btn", children: "Clear" }),
|
|
626
|
+
isEmpty && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "signature-hint", children: "Draw your signature above" })
|
|
659
627
|
] })
|
|
660
628
|
] });
|
|
661
629
|
}
|
|
662
630
|
|
|
631
|
+
// src/utils/apiKey.ts
|
|
632
|
+
var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
633
|
+
"exeq_live_2024"
|
|
634
|
+
]);
|
|
635
|
+
function isValidApiKey(key) {
|
|
636
|
+
if (!key) return false;
|
|
637
|
+
return VALID_KEYS.has(key);
|
|
638
|
+
}
|
|
639
|
+
|
|
663
640
|
// src/components/pdf-builder/DesignerView.tsx
|
|
664
|
-
var
|
|
641
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
642
|
+
var FIELD_TYPE_META = [
|
|
643
|
+
{ type: "text", label: "Text", icon: "T" },
|
|
644
|
+
{ type: "signature", label: "Signature", icon: "\u270D" },
|
|
645
|
+
{ type: "signed-date", label: "Date", icon: "\u{1F4C5}" },
|
|
646
|
+
{ type: "checkbox", label: "Checkbox", icon: "\u2611" },
|
|
647
|
+
{ type: "initials", label: "Initials", icon: "IN" },
|
|
648
|
+
{ type: "blackout", label: "Blackout", icon: "\u25A0", group: "redact" },
|
|
649
|
+
{ type: "whiteout", label: "Whiteout", icon: "\u25A1", group: "redact" }
|
|
650
|
+
];
|
|
665
651
|
function DesignerView({
|
|
652
|
+
apiKey,
|
|
666
653
|
initialPdfUrl,
|
|
667
654
|
initialTemplate,
|
|
668
|
-
onSave
|
|
655
|
+
onSave,
|
|
656
|
+
hideHeader,
|
|
657
|
+
headerPortalRef
|
|
669
658
|
} = {}) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
659
|
+
if (!isValidApiKey(apiKey)) {
|
|
660
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "designer-layout", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "empty-state", children: [
|
|
661
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Invalid API Key" }),
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
|
|
663
|
+
"A valid API key is required to use Exeq. Visit ",
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: "https://exeq.org", children: "exeq.org" }),
|
|
665
|
+
" to get one."
|
|
666
|
+
] })
|
|
667
|
+
] }) });
|
|
668
|
+
}
|
|
669
|
+
const [pages, setPages] = (0, import_react3.useState)([]);
|
|
670
|
+
const [fields, setFields] = (0, import_react3.useState)(initialTemplate?.fields ?? []);
|
|
671
|
+
const [selectedFieldId, setSelectedFieldId] = (0, import_react3.useState)(null);
|
|
672
|
+
const [signerRoles, setSignerRoles] = (0, import_react3.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
673
|
+
const [activeRole, setActiveRole] = (0, import_react3.useState)("Sender");
|
|
674
|
+
const [activeFieldType, setActiveFieldType] = (0, import_react3.useState)("text");
|
|
675
|
+
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
676
|
+
const [pdfSource, setPdfSource] = (0, import_react3.useState)(null);
|
|
677
|
+
const [rightTab, setRightTab] = (0, import_react3.useState)("properties");
|
|
678
|
+
const [isAddingRole, setIsAddingRole] = (0, import_react3.useState)(false);
|
|
679
|
+
const [newRoleName, setNewRoleName] = (0, import_react3.useState)("");
|
|
680
|
+
const [draggingFieldType, setDraggingFieldType] = (0, import_react3.useState)(null);
|
|
681
|
+
const [panelWidth, setPanelWidth] = (0, import_react3.useState)(380);
|
|
682
|
+
const dragGhostRef = (0, import_react3.useRef)(null);
|
|
683
|
+
const resizingRef = (0, import_react3.useRef)(false);
|
|
684
|
+
(0, import_react3.useEffect)(() => {
|
|
679
685
|
const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
|
|
680
686
|
if (pdfUrl) {
|
|
681
687
|
loadPdf(pdfUrl);
|
|
@@ -699,7 +705,7 @@ function DesignerView({
|
|
|
699
705
|
window.addEventListener("message", handleMessage);
|
|
700
706
|
return () => window.removeEventListener("message", handleMessage);
|
|
701
707
|
}, [initialPdfUrl, initialTemplate]);
|
|
702
|
-
const loadPdf = (0,
|
|
708
|
+
const loadPdf = (0, import_react3.useCallback)(async (source) => {
|
|
703
709
|
setLoading(true);
|
|
704
710
|
try {
|
|
705
711
|
const rendered = await renderPdfPages(source);
|
|
@@ -711,7 +717,7 @@ function DesignerView({
|
|
|
711
717
|
setLoading(false);
|
|
712
718
|
}
|
|
713
719
|
}, []);
|
|
714
|
-
const handleFileUpload = (0,
|
|
720
|
+
const handleFileUpload = (0, import_react3.useCallback)((e) => {
|
|
715
721
|
const file = e.target.files?.[0];
|
|
716
722
|
if (!file) return;
|
|
717
723
|
const reader = new FileReader();
|
|
@@ -720,35 +726,45 @@ function DesignerView({
|
|
|
720
726
|
};
|
|
721
727
|
reader.readAsArrayBuffer(file);
|
|
722
728
|
}, [loadPdf]);
|
|
723
|
-
const handlePageClick = (0,
|
|
724
|
-
const field = createField(activeFieldType, activeRole, page, x, y);
|
|
729
|
+
const handlePageClick = (0, import_react3.useCallback)((page, x, y) => {
|
|
730
|
+
const field = createField(activeFieldType, activeRole, page, x, y, fields);
|
|
725
731
|
setFields((prev) => [...prev, field]);
|
|
726
732
|
setSelectedFieldId(field.id);
|
|
727
|
-
|
|
728
|
-
|
|
733
|
+
setRightTab("properties");
|
|
734
|
+
}, [activeFieldType, activeRole, fields]);
|
|
735
|
+
const handleFieldMove = (0, import_react3.useCallback)((id, page, x, y) => {
|
|
729
736
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, page, x, y } : f));
|
|
730
737
|
}, []);
|
|
731
|
-
const handleFieldResize = (0,
|
|
738
|
+
const handleFieldResize = (0, import_react3.useCallback)((id, width, height) => {
|
|
732
739
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, width, height } : f));
|
|
733
740
|
}, []);
|
|
734
|
-
const handleFieldUpdate = (0,
|
|
735
|
-
setFields((prev) =>
|
|
741
|
+
const handleFieldUpdate = (0, import_react3.useCallback)((id, updates) => {
|
|
742
|
+
setFields((prev) => {
|
|
743
|
+
if (updates.label !== void 0) {
|
|
744
|
+
const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
|
|
745
|
+
updates.label = uniqueLabel(updates.label, otherLabels);
|
|
746
|
+
}
|
|
747
|
+
return prev.map((f) => f.id === id ? { ...f, ...updates } : f);
|
|
748
|
+
});
|
|
736
749
|
}, []);
|
|
737
|
-
const handleFieldDelete = (0,
|
|
750
|
+
const handleFieldDelete = (0, import_react3.useCallback)((id) => {
|
|
738
751
|
setFields((prev) => prev.filter((f) => f.id !== id));
|
|
739
752
|
if (selectedFieldId === id) setSelectedFieldId(null);
|
|
740
753
|
}, [selectedFieldId]);
|
|
741
|
-
const handleAddRole = (0,
|
|
742
|
-
|
|
743
|
-
|
|
754
|
+
const handleAddRole = (0, import_react3.useCallback)(() => {
|
|
755
|
+
const name = newRoleName.trim();
|
|
756
|
+
if (name && !signerRoles.includes(name)) {
|
|
757
|
+
setSignerRoles((prev) => [...prev, name]);
|
|
744
758
|
}
|
|
745
|
-
|
|
746
|
-
|
|
759
|
+
setNewRoleName("");
|
|
760
|
+
setIsAddingRole(false);
|
|
761
|
+
}, [newRoleName, signerRoles]);
|
|
762
|
+
const handleRemoveRole = (0, import_react3.useCallback)((role) => {
|
|
747
763
|
setSignerRoles((prev) => prev.filter((r) => r !== role));
|
|
748
764
|
setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
|
|
749
765
|
if (activeRole === role) setActiveRole(signerRoles[0]);
|
|
750
766
|
}, [signerRoles, activeRole]);
|
|
751
|
-
const handleExport = (0,
|
|
767
|
+
const handleExport = (0, import_react3.useCallback)(() => {
|
|
752
768
|
const template = {
|
|
753
769
|
fields,
|
|
754
770
|
signerRoles,
|
|
@@ -768,160 +784,310 @@ function DesignerView({
|
|
|
768
784
|
}
|
|
769
785
|
window.parent?.postMessage({ type: "template-saved", template }, "*");
|
|
770
786
|
}, [fields, signerRoles, pdfSource, onSave]);
|
|
787
|
+
const handlePaletteDragStart = (0, import_react3.useCallback)((e, type) => {
|
|
788
|
+
setDraggingFieldType(type);
|
|
789
|
+
e.dataTransfer.setData("application/exeq-field-type", type);
|
|
790
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
791
|
+
const ghost = document.createElement("div");
|
|
792
|
+
ghost.className = "palette-drag-ghost";
|
|
793
|
+
ghost.textContent = FIELD_TYPE_META.find((f) => f.type === type)?.label || type;
|
|
794
|
+
ghost.style.cssText = `
|
|
795
|
+
position: fixed; top: -200px; left: -200px;
|
|
796
|
+
padding: 6px 16px; background: ${getSignerColor(activeRole)};
|
|
797
|
+
color: #fff; border-radius: 4px; font-size: 12px;
|
|
798
|
+
font-family: system-ui; pointer-events: none; white-space: nowrap;
|
|
799
|
+
`;
|
|
800
|
+
document.body.appendChild(ghost);
|
|
801
|
+
e.dataTransfer.setDragImage(ghost, 40, 16);
|
|
802
|
+
dragGhostRef.current = ghost;
|
|
803
|
+
}, [activeRole]);
|
|
804
|
+
const handlePaletteDragEnd = (0, import_react3.useCallback)(() => {
|
|
805
|
+
setDraggingFieldType(null);
|
|
806
|
+
if (dragGhostRef.current) {
|
|
807
|
+
document.body.removeChild(dragGhostRef.current);
|
|
808
|
+
dragGhostRef.current = null;
|
|
809
|
+
}
|
|
810
|
+
}, []);
|
|
811
|
+
const handleDropOnPage = (0, import_react3.useCallback)((page, x, y, fieldType) => {
|
|
812
|
+
const field = createField(fieldType, activeRole, page, x, y, fields);
|
|
813
|
+
setFields((prev) => [...prev, field]);
|
|
814
|
+
setSelectedFieldId(field.id);
|
|
815
|
+
setRightTab("properties");
|
|
816
|
+
}, [activeRole, fields]);
|
|
817
|
+
(0, import_react3.useEffect)(() => {
|
|
818
|
+
const handleKeyDown = (e) => {
|
|
819
|
+
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
|
820
|
+
if (!selectedFieldId) return;
|
|
821
|
+
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
822
|
+
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
823
|
+
if (document.activeElement?.isContentEditable) return;
|
|
824
|
+
e.preventDefault();
|
|
825
|
+
if (window.confirm("Delete this field?")) {
|
|
826
|
+
handleFieldDelete(selectedFieldId);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
830
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
831
|
+
}, [selectedFieldId, handleFieldDelete]);
|
|
832
|
+
const handleResizeStart = (0, import_react3.useCallback)((e) => {
|
|
833
|
+
e.preventDefault();
|
|
834
|
+
resizingRef.current = true;
|
|
835
|
+
const startX = e.clientX;
|
|
836
|
+
const startWidth = panelWidth;
|
|
837
|
+
const handleMouseMove = (e2) => {
|
|
838
|
+
if (!resizingRef.current) return;
|
|
839
|
+
const delta = startX - e2.clientX;
|
|
840
|
+
setPanelWidth(Math.max(280, Math.min(600, startWidth + delta)));
|
|
841
|
+
};
|
|
842
|
+
const handleMouseUp = () => {
|
|
843
|
+
resizingRef.current = false;
|
|
844
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
845
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
846
|
+
};
|
|
847
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
848
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
849
|
+
}, [panelWidth]);
|
|
850
|
+
const sortedFields = [...fields].sort((a, b) => {
|
|
851
|
+
if (a.page !== b.page) return a.page - b.page;
|
|
852
|
+
const bandThreshold = 2;
|
|
853
|
+
if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
|
|
854
|
+
return a.x - b.x;
|
|
855
|
+
});
|
|
771
856
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
772
|
-
const renderFieldContent = (0,
|
|
857
|
+
const renderFieldContent = (0, import_react3.useCallback)((field) => {
|
|
858
|
+
if (field.type === "blackout" || field.type === "whiteout") {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
773
861
|
if (field.type === "signature" || field.type === "initials") {
|
|
774
862
|
if (field.value) {
|
|
775
|
-
return /* @__PURE__ */ (0,
|
|
863
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: field.value, alt: field.label, className: "field-signature-preview" });
|
|
776
864
|
}
|
|
777
865
|
}
|
|
778
|
-
|
|
866
|
+
const inkColor = field.inkColor || "#000000";
|
|
867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
868
|
+
"div",
|
|
869
|
+
{
|
|
870
|
+
className: "field-overlay-placeholder",
|
|
871
|
+
style: { color: field.value ? inkColor : void 0, fontSize: `${field.fontSize * 0.6}px` },
|
|
872
|
+
children: field.value || field.placeholder
|
|
873
|
+
}
|
|
874
|
+
);
|
|
779
875
|
}, []);
|
|
780
|
-
|
|
781
|
-
/* @__PURE__ */ (0,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
876
|
+
const headerButtons = pages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
877
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "header-btn header-btn-outline", children: [
|
|
878
|
+
"Change PDF",
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
880
|
+
] }),
|
|
881
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: handleExport, className: "header-btn header-btn-primary", children: "Export Template" })
|
|
882
|
+
] }) : null;
|
|
883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-layout", children: [
|
|
884
|
+
headerPortalRef?.current && headerButtons && (0, import_react_dom.createPortal)(headerButtons, headerPortalRef.current),
|
|
885
|
+
!hideHeader && !headerPortalRef && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "designer-header", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "designer-header-right", children: headerButtons }) }),
|
|
886
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-body", children: [
|
|
887
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-palette", children: [
|
|
888
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-heading", children: "New Field Role" }),
|
|
889
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "palette-role-section", children: [
|
|
890
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
891
|
+
"select",
|
|
794
892
|
{
|
|
795
|
-
className:
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
893
|
+
className: "palette-role-select",
|
|
894
|
+
value: activeRole,
|
|
895
|
+
onChange: (e) => setActiveRole(e.target.value),
|
|
896
|
+
style: { borderColor: getSignerColor(activeRole), color: getSignerColor(activeRole) },
|
|
897
|
+
children: signerRoles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: role, children: role }, role))
|
|
898
|
+
}
|
|
899
|
+
),
|
|
900
|
+
isAddingRole ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "palette-role-add-inline", children: [
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
902
|
+
"input",
|
|
903
|
+
{
|
|
904
|
+
type: "text",
|
|
905
|
+
value: newRoleName,
|
|
906
|
+
onChange: (e) => setNewRoleName(e.target.value),
|
|
907
|
+
onKeyDown: (e) => e.key === "Enter" && handleAddRole(),
|
|
908
|
+
placeholder: "Role name",
|
|
909
|
+
autoFocus: true
|
|
910
|
+
}
|
|
911
|
+
),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: handleAddRole, children: "Add" }),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => {
|
|
914
|
+
setIsAddingRole(false);
|
|
915
|
+
setNewRoleName("");
|
|
916
|
+
}, children: "Cancel" })
|
|
917
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "palette-role-add-link", onClick: () => setIsAddingRole(true), children: "+ Add Role" })
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-divider" }),
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-heading", children: "Fields" }),
|
|
921
|
+
FIELD_TYPE_META.filter((f) => !f.group).map(({ type, label, icon }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
922
|
+
"div",
|
|
923
|
+
{
|
|
924
|
+
className: `palette-item ${activeFieldType === type ? "active" : ""}`,
|
|
925
|
+
draggable: true,
|
|
926
|
+
onClick: () => setActiveFieldType(type),
|
|
927
|
+
onDragStart: (e) => handlePaletteDragStart(e, type),
|
|
928
|
+
onDragEnd: handlePaletteDragEnd,
|
|
929
|
+
children: [
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-icon", children: icon }),
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-label", children: label })
|
|
932
|
+
]
|
|
933
|
+
},
|
|
934
|
+
type
|
|
935
|
+
)),
|
|
936
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-divider" }),
|
|
937
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-heading", children: "Redact" }),
|
|
938
|
+
FIELD_TYPE_META.filter((f) => f.group === "redact").map(({ type, label, icon }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
939
|
+
"div",
|
|
940
|
+
{
|
|
941
|
+
className: `palette-item ${activeFieldType === type ? "active" : ""}`,
|
|
942
|
+
draggable: true,
|
|
943
|
+
onClick: () => setActiveFieldType(type),
|
|
944
|
+
onDragStart: (e) => handlePaletteDragStart(e, type),
|
|
945
|
+
onDragEnd: handlePaletteDragEnd,
|
|
946
|
+
children: [
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-icon", children: icon }),
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-label", children: label })
|
|
949
|
+
]
|
|
950
|
+
},
|
|
951
|
+
type
|
|
952
|
+
))
|
|
802
953
|
] }),
|
|
803
|
-
/* @__PURE__ */ (0,
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
onAddRole: handleAddRole,
|
|
812
|
-
onRemoveRole: handleRemoveRole
|
|
813
|
-
}
|
|
814
|
-
),
|
|
815
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "designer-content", children: [
|
|
816
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "designer-pdf-area", children: [
|
|
817
|
-
loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
818
|
-
!pages.length && !loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "empty-state", children: [
|
|
819
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: "Upload a PDF to get started" }),
|
|
820
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Upload a template PDF, then click on the page to place form fields." }),
|
|
821
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "upload-btn upload-btn-large", children: [
|
|
822
|
-
"Choose PDF File",
|
|
823
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
954
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-pdf-area", children: [
|
|
955
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
956
|
+
!pages.length && !loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "empty-state", children: [
|
|
957
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Open a PDF to get started" }),
|
|
958
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
|
|
959
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "upload-btn upload-btn-large", children: [
|
|
960
|
+
"Select PDF",
|
|
961
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
824
962
|
] })
|
|
825
963
|
] }),
|
|
826
|
-
pages.length > 0 && /* @__PURE__ */ (0,
|
|
964
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
827
965
|
PdfViewer,
|
|
828
966
|
{
|
|
829
967
|
pages,
|
|
830
968
|
fields,
|
|
831
969
|
selectedFieldId,
|
|
832
|
-
onSelectField:
|
|
970
|
+
onSelectField: (id) => {
|
|
971
|
+
setSelectedFieldId(id);
|
|
972
|
+
if (id) setRightTab("properties");
|
|
973
|
+
},
|
|
833
974
|
onFieldMove: handleFieldMove,
|
|
834
975
|
onFieldResize: handleFieldResize,
|
|
835
976
|
onPageClick: handlePageClick,
|
|
977
|
+
onDropField: handleDropOnPage,
|
|
836
978
|
mode: "designer",
|
|
837
979
|
renderFieldContent
|
|
838
980
|
}
|
|
839
981
|
)
|
|
840
982
|
] }),
|
|
841
|
-
/* @__PURE__ */ (0,
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
signerRoles,
|
|
848
|
-
onUpdate: handleFieldUpdate,
|
|
849
|
-
onDelete: handleFieldDelete
|
|
850
|
-
}
|
|
851
|
-
),
|
|
852
|
-
selectedField.assignee === activeRole && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "prefill-section", children: [
|
|
853
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h4", { children: "Pre-fill Value" }),
|
|
854
|
-
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
855
|
-
SignatureCanvas,
|
|
983
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
984
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-panel", style: { width: panelWidth }, children: [
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tabs", children: [
|
|
987
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
988
|
+
"button",
|
|
856
989
|
{
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
initialValue: selectedField.value
|
|
990
|
+
className: `panel-tab ${rightTab === "properties" ? "active" : ""}`,
|
|
991
|
+
onClick: () => setRightTab("properties"),
|
|
992
|
+
children: "Properties"
|
|
861
993
|
}
|
|
862
|
-
)
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
{
|
|
866
|
-
type: "checkbox",
|
|
867
|
-
checked: selectedField.value === "true",
|
|
868
|
-
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
869
|
-
}
|
|
870
|
-
),
|
|
871
|
-
"Checked"
|
|
872
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
873
|
-
"input",
|
|
994
|
+
),
|
|
995
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
996
|
+
"button",
|
|
874
997
|
{
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
998
|
+
className: `panel-tab ${rightTab === "fields" ? "active" : ""}`,
|
|
999
|
+
onClick: () => setRightTab("fields"),
|
|
1000
|
+
children: [
|
|
1001
|
+
"All Fields",
|
|
1002
|
+
fields.length > 0 ? ` (${fields.length})` : ""
|
|
1003
|
+
]
|
|
880
1004
|
}
|
|
881
1005
|
)
|
|
882
|
-
] })
|
|
883
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "panel-empty", children: pages.length > 0 ? "Click on the PDF to place a field, or select an existing field to edit its properties." : "Upload a PDF to begin designing your template." }),
|
|
884
|
-
fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-list", children: [
|
|
885
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h4", { children: [
|
|
886
|
-
"All Fields (",
|
|
887
|
-
fields.length,
|
|
888
|
-
")"
|
|
889
1006
|
] }),
|
|
890
|
-
|
|
891
|
-
"
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1007
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tab-content", children: [
|
|
1008
|
+
rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedField ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1009
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1010
|
+
FieldPropertyPanel,
|
|
1011
|
+
{
|
|
1012
|
+
field: selectedField,
|
|
1013
|
+
signerRoles,
|
|
1014
|
+
onUpdate: handleFieldUpdate,
|
|
1015
|
+
onDelete: handleFieldDelete
|
|
1016
|
+
}
|
|
1017
|
+
),
|
|
1018
|
+
selectedField.type !== "blackout" && selectedField.type !== "whiteout" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "prefill-section", children: [
|
|
1019
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Pre-fill Value" }),
|
|
1020
|
+
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1021
|
+
SignatureCanvas,
|
|
898
1022
|
{
|
|
899
|
-
|
|
900
|
-
|
|
1023
|
+
width: panelWidth - 40,
|
|
1024
|
+
height: selectedField.type === "initials" ? 100 : 150,
|
|
1025
|
+
onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
|
|
1026
|
+
initialValue: selectedField.value,
|
|
1027
|
+
inkColor: selectedField.inkColor
|
|
901
1028
|
}
|
|
902
|
-
),
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1029
|
+
) : selectedField.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
1030
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1031
|
+
"input",
|
|
1032
|
+
{
|
|
1033
|
+
type: "checkbox",
|
|
1034
|
+
checked: selectedField.value === "true",
|
|
1035
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
1036
|
+
}
|
|
1037
|
+
),
|
|
1038
|
+
"Checked"
|
|
1039
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1040
|
+
"input",
|
|
1041
|
+
{
|
|
1042
|
+
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
1043
|
+
value: selectedField.value,
|
|
1044
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
|
|
1045
|
+
placeholder: `Pre-fill ${selectedField.label}`,
|
|
1046
|
+
className: "prefill-input",
|
|
1047
|
+
style: { color: selectedField.inkColor || "#000000" }
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
] })
|
|
1051
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
|
|
1052
|
+
rightTab === "fields" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "No fields yet. Drag a field from the left palette onto the PDF, or click on the PDF to place one." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "field-list", children: sortedFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1053
|
+
"div",
|
|
1054
|
+
{
|
|
1055
|
+
className: `field-list-item ${f.id === selectedFieldId ? "active" : ""}`,
|
|
1056
|
+
onClick: () => {
|
|
1057
|
+
setSelectedFieldId(f.id);
|
|
1058
|
+
setRightTab("properties");
|
|
1059
|
+
},
|
|
1060
|
+
children: [
|
|
1061
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1062
|
+
"span",
|
|
1063
|
+
{
|
|
1064
|
+
className: "field-list-dot",
|
|
1065
|
+
style: { backgroundColor: getSignerColor(f.assignee) }
|
|
1066
|
+
}
|
|
1067
|
+
),
|
|
1068
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-name", children: f.label }),
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "field-list-page", children: [
|
|
1070
|
+
"p",
|
|
1071
|
+
f.page + 1
|
|
1072
|
+
] }),
|
|
1073
|
+
f.required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-required", children: "*" })
|
|
1074
|
+
]
|
|
1075
|
+
},
|
|
1076
|
+
f.id
|
|
1077
|
+
)) }) })
|
|
1078
|
+
] }),
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "powered-by", children: [
|
|
1080
|
+
"Powered by ",
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
|
|
1082
|
+
] })
|
|
913
1083
|
] })
|
|
914
1084
|
] })
|
|
915
|
-
] }),
|
|
916
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "powered-by", children: [
|
|
917
|
-
"Powered by ",
|
|
918
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
|
|
919
1085
|
] })
|
|
920
1086
|
] });
|
|
921
1087
|
}
|
|
922
1088
|
|
|
923
1089
|
// src/components/pdf-builder/SignerView.tsx
|
|
924
|
-
var
|
|
1090
|
+
var import_react4 = require("react");
|
|
925
1091
|
|
|
926
1092
|
// src/utils/pdfFiller.ts
|
|
927
1093
|
var import_pdf_lib = require("pdf-lib");
|
|
@@ -936,8 +1102,13 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
936
1102
|
const pdfDoc = await import_pdf_lib.PDFDocument.load(pdfBytes);
|
|
937
1103
|
const font = await pdfDoc.embedFont(import_pdf_lib.StandardFonts.Helvetica);
|
|
938
1104
|
const pages = pdfDoc.getPages();
|
|
1105
|
+
function hexToRgb(hex) {
|
|
1106
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
1107
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
1108
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
1109
|
+
return (0, import_pdf_lib.rgb)(r, g, b);
|
|
1110
|
+
}
|
|
939
1111
|
for (const field of fields) {
|
|
940
|
-
if (!field.value) continue;
|
|
941
1112
|
const page = pages[field.page];
|
|
942
1113
|
if (!page) continue;
|
|
943
1114
|
const pageWidth = page.getWidth();
|
|
@@ -946,6 +1117,18 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
946
1117
|
const y = pageHeight - field.y / 100 * pageHeight - field.height / 100 * pageHeight;
|
|
947
1118
|
const w = field.width / 100 * pageWidth;
|
|
948
1119
|
const h = field.height / 100 * pageHeight;
|
|
1120
|
+
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1121
|
+
page.drawRectangle({
|
|
1122
|
+
x,
|
|
1123
|
+
y,
|
|
1124
|
+
width: w,
|
|
1125
|
+
height: h,
|
|
1126
|
+
color: field.type === "blackout" ? (0, import_pdf_lib.rgb)(0, 0, 0) : (0, import_pdf_lib.rgb)(1, 1, 1)
|
|
1127
|
+
});
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (!field.value) continue;
|
|
1131
|
+
const inkColor = field.inkColor ? hexToRgb(field.inkColor) : (0, import_pdf_lib.rgb)(0, 0, 0);
|
|
949
1132
|
if (field.type === "checkbox") {
|
|
950
1133
|
if (field.value === "true") {
|
|
951
1134
|
const inset = Math.min(w, h) * 0.2;
|
|
@@ -954,13 +1137,13 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
954
1137
|
start: { x: x + inset, y: y + inset },
|
|
955
1138
|
end: { x: x + w - inset, y: y + h - inset },
|
|
956
1139
|
thickness: lineWidth,
|
|
957
|
-
color:
|
|
1140
|
+
color: inkColor
|
|
958
1141
|
});
|
|
959
1142
|
page.drawLine({
|
|
960
1143
|
start: { x: x + w - inset, y: y + inset },
|
|
961
1144
|
end: { x: x + inset, y: y + h - inset },
|
|
962
1145
|
thickness: lineWidth,
|
|
963
|
-
color:
|
|
1146
|
+
color: inkColor
|
|
964
1147
|
});
|
|
965
1148
|
}
|
|
966
1149
|
} else if (field.type === "signature" || field.type === "initials") {
|
|
@@ -984,7 +1167,7 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
984
1167
|
y: y + h * 0.3,
|
|
985
1168
|
size: fontSize,
|
|
986
1169
|
font,
|
|
987
|
-
color:
|
|
1170
|
+
color: inkColor,
|
|
988
1171
|
maxWidth: w - 4
|
|
989
1172
|
});
|
|
990
1173
|
}
|
|
@@ -1011,7 +1194,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
|
|
|
1011
1194
|
}
|
|
1012
1195
|
|
|
1013
1196
|
// src/components/pdf-builder/FieldNavigator.tsx
|
|
1014
|
-
var
|
|
1197
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1015
1198
|
function FieldNavigator({
|
|
1016
1199
|
fields,
|
|
1017
1200
|
currentFieldId,
|
|
@@ -1037,15 +1220,15 @@ function FieldNavigator({
|
|
|
1037
1220
|
if (f.type === "checkbox") return true;
|
|
1038
1221
|
return !!f.value;
|
|
1039
1222
|
}).length;
|
|
1040
|
-
return /* @__PURE__ */ (0,
|
|
1041
|
-
/* @__PURE__ */ (0,
|
|
1223
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-navigator", children: [
|
|
1224
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-navigator-progress", children: [
|
|
1042
1225
|
filledCount,
|
|
1043
1226
|
" of ",
|
|
1044
1227
|
fields.length,
|
|
1045
1228
|
" fields completed"
|
|
1046
1229
|
] }),
|
|
1047
|
-
/* @__PURE__ */ (0,
|
|
1048
|
-
/* @__PURE__ */ (0,
|
|
1230
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-navigator-controls", children: [
|
|
1231
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1049
1232
|
"button",
|
|
1050
1233
|
{
|
|
1051
1234
|
onClick: handlePrev,
|
|
@@ -1054,8 +1237,8 @@ function FieldNavigator({
|
|
|
1054
1237
|
children: "Prev"
|
|
1055
1238
|
}
|
|
1056
1239
|
),
|
|
1057
|
-
/* @__PURE__ */ (0,
|
|
1058
|
-
/* @__PURE__ */ (0,
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "field-navigator-position", children: currentIndex >= 0 ? `${currentIndex + 1} / ${fields.length}` : "-" }),
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1059
1242
|
"button",
|
|
1060
1243
|
{
|
|
1061
1244
|
onClick: handleNext,
|
|
@@ -1065,7 +1248,7 @@ function FieldNavigator({
|
|
|
1065
1248
|
}
|
|
1066
1249
|
)
|
|
1067
1250
|
] }),
|
|
1068
|
-
/* @__PURE__ */ (0,
|
|
1251
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1069
1252
|
"button",
|
|
1070
1253
|
{
|
|
1071
1254
|
onClick: onSubmit,
|
|
@@ -1078,26 +1261,49 @@ function FieldNavigator({
|
|
|
1078
1261
|
}
|
|
1079
1262
|
|
|
1080
1263
|
// src/components/pdf-builder/SignerView.tsx
|
|
1081
|
-
var
|
|
1264
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1082
1265
|
function SignerView({
|
|
1266
|
+
apiKey,
|
|
1083
1267
|
initialPdfUrl,
|
|
1084
1268
|
initialTemplate,
|
|
1085
1269
|
initialSigner,
|
|
1086
1270
|
callbackUrl: initialCallbackUrl,
|
|
1087
|
-
onComplete
|
|
1271
|
+
onComplete,
|
|
1272
|
+
initialValues
|
|
1088
1273
|
} = {}) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1274
|
+
if (!isValidApiKey(apiKey)) {
|
|
1275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "empty-state", children: [
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: "Invalid API Key" }),
|
|
1277
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
|
|
1278
|
+
"A valid API key is required to use Exeq. Visit ",
|
|
1279
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: "https://exeq.org", children: "exeq.org" }),
|
|
1280
|
+
" to get one."
|
|
1281
|
+
] })
|
|
1282
|
+
] }) });
|
|
1283
|
+
}
|
|
1284
|
+
const [pages, setPages] = (0, import_react4.useState)([]);
|
|
1285
|
+
const [fields, setFields] = (0, import_react4.useState)([]);
|
|
1286
|
+
const [selectedFieldId, setSelectedFieldId] = (0, import_react4.useState)(null);
|
|
1287
|
+
const [signer, setSigner] = (0, import_react4.useState)(initialSigner || "Signer 1");
|
|
1288
|
+
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
1289
|
+
const [submitting, setSubmitting] = (0, import_react4.useState)(false);
|
|
1290
|
+
const [pdfSource, setPdfSource] = (0, import_react4.useState)(null);
|
|
1291
|
+
const [callbackUrl, setCallbackUrl] = (0, import_react4.useState)(initialCallbackUrl || "");
|
|
1292
|
+
const containerRef = (0, import_react4.useRef)(null);
|
|
1293
|
+
(0, import_react4.useEffect)(() => {
|
|
1099
1294
|
if (initialTemplate) {
|
|
1100
|
-
|
|
1295
|
+
let templateFields = initialTemplate.fields;
|
|
1296
|
+
if (initialValues) {
|
|
1297
|
+
templateFields = templateFields.map((f) => {
|
|
1298
|
+
const byLabel = Object.entries(initialValues).find(
|
|
1299
|
+
([key]) => key.toLowerCase() === f.label.toLowerCase()
|
|
1300
|
+
);
|
|
1301
|
+
const byId = initialValues[f.id];
|
|
1302
|
+
const value = byLabel?.[1] ?? byId;
|
|
1303
|
+
return value !== void 0 ? { ...f, value } : f;
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
setFields(templateFields);
|
|
1101
1307
|
if (initialPdfUrl) loadPdf(initialPdfUrl);
|
|
1102
1308
|
return;
|
|
1103
1309
|
}
|
|
@@ -1123,7 +1329,7 @@ function SignerView({
|
|
|
1123
1329
|
window.addEventListener("message", handleMessage);
|
|
1124
1330
|
return () => window.removeEventListener("message", handleMessage);
|
|
1125
1331
|
}, [initialTemplate, initialPdfUrl]);
|
|
1126
|
-
const loadPdf = (0,
|
|
1332
|
+
const loadPdf = (0, import_react4.useCallback)(async (source) => {
|
|
1127
1333
|
setLoading(true);
|
|
1128
1334
|
try {
|
|
1129
1335
|
const rendered = await renderPdfPages(source);
|
|
@@ -1135,13 +1341,18 @@ function SignerView({
|
|
|
1135
1341
|
setLoading(false);
|
|
1136
1342
|
}
|
|
1137
1343
|
}, []);
|
|
1138
|
-
const editableFields = fields.filter((f) => f.assignee === signer)
|
|
1344
|
+
const editableFields = fields.filter((f) => f.assignee === signer).sort((a, b) => {
|
|
1345
|
+
if (a.page !== b.page) return a.page - b.page;
|
|
1346
|
+
const bandThreshold = 2;
|
|
1347
|
+
if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
|
|
1348
|
+
return a.x - b.x;
|
|
1349
|
+
});
|
|
1139
1350
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
1140
1351
|
const isFieldEditable = selectedField ? selectedField.assignee === signer : false;
|
|
1141
|
-
const handleFieldUpdate = (0,
|
|
1352
|
+
const handleFieldUpdate = (0, import_react4.useCallback)((id, value) => {
|
|
1142
1353
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1143
1354
|
}, []);
|
|
1144
|
-
const handleNavigate = (0,
|
|
1355
|
+
const handleNavigate = (0, import_react4.useCallback)((fieldId) => {
|
|
1145
1356
|
setSelectedFieldId(fieldId);
|
|
1146
1357
|
const field = fields.find((f) => f.id === fieldId);
|
|
1147
1358
|
if (field && containerRef.current) {
|
|
@@ -1156,7 +1367,7 @@ function SignerView({
|
|
|
1156
1367
|
if (f.type === "checkbox") return true;
|
|
1157
1368
|
return !!f.value;
|
|
1158
1369
|
});
|
|
1159
|
-
const handleSubmit = (0,
|
|
1370
|
+
const handleSubmit = (0, import_react4.useCallback)(async () => {
|
|
1160
1371
|
if (!pdfSource || !allRequiredFilled) return;
|
|
1161
1372
|
setSubmitting(true);
|
|
1162
1373
|
try {
|
|
@@ -1176,25 +1387,25 @@ function SignerView({
|
|
|
1176
1387
|
setSubmitting(false);
|
|
1177
1388
|
}
|
|
1178
1389
|
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete]);
|
|
1179
|
-
const renderFieldContent = (0,
|
|
1390
|
+
const renderFieldContent = (0, import_react4.useCallback)((field) => {
|
|
1180
1391
|
const editable = field.assignee === signer;
|
|
1181
1392
|
if (!editable) {
|
|
1182
1393
|
if (field.type === "signature" || field.type === "initials") {
|
|
1183
|
-
return field.value ? /* @__PURE__ */ (0,
|
|
1394
|
+
return field.value ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: field.value, alt: field.label, className: "field-signature-preview" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-placeholder readonly", children: field.placeholder });
|
|
1184
1395
|
}
|
|
1185
1396
|
if (field.type === "checkbox") {
|
|
1186
|
-
return /* @__PURE__ */ (0,
|
|
1397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-checkbox-display readonly", children: field.value === "true" ? "\u2713" : "" });
|
|
1187
1398
|
}
|
|
1188
|
-
return /* @__PURE__ */ (0,
|
|
1399
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-placeholder readonly", children: field.value || field.placeholder });
|
|
1189
1400
|
}
|
|
1190
1401
|
if (field.type === "signature" || field.type === "initials") {
|
|
1191
1402
|
if (field.value) {
|
|
1192
|
-
return /* @__PURE__ */ (0,
|
|
1403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-signature-filled", onClick: () => handleFieldUpdate(field.id, ""), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: field.value, alt: field.label, className: "field-signature-preview" }) });
|
|
1193
1404
|
}
|
|
1194
|
-
return /* @__PURE__ */ (0,
|
|
1405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-placeholder editable", children: field.placeholder });
|
|
1195
1406
|
}
|
|
1196
1407
|
if (field.type === "checkbox") {
|
|
1197
|
-
return /* @__PURE__ */ (0,
|
|
1408
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1198
1409
|
"div",
|
|
1199
1410
|
{
|
|
1200
1411
|
className: "field-checkbox-display editable",
|
|
@@ -1207,9 +1418,9 @@ function SignerView({
|
|
|
1207
1418
|
);
|
|
1208
1419
|
}
|
|
1209
1420
|
if (field.type === "signed-date") {
|
|
1210
|
-
return /* @__PURE__ */ (0,
|
|
1421
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-value", children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
|
|
1211
1422
|
}
|
|
1212
|
-
return /* @__PURE__ */ (0,
|
|
1423
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1213
1424
|
"input",
|
|
1214
1425
|
{
|
|
1215
1426
|
type: field.textSubtype === "email" ? "email" : field.textSubtype === "number" ? "number" : field.textSubtype === "phone" ? "tel" : field.textSubtype === "date" ? "date" : "text",
|
|
@@ -1222,7 +1433,7 @@ function SignerView({
|
|
|
1222
1433
|
}
|
|
1223
1434
|
);
|
|
1224
1435
|
}, [signer, handleFieldUpdate]);
|
|
1225
|
-
(0,
|
|
1436
|
+
(0, import_react4.useEffect)(() => {
|
|
1226
1437
|
const sigFields = fields.filter((f) => f.assignee === signer && f.type === "signature" && f.value);
|
|
1227
1438
|
if (sigFields.length > 0) {
|
|
1228
1439
|
const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
@@ -1234,10 +1445,10 @@ function SignerView({
|
|
|
1234
1445
|
}));
|
|
1235
1446
|
}
|
|
1236
1447
|
}, [fields.filter((f) => f.type === "signature" && f.value).length]);
|
|
1237
|
-
return /* @__PURE__ */ (0,
|
|
1238
|
-
loading && /* @__PURE__ */ (0,
|
|
1239
|
-
/* @__PURE__ */ (0,
|
|
1240
|
-
/* @__PURE__ */ (0,
|
|
1448
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-layout", ref: containerRef, children: [
|
|
1449
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "loading-indicator", children: "Loading document..." }),
|
|
1450
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-content", children: [
|
|
1451
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-pdf-area", children: pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1241
1452
|
PdfViewer,
|
|
1242
1453
|
{
|
|
1243
1454
|
pages,
|
|
@@ -1249,11 +1460,11 @@ function SignerView({
|
|
|
1249
1460
|
renderFieldContent
|
|
1250
1461
|
}
|
|
1251
1462
|
) }),
|
|
1252
|
-
/* @__PURE__ */ (0,
|
|
1253
|
-
selectedField && isFieldEditable && /* @__PURE__ */ (0,
|
|
1254
|
-
/* @__PURE__ */ (0,
|
|
1255
|
-
selectedField.required && /* @__PURE__ */ (0,
|
|
1256
|
-
(selectedField.type === "signature" || selectedField.type === "initials") && /* @__PURE__ */ (0,
|
|
1463
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-panel", children: [
|
|
1464
|
+
selectedField && isFieldEditable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-field-input", children: [
|
|
1465
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: selectedField.label }),
|
|
1466
|
+
selectedField.required && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "required-badge", children: "Required" }),
|
|
1467
|
+
(selectedField.type === "signature" || selectedField.type === "initials") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1257
1468
|
SignatureCanvas,
|
|
1258
1469
|
{
|
|
1259
1470
|
width: 280,
|
|
@@ -1262,7 +1473,7 @@ function SignerView({
|
|
|
1262
1473
|
initialValue: selectedField.value
|
|
1263
1474
|
}
|
|
1264
1475
|
),
|
|
1265
|
-
selectedField.type === "text" && /* @__PURE__ */ (0,
|
|
1476
|
+
selectedField.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1266
1477
|
"input",
|
|
1267
1478
|
{
|
|
1268
1479
|
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
@@ -1272,8 +1483,8 @@ function SignerView({
|
|
|
1272
1483
|
className: "signer-text-input"
|
|
1273
1484
|
}
|
|
1274
1485
|
),
|
|
1275
|
-
selectedField.type === "checkbox" && /* @__PURE__ */ (0,
|
|
1276
|
-
/* @__PURE__ */ (0,
|
|
1486
|
+
selectedField.type === "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "signer-checkbox-label", children: [
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1277
1488
|
"input",
|
|
1278
1489
|
{
|
|
1279
1490
|
type: "checkbox",
|
|
@@ -1283,14 +1494,14 @@ function SignerView({
|
|
|
1283
1494
|
),
|
|
1284
1495
|
selectedField.placeholder || "Check this box"
|
|
1285
1496
|
] }),
|
|
1286
|
-
selectedField.type === "signed-date" && /* @__PURE__ */ (0,
|
|
1497
|
+
selectedField.type === "signed-date" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-date-display", children: selectedField.value || "Will auto-fill when you sign" })
|
|
1287
1498
|
] }),
|
|
1288
|
-
selectedField && !isFieldEditable && /* @__PURE__ */ (0,
|
|
1289
|
-
/* @__PURE__ */ (0,
|
|
1290
|
-
/* @__PURE__ */ (0,
|
|
1499
|
+
selectedField && !isFieldEditable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-field-readonly", children: [
|
|
1500
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: selectedField.label }),
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "This field belongs to another signer." })
|
|
1291
1502
|
] }),
|
|
1292
|
-
!selectedField && editableFields.length > 0 && /* @__PURE__ */ (0,
|
|
1293
|
-
/* @__PURE__ */ (0,
|
|
1503
|
+
!selectedField && editableFields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "panel-empty", children: "Select a field to fill it in, or use the navigation below." }),
|
|
1504
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1294
1505
|
FieldNavigator,
|
|
1295
1506
|
{
|
|
1296
1507
|
fields: editableFields,
|
|
@@ -1300,12 +1511,87 @@ function SignerView({
|
|
|
1300
1511
|
onSubmit: handleSubmit
|
|
1301
1512
|
}
|
|
1302
1513
|
),
|
|
1303
|
-
submitting && /* @__PURE__ */ (0,
|
|
1514
|
+
submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "loading-indicator", children: "Generating PDF..." })
|
|
1304
1515
|
] })
|
|
1305
1516
|
] }),
|
|
1306
|
-
/* @__PURE__ */ (0,
|
|
1517
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "powered-by", children: [
|
|
1307
1518
|
"Powered by ",
|
|
1308
|
-
/* @__PURE__ */ (0,
|
|
1519
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
|
|
1520
|
+
] })
|
|
1521
|
+
] });
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
1525
|
+
var import_react5 = require("react");
|
|
1526
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1527
|
+
function SignerRoleSelector({
|
|
1528
|
+
roles,
|
|
1529
|
+
activeRole,
|
|
1530
|
+
onSetActiveRole,
|
|
1531
|
+
onAddRole,
|
|
1532
|
+
onRemoveRole
|
|
1533
|
+
}) {
|
|
1534
|
+
const [isAdding, setIsAdding] = (0, import_react5.useState)(false);
|
|
1535
|
+
const [newRoleName, setNewRoleName] = (0, import_react5.useState)("");
|
|
1536
|
+
const handleAdd = () => {
|
|
1537
|
+
if (newRoleName.trim()) {
|
|
1538
|
+
onAddRole(newRoleName.trim());
|
|
1539
|
+
setNewRoleName("");
|
|
1540
|
+
setIsAdding(false);
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "signer-role-selector", children: [
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "signer-role-label", children: "Signer Roles" }),
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "signer-role-list", children: [
|
|
1546
|
+
roles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1547
|
+
"button",
|
|
1548
|
+
{
|
|
1549
|
+
className: `signer-role-chip ${role === activeRole ? "active" : ""}`,
|
|
1550
|
+
style: {
|
|
1551
|
+
borderColor: getSignerColor(role),
|
|
1552
|
+
backgroundColor: role === activeRole ? getSignerColor(role) : "transparent",
|
|
1553
|
+
color: role === activeRole ? "#fff" : getSignerColor(role)
|
|
1554
|
+
},
|
|
1555
|
+
onClick: () => onSetActiveRole(role),
|
|
1556
|
+
children: [
|
|
1557
|
+
role,
|
|
1558
|
+
roles.length > 1 && role !== "Sender" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1559
|
+
"span",
|
|
1560
|
+
{
|
|
1561
|
+
className: "signer-role-remove",
|
|
1562
|
+
onClick: (e) => {
|
|
1563
|
+
e.stopPropagation();
|
|
1564
|
+
onRemoveRole(role);
|
|
1565
|
+
},
|
|
1566
|
+
children: "\xD7"
|
|
1567
|
+
}
|
|
1568
|
+
)
|
|
1569
|
+
]
|
|
1570
|
+
},
|
|
1571
|
+
role
|
|
1572
|
+
)),
|
|
1573
|
+
isAdding ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "signer-role-add-input", children: [
|
|
1574
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1575
|
+
"input",
|
|
1576
|
+
{
|
|
1577
|
+
type: "text",
|
|
1578
|
+
value: newRoleName,
|
|
1579
|
+
onChange: (e) => setNewRoleName(e.target.value),
|
|
1580
|
+
onKeyDown: (e) => e.key === "Enter" && handleAdd(),
|
|
1581
|
+
placeholder: "Role name",
|
|
1582
|
+
autoFocus: true
|
|
1583
|
+
}
|
|
1584
|
+
),
|
|
1585
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: handleAdd, children: "Add" }),
|
|
1586
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: () => setIsAdding(false), children: "Cancel" })
|
|
1587
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1588
|
+
"button",
|
|
1589
|
+
{
|
|
1590
|
+
className: "signer-role-add-btn",
|
|
1591
|
+
onClick: () => setIsAdding(true),
|
|
1592
|
+
children: "+ Add Role"
|
|
1593
|
+
}
|
|
1594
|
+
)
|
|
1309
1595
|
] })
|
|
1310
1596
|
] });
|
|
1311
1597
|
}
|
|
@@ -1326,6 +1612,7 @@ function SignerView({
|
|
|
1326
1612
|
generateFilledPdf,
|
|
1327
1613
|
getSignerColor,
|
|
1328
1614
|
postPdfToCallback,
|
|
1329
|
-
renderPdfPages
|
|
1615
|
+
renderPdfPages,
|
|
1616
|
+
uniqueLabel
|
|
1330
1617
|
});
|
|
1331
1618
|
//# sourceMappingURL=index.js.map
|