@portosaur/theme 0.1.5 → 0.3.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/package.json +12 -3
- package/src/plugins/theme.mjs +2 -0
- package/theme/DocCategoryGeneratedIndexPage/index.jsx +4 -10
- package/theme/MDXComponents.jsx +3 -2
- package/theme/Root.jsx +1 -1
- package/theme/components/AboutSection/index.jsx +89 -249
- package/theme/components/ContactSection/index.jsx +72 -153
- package/theme/components/ExperienceSection/index.jsx +35 -106
- package/theme/components/HeroSection/index.jsx +64 -186
- package/theme/components/Indent/index.jsx +10 -0
- package/theme/components/NavArrow/index.jsx +38 -55
- package/theme/components/NoteCards/index.jsx +110 -0
- package/theme/components/Preview/components/FeedbackStates.jsx +45 -190
- package/theme/components/Preview/components/FileTabs.jsx +17 -24
- package/theme/components/Preview/components/PreviewContent.jsx +37 -62
- package/theme/components/Preview/components/PreviewHeader.jsx +146 -380
- package/theme/components/Preview/components/Triggers/Pv.jsx +50 -78
- package/theme/components/Preview/components/Triggers/SrcPv.jsx +16 -47
- package/theme/components/Preview/components/Triggers/index.jsx +2 -2
- package/theme/components/Preview/components/ViewerWindow.jsx +160 -268
- package/theme/components/Preview/index.jsx +3 -3
- package/theme/components/Preview/renderers/CodeRenderer.jsx +81 -109
- package/theme/components/Preview/renderers/ImageRenderer.jsx +30 -67
- package/theme/components/Preview/renderers/PdfRenderer.jsx +31 -52
- package/theme/components/Preview/renderers/WebRenderer.jsx +18 -32
- package/theme/components/Preview/state/index.jsx +46 -30
- package/theme/components/ProjectsSection/index.jsx +278 -573
- package/theme/components/ScrollToTop/index.jsx +93 -0
- package/theme/components/ScrollToTop/styles.module.css +97 -0
- package/theme/components/SocialLinks/index.jsx +43 -55
- package/theme/components/Tooltip/index.jsx +85 -43
- package/theme/components/Tooltip/styles.module.css +13 -3
- package/theme/components/TopicList/index.jsx +31 -0
- package/theme/css/animations.css +61 -0
- package/theme/css/custom.css +16 -253
- package/theme/css/overrides/base.css +37 -0
- package/theme/css/overrides/footer.css +14 -0
- package/theme/css/overrides/markdown.css +62 -0
- package/theme/css/overrides/navbar.css +37 -0
- package/theme/css/overrides/pagination.css +64 -0
- package/theme/css/overrides/search.css +28 -0
- package/theme/css/{catppuccin.css → overrides/variables.css} +1 -1
- package/theme/pages/index.jsx +25 -87
- package/theme/pages/notes.jsx +27 -104
- package/theme/pages/tasks.jsx +293 -904
- package/theme/components/NoteIndex/index.jsx +0 -182
- package/theme/css/bootstrap.css +0 -5
- /package/theme/components/{NoteIndex → NoteCards}/styles.module.css +0 -0
- /package/theme/css/{tasks.css → tasks.module.css} +0 -0
|
@@ -2,11 +2,13 @@ import React, { useState, useEffect, useRef } from "react";
|
|
|
2
2
|
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
|
|
3
3
|
import Tooltip from "../Tooltip";
|
|
4
4
|
import styles from "./styles.module.css";
|
|
5
|
+
|
|
5
6
|
export default function NavArrow() {
|
|
6
7
|
const [direction, setDirection] = useState("down");
|
|
7
8
|
const [isVisible, setIsVisible] = useState(false);
|
|
8
9
|
const [isScrolling, setIsScrolling] = useState(false);
|
|
9
10
|
const scrollTimeoutRef = useRef(null);
|
|
11
|
+
|
|
10
12
|
const getSections = () => {
|
|
11
13
|
const main = document.querySelector("main");
|
|
12
14
|
if (!main) return [];
|
|
@@ -14,29 +16,36 @@ export default function NavArrow() {
|
|
|
14
16
|
.map((el) => el.id)
|
|
15
17
|
.filter((id) => id !== "nav-arrow");
|
|
16
18
|
};
|
|
19
|
+
|
|
17
20
|
useEffect(() => {
|
|
18
21
|
const handleScroll = () => {
|
|
19
22
|
const scrollTop =
|
|
20
23
|
window.pageYOffset || document.documentElement.scrollTop;
|
|
21
24
|
const windowHeight = window.innerHeight;
|
|
22
25
|
const fullHeight = document.documentElement.scrollHeight;
|
|
26
|
+
|
|
23
27
|
setIsVisible(scrollTop > 100);
|
|
28
|
+
|
|
24
29
|
if (scrollTop + windowHeight >= fullHeight - 50) {
|
|
25
30
|
setDirection("up");
|
|
26
31
|
} else {
|
|
27
32
|
setDirection("down");
|
|
28
33
|
}
|
|
34
|
+
|
|
29
35
|
setIsScrolling(true);
|
|
30
36
|
if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
|
|
31
37
|
scrollTimeoutRef.current = setTimeout(() => setIsScrolling(false), 800);
|
|
32
38
|
};
|
|
39
|
+
|
|
33
40
|
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
34
41
|
handleScroll();
|
|
42
|
+
|
|
35
43
|
return () => {
|
|
36
44
|
window.removeEventListener("scroll", handleScroll);
|
|
37
45
|
if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
|
|
38
46
|
};
|
|
39
47
|
}, []);
|
|
48
|
+
|
|
40
49
|
const handleClick = () => {
|
|
41
50
|
if (direction === "up") {
|
|
42
51
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
@@ -45,6 +54,7 @@ export default function NavArrow() {
|
|
|
45
54
|
const windowTop =
|
|
46
55
|
window.pageYOffset || document.documentElement.scrollTop;
|
|
47
56
|
let nextSectionId = null;
|
|
57
|
+
|
|
48
58
|
for (const id of sections) {
|
|
49
59
|
const element = document.getElementById(id);
|
|
50
60
|
if (element) {
|
|
@@ -56,9 +66,11 @@ export default function NavArrow() {
|
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
68
|
}
|
|
69
|
+
|
|
59
70
|
if (nextSectionId) {
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
document
|
|
72
|
+
.getElementById(nextSectionId)
|
|
73
|
+
.scrollIntoView({ behavior: "smooth" });
|
|
62
74
|
} else {
|
|
63
75
|
window.scrollTo({
|
|
64
76
|
top: document.documentElement.scrollHeight,
|
|
@@ -67,58 +79,29 @@ export default function NavArrow() {
|
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
className
|
|
74
|
-
onClick
|
|
75
|
-
|
|
76
|
-
direction === "down" ? "Scroll to next section" : "Scroll to top"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
false,
|
|
95
|
-
undefined,
|
|
96
|
-
this,
|
|
97
|
-
)
|
|
98
|
-
: jsxDEV_7x81h0kn(
|
|
99
|
-
FaChevronUp,
|
|
100
|
-
{ className: styles.chevron },
|
|
101
|
-
undefined,
|
|
102
|
-
false,
|
|
103
|
-
undefined,
|
|
104
|
-
this,
|
|
105
|
-
),
|
|
106
|
-
},
|
|
107
|
-
undefined,
|
|
108
|
-
false,
|
|
109
|
-
undefined,
|
|
110
|
-
this,
|
|
111
|
-
),
|
|
112
|
-
},
|
|
113
|
-
undefined,
|
|
114
|
-
false,
|
|
115
|
-
undefined,
|
|
116
|
-
this,
|
|
117
|
-
),
|
|
118
|
-
},
|
|
119
|
-
undefined,
|
|
120
|
-
false,
|
|
121
|
-
undefined,
|
|
122
|
-
this,
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<button
|
|
85
|
+
className={`${styles.navArrow} ${isVisible ? styles.visible : ""} ${isScrolling ? styles.scrolling : ""}`}
|
|
86
|
+
onClick={handleClick}
|
|
87
|
+
aria-label={
|
|
88
|
+
direction === "down" ? "Scroll to next section" : "Scroll to top"
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
<Tooltip
|
|
92
|
+
msg={direction === "down" ? "Next Section" : "Back to Top"}
|
|
93
|
+
position="top"
|
|
94
|
+
gap={25}
|
|
95
|
+
underline={false}
|
|
96
|
+
>
|
|
97
|
+
<div className={`${styles.iconWrapper} ${styles[direction]}`}>
|
|
98
|
+
{direction === "down" ? (
|
|
99
|
+
<FaChevronDown className={styles.chevron} />
|
|
100
|
+
) : (
|
|
101
|
+
<FaChevronUp className={styles.chevron} />
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</Tooltip>
|
|
105
|
+
</button>
|
|
123
106
|
);
|
|
124
107
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import useBaseUrl from "@docusaurus/useBaseUrl";
|
|
2
|
+
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
3
|
+
import Link from "@docusaurus/Link";
|
|
4
|
+
import { FaBook, FaChevronRight } from "react-icons/fa";
|
|
5
|
+
import Tooltip from "../Tooltip/index.jsx";
|
|
6
|
+
import { iconMap } from "../../config/iconMappings.jsx";
|
|
7
|
+
import styles from "./styles.module.css";
|
|
8
|
+
|
|
9
|
+
function useNotes() {
|
|
10
|
+
const context = require.context(`@site/notes`, true, /index\.mdx?$|\.mdx?$/);
|
|
11
|
+
|
|
12
|
+
return context
|
|
13
|
+
.keys()
|
|
14
|
+
.filter((path) => {
|
|
15
|
+
if (path === "./index.md" || path === "./index.mdx") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const pathParts = path.split("/");
|
|
19
|
+
const isTopLevelFile =
|
|
20
|
+
pathParts.length === 2 && !path.match(/index\.mdx?$/);
|
|
21
|
+
const isTopLevelDir =
|
|
22
|
+
pathParts.length === 3 && path.match(/index\.mdx?$/);
|
|
23
|
+
return isTopLevelFile || isTopLevelDir;
|
|
24
|
+
})
|
|
25
|
+
.map((path) => {
|
|
26
|
+
const { frontMatter } = context(path);
|
|
27
|
+
const pathParts = path.split("/");
|
|
28
|
+
const isTopLevelFile = pathParts.length === 2;
|
|
29
|
+
const fileSlug = isTopLevelFile
|
|
30
|
+
? path.replace("./", "").replace(/\.mdx?$/, "")
|
|
31
|
+
: pathParts[1];
|
|
32
|
+
const slug = frontMatter.slug || frontMatter.id || fileSlug;
|
|
33
|
+
const rawTitle = frontMatter.title || frontMatter.language || fileSlug;
|
|
34
|
+
const title = rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1);
|
|
35
|
+
const language = frontMatter.language
|
|
36
|
+
? frontMatter.language
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/ /g, "")
|
|
39
|
+
.replace(/[\s-]/g, "")
|
|
40
|
+
: slug.toLowerCase() || title.toLowerCase();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
title,
|
|
44
|
+
language,
|
|
45
|
+
slug,
|
|
46
|
+
desc: frontMatter.desc || "",
|
|
47
|
+
position: frontMatter.sidebar_position || 999,
|
|
48
|
+
};
|
|
49
|
+
})
|
|
50
|
+
.sort((a, b) => a.position - b.position);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function NoteCard({ title, language, slug, desc, index, docsBasePath }) {
|
|
54
|
+
const noteUrl = useBaseUrl(`${docsBasePath}/${slug}`);
|
|
55
|
+
const { icon: Icon = FaBook, color = "var(--ifm-color-primary)" } =
|
|
56
|
+
iconMap[language] || iconMap[title.toLowerCase()] || {};
|
|
57
|
+
const tooltipContent = desc ? desc : null;
|
|
58
|
+
|
|
59
|
+
const cardInner = (
|
|
60
|
+
<Link
|
|
61
|
+
to={noteUrl}
|
|
62
|
+
className={styles.noteCard}
|
|
63
|
+
style={{ "--card-index": index, "--note-color": color }}
|
|
64
|
+
aria-label={`Read note: ${title}`}
|
|
65
|
+
>
|
|
66
|
+
<div className={styles.iconWrapper}>
|
|
67
|
+
<Icon className={styles.noteIcon} />
|
|
68
|
+
</div>
|
|
69
|
+
<div className={styles.cardContent}>
|
|
70
|
+
<h3 className={styles.noteTitle}>{title}</h3>
|
|
71
|
+
</div>
|
|
72
|
+
<FaChevronRight className={styles.mobileChevron} />
|
|
73
|
+
</Link>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return tooltipContent ? (
|
|
77
|
+
<Tooltip msg={tooltipContent} position="top" underline={false} gap={-8}>
|
|
78
|
+
{cardInner}
|
|
79
|
+
</Tooltip>
|
|
80
|
+
) : (
|
|
81
|
+
cardInner
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default function NoteCards() {
|
|
86
|
+
const notes = useNotes();
|
|
87
|
+
const { path: docsBasePath } = usePluginData(
|
|
88
|
+
"docusaurus-plugin-content-docs",
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!notes.length) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className={styles.notesGrid} role="list">
|
|
97
|
+
{notes.map((note, index) => (
|
|
98
|
+
<NoteCard
|
|
99
|
+
key={note.slug}
|
|
100
|
+
title={note.title}
|
|
101
|
+
language={note.language}
|
|
102
|
+
slug={note.slug}
|
|
103
|
+
desc={note.desc}
|
|
104
|
+
index={index}
|
|
105
|
+
docsBasePath={docsBasePath}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -1,200 +1,55 @@
|
|
|
1
1
|
import styles from "../styles.module.css";
|
|
2
|
+
|
|
2
3
|
export function LoadingState() {
|
|
3
|
-
return
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"div",
|
|
14
|
-
{ className: styles.spinner },
|
|
15
|
-
undefined,
|
|
16
|
-
false,
|
|
17
|
-
undefined,
|
|
18
|
-
this,
|
|
19
|
-
),
|
|
20
|
-
},
|
|
21
|
-
undefined,
|
|
22
|
-
false,
|
|
23
|
-
undefined,
|
|
24
|
-
this,
|
|
25
|
-
),
|
|
26
|
-
jsxDEV_7x81h0kn(
|
|
27
|
-
"div",
|
|
28
|
-
{
|
|
29
|
-
className: styles.loadingText,
|
|
30
|
-
children: [
|
|
31
|
-
jsxDEV_7x81h0kn(
|
|
32
|
-
"p",
|
|
33
|
-
{ children: "Preparing preview..." },
|
|
34
|
-
undefined,
|
|
35
|
-
false,
|
|
36
|
-
undefined,
|
|
37
|
-
this,
|
|
38
|
-
),
|
|
39
|
-
jsxDEV_7x81h0kn(
|
|
40
|
-
"span",
|
|
41
|
-
{ children: "Fetching content from source" },
|
|
42
|
-
undefined,
|
|
43
|
-
false,
|
|
44
|
-
undefined,
|
|
45
|
-
this,
|
|
46
|
-
),
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
undefined,
|
|
50
|
-
true,
|
|
51
|
-
undefined,
|
|
52
|
-
this,
|
|
53
|
-
),
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
undefined,
|
|
57
|
-
true,
|
|
58
|
-
undefined,
|
|
59
|
-
this,
|
|
4
|
+
return (
|
|
5
|
+
<div className={styles.loading}>
|
|
6
|
+
<div className={styles.loadingIcon}>
|
|
7
|
+
<div className={styles.spinner} />
|
|
8
|
+
</div>
|
|
9
|
+
<div className={styles.loadingText}>
|
|
10
|
+
<p>Preparing preview...</p>
|
|
11
|
+
<span>Fetching content from source</span>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
60
14
|
);
|
|
61
15
|
}
|
|
16
|
+
|
|
62
17
|
export function ErrorState({ path, message, fileType, fileUrl, onRetry }) {
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
undefined,
|
|
87
|
-
this,
|
|
88
|
-
),
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
undefined,
|
|
92
|
-
true,
|
|
93
|
-
undefined,
|
|
94
|
-
this,
|
|
95
|
-
),
|
|
96
|
-
jsxDEV_7x81h0kn(
|
|
97
|
-
"p",
|
|
98
|
-
{ className: styles.errorMsg, children: message },
|
|
99
|
-
undefined,
|
|
100
|
-
false,
|
|
101
|
-
undefined,
|
|
102
|
-
this,
|
|
103
|
-
),
|
|
104
|
-
jsxDEV_7x81h0kn(
|
|
105
|
-
"div",
|
|
106
|
-
{
|
|
107
|
-
className: styles.errorActions,
|
|
108
|
-
children: [
|
|
109
|
-
jsxDEV_7x81h0kn(
|
|
110
|
-
"button",
|
|
111
|
-
{
|
|
112
|
-
onClick: onRetry,
|
|
113
|
-
className: styles.retryButton,
|
|
114
|
-
children: "Retry",
|
|
115
|
-
},
|
|
116
|
-
undefined,
|
|
117
|
-
false,
|
|
118
|
-
undefined,
|
|
119
|
-
this,
|
|
120
|
-
),
|
|
121
|
-
fileType === "web" &&
|
|
122
|
-
jsxDEV_7x81h0kn(
|
|
123
|
-
"a",
|
|
124
|
-
{
|
|
125
|
-
href: fileUrl,
|
|
126
|
-
target: "_blank",
|
|
127
|
-
rel: "noopener",
|
|
128
|
-
className: styles.visitButton,
|
|
129
|
-
children: "Visit Website",
|
|
130
|
-
},
|
|
131
|
-
undefined,
|
|
132
|
-
false,
|
|
133
|
-
undefined,
|
|
134
|
-
this,
|
|
135
|
-
),
|
|
136
|
-
],
|
|
137
|
-
},
|
|
138
|
-
undefined,
|
|
139
|
-
true,
|
|
140
|
-
undefined,
|
|
141
|
-
this,
|
|
142
|
-
),
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
undefined,
|
|
146
|
-
true,
|
|
147
|
-
undefined,
|
|
148
|
-
this,
|
|
18
|
+
return (
|
|
19
|
+
<div className={styles.errorState}>
|
|
20
|
+
<div className={styles.errorIcon}>⚠️</div>
|
|
21
|
+
<p>
|
|
22
|
+
Could not load: <code>{path?.split("/").pop()}</code>
|
|
23
|
+
</p>
|
|
24
|
+
<p className={styles.errorMsg}>{message}</p>
|
|
25
|
+
<div className={styles.errorActions}>
|
|
26
|
+
<button onClick={onRetry} className={styles.retryButton}>
|
|
27
|
+
Retry
|
|
28
|
+
</button>
|
|
29
|
+
{fileType === "web" && (
|
|
30
|
+
<a
|
|
31
|
+
href={fileUrl}
|
|
32
|
+
target="_blank"
|
|
33
|
+
rel="noopener"
|
|
34
|
+
className={styles.visitButton}
|
|
35
|
+
>
|
|
36
|
+
Visit Website
|
|
37
|
+
</a>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
149
41
|
);
|
|
150
42
|
}
|
|
43
|
+
|
|
151
44
|
export function OfflineState({ onRetry }) {
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
false,
|
|
162
|
-
undefined,
|
|
163
|
-
this,
|
|
164
|
-
),
|
|
165
|
-
jsxDEV_7x81h0kn(
|
|
166
|
-
"h3",
|
|
167
|
-
{ children: "No Connection" },
|
|
168
|
-
undefined,
|
|
169
|
-
false,
|
|
170
|
-
undefined,
|
|
171
|
-
this,
|
|
172
|
-
),
|
|
173
|
-
jsxDEV_7x81h0kn(
|
|
174
|
-
"p",
|
|
175
|
-
{ children: "This resource requires an active internet connection." },
|
|
176
|
-
undefined,
|
|
177
|
-
false,
|
|
178
|
-
undefined,
|
|
179
|
-
this,
|
|
180
|
-
),
|
|
181
|
-
jsxDEV_7x81h0kn(
|
|
182
|
-
"button",
|
|
183
|
-
{
|
|
184
|
-
onClick: onRetry,
|
|
185
|
-
className: styles.retryButton,
|
|
186
|
-
children: "Try Again",
|
|
187
|
-
},
|
|
188
|
-
undefined,
|
|
189
|
-
false,
|
|
190
|
-
undefined,
|
|
191
|
-
this,
|
|
192
|
-
),
|
|
193
|
-
],
|
|
194
|
-
},
|
|
195
|
-
undefined,
|
|
196
|
-
true,
|
|
197
|
-
undefined,
|
|
198
|
-
this,
|
|
45
|
+
return (
|
|
46
|
+
<div className={styles.errorState}>
|
|
47
|
+
<div className={styles.errorIcon}>📡</div>
|
|
48
|
+
<h3>No Connection</h3>
|
|
49
|
+
<p>This resource requires an active internet connection.</p>
|
|
50
|
+
<button onClick={onRetry} className={styles.retryButton}>
|
|
51
|
+
Try Again
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
199
54
|
);
|
|
200
55
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { useRef, useEffect } from "react";
|
|
2
2
|
import styles from "../styles.module.css";
|
|
3
|
+
|
|
3
4
|
export default function FileTabs({ sources, activeIndex, onSelect }) {
|
|
4
5
|
const tabRefs = useRef([]);
|
|
6
|
+
|
|
5
7
|
useEffect(() => {
|
|
6
8
|
const el = tabRefs.current[activeIndex];
|
|
7
9
|
if (el) {
|
|
@@ -12,30 +14,21 @@ export default function FileTabs({ sources, activeIndex, onSelect }) {
|
|
|
12
14
|
});
|
|
13
15
|
}
|
|
14
16
|
}, [activeIndex]);
|
|
17
|
+
|
|
15
18
|
if (!sources || sources.length <= 1) return null;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
false,
|
|
31
|
-
undefined,
|
|
32
|
-
this,
|
|
33
|
-
),
|
|
34
|
-
),
|
|
35
|
-
},
|
|
36
|
-
undefined,
|
|
37
|
-
false,
|
|
38
|
-
undefined,
|
|
39
|
-
this,
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles.tabs}>
|
|
22
|
+
{sources.map((src, i) => (
|
|
23
|
+
<button
|
|
24
|
+
key={i}
|
|
25
|
+
ref={(el) => (tabRefs.current[i] = el)}
|
|
26
|
+
className={`${styles.tab} ${i === activeIndex ? styles.activeTab : ""}`}
|
|
27
|
+
onClick={() => onSelect(i)}
|
|
28
|
+
>
|
|
29
|
+
{src.label || src.path.split("/").pop()}
|
|
30
|
+
</button>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
40
33
|
);
|
|
41
34
|
}
|
|
@@ -5,6 +5,7 @@ import ImageRenderer from "../renderers/ImageRenderer";
|
|
|
5
5
|
import PdfRenderer from "../renderers/PdfRenderer";
|
|
6
6
|
import WebRenderer from "../renderers/WebRenderer";
|
|
7
7
|
import CodeRenderer from "../renderers/CodeRenderer";
|
|
8
|
+
|
|
8
9
|
export default function PreviewContent({
|
|
9
10
|
currentFile,
|
|
10
11
|
fileType,
|
|
@@ -20,84 +21,58 @@ export default function PreviewContent({
|
|
|
20
21
|
}) {
|
|
21
22
|
const path = currentFile?.path;
|
|
22
23
|
const isExternal = path?.startsWith("http") || path?.startsWith("//");
|
|
24
|
+
|
|
23
25
|
if (!isOnline && isExternal) {
|
|
24
|
-
return
|
|
25
|
-
OfflineState,
|
|
26
|
-
{ onRetry: retryFetch },
|
|
27
|
-
undefined,
|
|
28
|
-
false,
|
|
29
|
-
undefined,
|
|
30
|
-
this,
|
|
31
|
-
);
|
|
26
|
+
return <OfflineState onRetry={retryFetch} />;
|
|
32
27
|
}
|
|
28
|
+
|
|
33
29
|
const errorMsg = fetchErrors?.[path];
|
|
34
30
|
if (errorMsg) {
|
|
35
|
-
return
|
|
36
|
-
ErrorState
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
return (
|
|
32
|
+
<ErrorState
|
|
33
|
+
path={path}
|
|
34
|
+
message={errorMsg}
|
|
35
|
+
fileType={fileType}
|
|
36
|
+
fileUrl={fileUrl}
|
|
37
|
+
onRetry={retryFetch}
|
|
38
|
+
/>
|
|
42
39
|
);
|
|
43
40
|
}
|
|
41
|
+
|
|
44
42
|
if (textLoading && fileType === "text") {
|
|
45
|
-
return
|
|
43
|
+
return <LoadingState />;
|
|
46
44
|
}
|
|
45
|
+
|
|
47
46
|
switch (fileType) {
|
|
48
47
|
case "image":
|
|
49
|
-
return
|
|
50
|
-
ImageRenderer
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
fileUrl,
|
|
58
|
-
false,
|
|
59
|
-
undefined,
|
|
60
|
-
this,
|
|
48
|
+
return (
|
|
49
|
+
<ImageRenderer
|
|
50
|
+
fileUrl={fileUrl}
|
|
51
|
+
label={currentFile.label}
|
|
52
|
+
zoomLevel={zoomLevel}
|
|
53
|
+
onError={(msg) => setError(path, msg)}
|
|
54
|
+
/>
|
|
61
55
|
);
|
|
62
56
|
case "pdf":
|
|
63
|
-
return
|
|
64
|
-
PdfRenderer
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this,
|
|
57
|
+
return (
|
|
58
|
+
<PdfRenderer
|
|
59
|
+
fileUrl={fileUrl}
|
|
60
|
+
zoomLevel={zoomLevel}
|
|
61
|
+
onError={(msg) => setError(path, msg)}
|
|
62
|
+
/>
|
|
70
63
|
);
|
|
71
64
|
case "web":
|
|
72
|
-
return
|
|
73
|
-
WebRenderer
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
fileUrl,
|
|
80
|
-
false,
|
|
81
|
-
undefined,
|
|
82
|
-
this,
|
|
65
|
+
return (
|
|
66
|
+
<WebRenderer
|
|
67
|
+
fileUrl={fileUrl}
|
|
68
|
+
label={currentFile.label}
|
|
69
|
+
onError={(msg) => setError(path, msg)}
|
|
70
|
+
/>
|
|
83
71
|
);
|
|
84
72
|
default: {
|
|
85
|
-
if (!textContent)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{},
|
|
89
|
-
undefined,
|
|
90
|
-
false,
|
|
91
|
-
undefined,
|
|
92
|
-
this,
|
|
93
|
-
);
|
|
94
|
-
return jsxDEV_7x81h0kn(
|
|
95
|
-
CodeRenderer,
|
|
96
|
-
{ code: textContent, language: ext, zoomLevel },
|
|
97
|
-
undefined,
|
|
98
|
-
false,
|
|
99
|
-
undefined,
|
|
100
|
-
this,
|
|
73
|
+
if (!textContent) return <LoadingState />;
|
|
74
|
+
return (
|
|
75
|
+
<CodeRenderer code={textContent} language={ext} zoomLevel={zoomLevel} />
|
|
101
76
|
);
|
|
102
77
|
}
|
|
103
78
|
}
|