@zerohive/hive-viewer 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -6
- package/dist/index.cjs +237 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +241 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +241 -105
- package/dist/styles.css +75 -0
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3, useSta
|
|
|
3
3
|
|
|
4
4
|
// src/utils/locale.ts
|
|
5
5
|
var defaultLocale = {
|
|
6
|
-
|
|
6
|
+
loading: "Loading\u2026",
|
|
7
7
|
"error.title": "Error",
|
|
8
8
|
"toolbar.layout.single": "Single page",
|
|
9
9
|
"toolbar.layout.two": "Side-by-side",
|
|
@@ -26,7 +26,17 @@ var defaultLocale = {
|
|
|
26
26
|
function guessFileType(name, explicit) {
|
|
27
27
|
if (explicit) return explicit;
|
|
28
28
|
const ext = (name?.split(".").pop() || "").toLowerCase();
|
|
29
|
-
const allowed = [
|
|
29
|
+
const allowed = [
|
|
30
|
+
"pdf",
|
|
31
|
+
"md",
|
|
32
|
+
"docx",
|
|
33
|
+
"xlsx",
|
|
34
|
+
"pptx",
|
|
35
|
+
"txt",
|
|
36
|
+
"png",
|
|
37
|
+
"jpg",
|
|
38
|
+
"svg"
|
|
39
|
+
];
|
|
30
40
|
return allowed.includes(ext) ? ext : "txt";
|
|
31
41
|
}
|
|
32
42
|
function arrayBufferToBase64(buf) {
|
|
@@ -57,7 +67,8 @@ async function resolveSource(args) {
|
|
|
57
67
|
const ab = await base64ToArrayBuffer(args.base64);
|
|
58
68
|
return { fileType, fileName, arrayBuffer: ab };
|
|
59
69
|
}
|
|
60
|
-
if (!args.fileUrl)
|
|
70
|
+
if (!args.fileUrl)
|
|
71
|
+
throw new Error("No file source provided. Use fileUrl, blob, or base64.");
|
|
61
72
|
const res = await fetch(args.fileUrl);
|
|
62
73
|
if (!res.ok) throw new Error(`Failed to fetch file (${res.status})`);
|
|
63
74
|
const total = Number(res.headers.get("content-length") || "") || void 0;
|
|
@@ -173,44 +184,69 @@ function SignaturePanel(props) {
|
|
|
173
184
|
|
|
174
185
|
// src/renderers/PdfRenderer.tsx
|
|
175
186
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
176
|
-
import {
|
|
187
|
+
import {
|
|
188
|
+
GlobalWorkerOptions,
|
|
189
|
+
getDocument
|
|
190
|
+
} from "pdfjs-dist";
|
|
177
191
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
178
192
|
function PdfRenderer(props) {
|
|
179
193
|
const { url, arrayBuffer } = props;
|
|
180
194
|
const [doc, setDoc] = useState(null);
|
|
181
195
|
const [pageCount, setPageCount] = useState(0);
|
|
182
|
-
const [rendered, setRendered] = useState(
|
|
196
|
+
const [rendered, setRendered] = useState(
|
|
197
|
+
/* @__PURE__ */ new Map()
|
|
198
|
+
);
|
|
183
199
|
const [thumbs, setThumbs] = useState([]);
|
|
184
200
|
const [size, setSize] = useState({ w: 840, h: 1188 });
|
|
201
|
+
const [error, setError] = useState(null);
|
|
202
|
+
const [loading, setLoading] = useState(false);
|
|
185
203
|
const containerRef = useRef(null);
|
|
186
204
|
useEffect(() => {
|
|
187
205
|
try {
|
|
188
|
-
GlobalWorkerOptions.workerSrc = new URL(
|
|
206
|
+
GlobalWorkerOptions.workerSrc = new URL(
|
|
207
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
208
|
+
import.meta.url
|
|
209
|
+
).toString();
|
|
189
210
|
} catch {
|
|
190
211
|
}
|
|
191
212
|
}, []);
|
|
192
213
|
useEffect(() => {
|
|
193
214
|
let cancel = false;
|
|
215
|
+
setError(null);
|
|
216
|
+
setLoading(true);
|
|
194
217
|
(async () => {
|
|
195
218
|
setDoc(null);
|
|
196
219
|
setRendered(/* @__PURE__ */ new Map());
|
|
197
220
|
setThumbs([]);
|
|
198
|
-
if (!url && !arrayBuffer)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
if (!url && !arrayBuffer) {
|
|
222
|
+
setError("No PDF source provided.");
|
|
223
|
+
setLoading(false);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const task = getDocument(
|
|
228
|
+
url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
|
|
229
|
+
);
|
|
230
|
+
const pdf = await task.promise;
|
|
231
|
+
if (cancel) return;
|
|
232
|
+
setDoc(pdf);
|
|
233
|
+
setPageCount(pdf.numPages);
|
|
234
|
+
props.onPageCount(pdf.numPages);
|
|
235
|
+
setThumbs(Array.from({ length: pdf.numPages }));
|
|
236
|
+
const p1 = await pdf.getPage(1);
|
|
237
|
+
const base = p1.getViewport({ scale: 1 });
|
|
238
|
+
const w = Math.min(980, Math.max(640, base.width));
|
|
239
|
+
const s = w / base.width;
|
|
240
|
+
const vp = p1.getViewport({ scale: s });
|
|
241
|
+
setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
|
|
242
|
+
} catch (e) {
|
|
243
|
+
setError(
|
|
244
|
+
"Failed to load PDF. " + (e instanceof Error ? e.message : "")
|
|
245
|
+
);
|
|
246
|
+
} finally {
|
|
247
|
+
setLoading(false);
|
|
248
|
+
}
|
|
249
|
+
})();
|
|
214
250
|
return () => {
|
|
215
251
|
cancel = true;
|
|
216
252
|
};
|
|
@@ -232,37 +268,43 @@ function PdfRenderer(props) {
|
|
|
232
268
|
(async () => {
|
|
233
269
|
for (const p of pagesToShow) {
|
|
234
270
|
if (rendered.has(p)) continue;
|
|
235
|
-
const page = await doc.getPage(p);
|
|
236
|
-
if (cancel) return;
|
|
237
|
-
const base = page.getViewport({ scale: 1 });
|
|
238
|
-
const vp = page.getViewport({ scale: size.w / base.width });
|
|
239
|
-
const canvas = document.createElement("canvas");
|
|
240
|
-
canvas.width = Math.round(vp.width);
|
|
241
|
-
canvas.height = Math.round(vp.height);
|
|
242
|
-
const ctx = canvas.getContext("2d", { alpha: false });
|
|
243
|
-
if (!ctx) continue;
|
|
244
|
-
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
245
|
-
if (cancel) return;
|
|
246
|
-
setRendered((prev) => {
|
|
247
|
-
const next = new Map(prev);
|
|
248
|
-
next.set(p, canvas);
|
|
249
|
-
return next;
|
|
250
|
-
});
|
|
251
271
|
try {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
const page = await doc.getPage(p);
|
|
273
|
+
if (cancel) return;
|
|
274
|
+
const base = page.getViewport({ scale: 1 });
|
|
275
|
+
const vp = page.getViewport({ scale: size.w / base.width });
|
|
276
|
+
const canvas = document.createElement("canvas");
|
|
277
|
+
canvas.width = Math.round(vp.width);
|
|
278
|
+
canvas.height = Math.round(vp.height);
|
|
279
|
+
const ctx = canvas.getContext("2d", { alpha: false });
|
|
280
|
+
if (!ctx) continue;
|
|
281
|
+
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
282
|
+
if (cancel) return;
|
|
283
|
+
setRendered((prev) => {
|
|
284
|
+
const next = new Map(prev);
|
|
285
|
+
next.set(p, canvas);
|
|
286
|
+
return next;
|
|
287
|
+
});
|
|
288
|
+
if (!thumbs[p - 1]) {
|
|
289
|
+
const thumbCanvas = document.createElement("canvas");
|
|
290
|
+
const thumbScale = 120 / vp.width;
|
|
291
|
+
thumbCanvas.width = Math.round(vp.width * thumbScale);
|
|
292
|
+
thumbCanvas.height = Math.round(vp.height * thumbScale);
|
|
293
|
+
const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
|
|
294
|
+
if (thumbCtx) {
|
|
295
|
+
thumbCtx.drawImage(
|
|
296
|
+
canvas,
|
|
297
|
+
0,
|
|
298
|
+
0,
|
|
299
|
+
thumbCanvas.width,
|
|
300
|
+
thumbCanvas.height
|
|
301
|
+
);
|
|
302
|
+
setThumbs((prev) => {
|
|
303
|
+
const arr = prev.slice();
|
|
304
|
+
arr[p - 1] = thumbCanvas.toDataURL("image/png");
|
|
305
|
+
return arr;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
266
308
|
}
|
|
267
309
|
} catch {
|
|
268
310
|
}
|
|
@@ -271,13 +313,16 @@ function PdfRenderer(props) {
|
|
|
271
313
|
return () => {
|
|
272
314
|
cancel = true;
|
|
273
315
|
};
|
|
274
|
-
}, [doc, pagesToShow, size.w, rendered]);
|
|
316
|
+
}, [doc, pagesToShow, size.w, rendered, thumbs]);
|
|
275
317
|
function onWheel(e) {
|
|
276
318
|
if (!pageCount) return;
|
|
277
319
|
if (Math.abs(e.deltaY) < 10) return;
|
|
278
320
|
const dir = e.deltaY > 0 ? 1 : -1;
|
|
279
321
|
const step = props.layout === "side-by-side" ? 2 : 1;
|
|
280
|
-
const next = Math.max(
|
|
322
|
+
const next = Math.max(
|
|
323
|
+
1,
|
|
324
|
+
Math.min(pageCount, props.currentPage + dir * step)
|
|
325
|
+
);
|
|
281
326
|
props.onCurrentPageChange(next);
|
|
282
327
|
}
|
|
283
328
|
function clickPlace(e, page) {
|
|
@@ -290,22 +335,37 @@ function PdfRenderer(props) {
|
|
|
290
335
|
}
|
|
291
336
|
return /* @__PURE__ */ jsxs4("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
|
|
292
337
|
!doc ? /* @__PURE__ */ jsx4("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
|
|
293
|
-
doc ? /* @__PURE__ */ jsx4(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"
|
|
297
|
-
{
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
338
|
+
doc ? /* @__PURE__ */ jsx4(
|
|
339
|
+
"div",
|
|
340
|
+
{
|
|
341
|
+
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
342
|
+
children: pagesToShow.map((p) => {
|
|
343
|
+
const c = rendered.get(p);
|
|
344
|
+
return /* @__PURE__ */ jsx4(
|
|
345
|
+
"div",
|
|
346
|
+
{
|
|
347
|
+
className: "hv-page",
|
|
348
|
+
style: { width: size.w, height: size.h },
|
|
349
|
+
onClick: (e) => clickPlace(e, p),
|
|
350
|
+
children: c ? /* @__PURE__ */ jsx4(
|
|
351
|
+
"canvas",
|
|
352
|
+
{
|
|
353
|
+
className: "hv-canvas",
|
|
354
|
+
width: c.width,
|
|
355
|
+
height: c.height,
|
|
356
|
+
ref: (node) => {
|
|
357
|
+
if (!node) return;
|
|
358
|
+
const ctx = node.getContext("2d");
|
|
359
|
+
if (ctx) ctx.drawImage(c, 0, 0);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
) : /* @__PURE__ */ jsx4("div", { className: "hv-loading", children: "Rendering\u2026" })
|
|
363
|
+
},
|
|
364
|
+
p
|
|
365
|
+
);
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
) : null
|
|
309
369
|
] });
|
|
310
370
|
}
|
|
311
371
|
|
|
@@ -613,7 +673,11 @@ function ensureExt(name, ext) {
|
|
|
613
673
|
// src/renderers/ImageRenderer.tsx
|
|
614
674
|
import { useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
615
675
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
616
|
-
function ImageRenderer({
|
|
676
|
+
function ImageRenderer({
|
|
677
|
+
arrayBuffer,
|
|
678
|
+
fileType,
|
|
679
|
+
fileName
|
|
680
|
+
}) {
|
|
617
681
|
const [zoom, setZoom] = useState4(1);
|
|
618
682
|
const url = useMemo4(() => {
|
|
619
683
|
if (!arrayBuffer) return void 0;
|
|
@@ -629,14 +693,42 @@ function ImageRenderer({ arrayBuffer, fileType, fileName }) {
|
|
|
629
693
|
/* @__PURE__ */ jsxs7("div", { className: "hv-mini-toolbar", children: [
|
|
630
694
|
/* @__PURE__ */ jsx7("div", { className: "hv-title", children: fileName }),
|
|
631
695
|
/* @__PURE__ */ jsx7("div", { className: "hv-spacer" }),
|
|
632
|
-
/* @__PURE__ */ jsx7(
|
|
696
|
+
/* @__PURE__ */ jsx7(
|
|
697
|
+
"button",
|
|
698
|
+
{
|
|
699
|
+
type: "button",
|
|
700
|
+
className: "hv-btn",
|
|
701
|
+
onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
|
|
702
|
+
children: "-"
|
|
703
|
+
}
|
|
704
|
+
),
|
|
633
705
|
/* @__PURE__ */ jsxs7("div", { className: "hv-zoom", children: [
|
|
634
706
|
Math.round(zoom * 100),
|
|
635
707
|
"%"
|
|
636
708
|
] }),
|
|
637
|
-
/* @__PURE__ */ jsx7(
|
|
709
|
+
/* @__PURE__ */ jsx7(
|
|
710
|
+
"button",
|
|
711
|
+
{
|
|
712
|
+
type: "button",
|
|
713
|
+
className: "hv-btn",
|
|
714
|
+
onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
|
|
715
|
+
children: "+"
|
|
716
|
+
}
|
|
717
|
+
)
|
|
638
718
|
] }),
|
|
639
|
-
/* @__PURE__ */
|
|
719
|
+
/* @__PURE__ */ jsxs7("div", { className: "hv-center", children: [
|
|
720
|
+
!arrayBuffer && /* @__PURE__ */ jsx7("div", { className: "hv-error", children: "No image data provided." }),
|
|
721
|
+
arrayBuffer && !url && /* @__PURE__ */ jsx7("div", { className: "hv-error", children: "Failed to load image." }),
|
|
722
|
+
url && /* @__PURE__ */ jsx7(
|
|
723
|
+
"img",
|
|
724
|
+
{
|
|
725
|
+
src: url,
|
|
726
|
+
alt: fileName,
|
|
727
|
+
style: { transform: `scale(${zoom})` },
|
|
728
|
+
className: "hv-image"
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
] })
|
|
640
732
|
] });
|
|
641
733
|
}
|
|
642
734
|
|
|
@@ -653,37 +745,57 @@ function extractText(xml) {
|
|
|
653
745
|
function PptxRenderer(props) {
|
|
654
746
|
const [slides, setSlides] = useState5([]);
|
|
655
747
|
const [thumbs, setThumbs] = useState5([]);
|
|
748
|
+
const [error, setError] = useState5(null);
|
|
749
|
+
const [loading, setLoading] = useState5(false);
|
|
656
750
|
useEffect5(() => {
|
|
657
751
|
let cancelled = false;
|
|
752
|
+
setError(null);
|
|
753
|
+
setLoading(true);
|
|
658
754
|
(async () => {
|
|
659
755
|
setSlides([]);
|
|
660
756
|
setThumbs([]);
|
|
661
757
|
if (!props.arrayBuffer) {
|
|
662
758
|
props.onSlideCount(1);
|
|
663
759
|
setSlides([{ index: 1, text: "No content" }]);
|
|
760
|
+
setError("No PPTX data provided.");
|
|
761
|
+
setLoading(false);
|
|
664
762
|
return;
|
|
665
763
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
764
|
+
try {
|
|
765
|
+
const zip = await JSZip.loadAsync(props.arrayBuffer);
|
|
766
|
+
const files = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort((a, b) => {
|
|
767
|
+
const na = Number(a.match(/slide(\d+)\.xml/)?.[1] || 0);
|
|
768
|
+
const nb = Number(b.match(/slide(\d+)\.xml/)?.[1] || 0);
|
|
769
|
+
return na - nb;
|
|
770
|
+
});
|
|
771
|
+
const out = [];
|
|
772
|
+
for (let i = 0; i < files.length; i++) {
|
|
773
|
+
const xml = await zip.file(files[i]).async("string");
|
|
774
|
+
out.push({ index: i + 1, text: extractText(xml) });
|
|
775
|
+
}
|
|
776
|
+
if (cancelled) return;
|
|
777
|
+
const count = Math.max(1, out.length);
|
|
778
|
+
props.onSlideCount(count);
|
|
779
|
+
setSlides(out.length ? out : [{ index: 1, text: "(empty)" }]);
|
|
780
|
+
setThumbs(
|
|
781
|
+
Array.from(
|
|
782
|
+
{ length: count },
|
|
783
|
+
(_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
|
|
784
|
+
)
|
|
785
|
+
);
|
|
786
|
+
} catch (e) {
|
|
787
|
+
props.onSlideCount(1);
|
|
788
|
+
setSlides([
|
|
789
|
+
{ index: 1, text: "Unable to render this .pptx in-browser." }
|
|
790
|
+
]);
|
|
791
|
+
setThumbs([void 0]);
|
|
792
|
+
setError(
|
|
793
|
+
"Failed to load PPTX. " + (e instanceof Error ? e.message : "")
|
|
794
|
+
);
|
|
795
|
+
} finally {
|
|
796
|
+
setLoading(false);
|
|
676
797
|
}
|
|
677
|
-
|
|
678
|
-
const count = Math.max(1, out.length);
|
|
679
|
-
props.onSlideCount(count);
|
|
680
|
-
setSlides(out.length ? out : [{ index: 1, text: "(empty)" }]);
|
|
681
|
-
setThumbs(Array.from({ length: count }, (_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`));
|
|
682
|
-
})().catch(() => {
|
|
683
|
-
props.onSlideCount(1);
|
|
684
|
-
setSlides([{ index: 1, text: "Unable to render this .pptx in-browser." }]);
|
|
685
|
-
setThumbs([void 0]);
|
|
686
|
-
});
|
|
798
|
+
})();
|
|
687
799
|
return () => {
|
|
688
800
|
cancelled = true;
|
|
689
801
|
};
|
|
@@ -692,19 +804,43 @@ function PptxRenderer(props) {
|
|
|
692
804
|
props.onThumbs(thumbs);
|
|
693
805
|
}, [thumbs]);
|
|
694
806
|
const pagesToShow = useMemo5(() => {
|
|
695
|
-
if (props.layout === "side-by-side")
|
|
807
|
+
if (props.layout === "side-by-side")
|
|
808
|
+
return [
|
|
809
|
+
props.currentPage,
|
|
810
|
+
Math.min(slides.length || props.currentPage + 1, props.currentPage + 1)
|
|
811
|
+
];
|
|
696
812
|
return [props.currentPage];
|
|
697
813
|
}, [props.currentPage, props.layout, slides.length]);
|
|
698
|
-
return /* @__PURE__ */
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
814
|
+
return /* @__PURE__ */ jsxs8("div", { className: "hv-doc", children: [
|
|
815
|
+
loading && /* @__PURE__ */ jsx8("div", { className: "hv-loading", children: "Loading PPTX\u2026" }),
|
|
816
|
+
error && /* @__PURE__ */ jsx8("div", { className: "hv-error", children: error }),
|
|
817
|
+
!loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ jsx8("div", { className: "hv-error", children: "No slides to display." }),
|
|
818
|
+
!error && slides && slides.length > 0 && /* @__PURE__ */ jsx8(
|
|
819
|
+
"div",
|
|
820
|
+
{
|
|
821
|
+
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
822
|
+
children: pagesToShow.map((p) => {
|
|
823
|
+
const s = slides[p - 1];
|
|
824
|
+
return /* @__PURE__ */ jsxs8(
|
|
825
|
+
"div",
|
|
826
|
+
{
|
|
827
|
+
className: "hv-slide",
|
|
828
|
+
tabIndex: 0,
|
|
829
|
+
onFocus: () => props.onCurrentPageChange(p),
|
|
830
|
+
children: [
|
|
831
|
+
/* @__PURE__ */ jsxs8("div", { className: "hv-slide-title", children: [
|
|
832
|
+
"Slide ",
|
|
833
|
+
p
|
|
834
|
+
] }),
|
|
835
|
+
/* @__PURE__ */ jsx8("div", { className: "hv-slide-text", children: s?.text || "" })
|
|
836
|
+
]
|
|
837
|
+
},
|
|
838
|
+
p
|
|
839
|
+
);
|
|
840
|
+
})
|
|
841
|
+
}
|
|
842
|
+
)
|
|
843
|
+
] });
|
|
708
844
|
}
|
|
709
845
|
function svgThumb(n) {
|
|
710
846
|
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>`;
|
package/dist/styles.css
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
+
/* Focus ring for accessibility */
|
|
2
|
+
.hv-btn:focus, .hv-thumb:focus, .hv-modal-close:focus {
|
|
3
|
+
outline: 2px solid #6366f1;
|
|
4
|
+
outline-offset: 2px;
|
|
5
|
+
z-index: 2;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Modal overlay and modal polish */
|
|
9
|
+
.hv-modal-overlay {
|
|
10
|
+
position: fixed;
|
|
11
|
+
inset: 0;
|
|
12
|
+
background: rgba(0,0,0,0.45);
|
|
13
|
+
z-index: 1000;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
padding: 16px;
|
|
18
|
+
}
|
|
19
|
+
.hv-modal {
|
|
20
|
+
background: #fff;
|
|
21
|
+
border-radius: 16px;
|
|
22
|
+
max-width: 95vw;
|
|
23
|
+
max-height: 95vh;
|
|
24
|
+
overflow: auto;
|
|
25
|
+
position: relative;
|
|
26
|
+
padding: 0;
|
|
27
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
}
|
|
31
|
+
.hv-modal-close {
|
|
32
|
+
position: absolute;
|
|
33
|
+
top: 12px;
|
|
34
|
+
right: 16px;
|
|
35
|
+
background: none;
|
|
36
|
+
border: none;
|
|
37
|
+
font-size: 2rem;
|
|
38
|
+
color: #888;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
z-index: 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Error and loading states */
|
|
44
|
+
.hv-error {
|
|
45
|
+
padding: 18px 24px;
|
|
46
|
+
color: #b91c1c;
|
|
47
|
+
background: #fef2f2;
|
|
48
|
+
border-radius: 10px;
|
|
49
|
+
margin: 16px auto;
|
|
50
|
+
max-width: 480px;
|
|
51
|
+
font-size: 1rem;
|
|
52
|
+
text-align: center;
|
|
53
|
+
}
|
|
54
|
+
.hv-loading {
|
|
55
|
+
padding: 18px 24px;
|
|
56
|
+
color: #64748b;
|
|
57
|
+
background: #f1f5f9;
|
|
58
|
+
border-radius: 10px;
|
|
59
|
+
margin: 16px auto;
|
|
60
|
+
max-width: 480px;
|
|
61
|
+
font-size: 1rem;
|
|
62
|
+
text-align: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Touch-friendly spacing */
|
|
66
|
+
@media (pointer: coarse) {
|
|
67
|
+
.hv-btn, .hv-thumb, .hv-modal-close {
|
|
68
|
+
min-height: 44px;
|
|
69
|
+
min-width: 44px;
|
|
70
|
+
font-size: 1.1rem;
|
|
71
|
+
}
|
|
72
|
+
.hv-toolbar, .hv-ribbon {
|
|
73
|
+
padding: 16px 8px;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
1
76
|
.hv-root{--hv-bg:#0b1020;--hv-fg:#e5e7eb;--hv-muted:#9ca3af;--hv-panel:#0f172a;--hv-border:rgba(255,255,255,.10);--hv-btn:rgba(255,255,255,.06);--hv-btnh:rgba(255,255,255,.10);background:var(--hv-bg);color:var(--hv-fg);border:1px solid var(--hv-border);border-radius:16px;overflow:hidden}
|
|
2
77
|
.hv-root[data-hv-theme="light"]{--hv-bg:#f8fafc;--hv-fg:#0f172a;--hv-muted:#64748b;--hv-panel:#ffffff;--hv-border:rgba(15,23,42,.12);--hv-btn:rgba(15,23,42,.06);--hv-btnh:rgba(15,23,42,.10)}
|
|
3
78
|
.hv-toolbar{display:flex;gap:10px;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--hv-panel);border-bottom:1px solid var(--hv-border)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerohive/hive-viewer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"xlsx": "^0.18.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@types/markdown-it": "^14.1.2",
|
|
47
|
+
"@types/node": "^25.0.9",
|
|
46
48
|
"@types/react": "^18.3.11",
|
|
47
49
|
"@types/react-dom": "^18.3.1",
|
|
48
50
|
"react": "^18.3.1",
|