@portosaur/theme 0.1.4 → 0.2.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 +10 -3
- package/src/plugins/theme.mjs +2 -0
- package/theme/DocCategoryGeneratedIndexPage/index.jsx +4 -10
- package/theme/MDXComponents.jsx +1 -1
- 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/NavArrow/index.jsx +38 -55
- package/theme/components/NoteIndex/index.jsx +50 -116
- 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/SocialLinks/index.jsx +43 -55
- package/theme/components/Tooltip/index.jsx +28 -39
- package/theme/pages/index.jsx +23 -87
- package/theme/pages/notes.jsx +26 -104
- package/theme/pages/tasks.jsx +220 -903
|
@@ -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
|
}
|
|
@@ -2,12 +2,14 @@ import useBaseUrl from "@docusaurus/useBaseUrl";
|
|
|
2
2
|
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
3
3
|
import Link from "@docusaurus/Link";
|
|
4
4
|
import { FaBook, FaChevronRight } from "react-icons/fa";
|
|
5
|
-
import Tooltip from "../Tooltip/index.
|
|
6
|
-
import { iconMap } from "../../config/iconMappings.
|
|
5
|
+
import Tooltip from "../Tooltip/index.jsx";
|
|
6
|
+
import { iconMap } from "../../config/iconMappings.jsx";
|
|
7
7
|
import DocCardList from "@theme/DocCardList";
|
|
8
8
|
import styles from "./styles.module.css";
|
|
9
|
+
|
|
9
10
|
function useNotes() {
|
|
10
11
|
const context = require.context(`@site/notes`, true, /index\.mdx?$|\.mdx?$/);
|
|
12
|
+
|
|
11
13
|
return context
|
|
12
14
|
.keys()
|
|
13
15
|
.filter((path) => {
|
|
@@ -35,6 +37,7 @@ function useNotes() {
|
|
|
35
37
|
.replace(/ /g, "")
|
|
36
38
|
.replace(/[\s-]/g, "")
|
|
37
39
|
: slug.toLowerCase() || title.toLowerCase();
|
|
40
|
+
|
|
38
41
|
return {
|
|
39
42
|
title,
|
|
40
43
|
language,
|
|
@@ -45,138 +48,69 @@ function useNotes() {
|
|
|
45
48
|
})
|
|
46
49
|
.sort((a, b) => a.position - b.position);
|
|
47
50
|
}
|
|
51
|
+
|
|
48
52
|
function NoteCard({ title, language, slug, desc, index, docsBasePath }) {
|
|
49
53
|
const noteUrl = useBaseUrl(`${docsBasePath}/${slug}`);
|
|
50
54
|
const { icon: Icon = FaBook, color = "var(--ifm-color-primary)" } =
|
|
51
55
|
iconMap[language] || iconMap[title.toLowerCase()] || {};
|
|
52
56
|
const tooltipContent = desc ? desc : null;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
to
|
|
57
|
-
className
|
|
58
|
-
style
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this,
|
|
78
|
-
),
|
|
79
|
-
jsxDEV_7x81h0kn(
|
|
80
|
-
"div",
|
|
81
|
-
{
|
|
82
|
-
className: styles.cardContent,
|
|
83
|
-
children: jsxDEV_7x81h0kn(
|
|
84
|
-
"h3",
|
|
85
|
-
{ className: styles.noteTitle, children: title },
|
|
86
|
-
undefined,
|
|
87
|
-
false,
|
|
88
|
-
undefined,
|
|
89
|
-
this,
|
|
90
|
-
),
|
|
91
|
-
},
|
|
92
|
-
undefined,
|
|
93
|
-
false,
|
|
94
|
-
undefined,
|
|
95
|
-
this,
|
|
96
|
-
),
|
|
97
|
-
jsxDEV_7x81h0kn(
|
|
98
|
-
FaChevronRight,
|
|
99
|
-
{ className: styles.mobileChevron },
|
|
100
|
-
undefined,
|
|
101
|
-
false,
|
|
102
|
-
undefined,
|
|
103
|
-
this,
|
|
104
|
-
),
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
undefined,
|
|
108
|
-
true,
|
|
109
|
-
undefined,
|
|
110
|
-
this,
|
|
57
|
+
|
|
58
|
+
const cardInner = (
|
|
59
|
+
<Link
|
|
60
|
+
to={noteUrl}
|
|
61
|
+
className={styles.noteCard}
|
|
62
|
+
style={{ "--card-index": index, "--note-color": color }}
|
|
63
|
+
aria-label={`Read note: ${title}`}
|
|
64
|
+
>
|
|
65
|
+
<div className={styles.iconWrapper}>
|
|
66
|
+
<Icon className={styles.noteIcon} />
|
|
67
|
+
</div>
|
|
68
|
+
<div className={styles.cardContent}>
|
|
69
|
+
<h3 className={styles.noteTitle}>{title}</h3>
|
|
70
|
+
</div>
|
|
71
|
+
<FaChevronRight className={styles.mobileChevron} />
|
|
72
|
+
</Link>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return tooltipContent ? (
|
|
76
|
+
<Tooltip msg={tooltipContent} position="top" underline={false} gap={-8}>
|
|
77
|
+
{cardInner}
|
|
78
|
+
</Tooltip>
|
|
79
|
+
) : (
|
|
80
|
+
cardInner
|
|
111
81
|
);
|
|
112
|
-
return tooltipContent
|
|
113
|
-
? jsxDEV_7x81h0kn(
|
|
114
|
-
Tooltip,
|
|
115
|
-
{
|
|
116
|
-
msg: tooltipContent,
|
|
117
|
-
position: "top",
|
|
118
|
-
underline: false,
|
|
119
|
-
gap: -8,
|
|
120
|
-
children: cardInner,
|
|
121
|
-
},
|
|
122
|
-
undefined,
|
|
123
|
-
false,
|
|
124
|
-
undefined,
|
|
125
|
-
this,
|
|
126
|
-
)
|
|
127
|
-
: cardInner;
|
|
128
82
|
}
|
|
83
|
+
|
|
129
84
|
export default function NoteCards() {
|
|
130
85
|
const notes = useNotes();
|
|
131
86
|
const { path: docsBasePath } = usePluginData(
|
|
132
87
|
"docusaurus-plugin-content-docs",
|
|
133
88
|
);
|
|
89
|
+
|
|
134
90
|
if (!notes.length) return null;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
this,
|
|
148
|
-
),
|
|
149
|
-
),
|
|
150
|
-
},
|
|
151
|
-
undefined,
|
|
152
|
-
false,
|
|
153
|
-
undefined,
|
|
154
|
-
this,
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className={styles.notesGrid} role="list">
|
|
94
|
+
{notes.map((note, index) => (
|
|
95
|
+
<NoteCard
|
|
96
|
+
key={note.slug}
|
|
97
|
+
{...note}
|
|
98
|
+
index={index}
|
|
99
|
+
docsBasePath={docsBasePath}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
155
103
|
);
|
|
156
104
|
}
|
|
105
|
+
|
|
157
106
|
export function TopicList({
|
|
158
107
|
desc = "Click on the links below to explore the topics.",
|
|
159
108
|
style = { marginTop: "-2.5rem", marginBottom: "2.5rem", textAlign: "center" },
|
|
160
109
|
}) {
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
jsxDEV_7x81h0kn(
|
|
167
|
-
"p",
|
|
168
|
-
{ dangerouslySetInnerHTML: { __html: desc } },
|
|
169
|
-
undefined,
|
|
170
|
-
false,
|
|
171
|
-
undefined,
|
|
172
|
-
this,
|
|
173
|
-
),
|
|
174
|
-
jsxDEV_7x81h0kn(DocCardList, {}, undefined, false, undefined, this),
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
undefined,
|
|
178
|
-
true,
|
|
179
|
-
undefined,
|
|
180
|
-
this,
|
|
110
|
+
return (
|
|
111
|
+
<div style={style}>
|
|
112
|
+
<p dangerouslySetInnerHTML={{ __html: desc }} />
|
|
113
|
+
<DocCardList />
|
|
114
|
+
</div>
|
|
181
115
|
);
|
|
182
116
|
}
|
|
@@ -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
|
}
|