@portosaur/theme 0.1.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/README.md +13 -0
- package/assets/img/icon-old.png +0 -0
- package/assets/img/icon.png +0 -0
- package/assets/img/project-blank.png +0 -0
- package/assets/img/social-card.jpeg +0 -0
- package/assets/img/svg/icon-blog.svg +2 -0
- package/assets/img/svg/icon-close.svg +3 -0
- package/assets/img/svg/icon-dock.svg +4 -0
- package/assets/img/svg/icon-link.svg +5 -0
- package/assets/img/svg/icon-note.svg +2 -0
- package/assets/img/svg/icon-popup.svg +1 -0
- package/assets/img/svg/icon-save.svg +5 -0
- package/assets/img/svg/icon.svg +240 -0
- package/assets/img/svg/project-blank.svg +140 -0
- package/assets/sample-resume.pdf +0 -0
- package/package.json +41 -0
- package/plugins/README.md +8 -0
- package/src/index.d.ts +11 -0
- package/src/index.mjs +14 -0
- package/src/plugins/theme.mjs +13 -0
- package/theme/DocCategoryGeneratedIndexPage/index.jsx +15 -0
- package/theme/MDXComponents.jsx +19 -0
- package/theme/README.md +9 -0
- package/theme/Root.jsx +11 -0
- package/theme/components/AboutSection/index.jsx +264 -0
- package/theme/components/AboutSection/styles.module.css +309 -0
- package/theme/components/ContactSection/index.jsx +188 -0
- package/theme/components/ContactSection/styles.module.css +343 -0
- package/theme/components/ExperienceSection/index.jsx +119 -0
- package/theme/components/ExperienceSection/styles.module.css +183 -0
- package/theme/components/HeroSection/index.jsx +198 -0
- package/theme/components/HeroSection/styles.module.css +484 -0
- package/theme/components/NavArrow/index.jsx +124 -0
- package/theme/components/NavArrow/styles.module.css +107 -0
- package/theme/components/NoteIndex/index.jsx +182 -0
- package/theme/components/NoteIndex/styles.module.css +167 -0
- package/theme/components/Preview/components/FeedbackStates.jsx +200 -0
- package/theme/components/Preview/components/FileTabs.jsx +41 -0
- package/theme/components/Preview/components/PreviewContent.jsx +104 -0
- package/theme/components/Preview/components/PreviewHeader.jsx +411 -0
- package/theme/components/Preview/components/Triggers/Pv.jsx +253 -0
- package/theme/components/Preview/components/Triggers/SrcPv.jsx +55 -0
- package/theme/components/Preview/components/Triggers/index.jsx +2 -0
- package/theme/components/Preview/components/ViewerWindow.jsx +489 -0
- package/theme/components/Preview/hooks/useAdaptiveSizing.jsx +90 -0
- package/theme/components/Preview/hooks/useDeepLinkHash.jsx +24 -0
- package/theme/components/Preview/hooks/useDockLayout.jsx +86 -0
- package/theme/components/Preview/hooks/useFileFetch.jsx +38 -0
- package/theme/components/Preview/hooks/useTouchZoom.jsx +98 -0
- package/theme/components/Preview/index.jsx +3 -0
- package/theme/components/Preview/renderers/CodeRenderer.jsx +124 -0
- package/theme/components/Preview/renderers/ImageRenderer.jsx +74 -0
- package/theme/components/Preview/renderers/PdfRenderer.jsx +93 -0
- package/theme/components/Preview/renderers/WebRenderer.jsx +59 -0
- package/theme/components/Preview/state/index.jsx +177 -0
- package/theme/components/Preview/styles.module.css +776 -0
- package/theme/components/Preview/utils/index.jsx +62 -0
- package/theme/components/ProjectsSection/index.jsx +790 -0
- package/theme/components/ProjectsSection/styles.module.css +900 -0
- package/theme/components/SocialLinks/index.jsx +115 -0
- package/theme/components/SocialLinks/styles.module.css +57 -0
- package/theme/components/Tooltip/index.jsx +104 -0
- package/theme/components/Tooltip/styles.module.css +168 -0
- package/theme/config/iconMappings.jsx +427 -0
- package/theme/config/prism.jsx +72 -0
- package/theme/config/sidebar.jsx +11 -0
- package/theme/css/bootstrap.css +5 -0
- package/theme/css/catppuccin.css +618 -0
- package/theme/css/custom.css +253 -0
- package/theme/css/tasks.css +874 -0
- package/theme/hooks/useScrollReveal.jsx +20 -0
- package/theme/pages/index.jsx +104 -0
- package/theme/pages/notes.jsx +131 -0
- package/theme/pages/tasks.jsx +989 -0
- package/theme/utils/HashNavigation.jsx +185 -0
- package/theme/utils/updateTitle.jsx +65 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
export function useDockLayout({
|
|
3
|
+
isOpen,
|
|
4
|
+
isPopupMode,
|
|
5
|
+
isSidebarDock,
|
|
6
|
+
isPeekDock,
|
|
7
|
+
dockWidth,
|
|
8
|
+
peekHeight,
|
|
9
|
+
}) {
|
|
10
|
+
const weCollapsedSidebar = useRef(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (typeof document === "undefined") return;
|
|
13
|
+
if (isOpen && isPopupMode) {
|
|
14
|
+
document.body.style.overflow = "hidden";
|
|
15
|
+
} else {
|
|
16
|
+
document.body.style.overflow = "";
|
|
17
|
+
}
|
|
18
|
+
if (isSidebarDock) {
|
|
19
|
+
document.body.classList.add("pv-dock-active");
|
|
20
|
+
document.body.style.setProperty("--pv-dock-width", `${dockWidth}px`);
|
|
21
|
+
} else {
|
|
22
|
+
document.body.classList.remove("pv-dock-active");
|
|
23
|
+
document.body.style.setProperty("--pv-dock-width", "0px");
|
|
24
|
+
}
|
|
25
|
+
if (isPeekDock) {
|
|
26
|
+
document.body.classList.add("pv-peek-active");
|
|
27
|
+
document.body.style.setProperty(
|
|
28
|
+
"--mobile-peek-height",
|
|
29
|
+
`${peekHeight}px`,
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
document.body.classList.remove("pv-peek-active");
|
|
33
|
+
document.body.style.setProperty("--mobile-peek-height", "0px");
|
|
34
|
+
}
|
|
35
|
+
const sidebarToggleBtn = document.querySelector(
|
|
36
|
+
'[class*="collapseSidebarButton"]',
|
|
37
|
+
);
|
|
38
|
+
const isCollapsed = !!document.querySelector(
|
|
39
|
+
'[aria-label="Expand sidebar"]',
|
|
40
|
+
);
|
|
41
|
+
if (isSidebarDock) {
|
|
42
|
+
if (sidebarToggleBtn && !isCollapsed) {
|
|
43
|
+
weCollapsedSidebar.current = true;
|
|
44
|
+
sidebarToggleBtn.click();
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
if (weCollapsedSidebar.current && isCollapsed && sidebarToggleBtn) {
|
|
48
|
+
weCollapsedSidebar.current = false;
|
|
49
|
+
sidebarToggleBtn.click();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const updateNavOffset = () => {
|
|
53
|
+
const nav = document.querySelector(".navbar");
|
|
54
|
+
if (nav) {
|
|
55
|
+
document.documentElement.style.setProperty(
|
|
56
|
+
"--ifm-navbar-height",
|
|
57
|
+
`${nav.offsetHeight}px`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
if (isSidebarDock) {
|
|
62
|
+
updateNavOffset();
|
|
63
|
+
window.addEventListener("resize", updateNavOffset, { passive: true });
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
document.body.classList.remove("pv-dock-active");
|
|
67
|
+
document.body.classList.remove("pv-peek-active");
|
|
68
|
+
document.body.style.overflow = "";
|
|
69
|
+
document.body.style.removeProperty("--pv-dock-width");
|
|
70
|
+
document.body.style.removeProperty("--mobile-peek-height");
|
|
71
|
+
if (weCollapsedSidebar.current) {
|
|
72
|
+
const sidebarToggleBtn = document.querySelector(
|
|
73
|
+
'[class*="collapseSidebarButton"]',
|
|
74
|
+
);
|
|
75
|
+
const isCollapsed = !!document.querySelector(
|
|
76
|
+
'[aria-label="Expand sidebar"]',
|
|
77
|
+
);
|
|
78
|
+
if (sidebarToggleBtn && isCollapsed) {
|
|
79
|
+
sidebarToggleBtn.click();
|
|
80
|
+
}
|
|
81
|
+
weCollapsedSidebar.current = false;
|
|
82
|
+
}
|
|
83
|
+
window.removeEventListener("resize", updateNavOffset);
|
|
84
|
+
};
|
|
85
|
+
}, [isOpen, isPopupMode, isSidebarDock, isPeekDock, dockWidth, peekHeight]);
|
|
86
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { resolveUrl } from "../utils";
|
|
3
|
+
export function useFileFetch(path, fileType, isActive) {
|
|
4
|
+
const [cache, setCache] = useState({});
|
|
5
|
+
const [loading, setLoading] = useState(false);
|
|
6
|
+
const [errors, setErrors] = useState({});
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!isActive || !path || fileType !== "text") return;
|
|
9
|
+
if (cache[path] || errors[path]) return;
|
|
10
|
+
setLoading(true);
|
|
11
|
+
fetch(resolveUrl(path))
|
|
12
|
+
.then((r) => {
|
|
13
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
14
|
+
return r.text();
|
|
15
|
+
})
|
|
16
|
+
.then((text) => setCache((prev) => ({ ...prev, [path]: text })))
|
|
17
|
+
.catch((err) => setErrors((prev) => ({ ...prev, [path]: err.message })))
|
|
18
|
+
.finally(() => setLoading(false));
|
|
19
|
+
}, [isActive, path, fileType]);
|
|
20
|
+
const retry = useCallback(() => {
|
|
21
|
+
setErrors((prev) => {
|
|
22
|
+
const next = { ...prev };
|
|
23
|
+
delete next[path];
|
|
24
|
+
return next;
|
|
25
|
+
});
|
|
26
|
+
}, [path]);
|
|
27
|
+
const setError = useCallback((p, msg) => {
|
|
28
|
+
setErrors((prev) => ({ ...prev, [p]: msg }));
|
|
29
|
+
}, []);
|
|
30
|
+
return {
|
|
31
|
+
content: cache[path] || null,
|
|
32
|
+
loading,
|
|
33
|
+
error: errors[path] || null,
|
|
34
|
+
errors,
|
|
35
|
+
retry,
|
|
36
|
+
setError,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import styles from "../styles.module.css";
|
|
3
|
+
export function useTouchZoom({
|
|
4
|
+
containerRef,
|
|
5
|
+
isOpen,
|
|
6
|
+
zoomLevel,
|
|
7
|
+
setZoomLevel,
|
|
8
|
+
}) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const el = containerRef.current;
|
|
11
|
+
if (!el || !isOpen) return;
|
|
12
|
+
let initialDistance = null;
|
|
13
|
+
let initialZoom = zoomLevel;
|
|
14
|
+
const getDistance = (touches) => {
|
|
15
|
+
return Math.hypot(
|
|
16
|
+
touches[0].clientX - touches[1].clientX,
|
|
17
|
+
touches[0].clientY - touches[1].clientY,
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
const handleWheel = (e) => {
|
|
21
|
+
if (e.ctrlKey) {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
const delta = -e.deltaY * 0.01;
|
|
24
|
+
setZoomLevel((prev) => Math.min(Math.max(0.5, prev + delta), 3));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const handleTouchStart = (e) => {
|
|
28
|
+
if (e.touches.length === 2) {
|
|
29
|
+
initialDistance = getDistance(e.touches);
|
|
30
|
+
setZoomLevel((prev) => {
|
|
31
|
+
initialZoom = prev;
|
|
32
|
+
return prev;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const handleTouchMove = (e) => {
|
|
37
|
+
if (e.touches.length === 2 && initialDistance) {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
const currentDistance = getDistance(e.touches);
|
|
40
|
+
const ratio = currentDistance / initialDistance;
|
|
41
|
+
setZoomLevel(Math.min(Math.max(0.5, initialZoom * ratio), 3));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const handleTouchEnd = (e) => {
|
|
45
|
+
if (e.touches.length < 2) {
|
|
46
|
+
initialDistance = null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
let isPanning = false;
|
|
50
|
+
let startX = 0;
|
|
51
|
+
let startY = 0;
|
|
52
|
+
let initialScrollLeft = 0;
|
|
53
|
+
let initialScrollTop = 0;
|
|
54
|
+
const handleMouseDown = (e) => {
|
|
55
|
+
if (e.button !== 0) return;
|
|
56
|
+
isPanning = true;
|
|
57
|
+
startX = e.pageX;
|
|
58
|
+
startY = e.pageY;
|
|
59
|
+
initialScrollLeft = el.scrollLeft;
|
|
60
|
+
initialScrollTop = el.scrollTop;
|
|
61
|
+
el.classList.add(styles.isPanning);
|
|
62
|
+
document.body.classList.add(styles.isPanning);
|
|
63
|
+
};
|
|
64
|
+
const handleMouseMove = (e) => {
|
|
65
|
+
if (!isPanning) return;
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
const x = e.pageX;
|
|
68
|
+
const y = e.pageY;
|
|
69
|
+
const walkX = (x - startX) * 1;
|
|
70
|
+
const walkY = (y - startY) * 1;
|
|
71
|
+
el.scrollLeft = initialScrollLeft - walkX;
|
|
72
|
+
el.scrollTop = initialScrollTop - walkY;
|
|
73
|
+
};
|
|
74
|
+
const handleMouseUpOrLeave = () => {
|
|
75
|
+
isPanning = false;
|
|
76
|
+
el.classList.remove(styles.isPanning);
|
|
77
|
+
document.body.classList.remove(styles.isPanning);
|
|
78
|
+
};
|
|
79
|
+
el.addEventListener("wheel", handleWheel, { passive: false });
|
|
80
|
+
el.addEventListener("touchstart", handleTouchStart, { passive: false });
|
|
81
|
+
el.addEventListener("touchmove", handleTouchMove, { passive: false });
|
|
82
|
+
el.addEventListener("touchend", handleTouchEnd);
|
|
83
|
+
el.addEventListener("mousedown", handleMouseDown);
|
|
84
|
+
el.addEventListener("mousemove", handleMouseMove);
|
|
85
|
+
el.addEventListener("mouseup", handleMouseUpOrLeave);
|
|
86
|
+
el.addEventListener("mouseleave", handleMouseUpOrLeave);
|
|
87
|
+
return () => {
|
|
88
|
+
el.removeEventListener("wheel", handleWheel);
|
|
89
|
+
el.removeEventListener("touchstart", handleTouchStart);
|
|
90
|
+
el.removeEventListener("touchmove", handleTouchMove);
|
|
91
|
+
el.removeEventListener("touchend", handleTouchEnd);
|
|
92
|
+
el.removeEventListener("mousedown", handleMouseDown);
|
|
93
|
+
el.removeEventListener("mousemove", handleMouseMove);
|
|
94
|
+
el.removeEventListener("mouseup", handleMouseUpOrLeave);
|
|
95
|
+
el.removeEventListener("mouseleave", handleMouseUpOrLeave);
|
|
96
|
+
};
|
|
97
|
+
}, [isOpen, setZoomLevel, containerRef, zoomLevel]);
|
|
98
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
3
|
+
import { Highlight } from "prism-react-renderer";
|
|
4
|
+
export default function CodeRenderer({ code, language, zoomLevel = 1 }) {
|
|
5
|
+
const { siteConfig } = useDocusaurusContext();
|
|
6
|
+
const isDark =
|
|
7
|
+
typeof document !== "undefined" &&
|
|
8
|
+
document.documentElement.getAttribute("data-theme") === "dark";
|
|
9
|
+
const prismConfig = siteConfig?.themeConfig?.prism || {};
|
|
10
|
+
const prismTheme = isDark ? prismConfig.darkTheme : prismConfig.theme;
|
|
11
|
+
const normalizedLanguage = language === "patch" ? "diff" : language || "text";
|
|
12
|
+
return jsxDEV_7x81h0kn(
|
|
13
|
+
Highlight,
|
|
14
|
+
{
|
|
15
|
+
code,
|
|
16
|
+
language: normalizedLanguage,
|
|
17
|
+
theme: prismTheme,
|
|
18
|
+
...(typeof window !== "undefined" && window.Prism
|
|
19
|
+
? { prism: window.Prism }
|
|
20
|
+
: {}),
|
|
21
|
+
children: ({ className, style, tokens, getLineProps, getTokenProps }) =>
|
|
22
|
+
jsxDEV_7x81h0kn(
|
|
23
|
+
"pre",
|
|
24
|
+
{
|
|
25
|
+
className,
|
|
26
|
+
style: {
|
|
27
|
+
...style,
|
|
28
|
+
margin: 0,
|
|
29
|
+
borderRadius: 0,
|
|
30
|
+
padding: "14px 0",
|
|
31
|
+
fontSize: `calc(0.85rem * ${zoomLevel})`,
|
|
32
|
+
lineHeight: 1.6,
|
|
33
|
+
minHeight: "100%",
|
|
34
|
+
background: "var(--ifm-background-color)",
|
|
35
|
+
},
|
|
36
|
+
children: tokens.map((line, i) => {
|
|
37
|
+
const lineProps = getLineProps({ line });
|
|
38
|
+
const lineContent = line.map((t) => t.content).join("");
|
|
39
|
+
let diffStyle = {};
|
|
40
|
+
if (normalizedLanguage === "diff") {
|
|
41
|
+
if (lineContent.startsWith("+")) {
|
|
42
|
+
diffStyle = {
|
|
43
|
+
background: "rgba(var(--ifm-color-success-rgb), 0.15)",
|
|
44
|
+
borderLeft: "3px solid var(--ifm-color-success)",
|
|
45
|
+
};
|
|
46
|
+
} else if (lineContent.startsWith("-")) {
|
|
47
|
+
diffStyle = {
|
|
48
|
+
background: "rgba(var(--ifm-color-danger-rgb), 0.15)",
|
|
49
|
+
borderLeft: "3px solid var(--ifm-color-danger)",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return jsxDEV_7x81h0kn(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
...lineProps,
|
|
57
|
+
style: {
|
|
58
|
+
...lineProps.style,
|
|
59
|
+
...diffStyle,
|
|
60
|
+
display: "flex",
|
|
61
|
+
paddingLeft: diffStyle.borderLeft ? "0px" : "3px",
|
|
62
|
+
},
|
|
63
|
+
children: [
|
|
64
|
+
jsxDEV_7x81h0kn(
|
|
65
|
+
"span",
|
|
66
|
+
{
|
|
67
|
+
style: {
|
|
68
|
+
display: "inline-block",
|
|
69
|
+
width: "1.7em",
|
|
70
|
+
textAlign: "right",
|
|
71
|
+
marginRight: "12px",
|
|
72
|
+
userSelect: "none",
|
|
73
|
+
opacity: 0.35,
|
|
74
|
+
flexShrink: 0,
|
|
75
|
+
fontFamily: "var(--ifm-font-family-monospace)",
|
|
76
|
+
},
|
|
77
|
+
children: i + 1,
|
|
78
|
+
},
|
|
79
|
+
undefined,
|
|
80
|
+
false,
|
|
81
|
+
undefined,
|
|
82
|
+
this,
|
|
83
|
+
),
|
|
84
|
+
jsxDEV_7x81h0kn(
|
|
85
|
+
"span",
|
|
86
|
+
{
|
|
87
|
+
style: { paddingRight: "14px", flex: 1 },
|
|
88
|
+
children: line.map((token, key) =>
|
|
89
|
+
jsxDEV_7x81h0kn(
|
|
90
|
+
"span",
|
|
91
|
+
{ ...getTokenProps({ token }) },
|
|
92
|
+
key,
|
|
93
|
+
false,
|
|
94
|
+
undefined,
|
|
95
|
+
this,
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
undefined,
|
|
100
|
+
false,
|
|
101
|
+
undefined,
|
|
102
|
+
this,
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
i,
|
|
107
|
+
true,
|
|
108
|
+
undefined,
|
|
109
|
+
this,
|
|
110
|
+
);
|
|
111
|
+
}),
|
|
112
|
+
},
|
|
113
|
+
undefined,
|
|
114
|
+
false,
|
|
115
|
+
undefined,
|
|
116
|
+
this,
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
undefined,
|
|
120
|
+
false,
|
|
121
|
+
undefined,
|
|
122
|
+
this,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { LoadingState } from "../components/FeedbackStates";
|
|
3
|
+
import styles from "../styles.module.css";
|
|
4
|
+
export default function ImageRenderer({ fileUrl, label, zoomLevel, onError }) {
|
|
5
|
+
const [loading, setLoading] = useState(true);
|
|
6
|
+
return jsxDEV_7x81h0kn(
|
|
7
|
+
"div",
|
|
8
|
+
{
|
|
9
|
+
className: styles.imageView,
|
|
10
|
+
style: { "--zoom": zoomLevel, position: "relative" },
|
|
11
|
+
children: [
|
|
12
|
+
loading &&
|
|
13
|
+
jsxDEV_7x81h0kn(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
style: {
|
|
17
|
+
position: "absolute",
|
|
18
|
+
inset: 0,
|
|
19
|
+
zIndex: 5,
|
|
20
|
+
display: "flex",
|
|
21
|
+
},
|
|
22
|
+
children: jsxDEV_7x81h0kn(
|
|
23
|
+
LoadingState,
|
|
24
|
+
{},
|
|
25
|
+
undefined,
|
|
26
|
+
false,
|
|
27
|
+
undefined,
|
|
28
|
+
this,
|
|
29
|
+
),
|
|
30
|
+
},
|
|
31
|
+
undefined,
|
|
32
|
+
false,
|
|
33
|
+
undefined,
|
|
34
|
+
this,
|
|
35
|
+
),
|
|
36
|
+
jsxDEV_7x81h0kn(
|
|
37
|
+
"div",
|
|
38
|
+
{
|
|
39
|
+
className: styles.imageScrollArea,
|
|
40
|
+
style: {
|
|
41
|
+
opacity: loading ? 0 : 1,
|
|
42
|
+
transition: "opacity 0.3s ease",
|
|
43
|
+
},
|
|
44
|
+
children: jsxDEV_7x81h0kn(
|
|
45
|
+
"img",
|
|
46
|
+
{
|
|
47
|
+
src: fileUrl,
|
|
48
|
+
alt: label || "",
|
|
49
|
+
className: styles.image,
|
|
50
|
+
onLoad: () => setLoading(false),
|
|
51
|
+
onError: () => {
|
|
52
|
+
setLoading(false);
|
|
53
|
+
onError?.("Failed to load image.");
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
undefined,
|
|
57
|
+
false,
|
|
58
|
+
undefined,
|
|
59
|
+
this,
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
undefined,
|
|
63
|
+
false,
|
|
64
|
+
undefined,
|
|
65
|
+
this,
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
undefined,
|
|
70
|
+
true,
|
|
71
|
+
undefined,
|
|
72
|
+
this,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { LoadingState } from "../components/FeedbackStates";
|
|
3
|
+
import styles from "../styles.module.css";
|
|
4
|
+
import "react-pdf/dist/Page/AnnotationLayer.css";
|
|
5
|
+
import "react-pdf/dist/Page/TextLayer.css";
|
|
6
|
+
let PdfDocument, PdfPage, pdfjs;
|
|
7
|
+
export default function PdfRenderer({ fileUrl, zoomLevel, onError }) {
|
|
8
|
+
const [pdfReady, setPdfReady] = useState(!!PdfDocument);
|
|
9
|
+
const [numPages, setNumPages] = useState(null);
|
|
10
|
+
const [containerWidth, setContainerWidth] = useState(760);
|
|
11
|
+
const wrapperRef = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (PdfDocument) return;
|
|
14
|
+
import("react-pdf").then((mod) => {
|
|
15
|
+
PdfDocument = mod.Document;
|
|
16
|
+
PdfPage = mod.Page;
|
|
17
|
+
pdfjs = mod.pdfjs;
|
|
18
|
+
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
19
|
+
setPdfReady(true);
|
|
20
|
+
});
|
|
21
|
+
}, []);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!wrapperRef.current) return;
|
|
24
|
+
let timeoutId = null;
|
|
25
|
+
const observer = new ResizeObserver((entries) => {
|
|
26
|
+
const { width } = entries[0].contentRect;
|
|
27
|
+
if (width <= 0) return;
|
|
28
|
+
const diff = Math.abs(width - containerWidth);
|
|
29
|
+
if (diff < 15) return;
|
|
30
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
31
|
+
timeoutId = setTimeout(() => {
|
|
32
|
+
setContainerWidth(width);
|
|
33
|
+
}, 150);
|
|
34
|
+
});
|
|
35
|
+
observer.observe(wrapperRef.current);
|
|
36
|
+
return () => {
|
|
37
|
+
observer.disconnect();
|
|
38
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
39
|
+
};
|
|
40
|
+
}, [pdfReady, containerWidth]);
|
|
41
|
+
if (!pdfReady || !PdfDocument) {
|
|
42
|
+
return jsxDEV_7x81h0kn(LoadingState, {}, undefined, false, undefined, this);
|
|
43
|
+
}
|
|
44
|
+
return jsxDEV_7x81h0kn(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
className: styles.pdfView,
|
|
48
|
+
ref: wrapperRef,
|
|
49
|
+
children: jsxDEV_7x81h0kn(
|
|
50
|
+
PdfDocument,
|
|
51
|
+
{
|
|
52
|
+
file: fileUrl,
|
|
53
|
+
onLoadSuccess: ({ numPages: n }) => setNumPages(n),
|
|
54
|
+
onLoadError: (err) =>
|
|
55
|
+
onError?.(err.message || "Invalid or missing PDF file."),
|
|
56
|
+
loading: jsxDEV_7x81h0kn(
|
|
57
|
+
LoadingState,
|
|
58
|
+
{},
|
|
59
|
+
undefined,
|
|
60
|
+
false,
|
|
61
|
+
undefined,
|
|
62
|
+
this,
|
|
63
|
+
),
|
|
64
|
+
children: Array.from({ length: numPages || 0 }, (_, i) =>
|
|
65
|
+
jsxDEV_7x81h0kn(
|
|
66
|
+
PdfPage,
|
|
67
|
+
{
|
|
68
|
+
pageNumber: i + 1,
|
|
69
|
+
width: containerWidth,
|
|
70
|
+
scale: zoomLevel,
|
|
71
|
+
className: styles.pdfPage,
|
|
72
|
+
renderTextLayer: true,
|
|
73
|
+
renderAnnotationLayer: true,
|
|
74
|
+
},
|
|
75
|
+
i,
|
|
76
|
+
false,
|
|
77
|
+
undefined,
|
|
78
|
+
this,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
undefined,
|
|
83
|
+
false,
|
|
84
|
+
undefined,
|
|
85
|
+
this,
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
undefined,
|
|
89
|
+
false,
|
|
90
|
+
undefined,
|
|
91
|
+
this,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { LoadingState } from "../components/FeedbackStates";
|
|
3
|
+
import styles from "../styles.module.css";
|
|
4
|
+
export default function WebRenderer({ fileUrl, label, onError }) {
|
|
5
|
+
const [loaded, setLoaded] = useState(false);
|
|
6
|
+
const loadedRef = useRef(false);
|
|
7
|
+
const timeoutRef = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
setLoaded(false);
|
|
10
|
+
loadedRef.current = false;
|
|
11
|
+
clearTimeout(timeoutRef.current);
|
|
12
|
+
timeoutRef.current = setTimeout(() => {
|
|
13
|
+
if (!loadedRef.current) {
|
|
14
|
+
onError?.(
|
|
15
|
+
"The page took too long to respond. It may block embedding or you may be offline.",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}, 1e4);
|
|
19
|
+
return () => clearTimeout(timeoutRef.current);
|
|
20
|
+
}, [fileUrl]);
|
|
21
|
+
const handleLoad = () => {
|
|
22
|
+
setLoaded(true);
|
|
23
|
+
loadedRef.current = true;
|
|
24
|
+
clearTimeout(timeoutRef.current);
|
|
25
|
+
};
|
|
26
|
+
return jsxDEV_7x81h0kn(
|
|
27
|
+
"div",
|
|
28
|
+
{
|
|
29
|
+
className: styles.webView,
|
|
30
|
+
children: [
|
|
31
|
+
!loaded &&
|
|
32
|
+
jsxDEV_7x81h0kn(LoadingState, {}, undefined, false, undefined, this),
|
|
33
|
+
jsxDEV_7x81h0kn(
|
|
34
|
+
"iframe",
|
|
35
|
+
{
|
|
36
|
+
src: fileUrl,
|
|
37
|
+
title: label,
|
|
38
|
+
className: styles.webFrame,
|
|
39
|
+
onLoad: handleLoad,
|
|
40
|
+
style: { opacity: loaded ? 1 : 0, transition: "opacity 0.3s ease" },
|
|
41
|
+
allow:
|
|
42
|
+
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
|
|
43
|
+
allowFullScreen: true,
|
|
44
|
+
sandbox:
|
|
45
|
+
"allow-scripts allow-same-origin allow-popups allow-forms allow-presentation",
|
|
46
|
+
},
|
|
47
|
+
undefined,
|
|
48
|
+
false,
|
|
49
|
+
undefined,
|
|
50
|
+
this,
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
undefined,
|
|
55
|
+
true,
|
|
56
|
+
undefined,
|
|
57
|
+
this,
|
|
58
|
+
);
|
|
59
|
+
}
|