portosaurus 3.0.2 → 4.0.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 +26 -126
- package/bin/portosaurus.mjs +8 -0
- package/package.json +6 -3
- package/src/assets/img/icon.png +0 -0
- package/src/assets/img/{icon.svg → svg/icon.svg} +35 -37
- package/src/assets/img/svg/project-blank.svg +140 -0
- package/src/assets/sample-resume.pdf +0 -0
- package/src/cli/build.mjs +2 -5
- package/src/cli/dev.mjs +27 -5
- package/src/cli/init.mjs +6 -12
- package/src/cli/schema.mjs +211 -0
- package/src/core/buildDocuConfig.mjs +305 -188
- package/src/core/constants.mjs +7 -1
- package/src/template/config.yml +150 -0
- package/src/template/notes/welcome.mdx +6 -0
- package/src/template/package.json +3 -3
- package/src/theme/MDXComponents.js +0 -1
- package/src/theme/components/AboutSection/index.js +32 -17
- package/src/theme/components/AboutSection/styles.module.css +151 -344
- package/src/theme/components/ContactSection/index.js +23 -14
- package/src/theme/components/ContactSection/styles.module.css +19 -8
- package/src/theme/components/ExperienceSection/index.js +12 -5
- package/src/theme/components/HeroSection/index.js +4 -3
- package/src/theme/components/HeroSection/styles.module.css +17 -16
- package/src/theme/components/NavArrow/index.js +114 -0
- package/src/theme/components/NavArrow/styles.module.css +107 -0
- package/src/theme/components/NoteIndex/index.js +66 -95
- package/src/theme/components/NoteIndex/styles.module.css +85 -89
- package/src/theme/components/Preview/components/FeedbackStates.js +3 -1
- package/src/theme/components/Preview/components/PreviewContent.js +91 -0
- package/src/theme/components/Preview/components/PreviewHeader.js +41 -33
- package/src/theme/components/Preview/components/Triggers/Pv.js +129 -72
- package/src/theme/components/Preview/components/ViewerWindow.js +198 -234
- package/src/theme/components/Preview/hooks/useAdaptiveSizing.js +115 -0
- package/src/theme/components/Preview/hooks/useDeepLinkHash.js +18 -23
- package/src/theme/components/Preview/hooks/useDockLayout.js +48 -8
- package/src/theme/components/Preview/hooks/useTouchZoom.js +118 -0
- package/src/theme/components/Preview/renderers/CodeRenderer.js +64 -25
- package/src/theme/components/Preview/state/index.js +70 -17
- package/src/theme/components/Preview/styles.module.css +181 -45
- package/src/theme/components/Preview/utils/index.js +11 -10
- package/src/theme/components/ProjectsSection/index.js +138 -148
- package/src/theme/components/ProjectsSection/styles.module.css +178 -112
- package/src/theme/components/SocialLinks/index.js +9 -7
- package/src/theme/components/Tooltip/index.js +31 -20
- package/src/theme/components/Tooltip/styles.module.css +101 -38
- package/src/theme/config/iconMappings.js +2 -0
- package/src/theme/css/custom.css +72 -0
- package/src/theme/hooks/useScrollReveal.js +30 -0
- package/src/theme/pages/index.js +7 -27
- package/src/theme/pages/notes.js +2 -2
- package/src/theme/pages/tasks.js +12 -11
- package/src/utils/cliUtils.mjs +23 -51
- package/src/utils/configUtils.mjs +95 -84
- package/src/utils/systemUtils.mjs +171 -0
- package/src/template/config.js +0 -68
- package/src/theme/components/ScrollToTop/index.js +0 -95
- package/src/theme/components/ScrollToTop/styles.module.css +0 -97
- package/src/theme/config/metaTags.js +0 -21
- /package/src/template/{.nojekyll → static/.nojekyll} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { useLocation } from "@docusaurus/router";
|
|
4
4
|
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
@@ -10,33 +10,33 @@ import { classify, getExt, resolveUrl } from "../utils";
|
|
|
10
10
|
import { useFileFetch } from "../hooks/useFileFetch";
|
|
11
11
|
import { useDockLayout } from "../hooks/useDockLayout";
|
|
12
12
|
import { useDeepLinkHash } from "../hooks/useDeepLinkHash";
|
|
13
|
+
import { useAdaptiveSizing } from "../hooks/useAdaptiveSizing";
|
|
14
|
+
import { useTouchZoom } from "../hooks/useTouchZoom";
|
|
13
15
|
|
|
14
16
|
import PreviewHeader from "./PreviewHeader";
|
|
15
17
|
import FileTabs from "./FileTabs";
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
import ImageRenderer from "../renderers/ImageRenderer";
|
|
19
|
-
import PdfRenderer from "../renderers/PdfRenderer";
|
|
20
|
-
import CodeRenderer from "../renderers/CodeRenderer";
|
|
21
|
-
import WebRenderer from "../renderers/WebRenderer";
|
|
22
|
-
|
|
18
|
+
import PreviewContent from "./PreviewContent";
|
|
23
19
|
import styles from "../styles.module.css";
|
|
24
20
|
|
|
25
21
|
/**
|
|
26
22
|
* The main Preview Viewer — orchestrates layout modes (floating, docked, mobile peek)
|
|
27
|
-
*
|
|
23
|
+
* utilizing modular hooks for sizing, interactions, and layout management.
|
|
28
24
|
*/
|
|
29
25
|
export default function PreviewViewer() {
|
|
30
26
|
const {
|
|
31
27
|
isOpen,
|
|
32
|
-
|
|
28
|
+
mode,
|
|
33
29
|
sources,
|
|
34
30
|
activeIndex,
|
|
31
|
+
baseSlug,
|
|
35
32
|
dockWidth,
|
|
33
|
+
peekHeight,
|
|
34
|
+
modeSwitch,
|
|
36
35
|
closePreview,
|
|
37
|
-
|
|
36
|
+
toggleMode,
|
|
38
37
|
setActiveIndex,
|
|
39
38
|
setDockWidth,
|
|
39
|
+
setPeekHeight,
|
|
40
40
|
floatingState,
|
|
41
41
|
setFloatingState,
|
|
42
42
|
} = usePreview();
|
|
@@ -45,35 +45,26 @@ export default function PreviewViewer() {
|
|
|
45
45
|
const corsProxyList = siteConfig?.customFields?.corsProxyList || [];
|
|
46
46
|
|
|
47
47
|
const location = useLocation();
|
|
48
|
-
const [mounted, setMounted] = useState(
|
|
48
|
+
const [mounted, setMounted] = useState(typeof window !== "undefined");
|
|
49
49
|
const [zoomLevel, setZoomLevel] = useState(1.0);
|
|
50
50
|
const [isOnline, setIsOnline] = useState(
|
|
51
51
|
typeof window !== "undefined" ? window.navigator.onLine : true,
|
|
52
52
|
);
|
|
53
53
|
const [windowWidth, setWindowWidth] = useState(
|
|
54
|
-
typeof window !== "undefined" ?
|
|
54
|
+
typeof window !== "undefined" ? document.documentElement.clientWidth : 1200,
|
|
55
55
|
);
|
|
56
56
|
const [isInteracting, setIsInteracting] = useState(false);
|
|
57
57
|
const [isDownloading, setIsDownloading] = useState(false);
|
|
58
|
-
const tabRefs = useRef([]);
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
const popupBodyRef = useRef(null);
|
|
60
|
+
|
|
61
|
+
// --- 1. Mount & Basic Resize Management ---
|
|
61
62
|
useEffect(() => {
|
|
62
63
|
setMounted(true);
|
|
63
64
|
const handleOnline = () => setIsOnline(true);
|
|
64
65
|
const handleOffline = () => setIsOnline(false);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const newWidth = window.innerWidth;
|
|
68
|
-
// If docked, adjust dockWidth to maintain roughly the same ratio
|
|
69
|
-
if (isDocked && windowWidth > 0) {
|
|
70
|
-
const ratio = dockWidth / windowWidth;
|
|
71
|
-
const targetWidth = Math.floor(newWidth * ratio);
|
|
72
|
-
// Clamp between 380px and 80% of window
|
|
73
|
-
setDockWidth(Math.max(380, Math.min(targetWidth, newWidth * 0.8)));
|
|
74
|
-
}
|
|
75
|
-
setWindowWidth(newWidth);
|
|
76
|
-
};
|
|
66
|
+
const handleResize = () =>
|
|
67
|
+
setWindowWidth(document.documentElement.clientWidth);
|
|
77
68
|
|
|
78
69
|
window.addEventListener("online", handleOnline);
|
|
79
70
|
window.addEventListener("offline", handleOffline);
|
|
@@ -85,9 +76,41 @@ export default function PreviewViewer() {
|
|
|
85
76
|
window.removeEventListener("offline", handleOffline);
|
|
86
77
|
window.removeEventListener("resize", handleResize);
|
|
87
78
|
};
|
|
88
|
-
}, [
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
// --- 2. Adaptive Sizing & Layout Math ---
|
|
82
|
+
const layout = useAdaptiveSizing({
|
|
83
|
+
mode,
|
|
84
|
+
windowWidth,
|
|
85
|
+
floatingState,
|
|
86
|
+
dockWidth,
|
|
87
|
+
peekHeight,
|
|
88
|
+
setFloatingState,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const { isDockMode, showAsPeek, isPipMode, isPopupMode, isMobile } = layout;
|
|
89
92
|
|
|
90
|
-
// ---
|
|
93
|
+
// --- 3. Interaction & Animation State ---
|
|
94
|
+
useTouchZoom({
|
|
95
|
+
containerRef: popupBodyRef,
|
|
96
|
+
isOpen,
|
|
97
|
+
zoomLevel,
|
|
98
|
+
setZoomLevel,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// --- 4. Sidebar & URL Sync ---
|
|
102
|
+
useDockLayout({
|
|
103
|
+
isOpen,
|
|
104
|
+
isPopupMode,
|
|
105
|
+
isSidebarDock: isDockMode,
|
|
106
|
+
isPeekDock: showAsPeek,
|
|
107
|
+
dockWidth,
|
|
108
|
+
peekHeight,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
useDeepLinkHash(isOpen, sources, activeIndex, mode, baseSlug);
|
|
112
|
+
|
|
113
|
+
// --- 5. Navigation & Lifecycle ---
|
|
91
114
|
const prevPathRef = useRef(location.pathname);
|
|
92
115
|
useEffect(() => {
|
|
93
116
|
if (prevPathRef.current !== location.pathname) {
|
|
@@ -96,57 +119,45 @@ export default function PreviewViewer() {
|
|
|
96
119
|
}
|
|
97
120
|
}, [location.pathname, isOpen, closePreview]);
|
|
98
121
|
|
|
99
|
-
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (isOpen) setZoomLevel(1.0);
|
|
124
|
+
}, [mode, isOpen]);
|
|
125
|
+
|
|
126
|
+
// Escape key support
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!isOpen) return;
|
|
129
|
+
const handler = (e) => {
|
|
130
|
+
if (e.key === "Escape") closePreview();
|
|
131
|
+
};
|
|
132
|
+
window.addEventListener("keydown", handler, { capture: true });
|
|
133
|
+
return () =>
|
|
134
|
+
window.removeEventListener("keydown", handler, { capture: true });
|
|
135
|
+
}, [isOpen, closePreview]);
|
|
136
|
+
|
|
137
|
+
// --- 6. Content Fetching ---
|
|
100
138
|
const currentFile = sources[activeIndex] ?? sources[0] ?? null;
|
|
101
139
|
const fileType = currentFile ? classify(currentFile.path) : null;
|
|
102
140
|
const ext = currentFile ? getExt(currentFile.path) : "";
|
|
103
141
|
const fileUrl = currentFile ? resolveUrl(currentFile.path) : "";
|
|
104
142
|
|
|
105
|
-
// --- Hooks ---
|
|
106
143
|
const {
|
|
107
144
|
content: textContent,
|
|
108
145
|
loading: textLoading,
|
|
109
|
-
error: fetchError,
|
|
110
146
|
errors: fetchErrors,
|
|
111
147
|
retry: retryFetch,
|
|
112
148
|
setError,
|
|
113
149
|
} = useFileFetch(currentFile?.path, fileType, isOpen);
|
|
114
150
|
|
|
115
|
-
|
|
116
|
-
useDeepLinkHash(isOpen, sources, activeIndex, tabRefs, isDocked);
|
|
117
|
-
|
|
118
|
-
// --- Escape key to close floating window ---
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (!isOpen || isDocked) return;
|
|
121
|
-
const handler = (e) => {
|
|
122
|
-
if (e.key === "Escape") closePreview();
|
|
123
|
-
};
|
|
124
|
-
window.addEventListener("keydown", handler);
|
|
125
|
-
return () => window.removeEventListener("keydown", handler);
|
|
126
|
-
}, [isOpen, isDocked, closePreview]);
|
|
127
|
-
|
|
128
|
-
// Reset zoom when switching modes
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
if (isOpen) setZoomLevel(1.0);
|
|
131
|
-
}, [isDocked, isOpen]);
|
|
132
|
-
|
|
133
|
-
// --- Download handler ---
|
|
134
|
-
// --- Download handler ---
|
|
151
|
+
// --- 7. Download Handler ---
|
|
135
152
|
const handleDownload = useCallback(async () => {
|
|
136
153
|
if (!fileUrl) return;
|
|
137
154
|
setIsDownloading(true);
|
|
138
|
-
|
|
139
155
|
try {
|
|
140
156
|
const downloadName =
|
|
141
157
|
currentFile.label || currentFile.path.split("/").pop();
|
|
142
|
-
|
|
143
158
|
const triggerBlobDownload = async (url) => {
|
|
144
|
-
const resp = await fetch(url, {
|
|
145
|
-
mode: "cors",
|
|
146
|
-
cache: "no-cache", // Ensure we don't get a tainted cache version
|
|
147
|
-
});
|
|
159
|
+
const resp = await fetch(url, { mode: "cors", cache: "no-cache" });
|
|
148
160
|
if (!resp.ok) throw new Error("Fetch failed");
|
|
149
|
-
|
|
150
161
|
const blob = await resp.blob();
|
|
151
162
|
const blobUrl = URL.createObjectURL(blob);
|
|
152
163
|
const a = document.createElement("a");
|
|
@@ -157,47 +168,25 @@ export default function PreviewViewer() {
|
|
|
157
168
|
document.body.removeChild(a);
|
|
158
169
|
setTimeout(() => URL.revokeObjectURL(blobUrl), 100);
|
|
159
170
|
};
|
|
160
|
-
|
|
161
171
|
try {
|
|
162
|
-
// 1. Try standard CORS fetch with cache busting
|
|
163
172
|
const bustUrl = fileUrl.includes("?")
|
|
164
173
|
? `${fileUrl}&cb=${Date.now()}`
|
|
165
174
|
: `${fileUrl}?cb=${Date.now()}`;
|
|
166
175
|
await triggerBlobDownload(bustUrl);
|
|
167
|
-
return;
|
|
168
176
|
} catch (e1) {
|
|
169
|
-
console.warn("Standard download failed, trying proxies...", e1);
|
|
170
|
-
|
|
171
177
|
let proxySuccess = false;
|
|
172
|
-
|
|
173
|
-
// Iterate through our fallback proxy array
|
|
174
178
|
for (const proxyBaseUrl of corsProxyList) {
|
|
175
179
|
try {
|
|
176
|
-
console.warn(`Trying proxy: ${proxyBaseUrl}`);
|
|
177
180
|
const proxyUrl = `${proxyBaseUrl}${encodeURIComponent(fileUrl)}`;
|
|
178
181
|
await triggerBlobDownload(proxyUrl);
|
|
179
182
|
proxySuccess = true;
|
|
180
|
-
break;
|
|
181
|
-
} catch (
|
|
182
|
-
console.warn(`Proxy ${proxyBaseUrl} failed:`, proxyError);
|
|
183
|
-
// Continue loop to try the next proxy
|
|
184
|
-
}
|
|
183
|
+
break;
|
|
184
|
+
} catch (pE) {}
|
|
185
185
|
}
|
|
186
|
-
|
|
187
186
|
if (!proxySuccess) {
|
|
188
|
-
// Absolute Fallback: direct link redirect
|
|
189
|
-
console.warn(
|
|
190
|
-
"All proxy downloads failed, falling back to direct link",
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
alert(
|
|
194
|
-
"This file is heavily protected by browser security (CORS) and cannot be downloaded directly. It will be opened in a new tab instead.",
|
|
195
|
-
);
|
|
196
|
-
|
|
197
187
|
const link = document.createElement("a");
|
|
198
188
|
link.href = fileUrl;
|
|
199
189
|
link.target = "_blank";
|
|
200
|
-
link.rel = "noopener noreferrer";
|
|
201
190
|
link.setAttribute("download", downloadName);
|
|
202
191
|
document.body.appendChild(link);
|
|
203
192
|
link.click();
|
|
@@ -207,106 +196,36 @@ export default function PreviewViewer() {
|
|
|
207
196
|
} finally {
|
|
208
197
|
setIsDownloading(false);
|
|
209
198
|
}
|
|
210
|
-
}, [fileUrl, currentFile]);
|
|
199
|
+
}, [fileUrl, currentFile, corsProxyList]);
|
|
211
200
|
|
|
212
|
-
// ---
|
|
201
|
+
// --- Render Helpers ---
|
|
213
202
|
if (!mounted || !currentFile) return null;
|
|
214
203
|
|
|
215
|
-
// --- Display title ---
|
|
216
204
|
const displayTitle =
|
|
217
|
-
|
|
205
|
+
currentFile.title ||
|
|
206
|
+
(fileType === "web"
|
|
218
207
|
? currentFile.path.replace(/^https?:\/\//, "").split("/")[0]
|
|
219
|
-
: currentFile.label || currentFile.path.split("/").pop();
|
|
220
|
-
|
|
221
|
-
// --- Content Router ---
|
|
222
|
-
const renderContent = () => {
|
|
223
|
-
const path = currentFile?.path;
|
|
224
|
-
const isExternal = path?.startsWith("http") || path?.startsWith("//");
|
|
225
|
-
|
|
226
|
-
// Offline guard for external resources
|
|
227
|
-
if (!isOnline && isExternal) {
|
|
228
|
-
return <OfflineState onRetry={retryFetch} />;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Error state
|
|
232
|
-
const errorMsg = fetchErrors?.[path];
|
|
233
|
-
if (errorMsg) {
|
|
234
|
-
return (
|
|
235
|
-
<ErrorState
|
|
236
|
-
path={path}
|
|
237
|
-
message={errorMsg}
|
|
238
|
-
fileType={fileType}
|
|
239
|
-
fileUrl={fileUrl}
|
|
240
|
-
onRetry={retryFetch}
|
|
241
|
-
/>
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Loading state (for text files only — renderers handle their own loading)
|
|
246
|
-
if (textLoading && fileType === "text") {
|
|
247
|
-
return <LoadingState />;
|
|
248
|
-
}
|
|
208
|
+
: currentFile.label || currentFile.path.split("/").pop());
|
|
249
209
|
|
|
250
|
-
switch (fileType) {
|
|
251
|
-
case "image":
|
|
252
|
-
return (
|
|
253
|
-
<ImageRenderer
|
|
254
|
-
key={fileUrl}
|
|
255
|
-
fileUrl={fileUrl}
|
|
256
|
-
label={currentFile.label}
|
|
257
|
-
zoomLevel={zoomLevel}
|
|
258
|
-
onError={(msg) => setError(path, msg)}
|
|
259
|
-
/>
|
|
260
|
-
);
|
|
261
|
-
case "pdf":
|
|
262
|
-
return (
|
|
263
|
-
<PdfRenderer
|
|
264
|
-
key={fileUrl}
|
|
265
|
-
fileUrl={fileUrl}
|
|
266
|
-
zoomLevel={zoomLevel}
|
|
267
|
-
onError={(msg) => setError(path, msg)}
|
|
268
|
-
/>
|
|
269
|
-
);
|
|
270
|
-
case "web":
|
|
271
|
-
return (
|
|
272
|
-
<WebRenderer
|
|
273
|
-
key={fileUrl}
|
|
274
|
-
fileUrl={fileUrl}
|
|
275
|
-
label={currentFile.label}
|
|
276
|
-
onError={(msg) => setError(path, msg)}
|
|
277
|
-
/>
|
|
278
|
-
);
|
|
279
|
-
default: {
|
|
280
|
-
// Text / code
|
|
281
|
-
if (!textContent) return <LoadingState />;
|
|
282
|
-
return <CodeRenderer code={textContent} language={ext} />;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
// --- Layout calculations ---
|
|
288
|
-
const isMobile = windowWidth <= 768;
|
|
289
|
-
const showAsDock = !isMobile && isDocked;
|
|
290
|
-
const showAsFloating = !isMobile && !isDocked;
|
|
291
|
-
const showAsPeek = isMobile && isDocked;
|
|
292
|
-
|
|
293
|
-
// --- Header (shared across all modes) ---
|
|
294
210
|
const header = (
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
211
|
+
<div className={styles.headerWrapper}>
|
|
212
|
+
{showAsPeek && <div className={styles.peekHandle} />}
|
|
213
|
+
<PreviewHeader
|
|
214
|
+
displayTitle={displayTitle}
|
|
215
|
+
fileType={fileType}
|
|
216
|
+
fileUrl={fileUrl}
|
|
217
|
+
mode={mode}
|
|
218
|
+
zoomLevel={zoomLevel}
|
|
219
|
+
onZoomChange={setZoomLevel}
|
|
220
|
+
onToggleMode={toggleMode}
|
|
221
|
+
onClose={closePreview}
|
|
222
|
+
onDownload={handleDownload}
|
|
223
|
+
isDownloading={isDownloading}
|
|
224
|
+
modeSwitch={modeSwitch}
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
307
227
|
);
|
|
308
228
|
|
|
309
|
-
// --- Inner content (shared across all modes) ---
|
|
310
229
|
const innerContent = (
|
|
311
230
|
<div className={styles.windowContent}>
|
|
312
231
|
<FileTabs
|
|
@@ -314,106 +233,153 @@ export default function PreviewViewer() {
|
|
|
314
233
|
activeIndex={activeIndex}
|
|
315
234
|
onSelect={setActiveIndex}
|
|
316
235
|
/>
|
|
317
|
-
<div
|
|
236
|
+
<div
|
|
237
|
+
className={`${styles.popupBody} ${fileType === "text" ? styles.isText : styles.isGrabbable}`}
|
|
238
|
+
ref={(el) => {
|
|
239
|
+
popupBodyRef.current = el;
|
|
240
|
+
if (el && isOpen) el.focus({ preventScroll: true });
|
|
241
|
+
}}
|
|
242
|
+
tabIndex={-1}
|
|
243
|
+
>
|
|
244
|
+
<PreviewContent
|
|
245
|
+
currentFile={currentFile}
|
|
246
|
+
fileType={fileType}
|
|
247
|
+
fileUrl={fileUrl}
|
|
248
|
+
isOnline={isOnline}
|
|
249
|
+
fetchErrors={fetchErrors}
|
|
250
|
+
textLoading={textLoading}
|
|
251
|
+
textContent={textContent}
|
|
252
|
+
zoomLevel={zoomLevel}
|
|
253
|
+
ext={ext}
|
|
254
|
+
retryFetch={retryFetch}
|
|
255
|
+
setError={setError}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
318
258
|
</div>
|
|
319
259
|
);
|
|
320
260
|
|
|
321
|
-
//
|
|
322
|
-
const
|
|
323
|
-
? {
|
|
324
|
-
:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
y:
|
|
329
|
-
floatingState.y ??
|
|
330
|
-
Math.max(0, window.innerHeight - (floatingState.height ?? 500) - 20),
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const rndSize = showAsDock
|
|
334
|
-
? { width: dockWidth, height: window.innerHeight }
|
|
335
|
-
: {
|
|
336
|
-
width: floatingState.width ?? 800,
|
|
337
|
-
height: floatingState.height ?? 600,
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const rndEnableResizing = showAsDock
|
|
261
|
+
// Rnd Configuration
|
|
262
|
+
const rndEnableResizing = isDockMode
|
|
263
|
+
? { left: true }
|
|
264
|
+
: showAsPeek
|
|
265
|
+
? { top: true }
|
|
266
|
+
: true;
|
|
267
|
+
const rndResizeHandleStyles = showAsPeek
|
|
341
268
|
? {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
bottomLeft: false,
|
|
349
|
-
bottomRight: false,
|
|
269
|
+
top: {
|
|
270
|
+
height: "24px",
|
|
271
|
+
top: "-12px",
|
|
272
|
+
cursor: "row-resize",
|
|
273
|
+
zIndex: 100,
|
|
274
|
+
},
|
|
350
275
|
}
|
|
351
|
-
:
|
|
276
|
+
: isDockMode
|
|
277
|
+
? { left: { width: "20px", left: "-10px" } }
|
|
278
|
+
: isPipMode
|
|
279
|
+
? {
|
|
280
|
+
bottom: { height: "20px", bottom: "-10px" },
|
|
281
|
+
right: { width: "20px", right: "-10px" },
|
|
282
|
+
left: { width: "20px", left: "-10px" },
|
|
283
|
+
top: { height: "20px", top: "-10px" },
|
|
284
|
+
bottomRight: {
|
|
285
|
+
width: "30px",
|
|
286
|
+
height: "30px",
|
|
287
|
+
bottom: "-15px",
|
|
288
|
+
right: "-15px",
|
|
289
|
+
},
|
|
290
|
+
bottomLeft: {
|
|
291
|
+
width: "30px",
|
|
292
|
+
height: "30px",
|
|
293
|
+
bottom: "-15px",
|
|
294
|
+
left: "-15px",
|
|
295
|
+
},
|
|
296
|
+
topRight: {
|
|
297
|
+
width: "30px",
|
|
298
|
+
height: "30px",
|
|
299
|
+
top: "-15px",
|
|
300
|
+
right: "-15px",
|
|
301
|
+
},
|
|
302
|
+
topLeft: {
|
|
303
|
+
width: "30px",
|
|
304
|
+
height: "30px",
|
|
305
|
+
top: "-15px",
|
|
306
|
+
left: "-15px",
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
: {};
|
|
310
|
+
|
|
311
|
+
const rndMinWidth = isDockMode ? 380 : showAsPeek ? windowWidth : 380;
|
|
312
|
+
const rndMinHeight = showAsPeek ? 150 : isDockMode ? undefined : 60;
|
|
313
|
+
const rndMaxWidth = isDockMode
|
|
314
|
+
? windowWidth * 0.8
|
|
315
|
+
: showAsPeek
|
|
316
|
+
? windowWidth
|
|
317
|
+
: undefined;
|
|
318
|
+
const rndMaxHeight = showAsPeek ? layout.vh * 0.85 : undefined;
|
|
352
319
|
|
|
353
320
|
return createPortal(
|
|
354
321
|
<AnimatePresence>
|
|
355
322
|
{isOpen && (
|
|
356
323
|
<motion.div
|
|
357
324
|
id="pv-viewer"
|
|
358
|
-
data-mode={
|
|
359
|
-
className={`
|
|
360
|
-
${styles.previewSystem}
|
|
361
|
-
${showAsPeek ? styles.modePeek : ""}
|
|
362
|
-
${showAsDock ? styles.modeDock : ""}
|
|
363
|
-
${showAsFloating ? styles.modeFloating : ""}
|
|
364
|
-
`}
|
|
325
|
+
data-mode={mode}
|
|
326
|
+
className={`${styles.previewSystem} ${showAsPeek ? styles.modePeek : ""} ${isDockMode ? styles.modeDock : ""} ${isPipMode ? styles.modePip : ""} ${isPopupMode ? styles.modePopup : ""}`}
|
|
365
327
|
initial={{ opacity: 0 }}
|
|
366
328
|
animate={{ opacity: 1 }}
|
|
367
329
|
exit={{ opacity: 0 }}
|
|
368
330
|
transition={{ duration: 0.2 }}
|
|
331
|
+
onWheel={(e) => e.stopPropagation()}
|
|
369
332
|
>
|
|
370
|
-
{
|
|
371
|
-
|
|
333
|
+
{isPopupMode && (
|
|
334
|
+
<div className={styles.previewBackdrop} onClick={closePreview} />
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
{isPopupMode ? (
|
|
372
338
|
<motion.div
|
|
339
|
+
key="desktop-popup"
|
|
373
340
|
className={styles.windowFrame}
|
|
374
|
-
initial={{ y: "
|
|
375
|
-
animate={{ y:
|
|
376
|
-
exit={{ y: "
|
|
341
|
+
initial={{ opacity: 0, scale: 0.9, y: "-45%", x: "-50%" }}
|
|
342
|
+
animate={{ opacity: 1, scale: 1, y: "-50%", x: "-50%" }}
|
|
343
|
+
exit={{ opacity: 0, scale: 0.9, y: "-45%", x: "-50%" }}
|
|
377
344
|
transition={{ type: "spring", damping: 25, stiffness: 300 }}
|
|
378
345
|
onClick={(e) => e.stopPropagation()}
|
|
379
346
|
>
|
|
380
|
-
<div className={styles.peekHandle} />
|
|
381
347
|
<div className={styles.dragHandleWrapper}>{header}</div>
|
|
382
348
|
{innerContent}
|
|
383
349
|
</motion.div>
|
|
384
350
|
) : (
|
|
385
|
-
/* Desktop: Rnd-powered window for both floating and docked */
|
|
386
351
|
<Rnd
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
352
|
+
key={`${mode}-${showAsPeek}`}
|
|
353
|
+
position={layout.rndPosition}
|
|
354
|
+
size={layout.rndSize}
|
|
355
|
+
disableDragging={isDockMode || showAsPeek}
|
|
390
356
|
enableResizing={rndEnableResizing}
|
|
391
357
|
dragHandleClassName={styles.dragHandleWrapper}
|
|
392
|
-
minWidth={
|
|
393
|
-
minHeight={
|
|
394
|
-
maxWidth={
|
|
395
|
-
|
|
358
|
+
minWidth={rndMinWidth}
|
|
359
|
+
minHeight={rndMinHeight}
|
|
360
|
+
maxWidth={rndMaxWidth}
|
|
361
|
+
maxHeight={rndMaxHeight}
|
|
362
|
+
bounds={layout.rndBounds}
|
|
363
|
+
resizeHandleStyles={rndResizeHandleStyles}
|
|
396
364
|
onDragStart={() => setIsInteracting(true)}
|
|
397
365
|
onDragStop={(e, d) => {
|
|
398
366
|
setIsInteracting(false);
|
|
399
|
-
if (!
|
|
367
|
+
if (!isDockMode && !showAsPeek)
|
|
400
368
|
setFloatingState({ x: d.x, y: d.y });
|
|
401
|
-
}
|
|
402
369
|
}}
|
|
403
370
|
onResizeStart={() => setIsInteracting(true)}
|
|
404
371
|
onResizeStop={(e, direction, ref, delta, position) => {
|
|
405
372
|
setIsInteracting(false);
|
|
406
373
|
const newWidth = parseInt(ref.style.width, 10);
|
|
407
374
|
const newHeight = parseInt(ref.style.height, 10);
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
375
|
+
if (isDockMode) setDockWidth(newWidth);
|
|
376
|
+
else if (showAsPeek) setPeekHeight(newHeight);
|
|
377
|
+
else
|
|
411
378
|
setFloatingState({
|
|
412
379
|
width: newWidth,
|
|
413
380
|
height: newHeight,
|
|
414
381
|
...position,
|
|
415
382
|
});
|
|
416
|
-
}
|
|
417
383
|
}}
|
|
418
384
|
className={styles.rndWrapper}
|
|
419
385
|
style={{
|
|
@@ -424,9 +390,7 @@ export default function PreviewViewer() {
|
|
|
424
390
|
}}
|
|
425
391
|
>
|
|
426
392
|
<div
|
|
427
|
-
className={`${styles.windowFrame} ${
|
|
428
|
-
isInteracting ? styles.windowInteracting : ""
|
|
429
|
-
}`}
|
|
393
|
+
className={`${styles.windowFrame} ${isInteracting ? styles.windowInteracting : ""}`}
|
|
430
394
|
style={{ width: "100%", height: "100%", position: "relative" }}
|
|
431
395
|
onClick={(e) => e.stopPropagation()}
|
|
432
396
|
>
|