@zerohive/hive-viewer 0.2.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,151 +1,839 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DocumentViewer: () => DocumentViewer
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
1
37
  // src/components/DocumentViewer.tsx
2
- import { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3, useState as useState6 } from "react";
38
+ var import_react8 = require("react");
3
39
 
4
40
  // src/editors/RichTextEditor.tsx
5
- import html2canvas from "html2canvas";
6
- import mammoth from "mammoth";
7
- import MarkdownIt from "markdown-it";
8
- import {
9
- forwardRef,
10
- useEffect,
11
- useImperativeHandle,
12
- useMemo,
13
- useRef
14
- } from "react";
41
+ var import_mammoth = __toESM(require("mammoth"), 1);
42
+ var import_react = require("react");
43
+ var import_markdown_it = __toESM(require("markdown-it"), 1);
15
44
 
16
45
  // src/utils/sanitize.ts
17
- import DOMPurify from "dompurify";
46
+ var import_dompurify = __toESM(require("dompurify"), 1);
18
47
  function sanitizeHtml(html) {
19
- return DOMPurify.sanitize(html, {
48
+ return import_dompurify.default.sanitize(html, {
20
49
  USE_PROFILES: { html: true },
21
50
  ADD_ATTR: ["target", "rel"]
22
51
  });
23
52
  }
24
53
 
25
54
  // src/editors/RichTextEditor.tsx
26
- import { jsx, jsxs } from "react/jsx-runtime";
27
- var PAGE_H = 1122;
28
- var RichTextEditor = forwardRef(
29
- (props, ref) => {
30
- const readOnly = props.mode === "view";
31
- const md = useMemo(
32
- () => new MarkdownIt({ html: false, linkify: true, breaks: true }),
33
- []
34
- );
35
- const scrollerRef = useRef(null);
36
- const editorRef = useRef(null);
37
- const captureRef = useRef(null);
38
- const initialized = useRef(false);
39
- useEffect(() => {
40
- if (initialized.current) return;
41
- (async () => {
42
- if (props.mode === "create") {
43
- editorRef.current.innerHTML = "<p><br/></p>";
44
- initialized.current = true;
45
- return;
46
- }
47
- if (!props.arrayBuffer) return;
48
- if (props.fileType === "docx") {
49
- const res = await mammoth.convertToHtml({
55
+ var import_jsx_runtime = require("react/jsx-runtime");
56
+ function RichTextEditor(props) {
57
+ const editorRef = (0, import_react.useRef)(null);
58
+ const [contentHtml, setContentHtml] = (0, import_react.useState)("");
59
+ const [loading, setLoading] = (0, import_react.useState)(false);
60
+ const mdParser = (0, import_react.useRef)(new import_markdown_it.default({ html: true, linkify: true }));
61
+ (0, import_react.useEffect)(() => {
62
+ const loadDoc = async () => {
63
+ if (!props.arrayBuffer) return;
64
+ setLoading(true);
65
+ try {
66
+ if (props.fileName.endsWith(".docx") || props.fileType === "docx") {
67
+ const result = await import_mammoth.default.convertToHtml({
50
68
  arrayBuffer: props.arrayBuffer
51
69
  });
52
- editorRef.current.innerHTML = sanitizeHtml(
53
- res.value || "<p><br/></p>"
54
- );
70
+ setContentHtml(sanitizeHtml(result.value));
55
71
  } else {
56
- const text = new TextDecoder().decode(props.arrayBuffer);
57
- editorRef.current.innerHTML = props.fileType === "md" ? sanitizeHtml(md.render(text)) : `<pre>${escapeHtml(text)}</pre>`;
72
+ const decoder = new TextDecoder("utf-8");
73
+ const text = decoder.decode(props.arrayBuffer);
74
+ if (props.fileName.endsWith(".md") || props.fileType === "md") {
75
+ const html = mdParser.current.render(text);
76
+ setContentHtml(sanitizeHtml(html));
77
+ } else {
78
+ setContentHtml(
79
+ `<pre style="white-space: pre-wrap; font-family: monospace;">${text}</pre>`
80
+ );
81
+ }
58
82
  }
59
- initialized.current = true;
60
- })();
61
- }, [props.arrayBuffer, props.fileType, props.mode, md]);
62
- useEffect(() => {
63
- const el = scrollerRef.current;
64
- if (!el) return;
65
- const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
66
- recompute();
67
- const ro = new ResizeObserver(recompute);
68
- ro.observe(el);
69
- return () => ro.disconnect();
70
- }, [props.headerFooterEnabled]);
71
- function exec(cmd) {
72
- if (readOnly) return;
73
- document.execCommand(cmd);
74
- editorRef.current?.focus();
75
- }
76
- function onClickPage(e) {
77
- if (!props.armedSignatureUrl) return;
78
- const scroller = scrollerRef.current;
79
- if (!scroller) return;
80
- const rect = e.currentTarget.getBoundingClientRect();
81
- const absY = scroller.scrollTop + (e.clientY - rect.top);
82
- const page = Math.floor(absY / PAGE_H) + 1;
83
- props.onPlaceSignature({
84
- page,
85
- x: (e.clientX - rect.left) / rect.width,
86
- y: absY % PAGE_H / PAGE_H,
87
- w: 0.25,
88
- h: 0.1
89
- });
90
- }
91
- async function requestThumbnail(index) {
92
- const scroller = scrollerRef.current;
93
- const capture = captureRef.current;
94
- if (!scroller || !capture) return;
95
- const old = scroller.scrollTop;
96
- scroller.scrollTop = index * PAGE_H;
97
- await new Promise((r) => requestAnimationFrame(r));
98
- try {
99
- const canvas = await html2canvas(capture, {
100
- scale: 0.25,
101
- useCORS: true
102
- });
103
- return canvas.toDataURL("image/png");
83
+ props.onPageCount(1);
84
+ } catch (err) {
85
+ console.error("Doc Conversion failed", err);
86
+ setContentHtml("<p style='color:red'>Error parsing document.</p>");
104
87
  } finally {
105
- scroller.scrollTop = old;
88
+ setLoading(false);
106
89
  }
90
+ };
91
+ loadDoc();
92
+ }, [props.arrayBuffer, props.fileName, props.fileType]);
93
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-view-single", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-page-container", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-12 text-center text-gray-400", children: "Processing text..." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
94
+ "div",
95
+ {
96
+ ref: editorRef,
97
+ className: "hv-docx-content",
98
+ contentEditable: props.mode === "edit" || props.mode === "create",
99
+ dangerouslySetInnerHTML: { __html: contentHtml },
100
+ suppressContentEditableWarning: true
107
101
  }
108
- async function save(exportPdf) {
109
- const html = editorRef.current?.innerHTML ?? "";
110
- const stitched = `<!doctype html><html><body>${html}</body></html>`;
111
- const b64 = btoa(unescape(encodeURIComponent(stitched)));
112
- props.onSave(b64, {
113
- fileName: props.fileName,
114
- fileType: props.fileType,
115
- exportedAsPdf: exportPdf,
116
- annotations: { signaturePlacements: props.signaturePlacements }
102
+ ) }) });
103
+ }
104
+
105
+ // src/editors/SpreadsheetEditor.tsx
106
+ var import_react2 = require("react");
107
+ var XLSX = __toESM(require("xlsx"), 1);
108
+ var import_jsx_runtime2 = require("react/jsx-runtime");
109
+ var SpreadsheetEditor = (0, import_react2.forwardRef)(function SpreadsheetEditor2(props, ref) {
110
+ const readonly = props.mode === "view";
111
+ const [data, setData] = (0, import_react2.useState)([]);
112
+ const [cols, setCols] = (0, import_react2.useState)([]);
113
+ (0, import_react2.useEffect)(() => {
114
+ if (!props.arrayBuffer) return;
115
+ try {
116
+ const wb = XLSX.read(props.arrayBuffer, { type: "array" });
117
+ const wsName = wb.SheetNames[0];
118
+ const ws = wb.Sheets[wsName];
119
+ const jsonData = XLSX.utils.sheet_to_json(ws, {
120
+ header: 1,
121
+ defval: ""
117
122
  });
123
+ const minRows = 40;
124
+ const minCols = 15;
125
+ const rows = Math.max(minRows, jsonData.length);
126
+ const colCount = Math.max(minCols, jsonData[0]?.length || 0);
127
+ const normalized = Array.from({ length: rows }, (_, r) => {
128
+ const row = jsonData[r] || [];
129
+ return Array.from(
130
+ { length: colCount },
131
+ (_2, c) => row[c] !== void 0 && row[c] !== null ? String(row[c]) : ""
132
+ );
133
+ });
134
+ setData(normalized);
135
+ const headers = Array.from({ length: colCount }, (_, i) => {
136
+ let letter = "";
137
+ let temp = i;
138
+ while (temp >= 0) {
139
+ letter = String.fromCharCode(temp % 26 + 65) + letter;
140
+ temp = Math.floor(temp / 26) - 1;
141
+ }
142
+ return letter;
143
+ });
144
+ setCols(headers);
145
+ } catch (e) {
146
+ console.error("Spreadsheet load error:", e);
147
+ }
148
+ }, [props.arrayBuffer]);
149
+ async function save(exportPdf) {
150
+ if (!props.onSave) return;
151
+ const ws = XLSX.utils.aoa_to_sheet(data);
152
+ const wb = XLSX.utils.book_new();
153
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
154
+ const out = XLSX.write(wb, { type: "array", bookType: "xlsx" });
155
+ const bytes = new Uint8Array(out);
156
+ let binary = "";
157
+ for (let i = 0; i < bytes.byteLength; i++) {
158
+ binary += String.fromCharCode(bytes[i]);
159
+ }
160
+ props.onSave(btoa(binary), {
161
+ fileName: props.fileName,
162
+ fileType: "xlsx",
163
+ exportedAsPdf: !!exportPdf
164
+ });
165
+ }
166
+ (0, import_react2.useImperativeHandle)(ref, () => ({ save }));
167
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-view-single", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
168
+ "div",
169
+ {
170
+ className: "hv-page-container",
171
+ style: {
172
+ width: "auto",
173
+ maxWidth: "95vw",
174
+ minWidth: "800px",
175
+ padding: 0,
176
+ overflow: "hidden",
177
+ display: "flex",
178
+ flexDirection: "column"
179
+ },
180
+ children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
182
+ "div",
183
+ {
184
+ style: {
185
+ background: "#f8f9fa",
186
+ borderBottom: "1px solid #e2e8f0",
187
+ padding: "8px 16px",
188
+ fontSize: "12px",
189
+ color: "#64748b",
190
+ display: "flex",
191
+ gap: "12px",
192
+ flexShrink: 0
193
+ },
194
+ children: [
195
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "fx" }),
196
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
197
+ "div",
198
+ {
199
+ style: {
200
+ background: "white",
201
+ border: "1px solid #cbd5e1",
202
+ flex: 1,
203
+ height: "18px",
204
+ borderRadius: "2px"
205
+ }
206
+ }
207
+ )
208
+ ]
209
+ }
210
+ ),
211
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
212
+ "div",
213
+ {
214
+ style: {
215
+ overflow: "auto",
216
+ // FIX: Dynamic height ensures bottom isn't cut off on small screens
217
+ maxHeight: "calc(100vh - 140px)",
218
+ minHeight: "400px",
219
+ position: "relative",
220
+ // FIX: Padding prevents border clipping (first row/col edges)
221
+ padding: "1px"
222
+ },
223
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
224
+ "table",
225
+ {
226
+ style: {
227
+ borderCollapse: "collapse",
228
+ minWidth: "100%",
229
+ tableLayout: "fixed"
230
+ },
231
+ children: [
232
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
233
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
234
+ "th",
235
+ {
236
+ style: {
237
+ width: "40px",
238
+ background: "#f1f5f9",
239
+ border: "1px solid #cbd5e1",
240
+ position: "sticky",
241
+ top: 0,
242
+ left: 0,
243
+ zIndex: 30
244
+ // Increased zIndex
245
+ }
246
+ }
247
+ ),
248
+ cols.map((col, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
249
+ "th",
250
+ {
251
+ style: {
252
+ minWidth: "100px",
253
+ background: "#f1f5f9",
254
+ border: "1px solid #cbd5e1",
255
+ color: "#475569",
256
+ fontSize: "11px",
257
+ fontWeight: 600,
258
+ padding: "4px",
259
+ position: "sticky",
260
+ top: 0,
261
+ zIndex: 20
262
+ // Increased zIndex
263
+ },
264
+ children: col
265
+ },
266
+ i
267
+ ))
268
+ ] }) }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
271
+ "td",
272
+ {
273
+ style: {
274
+ background: "#f1f5f9",
275
+ border: "1px solid #cbd5e1",
276
+ textAlign: "center",
277
+ fontSize: "11px",
278
+ color: "#64748b",
279
+ position: "sticky",
280
+ left: 0,
281
+ zIndex: 20
282
+ // Increased zIndex to stay above cells
283
+ },
284
+ children: r + 1
285
+ }
286
+ ),
287
+ row.map((cell, c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
288
+ "td",
289
+ {
290
+ contentEditable: !readonly,
291
+ suppressContentEditableWarning: true,
292
+ onBlur: (e) => {
293
+ const val = e.currentTarget.innerText;
294
+ setData((prev) => {
295
+ const next = [...prev];
296
+ next[r] = [...next[r]];
297
+ next[r][c] = val;
298
+ return next;
299
+ });
300
+ },
301
+ style: {
302
+ border: "1px solid #e2e8f0",
303
+ padding: "4px 8px",
304
+ fontSize: "13px",
305
+ color: "#1e293b",
306
+ minWidth: "100px",
307
+ whiteSpace: "nowrap",
308
+ overflow: "hidden",
309
+ outline: "none",
310
+ zIndex: 1
311
+ },
312
+ children: cell
313
+ },
314
+ c
315
+ ))
316
+ ] }, r)) })
317
+ ]
318
+ }
319
+ )
320
+ }
321
+ )
322
+ ]
118
323
  }
119
- useImperativeHandle(ref, () => ({
120
- save,
121
- requestThumbnail
122
- }));
123
- return /* @__PURE__ */ jsxs("div", { className: "hv-root", children: [
124
- /* @__PURE__ */ jsxs("div", { className: "hv-toolbar", children: [
125
- /* @__PURE__ */ jsx("button", { onClick: () => exec("bold"), disabled: readOnly, children: "B" }),
126
- /* @__PURE__ */ jsx("button", { onClick: () => exec("italic"), disabled: readOnly, children: "I" }),
127
- /* @__PURE__ */ jsx("button", { onClick: () => exec("underline"), disabled: readOnly, children: "U" }),
128
- props.armedSignatureUrl && /* @__PURE__ */ jsx("span", { className: "hv-hint", children: "Click page to place signature" })
324
+ ) });
325
+ });
326
+
327
+ // src/renderers/ImageRenderer.tsx
328
+ var import_react3 = require("react");
329
+ var import_jsx_runtime3 = require("react/jsx-runtime");
330
+ function ImageRenderer({
331
+ arrayBuffer,
332
+ fileType,
333
+ fileName
334
+ }) {
335
+ const [zoom, setZoom] = (0, import_react3.useState)(1);
336
+ const url = (0, import_react3.useMemo)(() => {
337
+ if (!arrayBuffer) {
338
+ return void 0;
339
+ }
340
+ const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
341
+ return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
342
+ }, [arrayBuffer, fileType]);
343
+ (0, import_react3.useEffect)(() => {
344
+ return () => {
345
+ if (url) {
346
+ URL.revokeObjectURL(url);
347
+ }
348
+ };
349
+ }, [url]);
350
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col h-full bg-gray-50", children: [
351
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between px-6 py-4 bg-white border-b border-gray-200", children: [
352
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "text-sm font-medium text-gray-700 truncate max-w-md", children: fileName }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-3 bg-gray-100 rounded-lg p-1", children: [
354
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
355
+ "button",
356
+ {
357
+ type: "button",
358
+ onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
359
+ className: "w-9 h-9 flex items-center justify-center rounded-md bg-white hover:bg-gray-50 text-gray-700 transition-all shadow-sm hover:shadow",
360
+ "aria-label": "Zoom out",
361
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
362
+ "svg",
363
+ {
364
+ className: "w-4 h-4",
365
+ fill: "none",
366
+ viewBox: "0 0 24 24",
367
+ stroke: "currentColor",
368
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
369
+ "path",
370
+ {
371
+ strokeLinecap: "round",
372
+ strokeLinejoin: "round",
373
+ strokeWidth: 2,
374
+ d: "M20 12H4"
375
+ }
376
+ )
377
+ }
378
+ )
379
+ }
380
+ ),
381
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-sm font-semibold text-gray-700 min-w-[3.5rem] text-center px-2", children: [
382
+ Math.round(zoom * 100),
383
+ "%"
384
+ ] }),
385
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
390
+ className: "w-9 h-9 flex items-center justify-center rounded-md bg-white hover:bg-gray-50 text-gray-700 transition-all shadow-sm hover:shadow",
391
+ "aria-label": "Zoom in",
392
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
393
+ "svg",
394
+ {
395
+ className: "w-4 h-4",
396
+ fill: "none",
397
+ viewBox: "0 0 24 24",
398
+ stroke: "currentColor",
399
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
400
+ "path",
401
+ {
402
+ strokeLinecap: "round",
403
+ strokeLinejoin: "round",
404
+ strokeWidth: 2,
405
+ d: "M12 4v16m8-8H4"
406
+ }
407
+ )
408
+ }
409
+ )
410
+ }
411
+ )
412
+ ] })
413
+ ] }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 overflow-auto flex items-center justify-center p-8", children: [
415
+ !arrayBuffer && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center", children: [
416
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-16 h-16 mx-auto mb-3 rounded-full bg-gray-200 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
417
+ "svg",
418
+ {
419
+ className: "w-8 h-8 text-gray-400",
420
+ fill: "none",
421
+ viewBox: "0 0 24 24",
422
+ stroke: "currentColor",
423
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
424
+ "path",
425
+ {
426
+ strokeLinecap: "round",
427
+ strokeLinejoin: "round",
428
+ strokeWidth: 2,
429
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
430
+ }
431
+ )
432
+ }
433
+ ) }),
434
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500", children: "No image data provided" })
435
+ ] }),
436
+ arrayBuffer && !url && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center", children: [
437
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-16 h-16 mx-auto mb-3 rounded-full bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
438
+ "svg",
439
+ {
440
+ className: "w-8 h-8 text-red-500",
441
+ fill: "none",
442
+ viewBox: "0 0 24 24",
443
+ stroke: "currentColor",
444
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
445
+ "path",
446
+ {
447
+ strokeLinecap: "round",
448
+ strokeLinejoin: "round",
449
+ strokeWidth: 2,
450
+ d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
451
+ }
452
+ )
453
+ }
454
+ ) }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-600", children: "Failed to load image" })
129
456
  ] }),
130
- /* @__PURE__ */ jsx("div", { className: "hv-scroll", ref: scrollerRef, onClick: onClickPage, children: /* @__PURE__ */ jsx("div", { className: "hv-pageStage", ref: captureRef, children: /* @__PURE__ */ jsx(
457
+ url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
131
458
  "div",
132
459
  {
133
- ref: editorRef,
134
- className: `hv-editor ${readOnly ? "ro" : ""}`,
135
- contentEditable: !readOnly,
136
- suppressContentEditableWarning: true
460
+ style: { transform: `scale(${zoom})` },
461
+ className: "transition-transform duration-200 origin-center",
462
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
463
+ "img",
464
+ {
465
+ src: url,
466
+ alt: fileName,
467
+ style: { transform: `scale(${zoom})` },
468
+ className: "max-w-full h-auto rounded-lg shadow-lg transition-transform duration-200"
469
+ }
470
+ )
471
+ }
472
+ )
473
+ ] })
474
+ ] });
475
+ }
476
+
477
+ // src/renderers/PdfRenderer.tsx
478
+ var import_pdfjs_dist = require("pdfjs-dist");
479
+ var import_react4 = require("react");
480
+ var import_jsx_runtime4 = require("react/jsx-runtime");
481
+ var PDF_WORKER_URL = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.10.38/pdf.worker.min.mjs";
482
+ function PdfRenderer(props) {
483
+ const { url, arrayBuffer, layout, currentPage } = props;
484
+ const [doc, setDoc] = (0, import_react4.useState)(null);
485
+ const [error, setError] = (0, import_react4.useState)(null);
486
+ (0, import_react4.useEffect)(() => {
487
+ if (!import_pdfjs_dist.GlobalWorkerOptions.workerSrc) {
488
+ import_pdfjs_dist.GlobalWorkerOptions.workerSrc = PDF_WORKER_URL;
489
+ }
490
+ let active = true;
491
+ const loadPdf = async () => {
492
+ if (!url && !arrayBuffer) return;
493
+ setError(null);
494
+ try {
495
+ const dataSource = arrayBuffer ? { data: arrayBuffer.slice(0) } : { url };
496
+ const loadingTask = (0, import_pdfjs_dist.getDocument)(dataSource);
497
+ const pdf = await loadingTask.promise;
498
+ if (active) {
499
+ setDoc(pdf);
500
+ props.onPageCount(pdf.numPages);
501
+ generateThumbnails(pdf);
137
502
  }
138
- ) }) })
139
- ] });
503
+ } catch (err) {
504
+ console.error("PDF Load Error:", err);
505
+ if (active) setError(err.message || "Failed to load PDF");
506
+ }
507
+ };
508
+ loadPdf();
509
+ return () => {
510
+ active = false;
511
+ };
512
+ }, [url, arrayBuffer]);
513
+ const generateThumbnails = async (pdf) => {
514
+ try {
515
+ const thumbs = [];
516
+ const num = Math.min(pdf.numPages, 5);
517
+ for (let i = 1; i <= num; i++) {
518
+ const page = await pdf.getPage(i);
519
+ const viewport = page.getViewport({ scale: 0.2 });
520
+ const canvas = document.createElement("canvas");
521
+ canvas.width = viewport.width;
522
+ canvas.height = viewport.height;
523
+ const ctx = canvas.getContext("2d");
524
+ if (ctx) {
525
+ await page.render({ canvasContext: ctx, viewport }).promise;
526
+ thumbs.push(canvas.toDataURL());
527
+ }
528
+ }
529
+ props.onThumbs(thumbs);
530
+ } catch (e) {
531
+ }
532
+ };
533
+ const pagesToRender = (0, import_react4.useMemo)(() => {
534
+ if (!doc) return [];
535
+ const p = Math.max(1, Math.min(currentPage, doc.numPages));
536
+ if (layout === "side-by-side" && doc.numPages > 1) {
537
+ if (p === 1) return [1];
538
+ const left = p % 2 === 0 ? p : p - 1;
539
+ return left + 1 <= doc.numPages ? [left, left + 1] : [left];
540
+ }
541
+ return [p];
542
+ }, [doc, currentPage, layout]);
543
+ if (error) {
544
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
545
+ "div",
546
+ {
547
+ className: "hv-page-container",
548
+ style: { padding: "32px", textAlign: "center", color: "#dc2626" },
549
+ children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "Error loading PDF" }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm mt-2", children: error })
552
+ ]
553
+ }
554
+ );
140
555
  }
141
- );
142
- function escapeHtml(s) {
143
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
556
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
557
+ "div",
558
+ {
559
+ className: `hv-doc-scroll ${layout === "side-by-side" ? "hv-view-double" : "hv-view-single"}`,
560
+ children: pagesToRender.map((page) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PdfPage, { doc, pageNum: page }, page))
561
+ }
562
+ );
563
+ }
564
+ function PdfPage({
565
+ doc,
566
+ pageNum
567
+ }) {
568
+ const canvasRef = (0, import_react4.useRef)(null);
569
+ (0, import_react4.useEffect)(() => {
570
+ if (!doc || !canvasRef.current) return;
571
+ let active = true;
572
+ const render = async () => {
573
+ try {
574
+ const page = await doc.getPage(pageNum);
575
+ if (!active) return;
576
+ const scale = 1.5;
577
+ const viewport = page.getViewport({ scale });
578
+ const canvas = canvasRef.current;
579
+ const ctx = canvas.getContext("2d");
580
+ if (!ctx) return;
581
+ canvas.width = viewport.width;
582
+ canvas.height = viewport.height;
583
+ canvas.style.width = `${viewport.width / scale}px`;
584
+ canvas.style.height = `${viewport.height / scale}px`;
585
+ await page.render({ canvasContext: ctx, viewport }).promise;
586
+ } catch (e) {
587
+ console.error("Page render error:", e);
588
+ }
589
+ };
590
+ render();
591
+ return () => {
592
+ active = false;
593
+ };
594
+ }, [doc, pageNum]);
595
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
596
+ "div",
597
+ {
598
+ className: "hv-page-container",
599
+ style: {
600
+ minHeight: "600px",
601
+ display: "flex",
602
+ alignItems: "center",
603
+ justifyContent: "center"
604
+ },
605
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("canvas", { ref: canvasRef, className: "hv-pdf-canvas" })
606
+ }
607
+ );
144
608
  }
145
609
 
146
- // src/editors/SpreadsheetEditor.tsx
147
- import { forwardRef as forwardRef2, useEffect as useEffect2, useImperativeHandle as useImperativeHandle2, useMemo as useMemo2, useState as useState2 } from "react";
148
- import * as XLSX from "xlsx";
610
+ // src/renderers/PptxRenderer.tsx
611
+ var import_react5 = require("react");
612
+ var import_jszip = __toESM(require("jszip"), 1);
613
+ var import_jsx_runtime5 = require("react/jsx-runtime");
614
+ var NS_P = "http://schemas.openxmlformats.org/presentationml/2006/main";
615
+ var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
616
+ function parseColor(solidFill) {
617
+ if (!solidFill) return void 0;
618
+ const srgbClr = solidFill.getElementsByTagNameNS(NS_A, "srgbClr")[0];
619
+ if (srgbClr) {
620
+ const val = srgbClr.getAttribute("val");
621
+ return val ? `#${val}` : void 0;
622
+ }
623
+ const schemeClr = solidFill.getElementsByTagNameNS(NS_A, "schemeClr")[0];
624
+ if (schemeClr) {
625
+ const val = schemeClr.getAttribute("val");
626
+ const colorMap = {
627
+ tx1: "#000000",
628
+ bg1: "#FFFFFF",
629
+ tx2: "#1F1F1F",
630
+ accent1: "#4472C4",
631
+ accent2: "#ED7D31",
632
+ accent3: "#A5A5A5",
633
+ accent4: "#FFC000",
634
+ accent5: "#5B9BD5",
635
+ accent6: "#70AD47"
636
+ };
637
+ return colorMap[val || ""] || "#000000";
638
+ }
639
+ return void 0;
640
+ }
641
+ function parseSlideXml(xml) {
642
+ const parser = new DOMParser();
643
+ const doc = parser.parseFromString(xml, "application/xml");
644
+ let title;
645
+ let titleColor;
646
+ let bgColor;
647
+ const body = [];
648
+ const bgElements = doc.getElementsByTagNameNS(NS_P, "bg");
649
+ if (bgElements.length > 0) {
650
+ const bgPr = bgElements[0].getElementsByTagNameNS(NS_P, "bgPr")[0];
651
+ if (bgPr) {
652
+ const solidFill = bgPr.getElementsByTagNameNS(NS_A, "solidFill")[0];
653
+ bgColor = parseColor(solidFill);
654
+ }
655
+ }
656
+ const shapes = Array.from(doc.getElementsByTagNameNS(NS_P, "sp"));
657
+ shapes.forEach((shape) => {
658
+ const nvSpPr = shape.getElementsByTagNameNS(NS_P, "nvSpPr")[0];
659
+ const cNvPr = nvSpPr?.getElementsByTagNameNS(NS_P, "cNvPr")[0];
660
+ const name = cNvPr?.getAttribute("name")?.toLowerCase() || "";
661
+ const isTitle = name.includes("title") || name.includes("header");
662
+ const txBody = shape.getElementsByTagNameNS(NS_P, "txBody")[0];
663
+ if (!txBody) return;
664
+ const paragraphs = Array.from(txBody.getElementsByTagNameNS(NS_A, "p"));
665
+ paragraphs.forEach((p) => {
666
+ const runs = Array.from(p.getElementsByTagNameNS(NS_A, "r"));
667
+ runs.forEach((run) => {
668
+ const textEl = run.getElementsByTagNameNS(NS_A, "t")[0];
669
+ const text = textEl?.textContent?.trim() || "";
670
+ if (!text) return;
671
+ const rPr = run.getElementsByTagNameNS(NS_A, "rPr")[0];
672
+ let color;
673
+ let isBold = false;
674
+ let isItalic = false;
675
+ if (rPr) {
676
+ isBold = rPr.getAttribute("b") === "1";
677
+ isItalic = rPr.getAttribute("i") === "1";
678
+ color = parseColor(rPr.getElementsByTagNameNS(NS_A, "solidFill")[0]);
679
+ }
680
+ if (isTitle && !title) {
681
+ title = text;
682
+ titleColor = color;
683
+ } else {
684
+ body.push({ text, color, isBold, isItalic });
685
+ }
686
+ });
687
+ });
688
+ });
689
+ return { title, titleColor, body, bgColor };
690
+ }
691
+ function PptxRenderer(props) {
692
+ const [slides, setSlides] = (0, import_react5.useState)([]);
693
+ const [error, setError] = (0, import_react5.useState)(null);
694
+ (0, import_react5.useEffect)(() => {
695
+ if (!props.arrayBuffer) return;
696
+ const loadPptx = async () => {
697
+ try {
698
+ const zip = await import_jszip.default.loadAsync(props.arrayBuffer);
699
+ const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort((a, b) => {
700
+ const numA = parseInt(a.match(/\d+/)[0]);
701
+ const numB = parseInt(b.match(/\d+/)[0]);
702
+ return numA - numB;
703
+ });
704
+ const parsedSlides = [];
705
+ for (const path of slidePaths) {
706
+ const xml = await zip.files[path].async("string");
707
+ parsedSlides.push(parseSlideXml(xml));
708
+ }
709
+ setSlides(parsedSlides);
710
+ props.onPageCount(parsedSlides.length);
711
+ const thumbs = parsedSlides.map(
712
+ (_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
713
+ `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="56"><rect width="100%" height="100%" fill="#4f46e5"/><text x="50%" y="50%" fill="white" font-size="20" text-anchor="middle" dy=".3em">${i + 1}</text></svg>`
714
+ )}`
715
+ );
716
+ props.onThumbs(thumbs);
717
+ } catch (err) {
718
+ setError(err.message || "Failed to parse PPTX");
719
+ }
720
+ };
721
+ loadPptx();
722
+ }, [props.arrayBuffer]);
723
+ const pagesToShow = (0, import_react5.useMemo)(() => {
724
+ if (slides.length === 0) return [];
725
+ const validPage = Math.max(1, Math.min(props.currentPage, slides.length));
726
+ if (props.layout === "side-by-side" && slides.length > 1) {
727
+ if (validPage === 1) return [1];
728
+ const left = validPage % 2 === 0 ? validPage : validPage - 1;
729
+ return [left, left + 1].filter((p) => p <= slides.length);
730
+ }
731
+ return [validPage];
732
+ }, [slides.length, props.currentPage, props.layout]);
733
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-red-500 p-8 text-center", children: error });
734
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
735
+ "div",
736
+ {
737
+ className: props.layout === "side-by-side" ? "hv-view-double" : "hv-view-single",
738
+ children: pagesToShow.map((p) => {
739
+ const slide = slides[p - 1];
740
+ if (!slide) return null;
741
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
742
+ "div",
743
+ {
744
+ className: "hv-page-container",
745
+ style: {
746
+ width: "960px",
747
+ // Standard HD slide width
748
+ aspectRatio: "16/9",
749
+ padding: "48px",
750
+ backgroundColor: slide.bgColor || "#ffffff",
751
+ display: "flex",
752
+ flexDirection: "column",
753
+ justifyContent: "center",
754
+ position: "relative"
755
+ // For absolute positioning if we added it later
756
+ },
757
+ children: [
758
+ slide.title && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
759
+ "h1",
760
+ {
761
+ style: {
762
+ fontSize: "42px",
763
+ marginBottom: "32px",
764
+ fontWeight: "bold",
765
+ color: slide.titleColor || "#1a202c",
766
+ lineHeight: 1.2
767
+ },
768
+ children: slide.title
769
+ }
770
+ ),
771
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
772
+ "div",
773
+ {
774
+ style: { display: "flex", flexDirection: "column", gap: "16px" },
775
+ children: slide.body.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
776
+ "div",
777
+ {
778
+ style: {
779
+ display: "flex",
780
+ alignItems: "flex-start",
781
+ gap: "12px"
782
+ },
783
+ children: [
784
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
785
+ "div",
786
+ {
787
+ style: {
788
+ width: "8px",
789
+ height: "8px",
790
+ background: item.color || "#cbd5e1",
791
+ borderRadius: "50%",
792
+ marginTop: "12px",
793
+ flexShrink: 0
794
+ }
795
+ }
796
+ ),
797
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
798
+ "p",
799
+ {
800
+ style: {
801
+ fontSize: "24px",
802
+ color: item.color || "#4a5568",
803
+ fontWeight: item.isBold ? "bold" : "normal",
804
+ fontStyle: item.isItalic ? "italic" : "normal",
805
+ margin: 0
806
+ },
807
+ children: item.text
808
+ }
809
+ )
810
+ ]
811
+ },
812
+ i
813
+ ))
814
+ }
815
+ ),
816
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
817
+ "div",
818
+ {
819
+ style: {
820
+ position: "absolute",
821
+ bottom: "20px",
822
+ right: "30px",
823
+ color: "#cbd5e1",
824
+ fontWeight: "bold"
825
+ },
826
+ children: p
827
+ }
828
+ )
829
+ ]
830
+ },
831
+ p
832
+ );
833
+ })
834
+ }
835
+ );
836
+ }
149
837
 
150
838
  // src/utils/fileSource.ts
151
839
  function guessFileType(name, explicit) {
@@ -166,15 +854,6 @@ function guessFileType(name, explicit) {
166
854
  ];
167
855
  return allowed.includes(ext) ? ext : "txt";
168
856
  }
169
- function arrayBufferToBase64(buf) {
170
- const bytes = new Uint8Array(buf);
171
- let binary = "";
172
- const chunk = 32768;
173
- for (let i = 0; i < bytes.length; i += chunk) {
174
- binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
175
- }
176
- return btoa(binary);
177
- }
178
857
  async function base64ToArrayBuffer(b64) {
179
858
  const bin = atob(b64);
180
859
  const len = bin.length;
@@ -232,965 +911,626 @@ async function resolveSource(args) {
232
911
  return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
233
912
  }
234
913
 
235
- // src/editors/SpreadsheetEditor.tsx
236
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
237
- var SpreadsheetEditor = forwardRef2(function SpreadsheetEditor2(props, ref) {
238
- const readonly = props.mode === "view";
239
- const [grid, setGrid] = useState2(() => Array.from({ length: 30 }, () => Array.from({ length: 12 }, () => "")));
240
- useEffect2(() => {
241
- if (!props.arrayBuffer) {
242
- return;
243
- }
244
- try {
245
- const wb = XLSX.read(props.arrayBuffer, { type: "array" });
246
- const name = wb.SheetNames[0];
247
- const ws = wb.Sheets[name];
248
- const aoa = XLSX.utils.sheet_to_json(ws, { header: 1, raw: true });
249
- const rows = Math.max(30, aoa.length);
250
- const cols2 = Math.max(12, Math.max(...aoa.map((r) => r?.length ?? 0), 0));
251
- const next = Array.from({ length: rows }, (_, r) => Array.from({ length: cols2 }, (_2, c) => {
252
- const v = aoa[r]?.[c];
253
- return v == null ? "" : String(v);
254
- }));
255
- setGrid(next);
256
- } catch {
914
+ // src/components/ThumbnailsSidebar.tsx
915
+ var import_react6 = require("react");
916
+ var import_jsx_runtime6 = require("react/jsx-runtime");
917
+ function ThumbnailsSidebar(props) {
918
+ const { isOpen, thumbnails, currentPage, onSelectPage } = props;
919
+ const activeRef = (0, import_react6.useRef)(null);
920
+ (0, import_react6.useEffect)(() => {
921
+ if (activeRef.current) {
922
+ activeRef.current.scrollIntoView({
923
+ behavior: "smooth",
924
+ block: "nearest"
925
+ });
257
926
  }
258
- }, [props.arrayBuffer]);
259
- async function save(exportPdf) {
260
- const ws = XLSX.utils.aoa_to_sheet(grid);
261
- const wb = XLSX.utils.book_new();
262
- XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
263
- const out = XLSX.write(wb, { type: "array", bookType: "xlsx" });
264
- const b64 = arrayBufferToBase64(out);
265
- props.onSave(b64, { fileName: ensureExt(props.fileName, "xlsx"), fileType: "xlsx", exportedAsPdf: !!exportPdf });
266
- }
267
- useImperativeHandle2(ref, () => ({
268
- save,
269
- requestThumbnails: async () => void 0
270
- }));
271
- const cols = useMemo2(() => Array.from({ length: grid[0]?.length ?? 0 }, (_, i) => String.fromCharCode(65 + i % 26)), [grid]);
272
- return /* @__PURE__ */ jsxs2("div", { className: "hv-sheet", children: [
273
- /* @__PURE__ */ jsxs2("div", { className: "hv-sheetbar", children: [
274
- /* @__PURE__ */ jsx2("div", { className: "hv-sheetbar-title", children: props.fileName }),
275
- !readonly ? /* @__PURE__ */ jsx2("button", { className: "hv-btn", type: "button", onClick: () => void save(false), children: props.locale["toolbar.save"] ?? "Save" }) : null
276
- ] }),
277
- /* @__PURE__ */ jsxs2("div", { className: "hv-sheetgrid", role: "table", "aria-label": "Spreadsheet", children: [
278
- /* @__PURE__ */ jsxs2("div", { className: "hv-sheetrow hv-sheetrow--header", role: "row", children: [
279
- /* @__PURE__ */ jsx2("div", { className: "hv-sheetcell hv-sheetcell--corner", role: "columnheader" }),
280
- cols.map((c, i) => /* @__PURE__ */ jsx2("div", { className: "hv-sheetcell hv-sheetcell--header", role: "columnheader", children: c }, i))
281
- ] }),
282
- grid.map((row, r) => /* @__PURE__ */ jsxs2("div", { className: "hv-sheetrow", role: "row", children: [
283
- /* @__PURE__ */ jsx2("div", { className: "hv-sheetcell hv-sheetcell--header", role: "rowheader", children: r + 1 }),
284
- row.map((val, c) => /* @__PURE__ */ jsx2(
285
- "div",
286
- {
287
- className: "hv-sheetcell",
288
- role: "cell",
289
- contentEditable: !readonly,
290
- suppressContentEditableWarning: true,
291
- onInput: (e) => {
292
- const text = e.currentTarget.textContent ?? "";
293
- setGrid((prev) => {
294
- const next = prev.map((rr) => rr.slice());
295
- next[r][c] = text;
296
- return next;
297
- });
298
- },
299
- children: val
300
- },
301
- c
302
- ))
303
- ] }, r))
304
- ] })
305
- ] });
306
- });
307
- function ensureExt(name, ext) {
308
- const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
309
- return `${base}.${ext}`;
927
+ }, [currentPage, isOpen]);
928
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `hv-sidebar ${!isOpen ? "collapsed" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-thumb-list", children: [
929
+ thumbnails.map((src, index) => {
930
+ const pageNum = index + 1;
931
+ const isActive = pageNum === currentPage;
932
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
933
+ "div",
934
+ {
935
+ ref: isActive ? activeRef : null,
936
+ className: `hv-thumb-item ${isActive ? "active" : ""}`,
937
+ onClick: () => onSelectPage(pageNum),
938
+ children: [
939
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-thumb-preview", children: src ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
940
+ "img",
941
+ {
942
+ src,
943
+ alt: `Page ${pageNum}`,
944
+ className: "hv-thumb-img"
945
+ }
946
+ ) : (
947
+ // Skeleton loader state for thumbnail
948
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "w-full h-full bg-gray-100 animate-pulse flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-xs text-gray-300", children: "..." }) })
949
+ ) }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "hv-thumb-label", children: [
951
+ "Page ",
952
+ pageNum
953
+ ] })
954
+ ]
955
+ },
956
+ pageNum
957
+ );
958
+ }),
959
+ thumbnails.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-center p-4 text-xs text-gray-400", children: "No previews available" })
960
+ ] }) });
310
961
  }
311
962
 
312
- // src/renderers/ImageRenderer.tsx
313
- import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
314
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
315
- function ImageRenderer({
316
- arrayBuffer,
317
- fileType,
318
- fileName
319
- }) {
320
- const [zoom, setZoom] = useState3(1);
321
- const url = useMemo3(() => {
322
- if (!arrayBuffer) {
323
- return void 0;
963
+ // src/components/Toolbar.tsx
964
+ var import_lucide_react = require("lucide-react");
965
+ var import_jsx_runtime7 = require("react/jsx-runtime");
966
+ function Toolbar(props) {
967
+ const {
968
+ fileName,
969
+ pageCount,
970
+ currentPage,
971
+ onPageChange,
972
+ layout,
973
+ onLayoutChange
974
+ } = props;
975
+ const handlePrev = () => {
976
+ if (currentPage > 1) onPageChange(currentPage - 1);
977
+ };
978
+ const handleNext = () => {
979
+ if (currentPage < pageCount) onPageChange(currentPage + 1);
980
+ };
981
+ const handleInput = (e) => {
982
+ const val = parseInt(e.target.value);
983
+ if (!isNaN(val) && val >= 1 && val <= pageCount) {
984
+ onPageChange(val);
324
985
  }
325
- const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
326
- return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
327
- }, [arrayBuffer, fileType]);
328
- useEffect3(() => {
329
- return () => {
330
- if (url) {
331
- URL.revokeObjectURL(url);
332
- }
333
- };
334
- }, [url]);
335
- return /* @__PURE__ */ jsxs3("div", { className: "hv-doc", children: [
336
- /* @__PURE__ */ jsxs3("div", { className: "hv-mini-toolbar", children: [
337
- /* @__PURE__ */ jsx3("div", { className: "hv-title", children: fileName }),
338
- /* @__PURE__ */ jsx3("div", { className: "hv-spacer" }),
339
- /* @__PURE__ */ jsx3(
986
+ };
987
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-toolbar", children: [
988
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-toolbar-group", children: [
989
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
990
+ "button",
991
+ {
992
+ className: `hv-btn ${props.showThumbnails ? "hv-btn-active" : ""}`,
993
+ onClick: props.onToggleThumbnails,
994
+ title: "Toggle Thumbnails",
995
+ children: props.showThumbnails ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.PanelLeftClose, { size: 20 }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.PanelLeftOpen, { size: 20 })
996
+ }
997
+ ),
998
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
999
+ "div",
1000
+ {
1001
+ className: "hv-sep",
1002
+ style: {
1003
+ width: 1,
1004
+ height: 24,
1005
+ background: "var(--hv-border)",
1006
+ margin: "0 8px"
1007
+ }
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1011
+ "span",
1012
+ {
1013
+ style: {
1014
+ fontWeight: 600,
1015
+ fontSize: "14px",
1016
+ maxWidth: 200,
1017
+ overflow: "hidden",
1018
+ textOverflow: "ellipsis",
1019
+ whiteSpace: "nowrap"
1020
+ },
1021
+ children: fileName || "Document"
1022
+ }
1023
+ )
1024
+ ] }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-toolbar-group", children: [
1026
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
340
1027
  "button",
341
1028
  {
342
- type: "button",
343
1029
  className: "hv-btn",
344
- onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
345
- children: "-"
1030
+ disabled: currentPage <= 1,
1031
+ onClick: handlePrev,
1032
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.ChevronLeft, { size: 20 })
346
1033
  }
347
1034
  ),
348
- /* @__PURE__ */ jsxs3("div", { className: "hv-zoom", children: [
349
- Math.round(zoom * 100),
350
- "%"
1035
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-600", children: [
1036
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1037
+ "input",
1038
+ {
1039
+ type: "number",
1040
+ className: "w-12 text-center border rounded py-1 bg-gray-50 focus:bg-white focus:ring-2 focus:ring-indigo-500 outline-none transition-all",
1041
+ value: currentPage,
1042
+ onChange: handleInput,
1043
+ min: 1,
1044
+ max: pageCount
1045
+ }
1046
+ ),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-gray-400", children: "/" }),
1048
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: pageCount })
351
1049
  ] }),
352
- /* @__PURE__ */ jsx3(
1050
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
353
1051
  "button",
354
1052
  {
355
- type: "button",
356
1053
  className: "hv-btn",
357
- onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
358
- children: "+"
1054
+ disabled: currentPage >= pageCount,
1055
+ onClick: handleNext,
1056
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.ChevronRight, { size: 20 })
359
1057
  }
360
1058
  )
361
1059
  ] }),
362
- /* @__PURE__ */ jsxs3("div", { className: "hv-center", children: [
363
- !arrayBuffer && /* @__PURE__ */ jsx3("div", { className: "hv-error", children: "No image data provided." }),
364
- arrayBuffer && !url && /* @__PURE__ */ jsx3("div", { className: "hv-error", children: "Failed to load image." }),
365
- url && /* @__PURE__ */ jsx3(
366
- "img",
1060
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-toolbar-group", children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1062
+ "button",
367
1063
  {
368
- src: url,
369
- alt: fileName,
370
- style: { transform: `scale(${zoom})` },
371
- className: "hv-image"
1064
+ className: `hv-btn ${layout === "single" ? "hv-btn-active text-indigo-600 bg-indigo-50" : ""}`,
1065
+ onClick: () => onLayoutChange("single"),
1066
+ title: "Single Page View",
1067
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.LayoutTemplate, { size: 18 })
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1071
+ "button",
1072
+ {
1073
+ className: `hv-btn ${layout === "side-by-side" ? "hv-btn-active text-indigo-600 bg-indigo-50" : ""}`,
1074
+ onClick: () => onLayoutChange("side-by-side"),
1075
+ title: "Two Page View",
1076
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.Grid2X2, { size: 18 })
1077
+ }
1078
+ ),
1079
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1080
+ "div",
1081
+ {
1082
+ className: "hv-sep",
1083
+ style: {
1084
+ width: 1,
1085
+ height: 24,
1086
+ background: "var(--hv-border)",
1087
+ margin: "0 8px"
1088
+ }
1089
+ }
1090
+ ),
1091
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1092
+ "button",
1093
+ {
1094
+ className: `hv-btn hv-btn-primary ${props.showSignatures ? "ring-2 ring-indigo-300" : ""}`,
1095
+ onClick: props.onToggleSignatures,
1096
+ title: "Sign Document",
1097
+ children: [
1098
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.PenLine, { size: 18, className: "mr-2" }),
1099
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "hidden sm:inline", children: "Sign" })
1100
+ ]
372
1101
  }
373
1102
  )
374
1103
  ] })
375
1104
  ] });
376
1105
  }
377
1106
 
378
- // src/renderers/PdfRenderer.tsx
379
- import {
380
- getDocument,
381
- GlobalWorkerOptions
382
- } from "pdfjs-dist";
383
- import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef2, useState as useState4 } from "react";
384
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
385
- function PdfRenderer(props) {
386
- const { url, arrayBuffer } = props;
387
- const [doc, setDoc] = useState4(null);
388
- const [pageCount, setPageCount] = useState4(0);
389
- const [rendered, setRendered] = useState4(
390
- /* @__PURE__ */ new Map()
391
- );
392
- const [thumbs, setThumbs] = useState4([]);
393
- const [size, setSize] = useState4({ w: 840, h: 1188 });
394
- const [error, setError] = useState4(null);
395
- const [loading, setLoading] = useState4(false);
396
- const containerRef = useRef2(null);
397
- useEffect4(() => {
398
- try {
399
- GlobalWorkerOptions.workerSrc = new URL(
400
- "pdfjs-dist/build/pdf.worker.min.mjs",
401
- import.meta.url
402
- ).toString();
403
- } catch {
404
- }
405
- }, []);
406
- useEffect4(() => {
407
- let cancel = false;
408
- setError(null);
409
- setLoading(true);
410
- (async () => {
411
- setDoc(null);
412
- setRendered(/* @__PURE__ */ new Map());
413
- setThumbs([]);
414
- if (!url && !arrayBuffer) {
415
- setError("No PDF source provided.");
416
- setLoading(false);
417
- return;
418
- }
419
- try {
420
- const task = getDocument(
421
- url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
422
- );
423
- const pdf = await task.promise;
424
- if (cancel) {
425
- return;
426
- }
427
- setDoc(pdf);
428
- setPageCount(pdf.numPages);
429
- props.onPageCount(pdf.numPages);
430
- const p1 = await pdf.getPage(1);
431
- const base = p1.getViewport({ scale: 1 });
432
- const w = Math.min(980, Math.max(640, base.width));
433
- const s = w / base.width;
434
- const vp = p1.getViewport({ scale: s });
435
- setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
436
- const thumbWidth = 56;
437
- const thumbsArr = [];
438
- for (let i = 1; i <= pdf.numPages; i++) {
439
- const page = await pdf.getPage(i);
440
- const pageBase = page.getViewport({ scale: 1 });
441
- const thumbScale = thumbWidth / pageBase.width;
442
- const thumbVp = page.getViewport({ scale: thumbScale });
443
- const thumbCanvas = document.createElement("canvas");
444
- thumbCanvas.width = Math.round(thumbVp.width);
445
- thumbCanvas.height = Math.round(thumbVp.height);
446
- const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
447
- if (thumbCtx) {
448
- await page.render({ canvasContext: thumbCtx, viewport: thumbVp }).promise;
449
- thumbsArr.push(thumbCanvas.toDataURL("image/png"));
450
- } else {
451
- thumbsArr.push(void 0);
452
- }
453
- }
454
- setThumbs(thumbsArr);
455
- } catch (e) {
456
- setError(
457
- "Failed to load PDF. " + (e instanceof Error ? e.message : "")
458
- );
459
- } finally {
460
- setLoading(false);
461
- }
462
- })();
463
- return () => {
464
- cancel = true;
465
- };
466
- }, [url, arrayBuffer]);
467
- useEffect4(() => {
468
- props.onThumbs(thumbs);
469
- }, [thumbs]);
470
- const pagesToShow = useMemo4(() => {
471
- if (props.layout === "side-by-side" && pageCount > 1) {
472
- const left = Math.max(1, Math.min(props.currentPage, pageCount));
473
- const right = Math.max(1, Math.min(left + 1, pageCount));
474
- return left === right ? [left] : [left, right];
475
- }
476
- return [Math.max(1, Math.min(props.currentPage, pageCount))];
477
- }, [props.currentPage, props.layout, pageCount]);
478
- useEffect4(() => {
479
- if (!doc) {
480
- return;
481
- }
482
- let cancel = false;
483
- (async () => {
484
- for (const p of pagesToShow) {
485
- if (rendered.has(p)) {
486
- continue;
487
- }
488
- try {
489
- const page = await doc.getPage(p);
490
- if (cancel) {
491
- return;
492
- }
493
- const base = page.getViewport({ scale: 1 });
494
- const vp = page.getViewport({ scale: size.w / base.width });
495
- const canvas = document.createElement("canvas");
496
- canvas.width = Math.round(vp.width);
497
- canvas.height = Math.round(vp.height);
498
- const ctx = canvas.getContext("2d", { alpha: false });
499
- if (!ctx) {
500
- continue;
501
- }
502
- await page.render({ canvasContext: ctx, viewport: vp }).promise;
503
- if (cancel) {
504
- return;
505
- }
506
- setRendered((prev) => {
507
- const next = new Map(prev);
508
- next.set(p, canvas);
509
- return next;
510
- });
511
- } catch {
512
- }
1107
+ // src/components/SignaturePanel.tsx
1108
+ var import_lucide_react2 = require("lucide-react");
1109
+ var import_react7 = require("react");
1110
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1111
+ function SignaturePanel(props) {
1112
+ const { isOpen, onClose, onSelectSignature } = props;
1113
+ const [signatures, setSignatures] = (0, import_react7.useState)([]);
1114
+ const [showModal, setShowModal] = (0, import_react7.useState)(false);
1115
+ const canvasRef = (0, import_react7.useRef)(null);
1116
+ const [isDrawing, setIsDrawing] = (0, import_react7.useState)(false);
1117
+ const openCreateModal = () => {
1118
+ setShowModal(true);
1119
+ setTimeout(() => {
1120
+ const canvas = canvasRef.current;
1121
+ if (canvas) {
1122
+ const ctx = canvas.getContext("2d");
1123
+ ctx?.clearRect(0, 0, canvas.width, canvas.height);
513
1124
  }
514
- })();
515
- return () => {
516
- cancel = true;
1125
+ }, 100);
1126
+ };
1127
+ const saveSignature = () => {
1128
+ const canvas = canvasRef.current;
1129
+ if (!canvas) return;
1130
+ const newSig = {
1131
+ id: Date.now().toString(),
1132
+ signatureImageUrl: canvas.toDataURL("image/png"),
1133
+ signedBy: "Me",
1134
+ dateSigned: (/* @__PURE__ */ new Date()).toISOString()
517
1135
  };
518
- }, [doc, pagesToShow, size.w, rendered]);
519
- function onWheel(e) {
520
- if (!pageCount) {
521
- return;
522
- }
523
- if (Math.abs(e.deltaY) < 10) {
524
- return;
525
- }
526
- const dir = e.deltaY > 0 ? 1 : -1;
527
- const step = props.layout === "side-by-side" ? 2 : 1;
528
- const next = Math.max(
529
- 1,
530
- Math.min(pageCount, props.currentPage + dir * step)
531
- );
532
- props.onCurrentPageChange(next);
533
- }
534
- function clickPlace(e, page) {
535
- const stamp = props.signatureStamp;
536
- if (!stamp?.armed) {
537
- return;
538
- }
539
- const rect = e.currentTarget.getBoundingClientRect();
540
- const x = (e.clientX - rect.left) / rect.width;
541
- const y = (e.clientY - rect.top) / rect.height;
542
- stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
543
- }
544
- return /* @__PURE__ */ jsxs4("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
545
- !doc ? /* @__PURE__ */ jsx4("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
546
- doc ? /* @__PURE__ */ jsx4(
1136
+ setSignatures([...signatures, newSig]);
1137
+ setShowModal(false);
1138
+ onSelectSignature(newSig);
1139
+ };
1140
+ const startDraw = (e) => {
1141
+ setIsDrawing(true);
1142
+ draw(e);
1143
+ };
1144
+ const stopDraw = () => setIsDrawing(false);
1145
+ const draw = (e) => {
1146
+ if (!isDrawing || !canvasRef.current) return;
1147
+ const canvas = canvasRef.current;
1148
+ const ctx = canvas.getContext("2d");
1149
+ if (!ctx) return;
1150
+ const rect = canvas.getBoundingClientRect();
1151
+ const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
1152
+ const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
1153
+ const x = clientX - rect.left;
1154
+ const y = clientY - rect.top;
1155
+ ctx.lineWidth = 2;
1156
+ ctx.lineCap = "round";
1157
+ ctx.lineTo(x, y);
1158
+ ctx.stroke();
1159
+ ctx.beginPath();
1160
+ ctx.moveTo(x, y);
1161
+ };
1162
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
547
1164
  "div",
548
1165
  {
549
- className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
550
- children: pagesToShow.map((p) => {
551
- const c = rendered.get(p);
552
- return /* @__PURE__ */ jsx4(
1166
+ className: `hv-sidebar hv-sidebar-right ${!isOpen ? "collapsed" : ""}`,
1167
+ style: { width: isOpen ? "280px" : "0" },
1168
+ children: [
1169
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
553
1170
  "div",
554
1171
  {
555
- className: "hv-page",
556
- style: { width: size.w, height: size.h },
557
- onClick: (e) => clickPlace(e, p),
558
- children: c ? /* @__PURE__ */ jsx4(
559
- "canvas",
560
- {
561
- className: "hv-canvas",
562
- width: c.width,
563
- height: c.height,
564
- ref: (node) => {
565
- if (!node) {
566
- return;
1172
+ className: "hv-sidebar-header",
1173
+ style: { justifyContent: "space-between", padding: "12px 16px" },
1174
+ children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { style: { margin: 0, fontSize: "15px", fontWeight: 600 }, children: "Signatures" }),
1176
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1177
+ "button",
1178
+ {
1179
+ onClick: onClose,
1180
+ className: "hv-btn",
1181
+ style: { padding: "4px", border: "none" },
1182
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.X, { size: 18 })
1183
+ }
1184
+ )
1185
+ ]
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-thumb-list", children: [
1189
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1190
+ "button",
1191
+ {
1192
+ onClick: openCreateModal,
1193
+ className: "hv-btn",
1194
+ style: {
1195
+ width: "100%",
1196
+ justifyContent: "center",
1197
+ border: "2px dashed var(--hv-border)",
1198
+ marginBottom: "16px",
1199
+ color: "var(--hv-primary)"
1200
+ },
1201
+ children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Plus, { size: 18, style: { marginRight: "8px" } }),
1203
+ "New Signature"
1204
+ ]
1205
+ }
1206
+ ),
1207
+ signatures.map((sig) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1208
+ "div",
1209
+ {
1210
+ className: "hv-thumb-item",
1211
+ style: {
1212
+ position: "relative",
1213
+ padding: "12px",
1214
+ background: "var(--hv-bg)",
1215
+ borderRadius: "8px",
1216
+ border: "1px solid var(--hv-border)"
1217
+ },
1218
+ onClick: () => onSelectSignature(sig),
1219
+ children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1221
+ "img",
1222
+ {
1223
+ src: sig.signatureImageUrl,
1224
+ alt: "Signature",
1225
+ style: { height: "40px", objectFit: "contain" }
567
1226
  }
568
- const ctx = node.getContext("2d");
569
- if (ctx) {
570
- ctx.drawImage(c, 0, 0);
1227
+ ),
1228
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1229
+ "div",
1230
+ {
1231
+ style: {
1232
+ fontSize: "11px",
1233
+ color: "var(--hv-muted)",
1234
+ marginTop: "4px",
1235
+ textAlign: "center"
1236
+ },
1237
+ children: new Date(sig.dateSigned).toLocaleDateString()
571
1238
  }
572
- }
573
- }
574
- ) : /* @__PURE__ */ jsx4("div", { className: "hv-loading", children: "Rendering\u2026" })
575
- },
576
- p
577
- );
578
- })
579
- }
580
- ) : null
581
- ] });
582
- }
583
-
584
- // src/renderers/PptxRenderer.tsx
585
- import { useEffect as useEffect5, useMemo as useMemo5, useState as useState5 } from "react";
586
- import JSZip from "jszip";
587
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
588
- function decodeXml(s) {
589
- return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
590
- }
591
- function extractText(xml) {
592
- return [...xml.matchAll(/<a:t>(.*?)<\/a:t>/g)].map((m) => decodeXml(m[1] || "")).join(" ").trim();
593
- }
594
- function PptxRenderer(props) {
595
- const [slides, setSlides] = useState5([]);
596
- const [thumbs, setThumbs] = useState5([]);
597
- const [error, setError] = useState5(null);
598
- const [loading, setLoading] = useState5(false);
599
- useEffect5(() => {
600
- let cancel = false;
601
- setError(null);
602
- setLoading(true);
603
- (async () => {
604
- setSlides([]);
605
- setThumbs([]);
606
- if (!props.arrayBuffer) {
607
- setError("No PPTX source provided.");
608
- setLoading(false);
609
- return;
610
- }
611
- try {
612
- const zip = await JSZip.loadAsync(props.arrayBuffer);
613
- const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort();
614
- const slidesOut = [];
615
- for (let i = 0; i < slidePaths.length; i++) {
616
- const xml = await zip.files[slidePaths[i]].async("string");
617
- slidesOut.push({ index: i + 1, text: extractText(xml) });
618
- }
619
- if (cancel) return;
620
- setSlides(
621
- slidesOut.length ? slidesOut : [{ index: 1, text: "(empty)" }]
622
- );
623
- props.onSlideCount(slidesOut.length || 1);
624
- const thumbWidth = 56;
625
- const thumbsArr = [];
626
- for (let i = 0; i < (slidesOut.length || 1); i++) {
627
- thumbsArr.push(
628
- `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
629
- );
630
- }
631
- setThumbs(thumbsArr);
632
- } catch (e) {
633
- setSlides([
634
- { index: 1, text: "Unable to render this .pptx in-browser." }
635
- ]);
636
- setThumbs([void 0]);
637
- setError(
638
- "Failed to load PPTX. " + (e instanceof Error ? e.message : "")
639
- );
640
- } finally {
641
- setLoading(false);
1239
+ ),
1240
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1241
+ "button",
1242
+ {
1243
+ onClick: (e) => {
1244
+ e.stopPropagation();
1245
+ setSignatures(signatures.filter((s) => s.id !== sig.id));
1246
+ },
1247
+ className: "hv-btn",
1248
+ style: {
1249
+ position: "absolute",
1250
+ top: "4px",
1251
+ right: "4px",
1252
+ padding: "4px",
1253
+ color: "#ef4444",
1254
+ border: "none",
1255
+ background: "white"
1256
+ },
1257
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Trash2, { size: 12 })
1258
+ }
1259
+ )
1260
+ ]
1261
+ },
1262
+ sig.id
1263
+ ))
1264
+ ] })
1265
+ ]
642
1266
  }
643
- })();
644
- return () => {
645
- cancel = true;
646
- };
647
- }, [props.arrayBuffer]);
648
- useEffect5(() => {
649
- props.onThumbs(thumbs);
650
- }, [thumbs]);
651
- const pagesToShow = useMemo5(() => {
652
- const total = slides.length;
653
- if (props.layout === "side-by-side" && total > 1) {
654
- const left = Math.max(1, Math.min(props.currentPage, total));
655
- const right = Math.max(1, Math.min(left + 1, total));
656
- return left === right ? [left] : [left, right];
657
- }
658
- return [Math.max(1, Math.min(props.currentPage, total))];
659
- }, [props.currentPage, props.layout, slides.length]);
660
- return /* @__PURE__ */ jsxs5("div", { className: "hv-doc", children: [
661
- loading && /* @__PURE__ */ jsx5("div", { className: "hv-loading", children: "Loading PPTX\u2026" }),
662
- error && /* @__PURE__ */ jsx5("div", { className: "hv-error", children: error }),
663
- !loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ jsx5("div", { className: "hv-error", children: "No slides to display." }),
664
- !error && slides && slides.length > 0 && /* @__PURE__ */ jsx5(
1267
+ ),
1268
+ showModal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hv-modal-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
665
1269
  "div",
666
1270
  {
667
- className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
668
- children: pagesToShow.map((p) => {
669
- const s = slides[p - 1];
670
- return /* @__PURE__ */ jsxs5(
1271
+ className: "hv-modal",
1272
+ style: { width: "450px", maxWidth: "90vw" },
1273
+ children: [
1274
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
671
1275
  "div",
672
1276
  {
673
- className: "hv-slide",
674
- tabIndex: 0,
675
- onFocus: () => props.onCurrentPageChange(p),
1277
+ style: {
1278
+ padding: "16px 24px",
1279
+ borderBottom: "1px solid var(--hv-border)",
1280
+ display: "flex",
1281
+ justifyContent: "space-between",
1282
+ alignItems: "center"
1283
+ },
676
1284
  children: [
677
- /* @__PURE__ */ jsxs5("div", { className: "hv-slide-title", children: [
678
- "Slide ",
679
- p
680
- ] }),
681
- /* @__PURE__ */ jsx5("div", { className: "hv-slide-text", children: s?.text || "" })
1285
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { style: { margin: 0, fontSize: "18px", fontWeight: 600 }, children: "Draw Signature" }),
1286
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1287
+ "button",
1288
+ {
1289
+ onClick: () => setShowModal(false),
1290
+ className: "hv-btn",
1291
+ style: { border: "none" },
1292
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.X, { size: 20 })
1293
+ }
1294
+ )
682
1295
  ]
683
- },
684
- p
685
- );
686
- })
687
- }
688
- )
689
- ] });
690
- }
691
- function svgThumb(n) {
692
- 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>`;
693
- }
694
-
695
- // src/utils/locale.ts
696
- var defaultLocale = {
697
- "loading": "Loading\u2026",
698
- "error.title": "Error",
699
- "toolbar.layout.single": "Single page",
700
- "toolbar.layout.two": "Side-by-side",
701
- "toolbar.thumbs": "Thumbnails",
702
- "toolbar.signatures": "Signatures",
703
- "toolbar.sign": "Sign Document",
704
- "toolbar.save": "Save",
705
- "toolbar.exportPdf": "Export as PDF",
706
- "thumbnails.title": "Thumbnails",
707
- "thumbnails.page": "Page",
708
- "signatures.title": "Signatures",
709
- "signatures.empty": "No signatures",
710
- "signatures.placeHint": "Click on the document to place the signature.",
711
- "a11y.viewer": "Document viewer",
712
- "a11y.ribbon": "Ribbon",
713
- "a11y.editor": "Document editor"
714
- };
715
-
716
- // src/components/SignaturePanel.tsx
717
- import React6 from "react";
718
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
719
- function SignaturePanel(props) {
720
- const title = props.locale["signatures.title"] ?? "Signatures";
721
- const deduped = React6.useMemo(() => {
722
- const seen = /* @__PURE__ */ new Set();
723
- return props.signatures.filter((s) => {
724
- const key = `${s.signedBy}|${s.dateSigned}|${s.signatureImageUrl}`;
725
- if (seen.has(key)) return false;
726
- seen.add(key);
727
- return true;
728
- });
729
- }, [props.signatures]);
730
- return /* @__PURE__ */ jsxs6(
731
- "aside",
732
- {
733
- className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side",
734
- "aria-label": title,
735
- children: [
736
- /* @__PURE__ */ jsxs6("div", { className: "hv-sidebar-header", children: [
737
- /* @__PURE__ */ jsx6(
738
- "button",
739
- {
740
- type: "button",
741
- className: "hv-icon",
742
- onClick: props.onToggle,
743
- "aria-label": props.locale["toolbar.signatures"] ?? "Signatures",
744
- children: /* @__PURE__ */ jsx6("span", { "aria-hidden": true, children: "\u270D" })
745
1296
  }
746
1297
  ),
747
- /* @__PURE__ */ jsx6("div", { className: "hv-sidebar-title", children: title })
748
- ] }),
749
- /* @__PURE__ */ jsxs6("div", { className: "hv-sidebar-body", children: [
750
- deduped.length === 0 && /* @__PURE__ */ jsx6("div", { className: "hv-signature-empty", "aria-live": "polite", children: props.locale["signatures.empty"] ?? "No signatures yet." }),
751
- deduped.map((s, idx) => /* @__PURE__ */ jsxs6(
1298
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
752
1299
  "div",
753
1300
  {
754
- className: "hv-signature-card",
755
- tabIndex: 0,
756
- "aria-label": `Signature by ${s.signedBy}`,
1301
+ style: {
1302
+ padding: "24px",
1303
+ background: "#f9fafb",
1304
+ display: "flex",
1305
+ flexDirection: "column",
1306
+ alignItems: "center"
1307
+ },
757
1308
  children: [
758
- /* @__PURE__ */ jsx6(
759
- "img",
1309
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1310
+ "div",
760
1311
  {
761
- src: s.signatureImageUrl,
762
- alt: props.locale["signatures.imgAlt"] ? props.locale["signatures.imgAlt"].replace(
763
- "{name}",
764
- s.signedBy
765
- ) : `Signature by ${s.signedBy}`,
766
- className: "hv-signature-img"
1312
+ style: {
1313
+ background: "white",
1314
+ border: "1px solid #e5e7eb",
1315
+ borderRadius: "8px",
1316
+ overflow: "hidden",
1317
+ boxShadow: "inset 0 2px 4px 0 rgba(0,0,0,0.05)"
1318
+ },
1319
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1320
+ "canvas",
1321
+ {
1322
+ ref: canvasRef,
1323
+ width: 400,
1324
+ height: 200,
1325
+ style: {
1326
+ display: "block",
1327
+ cursor: "crosshair",
1328
+ touchAction: "none"
1329
+ },
1330
+ onMouseDown: startDraw,
1331
+ onMouseUp: stopDraw,
1332
+ onMouseMove: draw,
1333
+ onTouchStart: startDraw,
1334
+ onTouchEnd: stopDraw,
1335
+ onTouchMove: draw
1336
+ }
1337
+ )
767
1338
  }
768
1339
  ),
769
- /* @__PURE__ */ jsxs6("div", { className: "hv-signature-meta", children: [
770
- /* @__PURE__ */ jsx6("div", { className: "hv-signature-name", children: s.signedBy }),
771
- /* @__PURE__ */ jsx6("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
772
- s.comment ? /* @__PURE__ */ jsx6("div", { className: "hv-signature-comment", children: s.comment }) : null
773
- ] })
1340
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1341
+ "p",
1342
+ {
1343
+ style: {
1344
+ fontSize: "12px",
1345
+ color: "var(--hv-muted)",
1346
+ marginTop: "8px"
1347
+ },
1348
+ children: "Sign above using your mouse or finger"
1349
+ }
1350
+ )
774
1351
  ]
775
- },
776
- `${s.signedBy}-${s.dateSigned}-${s.signatureImageUrl}`
777
- ))
778
- ] })
779
- ]
780
- }
781
- );
782
- }
783
-
784
- // src/components/ThumbnailsSidebar.tsx
785
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
786
- function ThumbnailsSidebar(props) {
787
- const t = props.locale["thumbnails.title"] ?? "Thumbnails";
788
- return /* @__PURE__ */ jsxs7(
789
- "aside",
790
- {
791
- className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs",
792
- "aria-label": t,
793
- children: [
794
- /* @__PURE__ */ jsxs7("div", { className: "hv-thumbs-header", children: [
795
- /* @__PURE__ */ jsx7(
796
- "button",
797
- {
798
- type: "button",
799
- className: "hv-thumbs-toggle",
800
- onClick: props.onToggle,
801
- "aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
802
- children: /* @__PURE__ */ jsx7("span", { className: "hv-thumbs-toggle-icon", children: props.collapsed ? "\u25B8" : "\u25BE" })
803
1352
  }
804
1353
  ),
805
- !props.collapsed && /* @__PURE__ */ jsx7("div", { className: "hv-thumbs-title", children: t })
806
- ] }),
807
- !props.collapsed && /* @__PURE__ */ jsx7("div", { className: "hv-thumbs-list", role: "list", children: props.thumbnails.map((th, idx) => {
808
- const p = idx + 1;
809
- const active = p === props.currentPage;
810
- return /* @__PURE__ */ jsxs7(
811
- "button",
1354
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1355
+ "div",
812
1356
  {
813
- type: "button",
814
- role: "listitem",
815
- className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
816
- onClick: () => props.onSelectPage(p),
817
- "aria-current": active ? "page" : void 0,
818
- tabIndex: 0,
1357
+ style: {
1358
+ padding: "16px 24px",
1359
+ borderTop: "1px solid var(--hv-border)",
1360
+ display: "flex",
1361
+ justifyContent: "flex-end",
1362
+ gap: "12px"
1363
+ },
819
1364
  children: [
820
- /* @__PURE__ */ jsx7("div", { className: "hv-thumb-img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ jsx7("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ jsx7("div", { className: "hv-thumb-placeholder" }) }),
821
- /* @__PURE__ */ jsx7("div", { className: "hv-thumb-label", children: th.label })
1365
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1366
+ "button",
1367
+ {
1368
+ onClick: () => {
1369
+ const ctx = canvasRef.current?.getContext("2d");
1370
+ ctx?.clearRect(0, 0, 400, 200);
1371
+ },
1372
+ className: "hv-btn",
1373
+ children: "Clear"
1374
+ }
1375
+ ),
1376
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: saveSignature, className: "hv-btn hv-btn-primary", children: "Create & Use" })
822
1377
  ]
823
- },
824
- th.id
825
- );
826
- }) })
827
- ]
828
- }
829
- );
830
- }
831
-
832
- // src/components/Toolbar.tsx
833
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
834
- function Toolbar(props) {
835
- const t = (k, fallback) => props.locale[k] ?? fallback;
836
- return /* @__PURE__ */ jsxs8(
837
- "div",
838
- {
839
- className: "hv-toolbar",
840
- role: "toolbar",
841
- "aria-label": t("a11y.toolbar", "Document toolbar"),
842
- children: [
843
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__group", children: [
844
- /* @__PURE__ */ jsx8(
845
- "button",
846
- {
847
- className: `hv-btn ${props.showThumbnails ? "hv-btn--active" : ""}`,
848
- onClick: props.onToggleThumbnails,
849
- "aria-pressed": props.showThumbnails,
850
- children: "Thumbnails"
851
- }
852
- ),
853
- props.mode !== "create" && /* @__PURE__ */ jsx8(
854
- "button",
855
- {
856
- className: `hv-btn ${props.showSignatures ? "hv-btn--active" : ""}`,
857
- onClick: props.onToggleSignatures,
858
- "aria-pressed": props.showSignatures,
859
- children: "Signatures"
860
- }
861
- )
862
- ] }),
863
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__group hv-segment", children: [
864
- /* @__PURE__ */ jsx8(
865
- "button",
866
- {
867
- className: `hv-btn ${props.layout === "single" ? "hv-btn--active" : ""}`,
868
- onClick: () => props.onChangeLayout("single"),
869
- children: "Single page"
870
- }
871
- ),
872
- /* @__PURE__ */ jsx8(
873
- "button",
874
- {
875
- className: `hv-btn ${props.layout === "side-by-side" ? "hv-btn--active" : ""}`,
876
- onClick: () => props.onChangeLayout("side-by-side"),
877
- children: "Side-by-side"
878
1378
  }
879
1379
  )
880
- ] }),
881
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__group hv-toolbar__actions", children: [
882
- props.showHeaderFooterToggle && /* @__PURE__ */ jsxs8("label", { className: "hv-switch", children: [
883
- /* @__PURE__ */ jsx8(
884
- "input",
885
- {
886
- type: "checkbox",
887
- checked: props.headerFooterEnabled,
888
- onChange: props.onToggleHeaderFooter
889
- }
890
- ),
891
- /* @__PURE__ */ jsx8("span", { className: "hv-switch__slider" }),
892
- /* @__PURE__ */ jsx8("span", { className: "hv-switch__label", children: t("toolbar.letterhead", "Letterhead") })
893
- ] }),
894
- props.allowSigning && /* @__PURE__ */ jsx8(
895
- "button",
896
- {
897
- className: "hv-btn hv-btn--primary",
898
- onClick: props.onSign,
899
- disabled: props.signingDisabled,
900
- children: "Sign document"
901
- }
902
- ),
903
- props.canExportPdf && /* @__PURE__ */ jsx8("button", { className: "hv-btn", onClick: props.onExportPdf, children: "Export PDF" }),
904
- props.canSave && /* @__PURE__ */ jsx8("button", { className: "hv-btn hv-btn--primary", onClick: props.onSave, children: "Save" })
905
- ] })
906
- ]
907
- }
908
- );
1380
+ ]
1381
+ }
1382
+ ) })
1383
+ ] });
909
1384
  }
910
1385
 
911
1386
  // src/components/DocumentViewer.tsx
912
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1387
+ var import_jsx_runtime9 = require("react/jsx-runtime");
913
1388
  function DocumentViewer(props) {
914
1389
  const mode = props.mode ?? "view";
915
1390
  const theme = props.theme ?? "light";
916
- const locale = useMemo6(
917
- () => ({ ...defaultLocale, ...props.locale ?? {} }),
918
- [props.locale]
919
- );
920
- const [layout, setLayout] = useState6(
1391
+ const [layout, setLayout] = (0, import_react8.useState)(
921
1392
  props.defaultLayout ?? "single"
922
1393
  );
923
- const [showThumbnails, setShowThumbnails] = useState6(true);
924
- const [showSignatures, setShowSignatures] = useState6(true);
925
- const [headerFooterEnabled, setHeaderFooterEnabled] = useState6(true);
926
- const allowSigning = props.allowSigning ?? false;
927
- const [signingBusy, setSigningBusy] = useState6(false);
928
- const [resolved, setResolved] = useState6(null);
929
- const [error, setError] = useState6("");
930
- const [pageCount, setPageCount] = useState6(1);
931
- const [currentPage, setCurrentPage] = useState6(1);
932
- const [thumbs, setThumbs] = useState6([]);
933
- const [localSignatures, setLocalSignatures] = useState6(
934
- props.signatures ?? []
935
- );
936
- useEffect6(
937
- () => setLocalSignatures(props.signatures ?? []),
938
- [props.signatures]
939
- );
940
- const [sigPlacements, setSigPlacements] = useState6([]);
941
- const [armedSignatureUrl, setArmedSignatureUrl] = useState6(
942
- null
943
- );
944
- const editorRef = useRef3(null);
945
- useEffect6(() => {
946
- let cancelled = false;
947
- (async () => {
1394
+ const [showThumbnails, setShowThumbnails] = (0, import_react8.useState)(true);
1395
+ const [showSignatures, setShowSignatures] = (0, import_react8.useState)(false);
1396
+ const [resolved, setResolved] = (0, import_react8.useState)(null);
1397
+ const [loading, setLoading] = (0, import_react8.useState)(true);
1398
+ const [error, setError] = (0, import_react8.useState)("");
1399
+ const [pageCount, setPageCount] = (0, import_react8.useState)(1);
1400
+ const [currentPage, setCurrentPage] = (0, import_react8.useState)(1);
1401
+ const [thumbnails, setThumbnails] = (0, import_react8.useState)([]);
1402
+ (0, import_react8.useEffect)(() => {
1403
+ let active = true;
1404
+ const loadFile = async () => {
1405
+ setLoading(true);
948
1406
  setError("");
949
1407
  setResolved(null);
950
- setThumbs([]);
951
- setPageCount(1);
952
- setCurrentPage(1);
953
- setSigPlacements([]);
954
- setArmedSignatureUrl(null);
955
- if (mode === "create") {
956
- const ft = props.fileType ?? "docx";
957
- setResolved({
958
- fileType: ft,
959
- fileName: props.fileName ?? `Untitled.${ft}`
960
- });
961
- return;
962
- }
963
1408
  try {
964
- const res = await resolveSource({
965
- fileUrl: props.fileUrl,
966
- base64: props.base64,
967
- blob: props.blob,
968
- fileName: props.fileName,
969
- fileType: props.fileType
970
- });
971
- if (cancelled) {
972
- return;
973
- }
974
- setResolved({
975
- fileType: res.fileType,
976
- fileName: res.fileName,
977
- url: res.url,
978
- arrayBuffer: res.arrayBuffer
979
- });
980
- } catch (e) {
981
- if (cancelled) {
982
- return;
1409
+ if (mode === "create") {
1410
+ setResolved({
1411
+ fileType: props.fileType ?? "docx",
1412
+ fileName: props.fileName ?? "Untitled"
1413
+ });
1414
+ } else {
1415
+ const res = await resolveSource({
1416
+ fileUrl: props.fileUrl,
1417
+ base64: props.base64,
1418
+ blob: props.blob,
1419
+ fileName: props.fileName,
1420
+ fileType: props.fileType
1421
+ });
1422
+ if (active) setResolved(res);
983
1423
  }
984
- setError(e instanceof Error ? e.message : String(e));
1424
+ } catch (err) {
1425
+ if (active) setError(err.message || "Failed to load document");
1426
+ } finally {
1427
+ if (active) setLoading(false);
985
1428
  }
986
- })();
1429
+ };
1430
+ loadFile();
987
1431
  return () => {
988
- cancelled = true;
1432
+ active = false;
989
1433
  };
990
- }, [
991
- mode,
992
- props.fileUrl,
993
- props.base64,
994
- props.blob,
995
- props.fileName,
996
- props.fileType
997
- ]);
998
- const thumbnails = useMemo6(() => {
999
- const n = Math.max(1, pageCount);
1000
- return Array.from({ length: n }, (_, i) => ({
1001
- id: `p-${i + 1}`,
1002
- label: `${locale["thumbnails.page"] ?? "Page"} ${i + 1}`,
1003
- dataUrl: thumbs[i]
1004
- }));
1005
- }, [pageCount, thumbs, locale]);
1006
- async function handleSignRequest() {
1007
- if (!allowSigning || signingBusy || !props.onSignRequest) {
1008
- return;
1009
- }
1010
- setSigningBusy(true);
1011
- try {
1012
- const sig = await props.onSignRequest();
1013
- setLocalSignatures((prev) => [...prev, sig]);
1014
- setArmedSignatureUrl(sig.signatureImageUrl);
1015
- } finally {
1016
- setSigningBusy(false);
1434
+ }, [props.fileUrl, props.base64, props.blob, mode]);
1435
+ const handleSignatureSelect = (sig) => {
1436
+ if (props.onSign) {
1437
+ props.onSign(sig);
1017
1438
  }
1018
- }
1019
- function placeSignature(p) {
1020
- if (!armedSignatureUrl) {
1021
- return;
1022
- }
1023
- setSigPlacements((prev) => [
1024
- ...prev,
1025
- { ...p, signatureImageUrl: armedSignatureUrl }
1026
- ]);
1027
- setArmedSignatureUrl(null);
1028
- }
1029
- async function handleSave(exportPdf) {
1030
- if (editorRef.current) {
1031
- await editorRef.current.save(!!exportPdf);
1032
- return;
1439
+ console.log("Signature selected:", sig);
1440
+ };
1441
+ const renderContent = () => {
1442
+ if (error) {
1443
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-error-banner", children: [
1444
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: "Error loading document" }),
1445
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: error })
1446
+ ] });
1033
1447
  }
1034
- if (!resolved?.arrayBuffer) {
1035
- return;
1448
+ if (loading || !resolved) {
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-loader", children: [
1450
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-spinner" }),
1451
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Loading Document..." })
1452
+ ] });
1036
1453
  }
1037
- const b64 = arrayBufferToBase642(resolved.arrayBuffer);
1038
- props.onSave?.(b64, {
1454
+ const commonProps = {
1455
+ arrayBuffer: resolved.arrayBuffer,
1039
1456
  fileName: resolved.fileName,
1040
1457
  fileType: resolved.fileType,
1041
- annotations: { sigPlacements }
1042
- });
1043
- }
1044
- const canSave = mode === "edit" || mode === "create";
1045
- const canExportPdf = (mode === "edit" || mode === "create") && (resolved?.fileType === "docx" || resolved?.fileType === "md" || resolved?.fileType === "txt" || resolved?.fileType === "xlsx");
1046
- return /* @__PURE__ */ jsxs9("div", { className: `hv-root`, "data-hv-theme": theme, children: [
1047
- /* @__PURE__ */ jsx9(
1458
+ layout,
1459
+ currentPage,
1460
+ onPageCount: setPageCount,
1461
+ onCurrentPageChange: setCurrentPage,
1462
+ onThumbs: setThumbnails
1463
+ };
1464
+ switch (resolved.fileType) {
1465
+ case "pdf":
1466
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PdfRenderer, { url: resolved.url, ...commonProps });
1467
+ case "docx":
1468
+ case "doc":
1469
+ case "rtf":
1470
+ case "txt":
1471
+ case "md":
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RichTextEditor, { mode, ...commonProps });
1473
+ case "xlsx":
1474
+ case "csv":
1475
+ case "xls":
1476
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SpreadsheetEditor, { mode, ...commonProps });
1477
+ case "pptx":
1478
+ case "ppt":
1479
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PptxRenderer, { ...commonProps });
1480
+ case "jpg":
1481
+ case "jpeg":
1482
+ case "png":
1483
+ case "gif":
1484
+ case "bmp":
1485
+ case "svg":
1486
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ImageRenderer, { ...commonProps, fileType: resolved.fileType });
1487
+ default:
1488
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-error-banner", children: [
1489
+ "Unsupported file type: ",
1490
+ resolved.fileType
1491
+ ] });
1492
+ }
1493
+ };
1494
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-root", "data-hv-theme": theme, children: [
1495
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1048
1496
  Toolbar,
1049
1497
  {
1050
- locale,
1051
- mode,
1052
- fileType: resolved?.fileType,
1498
+ fileName: resolved?.fileName,
1499
+ pageCount,
1500
+ currentPage,
1501
+ onPageChange: setCurrentPage,
1053
1502
  layout,
1054
- onChangeLayout: setLayout,
1503
+ onLayoutChange: setLayout,
1055
1504
  showThumbnails,
1056
- onToggleThumbnails: () => setShowThumbnails((v) => !v),
1505
+ onToggleThumbnails: () => setShowThumbnails(!showThumbnails),
1057
1506
  showSignatures,
1058
- onToggleSignatures: () => setShowSignatures((v) => !v),
1059
- onSign: () => void handleSignRequest(),
1060
- allowSigning,
1061
- signingDisabled: signingBusy || !props.onSignRequest,
1062
- canSave,
1063
- onSave: () => void handleSave(false),
1064
- canExportPdf,
1065
- onExportPdf: () => void handleSave(true),
1066
- headerFooterEnabled,
1067
- showHeaderFooterToggle: (props.enableHeaderFooterToggle ?? true) && mode === "create",
1068
- onToggleHeaderFooter: () => setHeaderFooterEnabled((v) => !v)
1507
+ onToggleSignatures: () => setShowSignatures(!showSignatures)
1069
1508
  }
1070
1509
  ),
1071
- error ? /* @__PURE__ */ jsxs9("div", { className: "hv-error", role: "alert", children: [
1072
- /* @__PURE__ */ jsx9("div", { className: "hv-error-title", children: locale["error.title"] ?? "Error" }),
1073
- /* @__PURE__ */ jsx9("div", { className: "hv-error-body", children: error })
1074
- ] }) : null,
1075
- !resolved && !error ? /* @__PURE__ */ jsx9("div", { className: "hv-loading", "aria-busy": "true", children: locale.loading ?? "Loading\u2026" }) : null,
1076
- resolved ? /* @__PURE__ */ jsxs9("div", { className: "hv-shell", children: [
1077
- mode !== "create" ? /* @__PURE__ */ jsx9(
1510
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-shell", children: [
1511
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1078
1512
  ThumbnailsSidebar,
1079
1513
  {
1080
- locale,
1514
+ isOpen: showThumbnails,
1081
1515
  thumbnails,
1082
1516
  currentPage,
1083
- collapsed: !showThumbnails,
1084
- onToggle: () => setShowThumbnails((v) => !v),
1085
1517
  onSelectPage: setCurrentPage
1086
1518
  }
1087
- ) : null,
1088
- /* @__PURE__ */ jsxs9("main", { className: "hv-main", children: [
1089
- resolved.fileType === "pdf" ? /* @__PURE__ */ jsx9(
1090
- PdfRenderer,
1091
- {
1092
- url: resolved.url,
1093
- arrayBuffer: resolved.arrayBuffer,
1094
- layout,
1095
- currentPage,
1096
- onCurrentPageChange: setCurrentPage,
1097
- onPageCount: (n) => {
1098
- setPageCount(n);
1099
- setThumbs(
1100
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1101
- );
1102
- },
1103
- onThumbs: (t) => setThumbs(t),
1104
- signatureStamp: armedSignatureUrl ? {
1105
- imageUrl: armedSignatureUrl,
1106
- armed: true,
1107
- onPlaced: placeSignature
1108
- } : void 0
1109
- }
1110
- ) : null,
1111
- resolved.fileType === "docx" || resolved.fileType === "md" || resolved.fileType === "txt" ? /* @__PURE__ */ jsx9(
1112
- RichTextEditor,
1113
- {
1114
- ref: editorRef,
1115
- mode,
1116
- fileType: resolved.fileType,
1117
- fileName: resolved.fileName,
1118
- arrayBuffer: resolved.arrayBuffer,
1119
- headerComponent: props.headerComponent,
1120
- footerComponent: props.footerComponent,
1121
- headerFooterEnabled,
1122
- locale,
1123
- signatures: localSignatures,
1124
- signaturePlacements: sigPlacements,
1125
- onPageCount: (n) => {
1126
- setPageCount(n);
1127
- setThumbs(
1128
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1129
- );
1130
- },
1131
- onSave: (b64, meta) => props.onSave?.(b64, meta),
1132
- armedSignatureUrl,
1133
- onPlaceSignature: placeSignature
1134
- }
1135
- ) : null,
1136
- resolved.fileType === "xlsx" ? /* @__PURE__ */ jsx9(
1137
- SpreadsheetEditor,
1138
- {
1139
- ref: editorRef,
1140
- mode,
1141
- fileName: resolved.fileName,
1142
- arrayBuffer: resolved.arrayBuffer,
1143
- locale,
1144
- onSave: (b64, meta) => props.onSave?.(b64, meta)
1145
- }
1146
- ) : null,
1147
- resolved.fileType === "pptx" ? /* @__PURE__ */ jsx9(
1148
- PptxRenderer,
1149
- {
1150
- arrayBuffer: resolved.arrayBuffer,
1151
- layout,
1152
- currentPage,
1153
- onCurrentPageChange: setCurrentPage,
1154
- onSlideCount: (n) => {
1155
- setPageCount(n);
1156
- setThumbs(
1157
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1158
- );
1159
- },
1160
- onThumbs: (t) => setThumbs(t)
1161
- }
1162
- ) : null,
1163
- resolved.fileType === "png" || resolved.fileType === "jpg" || resolved.fileType === "svg" ? /* @__PURE__ */ jsx9(
1164
- ImageRenderer,
1165
- {
1166
- arrayBuffer: resolved.arrayBuffer,
1167
- fileType: resolved.fileType,
1168
- fileName: resolved.fileName
1169
- }
1170
- ) : null
1171
- ] }),
1172
- mode !== "create" && localSignatures.length ? /* @__PURE__ */ jsx9(
1519
+ ),
1520
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("main", { className: "hv-main", children: renderContent() }),
1521
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1173
1522
  SignaturePanel,
1174
1523
  {
1175
- locale,
1176
- signatures: localSignatures,
1177
- collapsed: !showSignatures,
1178
- onToggle: () => setShowSignatures((v) => !v)
1524
+ isOpen: showSignatures,
1525
+ onClose: () => setShowSignatures(false),
1526
+ onSelectSignature: handleSignatureSelect
1179
1527
  }
1180
- ) : null
1181
- ] }) : null
1528
+ )
1529
+ ] })
1182
1530
  ] });
1183
1531
  }
1184
- function arrayBufferToBase642(ab) {
1185
- const bytes = new Uint8Array(ab);
1186
- let binary = "";
1187
- const chunk = 32768;
1188
- for (let i = 0; i < bytes.length; i += chunk) {
1189
- binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
1190
- }
1191
- return btoa(binary);
1192
- }
1193
- export {
1532
+ // Annotate the CommonJS export names for ESM import in node:
1533
+ 0 && (module.exports = {
1194
1534
  DocumentViewer
1195
- };
1535
+ });
1196
1536
  //# sourceMappingURL=index.js.map