@zerohive/hive-viewer 0.2.2 → 0.2.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/dist/index.cjs +540 -479
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +543 -482
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +543 -482
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -37,377 +37,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
37
37
|
// src/components/DocumentViewer.tsx
|
|
38
38
|
var import_react6 = require("react");
|
|
39
39
|
|
|
40
|
-
// src/utils/locale.ts
|
|
41
|
-
var defaultLocale = {
|
|
42
|
-
loading: "Loading\u2026",
|
|
43
|
-
"error.title": "Error",
|
|
44
|
-
"toolbar.layout.single": "Single page",
|
|
45
|
-
"toolbar.layout.two": "Side-by-side",
|
|
46
|
-
"toolbar.thumbs": "Thumbnails",
|
|
47
|
-
"toolbar.signatures": "Signatures",
|
|
48
|
-
"toolbar.sign": "Sign Document",
|
|
49
|
-
"toolbar.save": "Save",
|
|
50
|
-
"toolbar.exportPdf": "Export as PDF",
|
|
51
|
-
"thumbnails.title": "Thumbnails",
|
|
52
|
-
"thumbnails.page": "Page",
|
|
53
|
-
"signatures.title": "Signatures",
|
|
54
|
-
"signatures.empty": "No signatures",
|
|
55
|
-
"signatures.placeHint": "Click on the document to place the signature.",
|
|
56
|
-
"a11y.viewer": "Document viewer",
|
|
57
|
-
"a11y.ribbon": "Ribbon",
|
|
58
|
-
"a11y.editor": "Document editor"
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// src/utils/fileSource.ts
|
|
62
|
-
function guessFileType(name, explicit) {
|
|
63
|
-
if (explicit) return explicit;
|
|
64
|
-
const ext = (name?.split(".").pop() || "").toLowerCase();
|
|
65
|
-
const allowed = [
|
|
66
|
-
"pdf",
|
|
67
|
-
"md",
|
|
68
|
-
"docx",
|
|
69
|
-
"xlsx",
|
|
70
|
-
"pptx",
|
|
71
|
-
"txt",
|
|
72
|
-
"png",
|
|
73
|
-
"jpg",
|
|
74
|
-
"svg"
|
|
75
|
-
];
|
|
76
|
-
return allowed.includes(ext) ? ext : "txt";
|
|
77
|
-
}
|
|
78
|
-
function arrayBufferToBase64(buf) {
|
|
79
|
-
const bytes = new Uint8Array(buf);
|
|
80
|
-
let binary = "";
|
|
81
|
-
const chunk = 32768;
|
|
82
|
-
for (let i = 0; i < bytes.length; i += chunk) {
|
|
83
|
-
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
84
|
-
}
|
|
85
|
-
return btoa(binary);
|
|
86
|
-
}
|
|
87
|
-
async function base64ToArrayBuffer(b64) {
|
|
88
|
-
const bin = atob(b64);
|
|
89
|
-
const len = bin.length;
|
|
90
|
-
const bytes = new Uint8Array(len);
|
|
91
|
-
for (let i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i);
|
|
92
|
-
return bytes.buffer;
|
|
93
|
-
}
|
|
94
|
-
async function resolveSource(args) {
|
|
95
|
-
const fileType = guessFileType(args.fileName, args.fileType);
|
|
96
|
-
const fileName = args.fileName ?? `document.${fileType}`;
|
|
97
|
-
if (args.blob) {
|
|
98
|
-
const ab = await args.blob.arrayBuffer();
|
|
99
|
-
const url = URL.createObjectURL(args.blob);
|
|
100
|
-
return { fileType, fileName, arrayBuffer: ab, url };
|
|
101
|
-
}
|
|
102
|
-
if (args.base64) {
|
|
103
|
-
const ab = await base64ToArrayBuffer(args.base64);
|
|
104
|
-
return { fileType, fileName, arrayBuffer: ab };
|
|
105
|
-
}
|
|
106
|
-
if (!args.fileUrl)
|
|
107
|
-
throw new Error("No file source provided. Use fileUrl, blob, or base64.");
|
|
108
|
-
const res = await fetch(args.fileUrl);
|
|
109
|
-
if (!res.ok) throw new Error(`Failed to fetch file (${res.status})`);
|
|
110
|
-
const total = Number(res.headers.get("content-length") || "") || void 0;
|
|
111
|
-
if (!res.body) {
|
|
112
|
-
const ab = await res.arrayBuffer();
|
|
113
|
-
args.onProgress?.(ab.byteLength, total);
|
|
114
|
-
return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
|
|
115
|
-
}
|
|
116
|
-
const reader = res.body.getReader();
|
|
117
|
-
const chunks = [];
|
|
118
|
-
let loaded = 0;
|
|
119
|
-
while (true) {
|
|
120
|
-
const { done, value } = await reader.read();
|
|
121
|
-
if (done) break;
|
|
122
|
-
if (value) {
|
|
123
|
-
chunks.push(value);
|
|
124
|
-
loaded += value.length;
|
|
125
|
-
args.onProgress?.(loaded, total);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const out = new Uint8Array(loaded);
|
|
129
|
-
let offset = 0;
|
|
130
|
-
for (const c of chunks) {
|
|
131
|
-
out.set(c, offset);
|
|
132
|
-
offset += c.length;
|
|
133
|
-
}
|
|
134
|
-
return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// src/components/Toolbar.tsx
|
|
138
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
139
|
-
function Toolbar(props) {
|
|
140
|
-
const t = (k, fallback) => props.locale[k] ?? fallback;
|
|
141
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar", role: "toolbar", "aria-label": t("a11y.toolbar", "Document toolbar"), children: [
|
|
142
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar__left", children: [
|
|
143
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleThumbnails, "aria-pressed": props.showThumbnails, children: t("toolbar.thumbs", "Thumbnails") }),
|
|
144
|
-
props.mode !== "create" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleSignatures, "aria-pressed": props.showSignatures, children: t("toolbar.signatures", "Signatures") }),
|
|
145
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hv-sep" }),
|
|
146
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: props.layout === "single" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("single"), children: t("toolbar.layout.single", "Single") }),
|
|
147
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: props.layout === "side-by-side" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("side-by-side"), children: t("toolbar.layout.two", "Two") })
|
|
148
|
-
] }),
|
|
149
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar__right", children: [
|
|
150
|
-
props.showHeaderFooterToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "hv-toggle", children: [
|
|
151
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: props.headerFooterEnabled, onChange: props.onToggleHeaderFooter }),
|
|
152
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("toolbar.letterhead", "Letterhead") })
|
|
153
|
-
] }),
|
|
154
|
-
props.allowSigning && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSign, disabled: props.signingDisabled, children: t("toolbar.sign", "Sign Document") }),
|
|
155
|
-
props.canExportPdf && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onExportPdf, children: t("toolbar.exportPdf", "Export as PDF") }),
|
|
156
|
-
props.canSave && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSave, children: t("toolbar.save", "Save") })
|
|
157
|
-
] })
|
|
158
|
-
] });
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// src/components/ThumbnailsSidebar.tsx
|
|
162
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
163
|
-
function ThumbnailsSidebar(props) {
|
|
164
|
-
const t = props.locale["thumbnails.title"] ?? "Thumbnails";
|
|
165
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("aside", { className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs", "aria-label": t, children: [
|
|
166
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-thumbs__header", children: [
|
|
167
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
168
|
-
"button",
|
|
169
|
-
{
|
|
170
|
-
type: "button",
|
|
171
|
-
className: "hv-icon",
|
|
172
|
-
onClick: props.onToggle,
|
|
173
|
-
"aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
|
|
174
|
-
children: props.collapsed ? "\u25B8" : "\u25BE"
|
|
175
|
-
}
|
|
176
|
-
),
|
|
177
|
-
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumbs__title", children: t }) : null
|
|
178
|
-
] }),
|
|
179
|
-
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumbs__list", role: "list", children: props.thumbnails.map((th, idx) => {
|
|
180
|
-
const p = idx + 1;
|
|
181
|
-
const active = p === props.currentPage;
|
|
182
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
183
|
-
"button",
|
|
184
|
-
{
|
|
185
|
-
type: "button",
|
|
186
|
-
role: "listitem",
|
|
187
|
-
className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
|
|
188
|
-
onClick: () => props.onSelectPage(p),
|
|
189
|
-
"aria-current": active ? "page" : void 0,
|
|
190
|
-
children: [
|
|
191
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__placeholder" }) }),
|
|
192
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__label", children: th.label })
|
|
193
|
-
]
|
|
194
|
-
},
|
|
195
|
-
th.id
|
|
196
|
-
);
|
|
197
|
-
}) }) : null
|
|
198
|
-
] });
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/components/SignaturePanel.tsx
|
|
202
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
203
|
-
function SignaturePanel(props) {
|
|
204
|
-
const title = props.locale["signatures.title"] ?? "Signatures";
|
|
205
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("aside", { className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side", "aria-label": title, children: [
|
|
206
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-sidebar-header", children: [
|
|
207
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "hv-icon", onClick: props.onToggle, "aria-label": props.locale["toolbar.signatures"] ?? "Signatures", children: "\u270D" }),
|
|
208
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-sidebar-title", children: title })
|
|
209
|
-
] }),
|
|
210
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-sidebar-body", children: props.signatures.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-signature-card", children: [
|
|
211
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: s.signatureImageUrl, alt: `Signature by ${s.signedBy}`, className: "hv-signature-img" }),
|
|
212
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-signature-meta", children: [
|
|
213
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-name", children: s.signedBy }),
|
|
214
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
|
|
215
|
-
s.comment ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-comment", children: s.comment }) : null
|
|
216
|
-
] })
|
|
217
|
-
] }, `${s.signedBy}-${s.dateSigned}-${idx}`)) })
|
|
218
|
-
] });
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// src/renderers/PdfRenderer.tsx
|
|
222
|
-
var import_react = require("react");
|
|
223
|
-
var import_pdfjs_dist = require("pdfjs-dist");
|
|
224
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
225
|
-
var import_meta = {};
|
|
226
|
-
function PdfRenderer(props) {
|
|
227
|
-
const { url, arrayBuffer } = props;
|
|
228
|
-
const [doc, setDoc] = (0, import_react.useState)(null);
|
|
229
|
-
const [pageCount, setPageCount] = (0, import_react.useState)(0);
|
|
230
|
-
const [rendered, setRendered] = (0, import_react.useState)(
|
|
231
|
-
/* @__PURE__ */ new Map()
|
|
232
|
-
);
|
|
233
|
-
const [thumbs, setThumbs] = (0, import_react.useState)([]);
|
|
234
|
-
const [size, setSize] = (0, import_react.useState)({ w: 840, h: 1188 });
|
|
235
|
-
const [error, setError] = (0, import_react.useState)(null);
|
|
236
|
-
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
237
|
-
const containerRef = (0, import_react.useRef)(null);
|
|
238
|
-
(0, import_react.useEffect)(() => {
|
|
239
|
-
try {
|
|
240
|
-
import_pdfjs_dist.GlobalWorkerOptions.workerSrc = new URL(
|
|
241
|
-
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
242
|
-
import_meta.url
|
|
243
|
-
).toString();
|
|
244
|
-
} catch {
|
|
245
|
-
}
|
|
246
|
-
}, []);
|
|
247
|
-
(0, import_react.useEffect)(() => {
|
|
248
|
-
let cancel = false;
|
|
249
|
-
setError(null);
|
|
250
|
-
setLoading(true);
|
|
251
|
-
(async () => {
|
|
252
|
-
setDoc(null);
|
|
253
|
-
setRendered(/* @__PURE__ */ new Map());
|
|
254
|
-
setThumbs([]);
|
|
255
|
-
if (!url && !arrayBuffer) {
|
|
256
|
-
setError("No PDF source provided.");
|
|
257
|
-
setLoading(false);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
try {
|
|
261
|
-
const task = (0, import_pdfjs_dist.getDocument)(
|
|
262
|
-
url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
|
|
263
|
-
);
|
|
264
|
-
const pdf = await task.promise;
|
|
265
|
-
if (cancel) return;
|
|
266
|
-
setDoc(pdf);
|
|
267
|
-
setPageCount(pdf.numPages);
|
|
268
|
-
props.onPageCount(pdf.numPages);
|
|
269
|
-
setThumbs(Array.from({ length: pdf.numPages }));
|
|
270
|
-
const p1 = await pdf.getPage(1);
|
|
271
|
-
const base = p1.getViewport({ scale: 1 });
|
|
272
|
-
const w = Math.min(980, Math.max(640, base.width));
|
|
273
|
-
const s = w / base.width;
|
|
274
|
-
const vp = p1.getViewport({ scale: s });
|
|
275
|
-
setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
|
|
276
|
-
} catch (e) {
|
|
277
|
-
setError(
|
|
278
|
-
"Failed to load PDF. " + (e instanceof Error ? e.message : "")
|
|
279
|
-
);
|
|
280
|
-
} finally {
|
|
281
|
-
setLoading(false);
|
|
282
|
-
}
|
|
283
|
-
})();
|
|
284
|
-
return () => {
|
|
285
|
-
cancel = true;
|
|
286
|
-
};
|
|
287
|
-
}, [url, arrayBuffer]);
|
|
288
|
-
(0, import_react.useEffect)(() => {
|
|
289
|
-
props.onThumbs(thumbs);
|
|
290
|
-
}, [thumbs]);
|
|
291
|
-
const pagesToShow = (0, import_react.useMemo)(() => {
|
|
292
|
-
if (props.layout === "side-by-side") {
|
|
293
|
-
const left = props.currentPage;
|
|
294
|
-
const right = Math.min(pageCount || left + 1, left + 1);
|
|
295
|
-
return [left, right];
|
|
296
|
-
}
|
|
297
|
-
return [props.currentPage];
|
|
298
|
-
}, [props.currentPage, props.layout, pageCount]);
|
|
299
|
-
(0, import_react.useEffect)(() => {
|
|
300
|
-
if (!doc) return;
|
|
301
|
-
let cancel = false;
|
|
302
|
-
(async () => {
|
|
303
|
-
for (const p of pagesToShow) {
|
|
304
|
-
if (rendered.has(p)) continue;
|
|
305
|
-
try {
|
|
306
|
-
const page = await doc.getPage(p);
|
|
307
|
-
if (cancel) return;
|
|
308
|
-
const base = page.getViewport({ scale: 1 });
|
|
309
|
-
const vp = page.getViewport({ scale: size.w / base.width });
|
|
310
|
-
const canvas = document.createElement("canvas");
|
|
311
|
-
canvas.width = Math.round(vp.width);
|
|
312
|
-
canvas.height = Math.round(vp.height);
|
|
313
|
-
const ctx = canvas.getContext("2d", { alpha: false });
|
|
314
|
-
if (!ctx) continue;
|
|
315
|
-
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
316
|
-
if (cancel) return;
|
|
317
|
-
setRendered((prev) => {
|
|
318
|
-
const next = new Map(prev);
|
|
319
|
-
next.set(p, canvas);
|
|
320
|
-
return next;
|
|
321
|
-
});
|
|
322
|
-
if (!thumbs[p - 1]) {
|
|
323
|
-
const thumbCanvas = document.createElement("canvas");
|
|
324
|
-
const thumbScale = 120 / vp.width;
|
|
325
|
-
thumbCanvas.width = Math.round(vp.width * thumbScale);
|
|
326
|
-
thumbCanvas.height = Math.round(vp.height * thumbScale);
|
|
327
|
-
const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
|
|
328
|
-
if (thumbCtx) {
|
|
329
|
-
thumbCtx.drawImage(
|
|
330
|
-
canvas,
|
|
331
|
-
0,
|
|
332
|
-
0,
|
|
333
|
-
thumbCanvas.width,
|
|
334
|
-
thumbCanvas.height
|
|
335
|
-
);
|
|
336
|
-
setThumbs((prev) => {
|
|
337
|
-
const arr = prev.slice();
|
|
338
|
-
arr[p - 1] = thumbCanvas.toDataURL("image/png");
|
|
339
|
-
return arr;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
})();
|
|
347
|
-
return () => {
|
|
348
|
-
cancel = true;
|
|
349
|
-
};
|
|
350
|
-
}, [doc, pagesToShow, size.w, rendered, thumbs]);
|
|
351
|
-
function onWheel(e) {
|
|
352
|
-
if (!pageCount) return;
|
|
353
|
-
if (Math.abs(e.deltaY) < 10) return;
|
|
354
|
-
const dir = e.deltaY > 0 ? 1 : -1;
|
|
355
|
-
const step = props.layout === "side-by-side" ? 2 : 1;
|
|
356
|
-
const next = Math.max(
|
|
357
|
-
1,
|
|
358
|
-
Math.min(pageCount, props.currentPage + dir * step)
|
|
359
|
-
);
|
|
360
|
-
props.onCurrentPageChange(next);
|
|
361
|
-
}
|
|
362
|
-
function clickPlace(e, page) {
|
|
363
|
-
const stamp = props.signatureStamp;
|
|
364
|
-
if (!stamp?.armed) return;
|
|
365
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
366
|
-
const x = (e.clientX - rect.left) / rect.width;
|
|
367
|
-
const y = (e.clientY - rect.top) / rect.height;
|
|
368
|
-
stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
|
|
369
|
-
}
|
|
370
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
|
|
371
|
-
!doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
|
|
372
|
-
doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
373
|
-
"div",
|
|
374
|
-
{
|
|
375
|
-
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
376
|
-
children: pagesToShow.map((p) => {
|
|
377
|
-
const c = rendered.get(p);
|
|
378
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
379
|
-
"div",
|
|
380
|
-
{
|
|
381
|
-
className: "hv-page",
|
|
382
|
-
style: { width: size.w, height: size.h },
|
|
383
|
-
onClick: (e) => clickPlace(e, p),
|
|
384
|
-
children: c ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
385
|
-
"canvas",
|
|
386
|
-
{
|
|
387
|
-
className: "hv-canvas",
|
|
388
|
-
width: c.width,
|
|
389
|
-
height: c.height,
|
|
390
|
-
ref: (node) => {
|
|
391
|
-
if (!node) return;
|
|
392
|
-
const ctx = node.getContext("2d");
|
|
393
|
-
if (ctx) ctx.drawImage(c, 0, 0);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Rendering\u2026" })
|
|
397
|
-
},
|
|
398
|
-
p
|
|
399
|
-
);
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
) : null
|
|
403
|
-
] });
|
|
404
|
-
}
|
|
405
|
-
|
|
406
40
|
// src/editors/RichTextEditor.tsx
|
|
407
|
-
var
|
|
41
|
+
var import_html2canvas = __toESM(require("html2canvas"), 1);
|
|
408
42
|
var import_mammoth = __toESM(require("mammoth"), 1);
|
|
409
43
|
var import_markdown_it = __toESM(require("markdown-it"), 1);
|
|
410
|
-
var
|
|
44
|
+
var import_react = require("react");
|
|
411
45
|
|
|
412
46
|
// src/utils/sanitize.ts
|
|
413
47
|
var import_dompurify = __toESM(require("dompurify"), 1);
|
|
@@ -419,44 +53,53 @@ function sanitizeHtml(html) {
|
|
|
419
53
|
}
|
|
420
54
|
|
|
421
55
|
// src/editors/RichTextEditor.tsx
|
|
422
|
-
var
|
|
56
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
423
57
|
var PAGE_H = 1122;
|
|
424
|
-
var RichTextEditor = (0,
|
|
58
|
+
var RichTextEditor = (0, import_react.forwardRef)((props, ref) => {
|
|
425
59
|
const readOnly = props.mode === "view";
|
|
426
|
-
const md = (0,
|
|
60
|
+
const md = (0, import_react.useMemo)(
|
|
427
61
|
() => new import_markdown_it.default({ html: false, linkify: true, breaks: true }),
|
|
428
62
|
[]
|
|
429
63
|
);
|
|
430
|
-
const scrollerRef = (0,
|
|
431
|
-
const editorRef = (0,
|
|
432
|
-
const captureRef = (0,
|
|
433
|
-
const [html, setHtml] = (0,
|
|
434
|
-
(0,
|
|
64
|
+
const scrollerRef = (0, import_react.useRef)(null);
|
|
65
|
+
const editorRef = (0, import_react.useRef)(null);
|
|
66
|
+
const captureRef = (0, import_react.useRef)(null);
|
|
67
|
+
const [html, setHtml] = (0, import_react.useState)("<p><br/></p>");
|
|
68
|
+
(0, import_react.useEffect)(() => {
|
|
435
69
|
let cancelled = false;
|
|
436
70
|
(async () => {
|
|
437
71
|
if (props.mode === "create") {
|
|
438
72
|
setHtml("<p><br/></p>");
|
|
439
73
|
return;
|
|
440
74
|
}
|
|
441
|
-
if (!props.arrayBuffer)
|
|
75
|
+
if (!props.arrayBuffer) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
442
78
|
if (props.fileType === "docx") {
|
|
443
79
|
const res = await import_mammoth.default.convertToHtml({
|
|
444
80
|
arrayBuffer: props.arrayBuffer
|
|
445
81
|
});
|
|
446
|
-
if (!cancelled)
|
|
82
|
+
if (!cancelled) {
|
|
83
|
+
setHtml(sanitizeHtml(res.value || "<p><br/></p>"));
|
|
84
|
+
}
|
|
447
85
|
} else {
|
|
448
86
|
const text = new TextDecoder().decode(props.arrayBuffer);
|
|
449
|
-
if (props.fileType === "md")
|
|
450
|
-
|
|
87
|
+
if (props.fileType === "md") {
|
|
88
|
+
setHtml(sanitizeHtml(md.render(text)));
|
|
89
|
+
} else {
|
|
90
|
+
setHtml(`<pre>${escapeHtml(text)}</pre>`);
|
|
91
|
+
}
|
|
451
92
|
}
|
|
452
93
|
})();
|
|
453
94
|
return () => {
|
|
454
95
|
cancelled = true;
|
|
455
96
|
};
|
|
456
97
|
}, [props.arrayBuffer, props.fileType, props.mode, md]);
|
|
457
|
-
(0,
|
|
98
|
+
(0, import_react.useEffect)(() => {
|
|
458
99
|
const el = scrollerRef.current;
|
|
459
|
-
if (!el)
|
|
100
|
+
if (!el) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
460
103
|
const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
|
|
461
104
|
recompute();
|
|
462
105
|
const ro = new ResizeObserver(recompute);
|
|
@@ -464,13 +107,19 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
464
107
|
return () => ro.disconnect();
|
|
465
108
|
}, [html, props.headerFooterEnabled]);
|
|
466
109
|
function exec(cmd) {
|
|
467
|
-
if (readOnly)
|
|
110
|
+
if (readOnly) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
468
113
|
document.execCommand(cmd);
|
|
469
114
|
}
|
|
470
115
|
function onClick(e) {
|
|
471
|
-
if (!props.armedSignatureUrl)
|
|
116
|
+
if (!props.armedSignatureUrl) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
472
119
|
const scroller = scrollerRef.current;
|
|
473
|
-
if (!scroller)
|
|
120
|
+
if (!scroller) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
474
123
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
475
124
|
const absY = scroller.scrollTop + (e.clientY - rect.top);
|
|
476
125
|
const page = Math.max(1, Math.floor(absY / PAGE_H) + 1);
|
|
@@ -482,7 +131,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
482
131
|
async function requestThumbnail(index) {
|
|
483
132
|
const scroller = scrollerRef.current;
|
|
484
133
|
const capture = captureRef.current;
|
|
485
|
-
if (!scroller || !capture)
|
|
134
|
+
if (!scroller || !capture) {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
486
137
|
const old = scroller.scrollTop;
|
|
487
138
|
scroller.scrollTop = index * PAGE_H;
|
|
488
139
|
await new Promise((r) => requestAnimationFrame(() => r(null)));
|
|
@@ -522,7 +173,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
522
173
|
fileName: replaceExt(props.fileName, "docx")
|
|
523
174
|
})
|
|
524
175
|
});
|
|
525
|
-
if (!response.ok)
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
throw new Error("Failed to generate DOCX");
|
|
178
|
+
}
|
|
526
179
|
const blob = await response.blob();
|
|
527
180
|
const url = window.URL.createObjectURL(blob);
|
|
528
181
|
const a = document.createElement("a");
|
|
@@ -549,10 +202,10 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
549
202
|
annotations: { signaturePlacements: props.signaturePlacements }
|
|
550
203
|
});
|
|
551
204
|
}
|
|
552
|
-
(0,
|
|
553
|
-
return /* @__PURE__ */ (0,
|
|
554
|
-
/* @__PURE__ */ (0,
|
|
555
|
-
/* @__PURE__ */ (0,
|
|
205
|
+
(0, import_react.useImperativeHandle)(ref, () => ({ save, requestThumbnail }));
|
|
206
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-doc", children: [
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-ribbon", role: "toolbar", children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
556
209
|
"button",
|
|
557
210
|
{
|
|
558
211
|
className: "hv-btn",
|
|
@@ -561,7 +214,7 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
561
214
|
children: "B"
|
|
562
215
|
}
|
|
563
216
|
),
|
|
564
|
-
/* @__PURE__ */ (0,
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
565
218
|
"button",
|
|
566
219
|
{
|
|
567
220
|
className: "hv-btn",
|
|
@@ -570,7 +223,7 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
570
223
|
children: "I"
|
|
571
224
|
}
|
|
572
225
|
),
|
|
573
|
-
/* @__PURE__ */ (0,
|
|
226
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
574
227
|
"button",
|
|
575
228
|
{
|
|
576
229
|
className: "hv-btn",
|
|
@@ -579,11 +232,11 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
579
232
|
children: "U"
|
|
580
233
|
}
|
|
581
234
|
),
|
|
582
|
-
props.armedSignatureUrl ? /* @__PURE__ */ (0,
|
|
235
|
+
props.armedSignatureUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-hint", children: "Click to place signature" }) : null
|
|
583
236
|
] }),
|
|
584
|
-
/* @__PURE__ */ (0,
|
|
585
|
-
props.headerFooterEnabled && props.headerComponent ? /* @__PURE__ */ (0,
|
|
586
|
-
/* @__PURE__ */ (0,
|
|
237
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-scroll", ref: scrollerRef, onClick, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-pageStage", ref: captureRef, children: [
|
|
238
|
+
props.headerFooterEnabled && props.headerComponent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-letterhead", children: props.headerComponent }) : null,
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
587
240
|
"div",
|
|
588
241
|
{
|
|
589
242
|
ref: editorRef,
|
|
@@ -594,9 +247,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
594
247
|
dangerouslySetInnerHTML: { __html: html }
|
|
595
248
|
}
|
|
596
249
|
),
|
|
597
|
-
props.headerFooterEnabled && props.footerComponent ? /* @__PURE__ */ (0,
|
|
598
|
-
props.mode === "create" && props.signatures.length ? /* @__PURE__ */ (0,
|
|
599
|
-
/* @__PURE__ */ (0,
|
|
250
|
+
props.headerFooterEnabled && props.footerComponent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-letterhead hv-letterhead--footer", children: props.footerComponent }) : null,
|
|
251
|
+
props.mode === "create" && props.signatures.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-signatures-inline", children: props.signatures.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-sign-inline", children: [
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
600
253
|
"img",
|
|
601
254
|
{
|
|
602
255
|
src: s.signatureImageUrl,
|
|
@@ -604,9 +257,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
604
257
|
className: "hv-sign-img"
|
|
605
258
|
}
|
|
606
259
|
),
|
|
607
|
-
/* @__PURE__ */ (0,
|
|
608
|
-
/* @__PURE__ */ (0,
|
|
609
|
-
/* @__PURE__ */ (0,
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
261
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-sign-name", children: s.signedBy }),
|
|
262
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-sign-date", children: new Date(s.dateSigned).toLocaleString() })
|
|
610
263
|
] })
|
|
611
264
|
] }, i)) }) : null
|
|
612
265
|
] }) })
|
|
@@ -621,14 +274,103 @@ function escapeHtml(s) {
|
|
|
621
274
|
}
|
|
622
275
|
|
|
623
276
|
// src/editors/SpreadsheetEditor.tsx
|
|
624
|
-
var
|
|
277
|
+
var import_react2 = require("react");
|
|
625
278
|
var XLSX = __toESM(require("xlsx"), 1);
|
|
626
|
-
|
|
627
|
-
|
|
279
|
+
|
|
280
|
+
// src/utils/fileSource.ts
|
|
281
|
+
function guessFileType(name, explicit) {
|
|
282
|
+
if (explicit) {
|
|
283
|
+
return explicit;
|
|
284
|
+
}
|
|
285
|
+
const ext = (name?.split(".").pop() || "").toLowerCase();
|
|
286
|
+
const allowed = [
|
|
287
|
+
"pdf",
|
|
288
|
+
"md",
|
|
289
|
+
"docx",
|
|
290
|
+
"xlsx",
|
|
291
|
+
"pptx",
|
|
292
|
+
"txt",
|
|
293
|
+
"png",
|
|
294
|
+
"jpg",
|
|
295
|
+
"svg"
|
|
296
|
+
];
|
|
297
|
+
return allowed.includes(ext) ? ext : "txt";
|
|
298
|
+
}
|
|
299
|
+
function arrayBufferToBase64(buf) {
|
|
300
|
+
const bytes = new Uint8Array(buf);
|
|
301
|
+
let binary = "";
|
|
302
|
+
const chunk = 32768;
|
|
303
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
304
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
305
|
+
}
|
|
306
|
+
return btoa(binary);
|
|
307
|
+
}
|
|
308
|
+
async function base64ToArrayBuffer(b64) {
|
|
309
|
+
const bin = atob(b64);
|
|
310
|
+
const len = bin.length;
|
|
311
|
+
const bytes = new Uint8Array(len);
|
|
312
|
+
for (let i = 0; i < len; i++) {
|
|
313
|
+
bytes[i] = bin.charCodeAt(i);
|
|
314
|
+
}
|
|
315
|
+
return bytes.buffer;
|
|
316
|
+
}
|
|
317
|
+
async function resolveSource(args) {
|
|
318
|
+
const fileType = guessFileType(args.fileName, args.fileType);
|
|
319
|
+
const fileName = args.fileName ?? `document.${fileType}`;
|
|
320
|
+
if (args.blob) {
|
|
321
|
+
const ab = await args.blob.arrayBuffer();
|
|
322
|
+
const url = URL.createObjectURL(args.blob);
|
|
323
|
+
return { fileType, fileName, arrayBuffer: ab, url };
|
|
324
|
+
}
|
|
325
|
+
if (args.base64) {
|
|
326
|
+
const ab = await base64ToArrayBuffer(args.base64);
|
|
327
|
+
return { fileType, fileName, arrayBuffer: ab };
|
|
328
|
+
}
|
|
329
|
+
if (!args.fileUrl) {
|
|
330
|
+
throw new Error("No file source provided. Use fileUrl, blob, or base64.");
|
|
331
|
+
}
|
|
332
|
+
const res = await fetch(args.fileUrl);
|
|
333
|
+
if (!res.ok) {
|
|
334
|
+
throw new Error(`Failed to fetch file (${res.status})`);
|
|
335
|
+
}
|
|
336
|
+
const total = Number(res.headers.get("content-length") || "") || void 0;
|
|
337
|
+
if (!res.body) {
|
|
338
|
+
const ab = await res.arrayBuffer();
|
|
339
|
+
args.onProgress?.(ab.byteLength, total);
|
|
340
|
+
return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
|
|
341
|
+
}
|
|
342
|
+
const reader = res.body.getReader();
|
|
343
|
+
const chunks = [];
|
|
344
|
+
let loaded = 0;
|
|
345
|
+
while (true) {
|
|
346
|
+
const { done, value } = await reader.read();
|
|
347
|
+
if (done) {
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
if (value) {
|
|
351
|
+
chunks.push(value);
|
|
352
|
+
loaded += value.length;
|
|
353
|
+
args.onProgress?.(loaded, total);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const out = new Uint8Array(loaded);
|
|
357
|
+
let offset = 0;
|
|
358
|
+
for (const c of chunks) {
|
|
359
|
+
out.set(c, offset);
|
|
360
|
+
offset += c.length;
|
|
361
|
+
}
|
|
362
|
+
return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/editors/SpreadsheetEditor.tsx
|
|
366
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
367
|
+
var SpreadsheetEditor = (0, import_react2.forwardRef)(function SpreadsheetEditor2(props, ref) {
|
|
628
368
|
const readonly = props.mode === "view";
|
|
629
|
-
const [grid, setGrid] = (0,
|
|
630
|
-
(0,
|
|
631
|
-
if (!props.arrayBuffer)
|
|
369
|
+
const [grid, setGrid] = (0, import_react2.useState)(() => Array.from({ length: 30 }, () => Array.from({ length: 12 }, () => "")));
|
|
370
|
+
(0, import_react2.useEffect)(() => {
|
|
371
|
+
if (!props.arrayBuffer) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
632
374
|
try {
|
|
633
375
|
const wb = XLSX.read(props.arrayBuffer, { type: "array" });
|
|
634
376
|
const name = wb.SheetNames[0];
|
|
@@ -652,24 +394,24 @@ var SpreadsheetEditor = (0, import_react3.forwardRef)(function SpreadsheetEditor
|
|
|
652
394
|
const b64 = arrayBufferToBase64(out);
|
|
653
395
|
props.onSave(b64, { fileName: ensureExt(props.fileName, "xlsx"), fileType: "xlsx", exportedAsPdf: !!exportPdf });
|
|
654
396
|
}
|
|
655
|
-
(0,
|
|
397
|
+
(0, import_react2.useImperativeHandle)(ref, () => ({
|
|
656
398
|
save,
|
|
657
399
|
requestThumbnails: async () => void 0
|
|
658
400
|
}));
|
|
659
|
-
const cols = (0,
|
|
660
|
-
return /* @__PURE__ */ (0,
|
|
661
|
-
/* @__PURE__ */ (0,
|
|
662
|
-
/* @__PURE__ */ (0,
|
|
663
|
-
!readonly ? /* @__PURE__ */ (0,
|
|
401
|
+
const cols = (0, import_react2.useMemo)(() => Array.from({ length: grid[0]?.length ?? 0 }, (_, i) => String.fromCharCode(65 + i % 26)), [grid]);
|
|
402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheet", children: [
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetbar", children: [
|
|
404
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetbar-title", children: props.fileName }),
|
|
405
|
+
!readonly ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "hv-btn", type: "button", onClick: () => void save(false), children: props.locale["toolbar.save"] ?? "Save" }) : null
|
|
664
406
|
] }),
|
|
665
|
-
/* @__PURE__ */ (0,
|
|
666
|
-
/* @__PURE__ */ (0,
|
|
667
|
-
/* @__PURE__ */ (0,
|
|
668
|
-
cols.map((c, i) => /* @__PURE__ */ (0,
|
|
407
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetgrid", role: "table", "aria-label": "Spreadsheet", children: [
|
|
408
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetrow hv-sheetrow--header", role: "row", children: [
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--corner", role: "columnheader" }),
|
|
410
|
+
cols.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "columnheader", children: c }, i))
|
|
669
411
|
] }),
|
|
670
|
-
grid.map((row, r) => /* @__PURE__ */ (0,
|
|
671
|
-
/* @__PURE__ */ (0,
|
|
672
|
-
row.map((val, c) => /* @__PURE__ */ (0,
|
|
412
|
+
grid.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetrow", role: "row", children: [
|
|
413
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "rowheader", children: r + 1 }),
|
|
414
|
+
row.map((val, c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
673
415
|
"div",
|
|
674
416
|
{
|
|
675
417
|
className: "hv-sheetcell",
|
|
@@ -698,29 +440,33 @@ function ensureExt(name, ext) {
|
|
|
698
440
|
}
|
|
699
441
|
|
|
700
442
|
// src/renderers/ImageRenderer.tsx
|
|
701
|
-
var
|
|
702
|
-
var
|
|
443
|
+
var import_react3 = require("react");
|
|
444
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
703
445
|
function ImageRenderer({
|
|
704
446
|
arrayBuffer,
|
|
705
447
|
fileType,
|
|
706
448
|
fileName
|
|
707
449
|
}) {
|
|
708
|
-
const [zoom, setZoom] = (0,
|
|
709
|
-
const url = (0,
|
|
710
|
-
if (!arrayBuffer)
|
|
450
|
+
const [zoom, setZoom] = (0, import_react3.useState)(1);
|
|
451
|
+
const url = (0, import_react3.useMemo)(() => {
|
|
452
|
+
if (!arrayBuffer) {
|
|
453
|
+
return void 0;
|
|
454
|
+
}
|
|
711
455
|
const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
|
|
712
456
|
return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
|
|
713
457
|
}, [arrayBuffer, fileType]);
|
|
714
|
-
(0,
|
|
458
|
+
(0, import_react3.useEffect)(() => {
|
|
715
459
|
return () => {
|
|
716
|
-
if (url)
|
|
460
|
+
if (url) {
|
|
461
|
+
URL.revokeObjectURL(url);
|
|
462
|
+
}
|
|
717
463
|
};
|
|
718
464
|
}, [url]);
|
|
719
|
-
return /* @__PURE__ */ (0,
|
|
720
|
-
/* @__PURE__ */ (0,
|
|
721
|
-
/* @__PURE__ */ (0,
|
|
722
|
-
/* @__PURE__ */ (0,
|
|
723
|
-
/* @__PURE__ */ (0,
|
|
465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-doc", children: [
|
|
466
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-mini-toolbar", children: [
|
|
467
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-title", children: fileName }),
|
|
468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-spacer" }),
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
724
470
|
"button",
|
|
725
471
|
{
|
|
726
472
|
type: "button",
|
|
@@ -729,11 +475,11 @@ function ImageRenderer({
|
|
|
729
475
|
children: "-"
|
|
730
476
|
}
|
|
731
477
|
),
|
|
732
|
-
/* @__PURE__ */ (0,
|
|
478
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-zoom", children: [
|
|
733
479
|
Math.round(zoom * 100),
|
|
734
480
|
"%"
|
|
735
481
|
] }),
|
|
736
|
-
/* @__PURE__ */ (0,
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
737
483
|
"button",
|
|
738
484
|
{
|
|
739
485
|
type: "button",
|
|
@@ -743,10 +489,10 @@ function ImageRenderer({
|
|
|
743
489
|
}
|
|
744
490
|
)
|
|
745
491
|
] }),
|
|
746
|
-
/* @__PURE__ */ (0,
|
|
747
|
-
!arrayBuffer && /* @__PURE__ */ (0,
|
|
748
|
-
arrayBuffer && !url && /* @__PURE__ */ (0,
|
|
749
|
-
url && /* @__PURE__ */ (0,
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-center", children: [
|
|
493
|
+
!arrayBuffer && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-error", children: "No image data provided." }),
|
|
494
|
+
arrayBuffer && !url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-error", children: "Failed to load image." }),
|
|
495
|
+
url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
750
496
|
"img",
|
|
751
497
|
{
|
|
752
498
|
src: url,
|
|
@@ -759,10 +505,214 @@ function ImageRenderer({
|
|
|
759
505
|
] });
|
|
760
506
|
}
|
|
761
507
|
|
|
508
|
+
// src/renderers/PdfRenderer.tsx
|
|
509
|
+
var import_pdfjs_dist = require("pdfjs-dist");
|
|
510
|
+
var import_react4 = require("react");
|
|
511
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
512
|
+
var import_meta = {};
|
|
513
|
+
function PdfRenderer(props) {
|
|
514
|
+
const { url, arrayBuffer } = props;
|
|
515
|
+
const [doc, setDoc] = (0, import_react4.useState)(null);
|
|
516
|
+
const [pageCount, setPageCount] = (0, import_react4.useState)(0);
|
|
517
|
+
const [rendered, setRendered] = (0, import_react4.useState)(
|
|
518
|
+
/* @__PURE__ */ new Map()
|
|
519
|
+
);
|
|
520
|
+
const [thumbs, setThumbs] = (0, import_react4.useState)([]);
|
|
521
|
+
const [size, setSize] = (0, import_react4.useState)({ w: 840, h: 1188 });
|
|
522
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
523
|
+
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
524
|
+
const containerRef = (0, import_react4.useRef)(null);
|
|
525
|
+
(0, import_react4.useEffect)(() => {
|
|
526
|
+
try {
|
|
527
|
+
import_pdfjs_dist.GlobalWorkerOptions.workerSrc = new URL(
|
|
528
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
529
|
+
import_meta.url
|
|
530
|
+
).toString();
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}, []);
|
|
534
|
+
(0, import_react4.useEffect)(() => {
|
|
535
|
+
let cancel = false;
|
|
536
|
+
setError(null);
|
|
537
|
+
setLoading(true);
|
|
538
|
+
(async () => {
|
|
539
|
+
setDoc(null);
|
|
540
|
+
setRendered(/* @__PURE__ */ new Map());
|
|
541
|
+
setThumbs([]);
|
|
542
|
+
if (!url && !arrayBuffer) {
|
|
543
|
+
setError("No PDF source provided.");
|
|
544
|
+
setLoading(false);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const task = (0, import_pdfjs_dist.getDocument)(
|
|
549
|
+
url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
|
|
550
|
+
);
|
|
551
|
+
const pdf = await task.promise;
|
|
552
|
+
if (cancel) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
setDoc(pdf);
|
|
556
|
+
setPageCount(pdf.numPages);
|
|
557
|
+
props.onPageCount(pdf.numPages);
|
|
558
|
+
const p1 = await pdf.getPage(1);
|
|
559
|
+
const base = p1.getViewport({ scale: 1 });
|
|
560
|
+
const w = Math.min(980, Math.max(640, base.width));
|
|
561
|
+
const s = w / base.width;
|
|
562
|
+
const vp = p1.getViewport({ scale: s });
|
|
563
|
+
setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
|
|
564
|
+
const thumbWidth = 56;
|
|
565
|
+
const thumbsArr = [];
|
|
566
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
567
|
+
const page = await pdf.getPage(i);
|
|
568
|
+
const pageBase = page.getViewport({ scale: 1 });
|
|
569
|
+
const thumbScale = thumbWidth / pageBase.width;
|
|
570
|
+
const thumbVp = page.getViewport({ scale: thumbScale });
|
|
571
|
+
const thumbCanvas = document.createElement("canvas");
|
|
572
|
+
thumbCanvas.width = Math.round(thumbVp.width);
|
|
573
|
+
thumbCanvas.height = Math.round(thumbVp.height);
|
|
574
|
+
const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
|
|
575
|
+
if (thumbCtx) {
|
|
576
|
+
await page.render({ canvasContext: thumbCtx, viewport: thumbVp }).promise;
|
|
577
|
+
thumbsArr.push(thumbCanvas.toDataURL("image/png"));
|
|
578
|
+
} else {
|
|
579
|
+
thumbsArr.push(void 0);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
setThumbs(thumbsArr);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
setError(
|
|
585
|
+
"Failed to load PDF. " + (e instanceof Error ? e.message : "")
|
|
586
|
+
);
|
|
587
|
+
} finally {
|
|
588
|
+
setLoading(false);
|
|
589
|
+
}
|
|
590
|
+
})();
|
|
591
|
+
return () => {
|
|
592
|
+
cancel = true;
|
|
593
|
+
};
|
|
594
|
+
}, [url, arrayBuffer]);
|
|
595
|
+
(0, import_react4.useEffect)(() => {
|
|
596
|
+
props.onThumbs(thumbs);
|
|
597
|
+
}, [thumbs]);
|
|
598
|
+
const pagesToShow = (0, import_react4.useMemo)(() => {
|
|
599
|
+
if (props.layout === "side-by-side") {
|
|
600
|
+
const left = props.currentPage;
|
|
601
|
+
const right = Math.min(pageCount || left + 1, left + 1);
|
|
602
|
+
return [left, right];
|
|
603
|
+
}
|
|
604
|
+
return [props.currentPage];
|
|
605
|
+
}, [props.currentPage, props.layout, pageCount]);
|
|
606
|
+
(0, import_react4.useEffect)(() => {
|
|
607
|
+
if (!doc) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
let cancel = false;
|
|
611
|
+
(async () => {
|
|
612
|
+
for (const p of pagesToShow) {
|
|
613
|
+
if (rendered.has(p)) {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
try {
|
|
617
|
+
const page = await doc.getPage(p);
|
|
618
|
+
if (cancel) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const base = page.getViewport({ scale: 1 });
|
|
622
|
+
const vp = page.getViewport({ scale: size.w / base.width });
|
|
623
|
+
const canvas = document.createElement("canvas");
|
|
624
|
+
canvas.width = Math.round(vp.width);
|
|
625
|
+
canvas.height = Math.round(vp.height);
|
|
626
|
+
const ctx = canvas.getContext("2d", { alpha: false });
|
|
627
|
+
if (!ctx) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
631
|
+
if (cancel) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
setRendered((prev) => {
|
|
635
|
+
const next = new Map(prev);
|
|
636
|
+
next.set(p, canvas);
|
|
637
|
+
return next;
|
|
638
|
+
});
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
})();
|
|
643
|
+
return () => {
|
|
644
|
+
cancel = true;
|
|
645
|
+
};
|
|
646
|
+
}, [doc, pagesToShow, size.w, rendered]);
|
|
647
|
+
function onWheel(e) {
|
|
648
|
+
if (!pageCount) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (Math.abs(e.deltaY) < 10) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const dir = e.deltaY > 0 ? 1 : -1;
|
|
655
|
+
const step = props.layout === "side-by-side" ? 2 : 1;
|
|
656
|
+
const next = Math.max(
|
|
657
|
+
1,
|
|
658
|
+
Math.min(pageCount, props.currentPage + dir * step)
|
|
659
|
+
);
|
|
660
|
+
props.onCurrentPageChange(next);
|
|
661
|
+
}
|
|
662
|
+
function clickPlace(e, page) {
|
|
663
|
+
const stamp = props.signatureStamp;
|
|
664
|
+
if (!stamp?.armed) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
668
|
+
const x = (e.clientX - rect.left) / rect.width;
|
|
669
|
+
const y = (e.clientY - rect.top) / rect.height;
|
|
670
|
+
stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
|
|
671
|
+
}
|
|
672
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
|
|
673
|
+
!doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
|
|
674
|
+
doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
675
|
+
"div",
|
|
676
|
+
{
|
|
677
|
+
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
678
|
+
children: pagesToShow.map((p) => {
|
|
679
|
+
const c = rendered.get(p);
|
|
680
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
681
|
+
"div",
|
|
682
|
+
{
|
|
683
|
+
className: "hv-page",
|
|
684
|
+
style: { width: size.w, height: size.h },
|
|
685
|
+
onClick: (e) => clickPlace(e, p),
|
|
686
|
+
children: c ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
687
|
+
"canvas",
|
|
688
|
+
{
|
|
689
|
+
className: "hv-canvas",
|
|
690
|
+
width: c.width,
|
|
691
|
+
height: c.height,
|
|
692
|
+
ref: (node) => {
|
|
693
|
+
if (!node) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const ctx = node.getContext("2d");
|
|
697
|
+
if (ctx) {
|
|
698
|
+
ctx.drawImage(c, 0, 0);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Rendering\u2026" })
|
|
703
|
+
},
|
|
704
|
+
p
|
|
705
|
+
);
|
|
706
|
+
})
|
|
707
|
+
}
|
|
708
|
+
) : null
|
|
709
|
+
] });
|
|
710
|
+
}
|
|
711
|
+
|
|
762
712
|
// src/renderers/PptxRenderer.tsx
|
|
763
713
|
var import_react5 = require("react");
|
|
764
714
|
var import_jszip = __toESM(require("jszip"), 1);
|
|
765
|
-
var
|
|
715
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
766
716
|
function decodeXml(s) {
|
|
767
717
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
768
718
|
}
|
|
@@ -775,43 +725,39 @@ function PptxRenderer(props) {
|
|
|
775
725
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
776
726
|
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
777
727
|
(0, import_react5.useEffect)(() => {
|
|
778
|
-
let
|
|
728
|
+
let cancel = false;
|
|
779
729
|
setError(null);
|
|
780
730
|
setLoading(true);
|
|
781
731
|
(async () => {
|
|
782
732
|
setSlides([]);
|
|
783
733
|
setThumbs([]);
|
|
784
734
|
if (!props.arrayBuffer) {
|
|
785
|
-
|
|
786
|
-
setSlides([{ index: 1, text: "No content" }]);
|
|
787
|
-
setError("No PPTX data provided.");
|
|
735
|
+
setError("No PPTX source provided.");
|
|
788
736
|
setLoading(false);
|
|
789
737
|
return;
|
|
790
738
|
}
|
|
791
739
|
try {
|
|
792
740
|
const zip = await import_jszip.default.loadAsync(props.arrayBuffer);
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const out = [];
|
|
799
|
-
for (let i = 0; i < files.length; i++) {
|
|
800
|
-
const xml = await zip.file(files[i]).async("string");
|
|
801
|
-
out.push({ index: i + 1, text: extractText(xml) });
|
|
741
|
+
const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort();
|
|
742
|
+
const slidesOut = [];
|
|
743
|
+
for (let i = 0; i < slidePaths.length; i++) {
|
|
744
|
+
const xml = await zip.files[slidePaths[i]].async("string");
|
|
745
|
+
slidesOut.push({ index: i + 1, text: extractText(xml) });
|
|
802
746
|
}
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
setSlides(out.length ? out : [{ index: 1, text: "(empty)" }]);
|
|
807
|
-
setThumbs(
|
|
808
|
-
Array.from(
|
|
809
|
-
{ length: count },
|
|
810
|
-
(_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
|
|
811
|
-
)
|
|
747
|
+
if (cancel) return;
|
|
748
|
+
setSlides(
|
|
749
|
+
slidesOut.length ? slidesOut : [{ index: 1, text: "(empty)" }]
|
|
812
750
|
);
|
|
751
|
+
props.onSlideCount(slidesOut.length || 1);
|
|
752
|
+
const thumbWidth = 56;
|
|
753
|
+
const thumbsArr = [];
|
|
754
|
+
for (let i = 0; i < (slidesOut.length || 1); i++) {
|
|
755
|
+
thumbsArr.push(
|
|
756
|
+
`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
setThumbs(thumbsArr);
|
|
813
760
|
} catch (e) {
|
|
814
|
-
props.onSlideCount(1);
|
|
815
761
|
setSlides([
|
|
816
762
|
{ index: 1, text: "Unable to render this .pptx in-browser." }
|
|
817
763
|
]);
|
|
@@ -824,7 +770,7 @@ function PptxRenderer(props) {
|
|
|
824
770
|
}
|
|
825
771
|
})();
|
|
826
772
|
return () => {
|
|
827
|
-
|
|
773
|
+
cancel = true;
|
|
828
774
|
};
|
|
829
775
|
}, [props.arrayBuffer]);
|
|
830
776
|
(0, import_react5.useEffect)(() => {
|
|
@@ -838,28 +784,28 @@ function PptxRenderer(props) {
|
|
|
838
784
|
];
|
|
839
785
|
return [props.currentPage];
|
|
840
786
|
}, [props.currentPage, props.layout, slides.length]);
|
|
841
|
-
return /* @__PURE__ */ (0,
|
|
842
|
-
loading && /* @__PURE__ */ (0,
|
|
843
|
-
error && /* @__PURE__ */ (0,
|
|
844
|
-
!loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ (0,
|
|
845
|
-
!error && slides && slides.length > 0 && /* @__PURE__ */ (0,
|
|
787
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-doc", children: [
|
|
788
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-loading", children: "Loading PPTX\u2026" }),
|
|
789
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-error", children: error }),
|
|
790
|
+
!loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-error", children: "No slides to display." }),
|
|
791
|
+
!error && slides && slides.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
846
792
|
"div",
|
|
847
793
|
{
|
|
848
794
|
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
849
795
|
children: pagesToShow.map((p) => {
|
|
850
796
|
const s = slides[p - 1];
|
|
851
|
-
return /* @__PURE__ */ (0,
|
|
797
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
852
798
|
"div",
|
|
853
799
|
{
|
|
854
800
|
className: "hv-slide",
|
|
855
801
|
tabIndex: 0,
|
|
856
802
|
onFocus: () => props.onCurrentPageChange(p),
|
|
857
803
|
children: [
|
|
858
|
-
/* @__PURE__ */ (0,
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-slide-title", children: [
|
|
859
805
|
"Slide ",
|
|
860
806
|
p
|
|
861
807
|
] }),
|
|
862
|
-
/* @__PURE__ */ (0,
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-slide-text", children: s?.text || "" })
|
|
863
809
|
]
|
|
864
810
|
},
|
|
865
811
|
p
|
|
@@ -873,6 +819,111 @@ function svgThumb(n) {
|
|
|
873
819
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="180" height="100"><rect width="100%" height="100%" rx="12" fill="#111827"/><text x="50%" y="54%" font-size="18" fill="#e5e7eb" text-anchor="middle">${n}</text></svg>`;
|
|
874
820
|
}
|
|
875
821
|
|
|
822
|
+
// src/utils/locale.ts
|
|
823
|
+
var defaultLocale = {
|
|
824
|
+
"loading": "Loading\u2026",
|
|
825
|
+
"error.title": "Error",
|
|
826
|
+
"toolbar.layout.single": "Single page",
|
|
827
|
+
"toolbar.layout.two": "Side-by-side",
|
|
828
|
+
"toolbar.thumbs": "Thumbnails",
|
|
829
|
+
"toolbar.signatures": "Signatures",
|
|
830
|
+
"toolbar.sign": "Sign Document",
|
|
831
|
+
"toolbar.save": "Save",
|
|
832
|
+
"toolbar.exportPdf": "Export as PDF",
|
|
833
|
+
"thumbnails.title": "Thumbnails",
|
|
834
|
+
"thumbnails.page": "Page",
|
|
835
|
+
"signatures.title": "Signatures",
|
|
836
|
+
"signatures.empty": "No signatures",
|
|
837
|
+
"signatures.placeHint": "Click on the document to place the signature.",
|
|
838
|
+
"a11y.viewer": "Document viewer",
|
|
839
|
+
"a11y.ribbon": "Ribbon",
|
|
840
|
+
"a11y.editor": "Document editor"
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// src/components/SignaturePanel.tsx
|
|
844
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
845
|
+
function SignaturePanel(props) {
|
|
846
|
+
const title = props.locale["signatures.title"] ?? "Signatures";
|
|
847
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("aside", { className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side", "aria-label": title, children: [
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sidebar-header", children: [
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "hv-icon", onClick: props.onToggle, "aria-label": props.locale["toolbar.signatures"] ?? "Signatures", children: "\u270D" }),
|
|
850
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sidebar-title", children: title })
|
|
851
|
+
] }),
|
|
852
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sidebar-body", children: props.signatures.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-signature-card", children: [
|
|
853
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: s.signatureImageUrl, alt: `Signature by ${s.signedBy}`, className: "hv-signature-img" }),
|
|
854
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-signature-meta", children: [
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-name", children: s.signedBy }),
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
|
|
857
|
+
s.comment ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-comment", children: s.comment }) : null
|
|
858
|
+
] })
|
|
859
|
+
] }, `${s.signedBy}-${s.dateSigned}-${idx}`)) })
|
|
860
|
+
] });
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/components/ThumbnailsSidebar.tsx
|
|
864
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
865
|
+
function ThumbnailsSidebar(props) {
|
|
866
|
+
const t = props.locale["thumbnails.title"] ?? "Thumbnails";
|
|
867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("aside", { className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs", "aria-label": t, children: [
|
|
868
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-thumbs__header", children: [
|
|
869
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
870
|
+
"button",
|
|
871
|
+
{
|
|
872
|
+
type: "button",
|
|
873
|
+
className: "hv-icon",
|
|
874
|
+
onClick: props.onToggle,
|
|
875
|
+
"aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
|
|
876
|
+
children: props.collapsed ? "\u25B8" : "\u25BE"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumbs__title", children: t }) : null
|
|
880
|
+
] }),
|
|
881
|
+
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumbs__list", role: "list", children: props.thumbnails.map((th, idx) => {
|
|
882
|
+
const p = idx + 1;
|
|
883
|
+
const active = p === props.currentPage;
|
|
884
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
885
|
+
"button",
|
|
886
|
+
{
|
|
887
|
+
type: "button",
|
|
888
|
+
role: "listitem",
|
|
889
|
+
className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
|
|
890
|
+
onClick: () => props.onSelectPage(p),
|
|
891
|
+
"aria-current": active ? "page" : void 0,
|
|
892
|
+
children: [
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb__img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb__placeholder" }) }),
|
|
894
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb__label", children: th.label })
|
|
895
|
+
]
|
|
896
|
+
},
|
|
897
|
+
th.id
|
|
898
|
+
);
|
|
899
|
+
}) }) : null
|
|
900
|
+
] });
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/components/Toolbar.tsx
|
|
904
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
905
|
+
function Toolbar(props) {
|
|
906
|
+
const t = (k, fallback) => props.locale[k] ?? fallback;
|
|
907
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar", role: "toolbar", "aria-label": t("a11y.toolbar", "Document toolbar"), children: [
|
|
908
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar__left", children: [
|
|
909
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleThumbnails, "aria-pressed": props.showThumbnails, children: t("toolbar.thumbs", "Thumbnails") }),
|
|
910
|
+
props.mode !== "create" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleSignatures, "aria-pressed": props.showSignatures, children: t("toolbar.signatures", "Signatures") }),
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hv-sep" }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: props.layout === "single" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("single"), children: t("toolbar.layout.single", "Single") }),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: props.layout === "side-by-side" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("side-by-side"), children: t("toolbar.layout.two", "Two") })
|
|
914
|
+
] }),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar__right", children: [
|
|
916
|
+
props.showHeaderFooterToggle && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "hv-toggle", children: [
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: props.headerFooterEnabled, onChange: props.onToggleHeaderFooter }),
|
|
918
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: t("toolbar.letterhead", "Letterhead") })
|
|
919
|
+
] }),
|
|
920
|
+
props.allowSigning && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSign, disabled: props.signingDisabled, children: t("toolbar.sign", "Sign Document") }),
|
|
921
|
+
props.canExportPdf && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onExportPdf, children: t("toolbar.exportPdf", "Export as PDF") }),
|
|
922
|
+
props.canSave && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSave, children: t("toolbar.save", "Save") })
|
|
923
|
+
] })
|
|
924
|
+
] });
|
|
925
|
+
}
|
|
926
|
+
|
|
876
927
|
// src/components/DocumentViewer.tsx
|
|
877
928
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
878
929
|
function DocumentViewer(props) {
|
|
@@ -918,10 +969,14 @@ function DocumentViewer(props) {
|
|
|
918
969
|
fileName: props.fileName,
|
|
919
970
|
fileType: props.fileType
|
|
920
971
|
});
|
|
921
|
-
if (cancelled)
|
|
972
|
+
if (cancelled) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
922
975
|
setResolved({ fileType: res.fileType, fileName: res.fileName, url: res.url, arrayBuffer: res.arrayBuffer });
|
|
923
976
|
} catch (e) {
|
|
924
|
-
if (cancelled)
|
|
977
|
+
if (cancelled) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
925
980
|
setError(e instanceof Error ? e.message : String(e));
|
|
926
981
|
}
|
|
927
982
|
})();
|
|
@@ -938,7 +993,9 @@ function DocumentViewer(props) {
|
|
|
938
993
|
}));
|
|
939
994
|
}, [pageCount, thumbs, locale]);
|
|
940
995
|
async function handleSignRequest() {
|
|
941
|
-
if (!allowSigning || signingBusy || !props.onSignRequest)
|
|
996
|
+
if (!allowSigning || signingBusy || !props.onSignRequest) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
942
999
|
setSigningBusy(true);
|
|
943
1000
|
try {
|
|
944
1001
|
const sig = await props.onSignRequest();
|
|
@@ -949,7 +1006,9 @@ function DocumentViewer(props) {
|
|
|
949
1006
|
}
|
|
950
1007
|
}
|
|
951
1008
|
function placeSignature(p) {
|
|
952
|
-
if (!armedSignatureUrl)
|
|
1009
|
+
if (!armedSignatureUrl) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
953
1012
|
setSigPlacements((prev) => [...prev, { ...p, signatureImageUrl: armedSignatureUrl }]);
|
|
954
1013
|
setArmedSignatureUrl(null);
|
|
955
1014
|
}
|
|
@@ -958,7 +1017,9 @@ function DocumentViewer(props) {
|
|
|
958
1017
|
await editorRef.current.save(!!exportPdf);
|
|
959
1018
|
return;
|
|
960
1019
|
}
|
|
961
|
-
if (!resolved?.arrayBuffer)
|
|
1020
|
+
if (!resolved?.arrayBuffer) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
962
1023
|
const b64 = arrayBufferToBase642(resolved.arrayBuffer);
|
|
963
1024
|
props.onSave?.(b64, { fileName: resolved.fileName, fileType: resolved.fileType, annotations: { sigPlacements } });
|
|
964
1025
|
}
|
|
@@ -993,7 +1054,7 @@ function DocumentViewer(props) {
|
|
|
993
1054
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-title", children: locale["error.title"] ?? "Error" }),
|
|
994
1055
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-body", children: error })
|
|
995
1056
|
] }) : null,
|
|
996
|
-
!resolved && !error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-loading", "aria-busy": "true", children: locale
|
|
1057
|
+
!resolved && !error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-loading", "aria-busy": "true", children: locale.loading ?? "Loading\u2026" }) : null,
|
|
997
1058
|
resolved ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-shell", children: [
|
|
998
1059
|
mode !== "create" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
999
1060
|
ThumbnailsSidebar,
|