@zerohive/hive-viewer 1.0.1 → 2.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 DELETED
@@ -1,1575 +0,0 @@
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
-
37
- // src/components/DocumentViewer.tsx
38
- var import_react8 = require("react");
39
-
40
- // src/editors/RichTextEditor.tsx
41
- var import_mammoth = __toESM(require("mammoth"), 1);
42
- var import_react = require("react");
43
- var import_markdown_it = __toESM(require("markdown-it"), 1);
44
-
45
- // src/utils/sanitize.ts
46
- var import_dompurify = __toESM(require("dompurify"), 1);
47
- function sanitizeHtml(html) {
48
- return import_dompurify.default.sanitize(html, {
49
- USE_PROFILES: { html: true },
50
- ADD_ATTR: ["target", "rel"]
51
- });
52
- }
53
-
54
- // src/editors/RichTextEditor.tsx
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({
68
- arrayBuffer: props.arrayBuffer
69
- });
70
- setContentHtml(sanitizeHtml(result.value));
71
- } else {
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
- }
82
- }
83
- props.onPageCount(1);
84
- } catch (err) {
85
- console.error("Doc Conversion failed", err);
86
- setContentHtml("<p style='color:red'>Error parsing document.</p>");
87
- } finally {
88
- setLoading(false);
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
101
- }
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: ""
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
- ]
323
- }
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" })
456
- ] }),
457
- url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
458
- "div",
459
- {
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);
502
- }
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
- );
555
- }
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
- );
608
- }
609
-
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
- }
837
-
838
- // src/utils/fileSource.ts
839
- function guessFileType(name, explicit) {
840
- if (explicit) {
841
- return explicit;
842
- }
843
- const ext = (name?.split(".").pop() || "").toLowerCase();
844
- const allowed = [
845
- "pdf",
846
- "md",
847
- "docx",
848
- "xlsx",
849
- "pptx",
850
- "txt",
851
- "png",
852
- "jpg",
853
- "svg"
854
- ];
855
- return allowed.includes(ext) ? ext : "txt";
856
- }
857
- async function base64ToArrayBuffer(b64) {
858
- const bin = atob(b64);
859
- const len = bin.length;
860
- const bytes = new Uint8Array(len);
861
- for (let i = 0; i < len; i++) {
862
- bytes[i] = bin.charCodeAt(i);
863
- }
864
- return bytes.buffer;
865
- }
866
- async function resolveSource(args) {
867
- const fileType = guessFileType(args.fileName, args.fileType);
868
- const fileName = args.fileName ?? `document.${fileType}`;
869
- if (args.blob) {
870
- const ab = await args.blob.arrayBuffer();
871
- const url = URL.createObjectURL(args.blob);
872
- return { fileType, fileName, arrayBuffer: ab, url };
873
- }
874
- if (args.base64) {
875
- const ab = await base64ToArrayBuffer(args.base64);
876
- return { fileType, fileName, arrayBuffer: ab };
877
- }
878
- if (!args.fileUrl) {
879
- throw new Error("No file source provided. Use fileUrl, blob, or base64.");
880
- }
881
- const res = await fetch(args.fileUrl);
882
- if (!res.ok) {
883
- throw new Error(`Failed to fetch file (${res.status})`);
884
- }
885
- const total = Number(res.headers.get("content-length") || "") || void 0;
886
- if (!res.body) {
887
- const ab = await res.arrayBuffer();
888
- args.onProgress?.(ab.byteLength, total);
889
- return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
890
- }
891
- const reader = res.body.getReader();
892
- const chunks = [];
893
- let loaded = 0;
894
- while (true) {
895
- const { done, value } = await reader.read();
896
- if (done) {
897
- break;
898
- }
899
- if (value) {
900
- chunks.push(value);
901
- loaded += value.length;
902
- args.onProgress?.(loaded, total);
903
- }
904
- }
905
- const out = new Uint8Array(loaded);
906
- let offset = 0;
907
- for (const c of chunks) {
908
- out.set(c, offset);
909
- offset += c.length;
910
- }
911
- return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
912
- }
913
-
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
- });
926
- }
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
- ] }) });
961
- }
962
-
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);
985
- }
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)(
1027
- "button",
1028
- {
1029
- className: "hv-btn",
1030
- disabled: currentPage <= 1,
1031
- onClick: handlePrev,
1032
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.ChevronLeft, { size: 20 })
1033
- }
1034
- ),
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 })
1049
- ] }),
1050
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1051
- "button",
1052
- {
1053
- className: "hv-btn",
1054
- disabled: currentPage >= pageCount,
1055
- onClick: handleNext,
1056
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.ChevronRight, { size: 20 })
1057
- }
1058
- )
1059
- ] }),
1060
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-toolbar-group", children: [
1061
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1062
- "button",
1063
- {
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
- !props.disableSigning && /* @__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
- ]
1101
- }
1102
- )
1103
- ] })
1104
- ] });
1105
- }
1106
-
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 {
1113
- isOpen,
1114
- onClose,
1115
- onSelectSignature,
1116
- externalSignatures = [],
1117
- onSignRequest
1118
- } = props;
1119
- const [localSignatures, setLocalSignatures] = (0, import_react7.useState)([]);
1120
- const [showModal, setShowModal] = (0, import_react7.useState)(false);
1121
- const signatures = [...externalSignatures, ...localSignatures];
1122
- const canvasRef = (0, import_react7.useRef)(null);
1123
- const [isDrawing, setIsDrawing] = (0, import_react7.useState)(false);
1124
- const handleCreateClick = async () => {
1125
- if (onSignRequest) {
1126
- try {
1127
- const newSig = await onSignRequest();
1128
- if (newSig) {
1129
- setLocalSignatures((prev) => [...prev, newSig]);
1130
- onSelectSignature(newSig);
1131
- }
1132
- } catch (err) {
1133
- console.error("Custom sign request failed", err);
1134
- }
1135
- } else {
1136
- openCreateModal();
1137
- }
1138
- };
1139
- const openCreateModal = () => {
1140
- setShowModal(true);
1141
- setTimeout(() => {
1142
- const canvas = canvasRef.current;
1143
- if (canvas) {
1144
- const ctx = canvas.getContext("2d");
1145
- ctx?.clearRect(0, 0, canvas.width, canvas.height);
1146
- }
1147
- }, 100);
1148
- };
1149
- const saveSignature = () => {
1150
- const canvas = canvasRef.current;
1151
- if (!canvas) return;
1152
- const newSig = {
1153
- id: Date.now().toString(),
1154
- signatureImageUrl: canvas.toDataURL("image/png"),
1155
- signedBy: "Me",
1156
- dateSigned: (/* @__PURE__ */ new Date()).toISOString()
1157
- };
1158
- setLocalSignatures([...localSignatures, newSig]);
1159
- setShowModal(false);
1160
- onSelectSignature(newSig);
1161
- };
1162
- const startDraw = (e) => {
1163
- setIsDrawing(true);
1164
- draw(e);
1165
- };
1166
- const stopDraw = () => setIsDrawing(false);
1167
- const draw = (e) => {
1168
- if (!isDrawing || !canvasRef.current) return;
1169
- const canvas = canvasRef.current;
1170
- const ctx = canvas.getContext("2d");
1171
- if (!ctx) return;
1172
- const rect = canvas.getBoundingClientRect();
1173
- const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
1174
- const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
1175
- const x = clientX - rect.left;
1176
- const y = clientY - rect.top;
1177
- ctx.lineWidth = 2;
1178
- ctx.lineCap = "round";
1179
- ctx.lineTo(x, y);
1180
- ctx.stroke();
1181
- ctx.beginPath();
1182
- ctx.moveTo(x, y);
1183
- };
1184
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1185
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1186
- "div",
1187
- {
1188
- className: `hv-sidebar hv-sidebar-right ${!isOpen ? "collapsed" : ""}`,
1189
- style: { width: isOpen ? "280px" : "0" },
1190
- children: [
1191
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1192
- "div",
1193
- {
1194
- className: "hv-sidebar-header",
1195
- style: { justifyContent: "space-between", padding: "12px 16px" },
1196
- children: [
1197
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { style: { margin: 0, fontSize: "15px", fontWeight: 600 }, children: "Signatures" }),
1198
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1199
- "button",
1200
- {
1201
- onClick: onClose,
1202
- className: "hv-btn",
1203
- style: { padding: "4px", border: "none" },
1204
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.X, { size: 18 })
1205
- }
1206
- )
1207
- ]
1208
- }
1209
- ),
1210
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-thumb-list", children: [
1211
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1212
- "button",
1213
- {
1214
- onClick: handleCreateClick,
1215
- className: "hv-btn",
1216
- style: {
1217
- width: "100%",
1218
- justifyContent: "center",
1219
- border: "2px dashed var(--hv-border)",
1220
- marginBottom: "16px",
1221
- color: "var(--hv-primary)"
1222
- },
1223
- children: [
1224
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Plus, { size: 18, style: { marginRight: "8px" } }),
1225
- "New Signature"
1226
- ]
1227
- }
1228
- ),
1229
- signatures.map((sig, idx) => {
1230
- const isLocal = localSignatures.some((s) => s.id === sig.id);
1231
- const showDelete = isLocal;
1232
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1233
- "div",
1234
- {
1235
- className: "hv-thumb-item",
1236
- style: {
1237
- position: "relative",
1238
- padding: "12px",
1239
- background: "var(--hv-bg)",
1240
- borderRadius: "8px",
1241
- border: "1px solid var(--hv-border)"
1242
- },
1243
- onClick: () => onSelectSignature(sig),
1244
- children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1246
- "img",
1247
- {
1248
- src: sig.signatureImageUrl,
1249
- alt: "Signature",
1250
- style: { height: "40px", objectFit: "contain" }
1251
- }
1252
- ),
1253
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1254
- "div",
1255
- {
1256
- style: {
1257
- fontSize: "11px",
1258
- color: "var(--hv-muted)",
1259
- marginTop: "4px",
1260
- textAlign: "center"
1261
- },
1262
- children: [
1263
- sig.signedBy || "User",
1264
- " \u2022",
1265
- " ",
1266
- new Date(sig.dateSigned).toLocaleDateString(),
1267
- sig.comment && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-xs text-gray-500 mt-1 italic", children: sig.comment })
1268
- ]
1269
- }
1270
- ),
1271
- showDelete && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1272
- "button",
1273
- {
1274
- onClick: (e) => {
1275
- e.stopPropagation();
1276
- setLocalSignatures(
1277
- localSignatures.filter((s) => s.id !== sig.id)
1278
- );
1279
- },
1280
- className: "hv-btn",
1281
- style: {
1282
- position: "absolute",
1283
- top: "4px",
1284
- right: "4px",
1285
- padding: "4px",
1286
- color: "#ef4444",
1287
- border: "none",
1288
- background: "white"
1289
- },
1290
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Trash2, { size: 12 })
1291
- }
1292
- )
1293
- ]
1294
- },
1295
- sig.id || idx
1296
- );
1297
- })
1298
- ] })
1299
- ]
1300
- }
1301
- ),
1302
- showModal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hv-modal-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1303
- "div",
1304
- {
1305
- className: "hv-modal",
1306
- style: { width: "450px", maxWidth: "90vw" },
1307
- children: [
1308
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1309
- "div",
1310
- {
1311
- style: {
1312
- padding: "16px 24px",
1313
- borderBottom: "1px solid var(--hv-border)",
1314
- display: "flex",
1315
- justifyContent: "space-between",
1316
- alignItems: "center"
1317
- },
1318
- children: [
1319
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { style: { margin: 0, fontSize: "18px", fontWeight: 600 }, children: "Draw Signature" }),
1320
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1321
- "button",
1322
- {
1323
- onClick: () => setShowModal(false),
1324
- className: "hv-btn",
1325
- style: { border: "none" },
1326
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.X, { size: 20 })
1327
- }
1328
- )
1329
- ]
1330
- }
1331
- ),
1332
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1333
- "div",
1334
- {
1335
- style: {
1336
- padding: "24px",
1337
- background: "#f9fafb",
1338
- display: "flex",
1339
- flexDirection: "column",
1340
- alignItems: "center"
1341
- },
1342
- children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1344
- "div",
1345
- {
1346
- style: {
1347
- background: "white",
1348
- border: "1px solid #e5e7eb",
1349
- borderRadius: "8px",
1350
- overflow: "hidden",
1351
- boxShadow: "inset 0 2px 4px 0 rgba(0,0,0,0.05)"
1352
- },
1353
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1354
- "canvas",
1355
- {
1356
- ref: canvasRef,
1357
- width: 400,
1358
- height: 200,
1359
- style: {
1360
- display: "block",
1361
- cursor: "crosshair",
1362
- touchAction: "none"
1363
- },
1364
- onMouseDown: startDraw,
1365
- onMouseUp: stopDraw,
1366
- onMouseMove: draw,
1367
- onTouchStart: startDraw,
1368
- onTouchEnd: stopDraw,
1369
- onTouchMove: draw
1370
- }
1371
- )
1372
- }
1373
- ),
1374
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1375
- "p",
1376
- {
1377
- style: {
1378
- fontSize: "12px",
1379
- color: "var(--hv-muted)",
1380
- marginTop: "8px"
1381
- },
1382
- children: "Sign above using your mouse or finger"
1383
- }
1384
- )
1385
- ]
1386
- }
1387
- ),
1388
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1389
- "div",
1390
- {
1391
- style: {
1392
- padding: "16px 24px",
1393
- borderTop: "1px solid var(--hv-border)",
1394
- display: "flex",
1395
- justifyContent: "flex-end",
1396
- gap: "12px"
1397
- },
1398
- children: [
1399
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1400
- "button",
1401
- {
1402
- onClick: () => {
1403
- const ctx = canvasRef.current?.getContext("2d");
1404
- ctx?.clearRect(0, 0, 400, 200);
1405
- },
1406
- className: "hv-btn",
1407
- children: "Clear"
1408
- }
1409
- ),
1410
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: saveSignature, className: "hv-btn hv-btn-primary", children: "Create & Use" })
1411
- ]
1412
- }
1413
- )
1414
- ]
1415
- }
1416
- ) })
1417
- ] });
1418
- }
1419
-
1420
- // src/components/DocumentViewer.tsx
1421
- var import_jsx_runtime9 = require("react/jsx-runtime");
1422
- function DocumentViewer(props) {
1423
- const mode = props.mode ?? "view";
1424
- const theme = props.theme ?? "light";
1425
- const [layout, setLayout] = (0, import_react8.useState)(
1426
- props.defaultLayout ?? "single"
1427
- );
1428
- const [showThumbnails, setShowThumbnails] = (0, import_react8.useState)(
1429
- props.defaultShowThumbnails ?? true
1430
- );
1431
- const [showSignatures, setShowSignatures] = (0, import_react8.useState)(false);
1432
- const [resolved, setResolved] = (0, import_react8.useState)(null);
1433
- const [loading, setLoading] = (0, import_react8.useState)(true);
1434
- const [error, setError] = (0, import_react8.useState)("");
1435
- const [pageCount, setPageCount] = (0, import_react8.useState)(1);
1436
- const [currentPage, setCurrentPage] = (0, import_react8.useState)(1);
1437
- const [thumbnails, setThumbnails] = (0, import_react8.useState)([]);
1438
- (0, import_react8.useEffect)(() => {
1439
- let active = true;
1440
- const loadFile = async () => {
1441
- setLoading(true);
1442
- setError("");
1443
- setResolved(null);
1444
- try {
1445
- if (mode === "create") {
1446
- setResolved({
1447
- fileType: props.fileType ?? "docx",
1448
- fileName: props.fileName ?? "Untitled"
1449
- });
1450
- } else {
1451
- const res = await resolveSource({
1452
- fileUrl: props.fileUrl,
1453
- base64: props.base64,
1454
- blob: props.blob,
1455
- fileName: props.fileName,
1456
- fileType: props.fileType
1457
- });
1458
- if (active) setResolved(res);
1459
- }
1460
- } catch (err) {
1461
- if (active) setError(err.message || "Failed to load document");
1462
- } finally {
1463
- if (active) setLoading(false);
1464
- }
1465
- };
1466
- loadFile();
1467
- return () => {
1468
- active = false;
1469
- };
1470
- }, [props.fileUrl, props.base64, props.blob, mode]);
1471
- const handleSignatureSelect = (sig) => {
1472
- if (props.onSign) {
1473
- props.onSign(sig);
1474
- }
1475
- console.log("Signature selected:", sig);
1476
- };
1477
- const renderContent = () => {
1478
- if (error) {
1479
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-error-banner", children: [
1480
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: "Error loading document" }),
1481
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: error })
1482
- ] });
1483
- }
1484
- if (loading || !resolved) {
1485
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-loader", children: [
1486
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-spinner" }),
1487
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Loading Document..." })
1488
- ] });
1489
- }
1490
- const commonProps = {
1491
- arrayBuffer: resolved.arrayBuffer,
1492
- fileName: resolved.fileName,
1493
- fileType: resolved.fileType,
1494
- layout,
1495
- currentPage,
1496
- onPageCount: setPageCount,
1497
- onCurrentPageChange: setCurrentPage,
1498
- onThumbs: setThumbnails
1499
- };
1500
- switch (resolved.fileType) {
1501
- case "pdf":
1502
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PdfRenderer, { url: resolved.url, ...commonProps });
1503
- case "docx":
1504
- case "doc":
1505
- case "rtf":
1506
- case "txt":
1507
- case "md":
1508
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RichTextEditor, { mode, ...commonProps });
1509
- case "xlsx":
1510
- case "csv":
1511
- case "xls":
1512
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SpreadsheetEditor, { mode, ...commonProps });
1513
- case "pptx":
1514
- case "ppt":
1515
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PptxRenderer, { ...commonProps });
1516
- case "jpg":
1517
- case "jpeg":
1518
- case "png":
1519
- case "gif":
1520
- case "bmp":
1521
- case "svg":
1522
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ImageRenderer, { ...commonProps, fileType: resolved.fileType });
1523
- default:
1524
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-error-banner", children: [
1525
- "Unsupported file type: ",
1526
- resolved.fileType
1527
- ] });
1528
- }
1529
- };
1530
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-root", "data-hv-theme": theme, children: [
1531
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1532
- Toolbar,
1533
- {
1534
- fileName: resolved?.fileName,
1535
- pageCount,
1536
- currentPage,
1537
- onPageChange: setCurrentPage,
1538
- layout,
1539
- onLayoutChange: setLayout,
1540
- showThumbnails,
1541
- onToggleThumbnails: () => setShowThumbnails(!showThumbnails),
1542
- showSignatures,
1543
- onToggleSignatures: () => setShowSignatures(!showSignatures),
1544
- disableSigning: props.disableSigning
1545
- }
1546
- ),
1547
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-shell", children: [
1548
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1549
- ThumbnailsSidebar,
1550
- {
1551
- isOpen: showThumbnails,
1552
- thumbnails,
1553
- currentPage,
1554
- onSelectPage: setCurrentPage
1555
- }
1556
- ),
1557
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("main", { className: "hv-main", children: renderContent() }),
1558
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1559
- SignaturePanel,
1560
- {
1561
- isOpen: showSignatures,
1562
- onClose: () => setShowSignatures(false),
1563
- onSelectSignature: handleSignatureSelect,
1564
- externalSignatures: props.signatures,
1565
- onSignRequest: props.onSignRequest
1566
- }
1567
- )
1568
- ] })
1569
- ] });
1570
- }
1571
- // Annotate the CommonJS export names for ESM import in node:
1572
- 0 && (module.exports = {
1573
- DocumentViewer
1574
- });
1575
- //# sourceMappingURL=index.js.map