diff --git a/app/history/page.js b/app/history/page.js index 3e857d6..e4a8186 100644 --- a/app/history/page.js +++ b/app/history/page.js @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useAuth } from '@/context/AuthContext'; import { getRecentlyViewed } from '@/lib/db'; import { getAllExperiments } from '@/data/labs'; @@ -12,6 +12,7 @@ export default function HistoryPage() { const { user, loading: authLoading } = useAuth(); const [historyItems, setHistoryItems] = useState([]); const [loading, setLoading] = useState(true); + const timeoutRef = useRef(null); useEffect(() => { if (!authLoading && user) { @@ -23,12 +24,15 @@ export default function HistoryPage() { // Listen for instant updates const handleUpdate = () => fetchHistory(); window.addEventListener('workspace-updated', handleUpdate); - return () => window.removeEventListener('workspace-updated', handleUpdate); + return () => { + window.removeEventListener('workspace-updated', handleUpdate); + if (timeoutRef.current) clearTimeout(timeoutRef.current); +};; }, [user, authLoading]); const fetchHistory = async () => { // Safety timeout to prevent infinite "Loading..." - const timeout = setTimeout(() => { + timeoutRef.current = setTimeout(() => { setLoading(false); console.warn('Fetch timed out (4s)'); }, 4000); @@ -54,7 +58,7 @@ export default function HistoryPage() { setHistoryItems(enriched); } } finally { - clearTimeout(timeout); + clearTimeout(timeoutRef.current); setLoading(false); } }; diff --git a/app/lab/[slug]/experiment/[experimentId]/page.js b/app/lab/[slug]/experiment/[experimentId]/page.js index 3c5eb7e..16411b5 100644 --- a/app/lab/[slug]/experiment/[experimentId]/page.js +++ b/app/lab/[slug]/experiment/[experimentId]/page.js @@ -6,6 +6,7 @@ import ExperimentLayout from '@/components/experiment/ExperimentLayout'; import ContentBlock from '@/components/experiment/ContentBlock'; import PrintSettingsWrapper from '@/components/experiment/PrintSettingsWrapper'; import styles from '@/components/experiment/Experiment.module.css'; +import ExportButtonClient from "@/components/experiment/ExportButtonClient"; /** * Validates and retrieves parameters (slug, experimentId). @@ -28,9 +29,9 @@ export default async function ExperimentPage({ params }) { // Standardize ID to include labSlug to avoid collisions const fullExperimentId = `${slug}/${experimentId}`; - return ( + {SECTION_ORDER.map((sectionKey) => { const section = experiment.sections[sectionKey]; // Safety check: if section data is missing for some reason diff --git a/app/observations/page.js b/app/observations/page.js index 1ae8e06..30553b3 100644 --- a/app/observations/page.js +++ b/app/observations/page.js @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useAuth } from '@/context/AuthContext'; import { getAllSavedObservations } from '@/lib/db'; import { getAllExperiments } from '@/data/labs'; @@ -13,6 +13,7 @@ export default function ObservationsPage() { const { user, loading: authLoading } = useAuth(); const [savedTables, setSavedTables] = useState([]); const [loading, setLoading] = useState(true); + const timeoutRef = useRef(null); useEffect(() => { if (!authLoading && user) { @@ -23,11 +24,14 @@ export default function ObservationsPage() { const handleUpdate = () => fetchObservations(); window.addEventListener('workspace-updated', handleUpdate); - return () => window.removeEventListener('workspace-updated', handleUpdate); + return () => { + window.removeEventListener('workspace-updated', handleUpdate); + if (timeoutRef.current) clearTimeout(timeoutRef.current); +}; }, [user, authLoading]); const fetchObservations = async () => { - const timeout = setTimeout(() => { + timeoutRef.current = setTimeout(() => { setLoading(false); console.warn('Fetch timed out for observations'); }, 8000); @@ -81,7 +85,7 @@ export default function ObservationsPage() { setSavedTables([]); } } finally { - clearTimeout(timeout); + clearTimeout(timeoutRef.current); setLoading(false); } }; diff --git a/app/starred/page.js b/app/starred/page.js index 9740bb7..a06f980 100644 --- a/app/starred/page.js +++ b/app/starred/page.js @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useAuth } from '@/context/AuthContext'; import { getStarredExperimentsDetailed } from '@/lib/db'; import { getAllExperiments } from '@/data/labs'; @@ -12,6 +12,7 @@ export default function StarredPage() { const { user, loading: authLoading } = useAuth(); const [starredItems, setStarredItems] = useState([]); const [loading, setLoading] = useState(true); + const timeoutRef = useRef(null); useEffect(() => { if (!authLoading && user) { @@ -23,12 +24,15 @@ export default function StarredPage() { // Listen for instant updates const handleUpdate = () => fetchStarred(); window.addEventListener('workspace-updated', handleUpdate); - return () => window.removeEventListener('workspace-updated', handleUpdate); + return () => { + window.removeEventListener('workspace-updated', handleUpdate); + if (timeoutRef.current) clearTimeout(timeoutRef.current); +}; }, [user, authLoading]); const fetchStarred = async () => { // Safety timeout to prevent infinite "Loading..." - const timeout = setTimeout(() => { + timeoutRef.current = setTimeout(() => { setLoading(false); console.warn('Fetch timed out (4s)'); }, 4000); @@ -61,7 +65,7 @@ export default function StarredPage() { setStarredItems(enriched); } } finally { - clearTimeout(timeout); + clearTimeout(timeoutRef.current); setLoading(false); } }; diff --git a/components/experiment/ExportButtonClient.jsx b/components/experiment/ExportButtonClient.jsx new file mode 100644 index 0000000..4784629 --- /dev/null +++ b/components/experiment/ExportButtonClient.jsx @@ -0,0 +1,11 @@ +"use client"; + +import { useLabReportExport } from "@/lib/useLabReportExport"; +import { ExportPDFWidget } from "@/components/experiment/ExportPDFButton"; + +export default function ExportButtonClient({ experiment }) { + const exportProps = useLabReportExport(experiment); + return ( + + ); +} \ No newline at end of file diff --git a/components/experiment/ExportPDFButton.jsx b/components/experiment/ExportPDFButton.jsx new file mode 100644 index 0000000..385d796 --- /dev/null +++ b/components/experiment/ExportPDFButton.jsx @@ -0,0 +1,537 @@ +// ExportPDFButton.jsx +// "Export as PDF" button + metadata dialog for IIT Bhilai Virtual Lab + +import React from "react"; + +// ─── Inline styles ───────────────────────────────────────────────────────────── +// Written as JS objects to keep the component self-contained (no CSS import needed). +// Swap to Tailwind / CSS Modules as required by the project setup. + +const S = { + // Export button + btn: { + display: "inline-flex", + alignItems: "center", + gap: 8, + padding: "9px 18px", + borderRadius: 6, + border: "1.5px solid #003087", + backgroundColor: "#003087", + color: "#fff", + fontSize: 13, + fontWeight: 600, + cursor: "pointer", + transition: "background 0.15s, transform 0.1s", + userSelect: "none", + letterSpacing: 0.2, + }, + btnHover: { + backgroundColor: "#00246b", + }, + btnDisabled: { + opacity: 0.55, + cursor: "not-allowed", + }, + btnIcon: { + width: 16, + height: 16, + flexShrink: 0, + }, + + // Overlay + overlay: { + position: "fixed", + inset: 0, + backgroundColor: "rgba(0,0,0,0.45)", + backdropFilter: "blur(3px)", + zIndex: 9998, + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + + // Dialog + dialog: { + backgroundColor: "#ffffff", + borderRadius: 12, + boxShadow: "0 24px 80px rgba(0,48,135,0.18)", + padding: 32, + width: "100%", + maxWidth: 480, + zIndex: 9999, + fontFamily: "'Segoe UI', system-ui, sans-serif", + position: "relative", + }, + dialogHeader: { + display: "flex", + alignItems: "flex-start", + justifyContent: "space-between", + marginBottom: 24, + }, + dialogTitleGroup: {}, + dialogTitle: { + fontSize: 18, + fontWeight: 700, + color: "#003087", + marginBottom: 2, + lineHeight: 1.3, + }, + dialogSubtitle: { + fontSize: 12, + color: "#888", + marginTop: 2, + }, + closeBtn: { + background: "none", + border: "none", + cursor: "pointer", + padding: 4, + color: "#aaa", + lineHeight: 1, + fontSize: 20, + borderRadius: 4, + }, + + // Experiment info strip + infoStrip: { + backgroundColor: "#f0f4ff", + borderRadius: 6, + padding: "10px 14px", + marginBottom: 20, + borderLeft: "3px solid #003087", + }, + infoStripLabel: { + fontSize: 10, + color: "#888", + textTransform: "uppercase", + letterSpacing: 0.8, + marginBottom: 2, + fontWeight: 600, + }, + infoStripTitle: { + fontSize: 13, + fontWeight: 600, + color: "#1a1a2e", + }, + + // Form + fieldGroup: { + marginBottom: 14, + }, + fieldRow: { + display: "flex", + gap: 12, + marginBottom: 14, + }, + fieldRowItem: { + flex: 1, + }, + label: { + display: "block", + fontSize: 11, + fontWeight: 600, + color: "#555", + marginBottom: 5, + textTransform: "uppercase", + letterSpacing: 0.5, + }, + input: { + width: "100%", + boxSizing: "border-box", + padding: "8px 10px", + borderRadius: 5, + border: "1px solid #d1d9ee", + fontSize: 13, + color: "#1a1a2e", + outline: "none", + transition: "border-color 0.15s", + backgroundColor: "#fafbff", + }, + + // Optional fields note + optionalNote: { + fontSize: 11, + color: "#aaa", + marginBottom: 20, + fontStyle: "italic", + }, + + // Action row + actionRow: { + display: "flex", + gap: 10, + justifyContent: "flex-end", + marginTop: 8, + }, + cancelBtn: { + padding: "9px 20px", + borderRadius: 6, + border: "1.5px solid #dde3f0", + backgroundColor: "#fff", + color: "#555", + fontSize: 13, + fontWeight: 600, + cursor: "pointer", + }, + exportBtn: { + display: "inline-flex", + alignItems: "center", + gap: 8, + padding: "9px 22px", + borderRadius: 6, + border: "none", + backgroundColor: "#003087", + color: "#fff", + fontSize: 13, + fontWeight: 600, + cursor: "pointer", + transition: "background 0.15s", + }, + exportBtnGenerating: { + backgroundColor: "#1a4aab", + }, + exportBtnDone: { + backgroundColor: "#1a7a4a", + }, + exportBtnError: { + backgroundColor: "#c0392b", + }, + + // Error message + errorBox: { + backgroundColor: "#fff0f0", + border: "1px solid #f5c6c6", + borderRadius: 5, + padding: "8px 12px", + fontSize: 12, + color: "#c0392b", + marginTop: 12, + }, + + // Success + successBox: { + backgroundColor: "#eefaf3", + border: "1px solid #a3d9b8", + borderRadius: 5, + padding: "8px 12px", + fontSize: 12, + color: "#1a7a4a", + marginTop: 12, + textAlign: "center", + fontWeight: 600, + }, + + // Spinner + spinner: { + width: 14, + height: 14, + border: "2px solid rgba(255,255,255,0.35)", + borderTopColor: "#fff", + borderRadius: "50%", + animation: "spin 0.7s linear infinite", + display: "inline-block", + }, +}; + +// ─── SVG Icons ───────────────────────────────────────────────────────────────── + +function DownloadIcon({ style }) { + return ( + + + + + ); +} + +function CloseIcon() { + return ( + + + + ); +} + +function CheckIcon() { + return ( + + + + ); +} + +// ─── Spinner CSS ─────────────────────────────────────────────────────────────── + +const spinnerKeyframes = ` +@keyframes spin { + to { transform: rotate(360deg); } +} +`; + +// ─── MetaDialog component ────────────────────────────────────────────────────── + +/** + * ExportMetaDialog + * + * Collects student metadata before triggering PDF download. + * + * Props come directly from useLabReportExport(): + * isDialogOpen, closeDialog, exportPDF, status, errorMsg, meta, setMeta + * Plus: + * experimentTitle {string} + */ +export function ExportMetaDialog({ + isDialogOpen, + closeDialog, + exportPDF, + status, + errorMsg, + meta, + setMeta, + experimentTitle = "Experiment", +}) { + if (!isDialogOpen) return null; + + const isGenerating = status === "generating"; + const isDone = status === "done"; + const isError = status === "error"; + + const updateMeta = (key) => (e) => + setMeta((prev) => ({ ...prev, [key]: e.target.value })); + + // Determine export button state + let exportBtnStyle = { ...S.exportBtn }; + let exportBtnContent = ( + <> + + Export PDF + + ); + if (isGenerating) { + exportBtnStyle = { ...S.exportBtn, ...S.exportBtnGenerating }; + exportBtnContent = ( + <> + + Generating… + + ); + } else if (isDone) { + exportBtnStyle = { ...S.exportBtn, ...S.exportBtnDone }; + exportBtnContent = ( + <> + + Downloaded! + + ); + } else if (isError) { + exportBtnStyle = { ...S.exportBtn, ...S.exportBtnError }; + exportBtnContent = <>Retry; + } + + return ( + <> + + {/* Overlay */} +
+ {/* Dialog — stop propagation so clicking inside doesn't close */} +
e.stopPropagation()}> + {/* Header */} +
+
+
Export Lab Report
+
+ Fill in your details to personalise the PDF (optional) +
+
+ +
+ + {/* Experiment info */} +
+
Experiment
+
{experimentTitle}
+
+ + {/* Form fields */} +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +

All fields are optional.

+ + {/* Feedback */} + {isError && errorMsg && ( +
⚠ {errorMsg}
+ )} + {isDone && ( +
✓ PDF downloaded successfully!
+ )} + + {/* Actions */} +
+ + +
+
+
+ + ); +} + +// ─── Export Button ───────────────────────────────────────────────────────────── + +/** + * ExportPDFButton + * + * Drop-in trigger button. Pass `onClick={openDialog}` from useLabReportExport. + * + * Props: + * onClick {function} - Opens the metadata dialog + * disabled {boolean} - Optionally disable the button + * label {string} - Button label (default: "Export as PDF") + */ +export function ExportPDFButton({ + onClick, + disabled = false, + label = "Export as PDF", +}) { + const [hovered, setHovered] = React.useState(false); + + const btnStyle = { + ...S.btn, + ...(hovered && !disabled ? S.btnHover : {}), + ...(disabled ? S.btnDisabled : {}), + }; + + return ( + + ); +} + +// ─── Convenience wrapper ─────────────────────────────────────────────────────── + +/** + * ExportPDFWidget + * + * Renders both the button and the dialog in one go. + * Accepts all props from useLabReportExport() spread in, plus experimentTitle. + * + * Usage: + * const exportProps = useLabReportExport(experimentData); + * + */ +export function ExportPDFWidget({ + openDialog, + isDialogOpen, + closeDialog, + exportPDF, + status, + errorMsg, + meta, + setMeta, + experimentTitle, + buttonLabel, + disabled, +}) { + return ( + <> + + + + ); +} \ No newline at end of file diff --git a/lib/labReportPDF.js b/lib/labReportPDF.js new file mode 100644 index 0000000..c43968b --- /dev/null +++ b/lib/labReportPDF.js @@ -0,0 +1,940 @@ +// labReportPDF.js +// Client-side PDF generation for IIT Bhilai lab reports +// Uses @react-pdf/renderer for structured PDF output + +import React from "react"; +import { + Document, + Page, + Text, + View, + StyleSheet, + Image, + Font, + pdf, +} from "@react-pdf/renderer"; + +// ─── Fonts ──────────────────────────────────────────────────────────────────── +// Using standard PDF fonts; swap to registered custom fonts if needed +Font.register({ + family: "Times-Roman", + fonts: [ + { src: "https://cdn.jsdelivr.net/npm/@canvas-fonts/times-new-roman@1.0.4/Times New Roman.ttf" }, + { src: "https://cdn.jsdelivr.net/npm/@canvas-fonts/times-new-roman@1.0.4/Times New Roman Bold.ttf", fontWeight: "bold" }, + { src: "https://cdn.jsdelivr.net/npm/@canvas-fonts/times-new-roman@1.0.4/Times New Roman Italic.ttf", fontStyle: "italic" }, + ], +}); + +// ─── Styles ────────────────────────────────────────────────────────────────── +const styles = StyleSheet.create({ + page: { + fontFamily: "Helvetica", + fontSize: 10, + paddingTop: 56, + paddingBottom: 56, + paddingLeft: 64, + paddingRight: 64, + color: "#1a1a2e", + backgroundColor: "#ffffff", + }, + + // ── Header / Cover ────────────────────────────────────────────────────── + coverPage: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + paddingTop: 80, + paddingBottom: 80, + paddingLeft: 64, + paddingRight: 64, + backgroundColor: "#ffffff", + }, + instituteBadge: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: "#003087", + marginBottom: 20, + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + instituteBadgeText: { + color: "#ffffff", + fontSize: 22, + fontFamily: "Helvetica-Bold", + textAlign: "center", + }, + instituteTitle: { + fontSize: 14, + fontFamily: "Helvetica-Bold", + color: "#003087", + textAlign: "center", + marginBottom: 4, + letterSpacing: 0.5, + }, + departmentTitle: { + fontSize: 11, + fontFamily: "Helvetica", + color: "#444", + textAlign: "center", + marginBottom: 32, + }, + dividerThick: { + height: 3, + backgroundColor: "#003087", + width: "100%", + marginBottom: 4, + }, + dividerThin: { + height: 1, + backgroundColor: "#003087", + width: "100%", + marginBottom: 24, + }, + coverExperimentLabel: { + fontSize: 10, + fontFamily: "Helvetica", + color: "#888", + textAlign: "center", + textTransform: "uppercase", + letterSpacing: 2, + marginBottom: 8, + }, + coverExperimentTitle: { + fontSize: 20, + fontFamily: "Helvetica-Bold", + color: "#1a1a2e", + textAlign: "center", + marginBottom: 40, + lineHeight: 1.4, + }, + coverMetaBox: { + borderWidth: 1, + borderColor: "#dde3f0", + borderRadius: 4, + padding: 20, + width: "100%", + marginBottom: 32, + backgroundColor: "#f7f9ff", + }, + coverMetaRow: { + flexDirection: "row", + marginBottom: 10, + }, + coverMetaLabel: { + fontSize: 9, + fontFamily: "Helvetica-Bold", + color: "#555", + width: 100, + textTransform: "uppercase", + letterSpacing: 0.5, + }, + coverMetaValue: { + fontSize: 10, + fontFamily: "Helvetica", + color: "#1a1a2e", + flex: 1, + }, + coverFooter: { + fontSize: 9, + color: "#aaa", + textAlign: "center", + position: "absolute", + bottom: 40, + left: 0, + right: 0, + }, + + // ── Page Header ───────────────────────────────────────────────────────── + pageHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + borderBottomWidth: 1, + borderBottomColor: "#003087", + paddingBottom: 6, + marginBottom: 20, + }, + pageHeaderLeft: { + fontSize: 8, + fontFamily: "Helvetica-Bold", + color: "#003087", + textTransform: "uppercase", + letterSpacing: 0.5, + }, + pageHeaderRight: { + fontSize: 8, + color: "#888", + }, + + // ── Page Footer ───────────────────────────────────────────────────────── + pageFooter: { + position: "absolute", + bottom: 28, + left: 64, + right: 64, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + borderTopWidth: 0.5, + borderTopColor: "#dde3f0", + paddingTop: 6, + }, + pageFooterText: { + fontSize: 8, + color: "#aaa", + }, + pageFooterPageNum: { + fontSize: 8, + color: "#666", + fontFamily: "Helvetica-Bold", + }, + + // ── Sections ──────────────────────────────────────────────────────────── + sectionContainer: { + marginBottom: 20, + }, + sectionHeading: { + fontSize: 13, + fontFamily: "Helvetica-Bold", + color: "#003087", + marginBottom: 8, + paddingBottom: 4, + borderBottomWidth: 1, + borderBottomColor: "#dde3f0", + textTransform: "uppercase", + letterSpacing: 0.5, + }, + sectionSubHeading: { + fontSize: 10, + fontFamily: "Helvetica-Bold", + color: "#333", + marginTop: 8, + marginBottom: 4, + }, + paragraph: { + fontSize: 10, + lineHeight: 1.6, + color: "#333", + marginBottom: 6, + textAlign: "justify", + }, + + // ── Aim / Objective ───────────────────────────────────────────────────── + aimBox: { + backgroundColor: "#f0f4ff", + borderLeftWidth: 3, + borderLeftColor: "#003087", + padding: 10, + marginBottom: 10, + borderRadius: 2, + }, + aimText: { + fontSize: 10, + color: "#1a1a2e", + lineHeight: 1.5, + }, + + // ── Apparatus / Requirements list ──────────────────────────────────────── + listItem: { + flexDirection: "row", + marginBottom: 3, + paddingLeft: 4, + }, + listBullet: { + fontSize: 9, + color: "#003087", + marginRight: 6, + marginTop: 1, + }, + listText: { + fontSize: 10, + color: "#333", + flex: 1, + lineHeight: 1.4, + }, + + // ── Math/Formula ──────────────────────────────────────────────────────── + formulaBox: { + backgroundColor: "#f8f9fe", + borderWidth: 1, + borderColor: "#e0e6f7", + borderRadius: 3, + padding: 10, + marginBottom: 10, + alignItems: "center", + }, + formulaText: { + fontSize: 11, + fontFamily: "Helvetica-Oblique", + color: "#1a1a2e", + textAlign: "center", + }, + formulaLabel: { + fontSize: 8, + color: "#888", + marginTop: 4, + }, + + // ── Observation Table ──────────────────────────────────────────────────── + tableContainer: { + marginBottom: 14, + borderWidth: 1, + borderColor: "#c8d4ee", + borderRadius: 3, + overflow: "hidden", + }, + tableHeaderRow: { + flexDirection: "row", + backgroundColor: "#003087", + }, + tableHeaderCell: { + flex: 1, + padding: 6, + borderRightWidth: 0.5, + borderRightColor: "#1a4aab", + }, + tableHeaderCellLast: { + flex: 1, + padding: 6, + }, + tableHeaderText: { + fontSize: 9, + fontFamily: "Helvetica-Bold", + color: "#ffffff", + textAlign: "center", + }, + tableRow: { + flexDirection: "row", + borderTopWidth: 0.5, + borderTopColor: "#dde3f0", + }, + tableRowAlt: { + flexDirection: "row", + borderTopWidth: 0.5, + borderTopColor: "#dde3f0", + backgroundColor: "#f7f9ff", + }, + tableCell: { + flex: 1, + padding: 5, + borderRightWidth: 0.5, + borderRightColor: "#dde3f0", + }, + tableCellLast: { + flex: 1, + padding: 5, + }, + tableCellText: { + fontSize: 9, + color: "#333", + textAlign: "center", + }, + tableCaption: { + fontSize: 8, + color: "#888", + textAlign: "center", + marginTop: 4, + fontStyle: "italic", + }, + + // ── Graph / Image ──────────────────────────────────────────────────────── + imageContainer: { + alignItems: "center", + marginBottom: 14, + }, + imageBox: { + borderWidth: 1, + borderColor: "#dde3f0", + borderRadius: 3, + padding: 4, + backgroundColor: "#fafbff", + }, + imageCaption: { + fontSize: 8, + color: "#888", + textAlign: "center", + marginTop: 6, + fontStyle: "italic", + }, + + // ── Result / Conclusion ────────────────────────────────────────────────── + resultBox: { + backgroundColor: "#eefaf3", + borderWidth: 1, + borderColor: "#a3d9b8", + borderRadius: 3, + padding: 12, + marginBottom: 10, + }, + resultText: { + fontSize: 10, + color: "#1a4a2e", + lineHeight: 1.5, + }, + + // ── Signature Block ────────────────────────────────────────────────────── + signatureSection: { + marginTop: 40, + flexDirection: "row", + justifyContent: "space-between", + }, + signatureBlock: { + alignItems: "center", + width: 140, + }, + signatureLine: { + borderTopWidth: 1, + borderTopColor: "#333", + width: 120, + marginBottom: 4, + }, + signatureLabel: { + fontSize: 8, + color: "#555", + textAlign: "center", + }, + + // ── Marks Table ───────────────────────────────────────────────────────── + marksTable: { + borderWidth: 1, + borderColor: "#c8d4ee", + borderRadius: 3, + marginTop: 16, + overflow: "hidden", + }, + marksRow: { + flexDirection: "row", + borderBottomWidth: 0.5, + borderBottomColor: "#dde3f0", + }, + marksCell: { + flex: 1, + padding: 8, + borderRightWidth: 0.5, + borderRightColor: "#dde3f0", + }, + marksCellText: { + fontSize: 9, + color: "#333", + textAlign: "center", + }, +}); + +// ─── Helper renderers ───────────────────────────────────────────────────────── + +/** + * Renders a single content block based on its `type`. + * Supported types: heading, text, aim, list, formula, table, image, result, calculation + */ +function renderBlock(block, index) { + switch (block.type) { + case "heading": + return ( + + {block.content} + + ); + + case "text": + case "paragraph": + return ( + + {block.content} + + ); + + case "aim": + case "objective": + return ( + + {block.content} + + ); + + case "list": + case "apparatus": + case "requirements": { + const items = Array.isArray(block.content) + ? block.content + : (block.content || "").split("\n").filter(Boolean); + return ( + + {items.map((item, i) => ( + + + {item} + + ))} + + ); + } + + case "formula": + case "equation": + case "calculation": + return ( + + {block.content} + {block.label && ( + {block.label} + )} + + ); + + case "table": + case "observation": { + const headers = block.headers || (block.data && block.data[0]) || []; + const rows = + block.rows || (block.data && block.data.slice(1)) || []; + return ( + + {block.caption && ( + + {block.caption} + + )} + + {/* Header row */} + + {headers.map((h, hi) => ( + + {String(h)} + + ))} + + {/* Data rows */} + {rows.map((row, ri) => { + const rowArr = Array.isArray(row) ? row : Object.values(row); + return ( + + {rowArr.map((cell, ci) => ( + + + {cell !== null && cell !== undefined ? String(cell) : "—"} + + + ))} + + ); + })} + + {block.note && ( + {block.note} + )} + + ); + } + + case "image": + case "circuit": + case "diagram": + case "graph": + case "plot": + if (!block.src && !block.base64) return null; + return ( + + + + + {block.caption && ( + {block.caption} + )} + + ); + + case "result": + case "conclusion": + return ( + + {block.content} + + ); + + default: + return ( + + {typeof block.content === "string" + ? block.content + : JSON.stringify(block.content)} + + ); + } +} + +// ─── Section component ──────────────────────────────────────────────────────── + +function LabSection({ title, blocks = [], children }) { + return ( + + {title && {title}} + {blocks.map((block, i) => renderBlock(block, i))} + {children} + + ); +} + +// ─── Page wrapper ───────────────────────────────────────────────────────────── + +function ReportPage({ experimentTitle, children, pageNumber, totalPages }) { + return ( + + {/* Running header */} + + {experimentTitle} + IIT Bhilai — Lab Report + + + {children} + + {/* Running footer */} + + + Generated on {new Date().toLocaleDateString("en-IN", { dateStyle: "long" })} + + + `Page ${pageNumber} of ${totalPages}` + } + /> + + + ); +} + +// ─── Cover Page ─────────────────────────────────────────────────────────────── + +function CoverPage({ experiment, meta }) { + const { + title = "Experiment", + course = "", + semester = "", + labGroup = "", + } = experiment; + const { + studentName = "", + rollNumber = "", + date = new Date().toLocaleDateString("en-IN"), + instructor = "", + } = meta || {}; + + return ( + + {/* Institute badge */} + + IIT + + + + Indian Institute of Technology Bhilai + + + {course || "Department of Electrical Engineering"} + + + + + + Lab Report + {title} + + {/* Meta info */} + + {studentName ? ( + + Student + {studentName} + + ) : null} + {rollNumber ? ( + + Roll No. + {rollNumber} + + ) : null} + {semester ? ( + + Semester + {semester} + + ) : null} + {labGroup ? ( + + Group + {labGroup} + + ) : null} + {instructor ? ( + + Instructor + {instructor} + + ) : null} + + Date + {date} + + + + + IIT Bhilai Virtual Lab — Confidential — For Academic Use Only + + + ); +} + +// ─── Main Document ──────────────────────────────────────────────────────────── + +/** + * LabReportDocument + * + * @param {Object} experiment - The experiment data object + * { + * title: string, + * course: string, + * semester: string, + * sections: Array<{ title: string, blocks: ContentBlock[] }> + * observations: ObservationTable[], + * calculations: ContentBlock[], + * results: ContentBlock[], + * chartImages: Array<{ base64: string, caption: string }>, + * circuitImages: Array<{ base64: string, caption: string }>, + * } + * @param {Object} meta - Student/session metadata + */ +export function LabReportDocument({ experiment, meta }) { + const { + title = "Experiment", + sections = [], + observations = [], + calculations = [], + results = [], + chartImages = [], + circuitImages = [], + precautions = [], + sources = [], + } = experiment; + + return ( + + {/* Cover Page */} + + + {/* Content Pages */} + + {/* Render standard sections from the experiment's `sections` array */} + {sections.map((section, si) => ( + + ))} + + {/* Circuit / diagram images */} + {circuitImages.length > 0 && ( + + {circuitImages.map((img, i) => ( + + + + + {img.caption && ( + {img.caption} + )} + + ))} + + )} + + {/* Observation tables */} + {observations.length > 0 && ( + + {observations.map((obs, oi) => + renderBlock({ type: "table", ...obs }, oi) + )} + + )} + + {/* Calculations */} + {calculations.length > 0 && ( + + {calculations.map((calc, ci) => renderBlock(calc, ci))} + + )} + + {/* Graphs / Plots */} + {chartImages.length > 0 && ( + + {chartImages.map((img, i) => ( + + + + + {img.caption && ( + {img.caption} + )} + + ))} + + )} + + {/* Results & Conclusion */} + {results.length > 0 && ( + + {results.map((r, ri) => renderBlock(r, ri))} + + )} + + {/* Precautions */} + {precautions.length > 0 && ( + + {precautions.map((p, pi) => + renderBlock( + typeof p === "string" + ? { type: "list", content: precautions } + : p, + pi + ) + )} + + )} + + {/* Sources / References */} + {sources.length > 0 && ( + + {sources.map((src, si) => ( + + [{si + 1}] + {src} + + ))} + + )} + + {/* Marks & Signature */} + + + {["Aim & Theory", "Observations", "Calculations", "Result", "Viva", "Total"].map( + (col) => ( + + + {col} + + + ) + )} + + + {["/10", "/20", "/20", "/10", "/20", "/80"].map((col, i) => ( + + + {col} + + + ))} + + + + + + + Student Signature + + + + Instructor Signature + + + + Date + + + + + ); +} + +// ─── Export function ────────────────────────────────────────────────────────── + +/** + * generateLabReportPDF + * + * Builds and triggers download of the lab report PDF. + * + * @param {Object} experiment - See LabReportDocument prop shape above + * @param {Object} meta - Student metadata { studentName, rollNumber, date, instructor, semester } + * @param {Object} charts - Map of chart refs: { [chartId]: chartInstance } + * + * Usage: + * import { generateLabReportPDF } from "./labReportPDF"; + * await generateLabReportPDF(experimentData, metaData, chartRefs); + */ +export async function generateLabReportPDF(experiment, meta = {}, charts = {}) { + // 1. Capture Chart.js plot images from live chart instances + const chartImages = []; + for (const [id, chartInstance] of Object.entries(charts)) { + if (chartInstance && typeof chartInstance.toBase64Image === "function") { + chartImages.push({ + base64: chartInstance.toBase64Image("image/png", 1), + caption: `Fig. ${chartImages.length + 1}: ${id}`, + }); + } + } + + // Merge captured charts with any pre-provided ones + const allChartImages = [ + ...(experiment.chartImages || []), + ...chartImages, + ]; + + const enrichedExperiment = { ...experiment, chartImages: allChartImages }; + + // 2. Generate PDF blob + const blob = await pdf( + + ).toBlob(); + + // 3. Trigger browser download + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${(experiment.title || "lab-report") + .toLowerCase() + .replace(/\s+/g, "-")}_${new Date().toISOString().split("T")[0]}.pdf`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} \ No newline at end of file diff --git a/lib/useLabReportExport.js b/lib/useLabReportExport.js new file mode 100644 index 0000000..d102c59 --- /dev/null +++ b/lib/useLabReportExport.js @@ -0,0 +1,99 @@ +// useLabReportExport.js +// React hook that manages PDF export state, metadata collection, and chart capture + +import { useState, useRef, useCallback } from "react"; +import { generateLabReportPDF } from "./labReportPDF"; + +/** + * useLabReportExport + * + * Manages the PDF export lifecycle: + * 1. Opens a metadata dialog (student name, roll no, etc.) + * 2. Captures live Chart.js instances via registerChart() + * 3. Calls generateLabReportPDF and triggers browser download + * + * @param {Object} experiment - The experiment data (sections, observations, etc.) + * @returns {Object} - { isDialogOpen, openDialog, closeDialog, exportPDF, registerChart, status, meta, setMeta } + * + * Usage: + * const { openDialog, registerChart, ...exportProps } = useLabReportExport(experimentData); + * + * // In Chart.js component: + * registerChart("IV Curve", ref?.chartInstance)} ... /> + * + * // Export button: + * + * + */ +export function useLabReportExport(experiment) { + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [status, setStatus] = useState("idle"); // idle | generating | done | error + const [errorMsg, setErrorMsg] = useState(""); + const [meta, setMeta] = useState({ + studentName: "", + rollNumber: "", + instructor: "", + semester: "", + labGroup: "", + date: new Date().toLocaleDateString("en-IN"), + }); + + // Registry of live Chart.js instances + const chartRegistryRef = useRef({}); + + /** + * Register a Chart.js instance by label. + * Pass chartRef.current from a react-chartjs-2 ref. + */ + const registerChart = useCallback((label, chartInstance) => { + if (chartInstance) { + chartRegistryRef.current[label] = chartInstance; + } else { + delete chartRegistryRef.current[label]; + } + }, []); + + const openDialog = useCallback(() => { + setStatus("idle"); + setErrorMsg(""); + setIsDialogOpen(true); + }, []); + + const closeDialog = useCallback(() => { + setIsDialogOpen(false); + setStatus("idle"); + }, []); + + const exportPDF = useCallback(async () => { + setStatus("generating"); + try { + await generateLabReportPDF( + experiment, + meta, + chartRegistryRef.current + ); + setStatus("done"); + // Auto-close after brief success state + setTimeout(() => { + setIsDialogOpen(false); + setStatus("idle"); + }, 1800); + } catch (err) { + console.error("[useLabReportExport] PDF generation failed:", err); + setErrorMsg(err?.message || "PDF generation failed. Please try again."); + setStatus("error"); + } + }, [experiment, meta]); + + return { + isDialogOpen, + openDialog, + closeDialog, + exportPDF, + registerChart, + status, + errorMsg, + meta, + setMeta, + }; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bdc4826..1f703ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "basic-labs-guide", "version": "0.1.0", "dependencies": { + "@react-pdf/renderer": "^4.4.0", "@supabase/supabase-js": "^2.99.1", "@vercel/analytics": "^2.0.0", "chart.js": "^4.5.1", @@ -398,6 +399,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -1819,6 +1829,30 @@ "node": ">= 10" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4198,6 +4232,182 @@ "license": "MIT", "peer": true }, + "node_modules/@react-pdf/fns": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.3.tgz", + "integrity": "sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==", + "license": "MIT" + }, + "node_modules/@react-pdf/font": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.6.tgz", + "integrity": "sha512-1RxR/hTyZcbgjESUjrMms574xuS9PLB4ovqQx6jvgdrIHXUyeUtSH6i3Szw1qVfUnA9MfaEm1FBuydQeJD39BQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/pdfkit": "^5.0.0", + "@react-pdf/types": "^2.10.0", + "fontkit": "^2.0.2", + "is-url": "^1.2.4" + } + }, + "node_modules/@react-pdf/image": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.4.tgz", + "integrity": "sha512-z0ogVQE0bKqgXQ5smgzIU857rLV7bMgVdrYsu3UfXDDLSzI7QPvzf6MFTFllX6Dx2rcsF13E01dqKPtJEM799g==", + "license": "MIT", + "dependencies": { + "@react-pdf/png-js": "^3.0.0", + "jay-peg": "^1.1.1" + } + }, + "node_modules/@react-pdf/layout": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.5.0.tgz", + "integrity": "sha512-XlGLzcZFdEYQHK6b9z9uPOVywbjv6pQ92D4RvqRmYNjpf7lQLnhdHr63tbiF7fB5k8Lg9/lGBXkHbzkeQW5geQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.3", + "@react-pdf/image": "^3.0.4", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/stylesheet": "^6.1.4", + "@react-pdf/textkit": "^6.1.1", + "@react-pdf/types": "^2.10.0", + "emoji-regex-xs": "^1.0.0", + "queue": "^6.0.1", + "yoga-layout": "^3.2.1" + } + }, + "node_modules/@react-pdf/pdfkit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-5.0.0.tgz", + "integrity": "sha512-FcQBWGtfhMGuOB0G3NcnF/cBq/JnFVs22i1tuafiT1XlmG6KjCxgTGng5bVh+b9RtTuwNpUGmCtB6CmG6B4ZVA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@noble/ciphers": "^1.0.0", + "@noble/hashes": "^1.6.0", + "@react-pdf/png-js": "^3.0.0", + "browserify-zlib": "^0.2.0", + "fontkit": "^2.0.2", + "jay-peg": "^1.1.1", + "js-md5": "^0.8.3", + "linebreak": "^1.1.0", + "vite-compatible-readable-stream": "^3.6.1" + } + }, + "node_modules/@react-pdf/png-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz", + "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==", + "license": "MIT", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/@react-pdf/primitives": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.2.0.tgz", + "integrity": "sha512-onlXLcA6SpsD7SX9HOyt55qdRRJCfauegPlo4ZNw0hA/IipaZTbT9MJliWKtEXm03ibGxAQyp/BgTuXm91fo0A==", + "license": "MIT" + }, + "node_modules/@react-pdf/reconciler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-2.0.0.tgz", + "integrity": "sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "scheduler": "0.25.0-rc-603e6108-20241029" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/reconciler/node_modules/scheduler": { + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", + "license": "MIT" + }, + "node_modules/@react-pdf/render": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.4.0.tgz", + "integrity": "sha512-+gGa9ymGosN6Ld3hFFSIVCV03Vva5S+asW7vmKetZY4LnhX5ea2gYNy6wL3e9VsLHqFDAefIXGtGDLH6XxQHag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.3", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/textkit": "^6.1.1", + "@react-pdf/types": "^2.10.0", + "abs-svg-path": "^0.1.1", + "color-string": "^1.9.1", + "normalize-svg-path": "^1.1.0", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + } + }, + "node_modules/@react-pdf/renderer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.4.0.tgz", + "integrity": "sha512-TtCcz1vyYD6fddhvBagwr9Aj3gRFLCqvduERQyNhTzHJi3zAKayHvJFr2PxcP4sRyIDRhibDW6ApqNhTqIVPoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.3", + "@react-pdf/font": "^4.0.6", + "@react-pdf/layout": "^4.5.0", + "@react-pdf/pdfkit": "^5.0.0", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/reconciler": "^2.0.0", + "@react-pdf/render": "^4.4.0", + "@react-pdf/types": "^2.10.0", + "events": "^3.3.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "queue": "^6.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/stylesheet": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.4.tgz", + "integrity": "sha512-jiwovO7lUwgccAh3JbVcXnh90AiSKZetdz2ETcWsKApPPLzLUzPkEs6wCVvZqh3lcGOAPFV3AfdMkFnLwv1ryg==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.3", + "@react-pdf/types": "^2.10.0", + "color-string": "^1.9.1", + "hsl-to-hex": "^1.0.0", + "media-engine": "^1.0.3", + "postcss-value-parser": "^4.1.0" + } + }, + "node_modules/@react-pdf/textkit": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.1.1.tgz", + "integrity": "sha512-HAHoa407q0UHLzwe/oL6VwgJj2cGKs5vORSVY+cRG/GC0kt7nxUV9N+2hA6VcqJA37gSRg7BTLsVr8Tt+4l5ow==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.3", + "bidi-js": "^1.0.2", + "hyphen": "^1.6.4", + "unicode-properties": "^1.4.1" + } + }, + "node_modules/@react-pdf/types": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.10.0.tgz", + "integrity": "sha512-iz0NusqQ/9ZHQirWhJqOaxY1UkpvuNkEDtH4/SPCnhZJKBO/IhlFLFHuzbHkmWByBoX6X3m8GCc2b/1QH6QNlA==", + "license": "MIT", + "dependencies": { + "@react-pdf/font": "^4.0.6", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/stylesheet": "^6.1.4" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", @@ -5368,6 +5578,12 @@ "node": ">=6.5" } }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -5822,8 +6038,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.10.0", @@ -5838,6 +6053,15 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -5891,6 +6115,24 @@ "node": ">=8" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -6191,6 +6433,15 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -6218,8 +6469,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "license": "MIT", - "peer": true + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } }, "node_modules/colord": { "version": "2.9.3", @@ -6670,6 +6930,12 @@ "license": "MIT", "peer": true }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/diff": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", @@ -6816,6 +7082,12 @@ "license": "MIT", "peer": true }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -6967,7 +7239,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -7013,6 +7284,12 @@ "license": "MIT", "peer": true }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -7098,6 +7375,23 @@ "node": ">=8" } }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7376,6 +7670,21 @@ "license": "MIT", "peer": true }, + "node_modules/hsl-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", + "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", + "license": "MIT", + "dependencies": { + "hsl-to-rgb-for-reals": "^1.1.0" + } + }, + "node_modules/hsl-to-rgb-for-reals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", + "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", + "license": "ISC" + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -7439,6 +7748,12 @@ "node": ">=16.17.0" } }, + "node_modules/hyphen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.14.1.tgz", + "integrity": "sha512-kvL8xYl5QMTh+LwohVN72ciOxC0OEV79IPdJSTwEXok9y9QHebXGdFgrED4sWfiax/ODx++CAMk3hMy4XPJPOw==", + "license": "ISC" + }, "node_modules/iceberg-js": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", @@ -7504,8 +7819,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ini": { "version": "4.1.1", @@ -7552,6 +7866,12 @@ "url": "https://github.com/sponsors/brc-dd" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -7706,6 +8026,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", @@ -7771,6 +8097,15 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jay-peg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", + "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", + "license": "MIT", + "dependencies": { + "restructure": "^3.0.0" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -7781,12 +8116,17 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", @@ -7927,6 +8267,25 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/listhen": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/listhen/-/listhen-1.9.0.tgz", @@ -8033,7 +8392,6 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -8138,6 +8496,12 @@ "license": "CC0-1.0", "peer": true }, + "node_modules/media-engine": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", + "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==", + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8618,6 +8982,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "license": "MIT", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -8768,7 +9141,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9020,6 +9392,18 @@ "license": "BlueOak-1.0.0", "peer": true }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -9172,8 +9556,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/pretty-bytes": { "version": "7.1.0", @@ -9210,7 +9593,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -9234,6 +9616,15 @@ "license": "MIT", "peer": true }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9328,8 +9719,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-katex": { "version": "3.1.0", @@ -9458,6 +9848,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -9489,6 +9888,12 @@ "node": ">=8" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9638,8 +10043,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sax": { "version": "1.5.0", @@ -9870,6 +10274,15 @@ "url": "https://github.com/steveukx/git-js?sponsor=1" } }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -10009,7 +10422,6 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -10161,6 +10573,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", + "license": "ISC" + }, "node_modules/svgo": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", @@ -10309,6 +10727,12 @@ "b4a": "^1.6.4" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -10518,6 +10942,32 @@ "license": "MIT", "peer": true }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/unicorn-magic": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", @@ -10890,8 +11340,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/vite": { "version": "7.3.1", @@ -10968,6 +11417,20 @@ } } }, + "node_modules/vite-compatible-readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", + "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/vite-dev-rpc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", @@ -11532,6 +11995,12 @@ "node": ">=12" } }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, "node_modules/youch": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0.tgz", diff --git a/package.json b/package.json index 52bf06b..3d82335 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "next start" }, "dependencies": { + "@react-pdf/renderer": "^4.4.0", "@supabase/supabase-js": "^2.99.1", "@vercel/analytics": "^2.0.0", "chart.js": "^4.5.1",