Skip to content

Commit 9e7fd29

Browse files
feat: Implemented page url construction. And also url refresh issue
1 parent 46df242 commit 9e7fd29

3 files changed

Lines changed: 401 additions & 225 deletions

File tree

app/components/FileManager.tsx

Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { useSearchParams, useRouter } from "next/navigation";
5+
import AuthWrapper from "./AuthWrapper";
6+
import Header from "./Header";
7+
import Sidebar from "./Sidebar";
8+
import Breadcrumb from "./Breadcrumb";
9+
import FileList from "./FileList";
10+
import PermissionsDialog, { Permission } from "./PermissionsDialog";
11+
import { FileItemData } from "./FileItem";
12+
import { useSolidStorages, useBrowseStorage } from "../lib/hooks";
13+
import { filterProfileItems, buildBreadcrumbItems } from "../lib/helpers";
14+
import LoadingSpinner from "./shared/LoadingSpinner";
15+
import ErrorDisplay from "./shared/ErrorDisplay";
16+
17+
export default function FileManager() {
18+
const searchParams = useSearchParams();
19+
const router = useRouter();
20+
const { storages, isLoading: isLoadingStorages, error: storagesError } = useSolidStorages();
21+
const [selectedStorageId, setSelectedStorageId] = useState<string | null>(null);
22+
const [currentPath, setCurrentPath] = useState<string>("/");
23+
const [selectedFileIds, setSelectedFileIds] = useState<string[]>([]);
24+
const [isInitialized, setIsInitialized] = useState(false);
25+
const [savedUrl, setSavedUrl] = useState<string | null>(() => {
26+
if (typeof window === "undefined") return null;
27+
28+
try {
29+
const fullUrl = window.location.href;
30+
const urlObj = new URL(fullUrl);
31+
let urlParam = urlObj.searchParams.get("url");
32+
33+
if (urlParam) {
34+
try {
35+
urlParam = decodeURIComponent(urlParam);
36+
} catch (e) {
37+
// Keep encoded if decode fails
38+
}
39+
return urlParam;
40+
}
41+
42+
const stored = sessionStorage.getItem("solid-file-manager-url");
43+
if (stored) {
44+
return stored;
45+
}
46+
} catch (e) {
47+
// Ignore
48+
}
49+
return null;
50+
});
51+
52+
useEffect(() => {
53+
if (typeof window === "undefined") return;
54+
55+
const fullUrl = window.location.href;
56+
const urlObj = new URL(fullUrl);
57+
let urlParam = urlObj.searchParams.get("url");
58+
59+
if (urlParam) {
60+
try {
61+
urlParam = decodeURIComponent(urlParam);
62+
} catch (e) {
63+
// Ignore
64+
}
65+
setSavedUrl(urlParam);
66+
sessionStorage.setItem("solid-file-manager-url", urlParam);
67+
}
68+
}, []);
69+
70+
useEffect(() => {
71+
if (isLoadingStorages || storages.length === 0) {
72+
return;
73+
}
74+
if (isInitialized) {
75+
return;
76+
}
77+
78+
const restoreFromUrl = () => {
79+
let urlParam: string | null = savedUrl;
80+
81+
if (!urlParam && typeof window !== "undefined") {
82+
const fullUrl = window.location.href;
83+
const urlObj = new URL(fullUrl);
84+
urlParam = urlObj.searchParams.get("url");
85+
86+
if (!urlParam && window.location.search) {
87+
const urlParams = new URLSearchParams(window.location.search);
88+
urlParam = urlParams.get("url");
89+
}
90+
}
91+
92+
if (!urlParam) {
93+
urlParam = searchParams.get("url");
94+
}
95+
96+
if (urlParam) {
97+
try {
98+
let decodedUrl = urlParam;
99+
try {
100+
decodedUrl = decodeURIComponent(urlParam);
101+
} catch (e) {
102+
decodedUrl = urlParam;
103+
}
104+
105+
const matchingStorage = storages.find((s) => {
106+
const storageUrl = s.url.endsWith("/") ? s.url : s.url + "/";
107+
const normalizedDecoded = decodedUrl.endsWith("/") ? decodedUrl : decodedUrl + "/";
108+
const normalizedStorage = storageUrl.endsWith("/") ? storageUrl : storageUrl + "/";
109+
return decodedUrl === s.url || decodedUrl === s.url + "/" || normalizedDecoded.startsWith(normalizedStorage);
110+
});
111+
112+
if (matchingStorage) {
113+
setSelectedStorageId(matchingStorage.id);
114+
115+
if (decodedUrl === matchingStorage.url || decodedUrl === matchingStorage.url + "/") {
116+
setCurrentPath("/");
117+
} else {
118+
setCurrentPath(decodedUrl);
119+
}
120+
121+
if (typeof window !== "undefined") {
122+
const encodedUrl = encodeURIComponent(decodedUrl);
123+
const params = new URLSearchParams();
124+
params.set("url", encodedUrl);
125+
const newUrl = `/?${params.toString()}`;
126+
router.replace(newUrl, { scroll: false });
127+
}
128+
129+
setIsInitialized(true);
130+
return;
131+
}
132+
} catch (e) {
133+
// Ignore errors
134+
}
135+
}
136+
137+
setIsInitialized(true);
138+
};
139+
140+
restoreFromUrl();
141+
}, [searchParams, storages, isLoadingStorages, isInitialized, savedUrl, router]);
142+
143+
const updateUrl = (url: string | null) => {
144+
if (!url || url === "/") {
145+
if (typeof window !== "undefined") {
146+
sessionStorage.removeItem("solid-file-manager-url");
147+
if (window.location.search) {
148+
router.replace("/", { scroll: false });
149+
}
150+
}
151+
return;
152+
}
153+
154+
let urlToEncode = url;
155+
try {
156+
urlToEncode = decodeURIComponent(url);
157+
} catch (e) {
158+
urlToEncode = url;
159+
}
160+
161+
const encodedUrl = encodeURIComponent(urlToEncode);
162+
const params = new URLSearchParams();
163+
params.set("url", encodedUrl);
164+
const newUrl = `/?${params.toString()}`;
165+
166+
if (typeof window !== "undefined") {
167+
sessionStorage.setItem("solid-file-manager-url", urlToEncode);
168+
}
169+
170+
router.replace(newUrl, { scroll: false });
171+
};
172+
173+
const containerUrlToBrowse = selectedStorageId
174+
? currentPath === "/"
175+
? storages.find((s) => s.id === selectedStorageId)?.url || null
176+
: currentPath
177+
: null;
178+
179+
const { files: browsedFiles, isLoading: isLoadingFiles, error: browseError } = useBrowseStorage(containerUrlToBrowse);
180+
const [permissionsDialogOpen, setPermissionsDialogOpen] = useState(false);
181+
const [selectedFileForPermissions, setSelectedFileForPermissions] =
182+
useState<FileItemData | null>(null);
183+
const [permissions, setPermissions] = useState<Permission[]>([]);
184+
const [sidebarOpen, setSidebarOpen] = useState(false);
185+
186+
const storageFiles: FileItemData[] = filterProfileItems(storages).map((storage) => ({
187+
id: storage.id,
188+
name: storage.name,
189+
type: "folder" as const,
190+
url: storage.url,
191+
}));
192+
193+
const filteredFiles = filterProfileItems(browsedFiles);
194+
const displayFiles = selectedStorageId ? filteredFiles : storageFiles;
195+
196+
const selectedStorage = storages.find((s) => s.id === selectedStorageId);
197+
const breadcrumbItems = buildBreadcrumbItems(
198+
selectedStorageId,
199+
selectedStorage?.url,
200+
selectedStorage?.name,
201+
currentPath
202+
);
203+
204+
const handleFileDoubleClick = (file: FileItemData) => {
205+
if (file.type === "folder") {
206+
const isStorage = storages.some(s => s.id === file.id);
207+
208+
if (!selectedStorageId && isStorage) {
209+
setSelectedStorageId(file.id);
210+
setCurrentPath("/");
211+
setSelectedFileIds([]);
212+
updateUrl(file.url);
213+
} else if (selectedStorageId) {
214+
setCurrentPath(file.url);
215+
setSelectedFileIds([]);
216+
updateUrl(file.url);
217+
}
218+
} else {
219+
window.open(file.url, "_blank");
220+
}
221+
};
222+
223+
const handleFileSelect = (file: FileItemData) => {
224+
setSelectedFileIds((prev) => {
225+
if (prev.includes(file.id)) {
226+
return prev.filter((id) => id !== file.id);
227+
}
228+
return [...prev, file.id];
229+
});
230+
};
231+
232+
const handleBreadcrumbNavigate = (path: string) => {
233+
if (path === "/") {
234+
setSelectedStorageId(null);
235+
setCurrentPath("/");
236+
setSelectedFileIds([]);
237+
updateUrl(null);
238+
} else {
239+
const selectedStorage = storages.find((s) => s.id === selectedStorageId);
240+
if (selectedStorage && path === selectedStorage.url) {
241+
setCurrentPath("/");
242+
updateUrl(selectedStorage.url);
243+
} else {
244+
setCurrentPath(path);
245+
updateUrl(path);
246+
}
247+
setSelectedFileIds([]);
248+
}
249+
};
250+
251+
const handleShareClickForFile = (file: FileItemData) => {
252+
setSelectedFileForPermissions(file);
253+
setPermissionsDialogOpen(true);
254+
setPermissions([
255+
{
256+
id: "1",
257+
type: "user",
258+
webId: "https://id.inrupt.com/user",
259+
name: "You",
260+
role: "owner",
261+
},
262+
]);
263+
};
264+
265+
const handleAddPermission = async (webId: string, role: "viewer" | "editor") => {
266+
const newPermission: Permission = {
267+
id: Date.now().toString(),
268+
type: "user",
269+
webId,
270+
name: webId.split("/").pop() || webId,
271+
role,
272+
};
273+
setPermissions((prev) => [...prev, newPermission]);
274+
};
275+
276+
const handleRemovePermission = (permissionId: string) => {
277+
setPermissions((prev) => prev.filter((p) => p.id !== permissionId));
278+
};
279+
280+
const handleUpdatePermission = (permissionId: string, role: "viewer" | "editor") => {
281+
setPermissions((prev) =>
282+
prev.map((p) => (p.id === permissionId ? { ...p, role } : p))
283+
);
284+
};
285+
286+
if (isLoadingStorages) {
287+
return (
288+
<AuthWrapper>
289+
<div className="flex min-h-screen items-center justify-center bg-white">
290+
<LoadingSpinner size="md" text="Loading your Solid storages..." />
291+
</div>
292+
</AuthWrapper>
293+
);
294+
}
295+
296+
const isBrowsing = selectedStorageId && isLoadingFiles;
297+
298+
if (storagesError) {
299+
return (
300+
<AuthWrapper>
301+
<ErrorDisplay
302+
title="Failed to Load Storages"
303+
message={storagesError.message || "Unable to discover your Solid storage roots. Please try again."}
304+
onRetry={() => window.location.reload()}
305+
/>
306+
</AuthWrapper>
307+
);
308+
}
309+
310+
if (browseError && selectedStorageId) {
311+
return (
312+
<AuthWrapper>
313+
<ErrorDisplay
314+
title="Failed to Load Container Contents"
315+
message={browseError.message || "Unable to browse the storage container. Please try again."}
316+
onRetry={() => {
317+
setCurrentPath("/");
318+
}}
319+
/>
320+
</AuthWrapper>
321+
);
322+
}
323+
324+
if (storages.length === 0) {
325+
return (
326+
<AuthWrapper>
327+
<div className="flex min-h-screen items-center justify-center bg-white">
328+
<div className="text-center">
329+
<h2 className="mb-2 text-xl font-semibold text-black">No Storages Found</h2>
330+
<p className="text-gray-600">
331+
Unable to discover any Solid storage roots from your WebID profile.
332+
</p>
333+
</div>
334+
</div>
335+
</AuthWrapper>
336+
);
337+
}
338+
339+
return (
340+
<AuthWrapper>
341+
<div className="flex h-screen flex-col overflow-hidden bg-white">
342+
<Header
343+
onMenuClick={() => setSidebarOpen(true)}
344+
/>
345+
<div className="flex flex-1 overflow-hidden">
346+
<Sidebar
347+
isOpen={sidebarOpen}
348+
onClose={() => setSidebarOpen(false)}
349+
activeTab="my-storages"
350+
/>
351+
<main className="flex flex-1 flex-col overflow-hidden">
352+
<Breadcrumb items={breadcrumbItems} onNavigate={handleBreadcrumbNavigate} />
353+
{isBrowsing ? (
354+
<div className="flex flex-1 items-center justify-center">
355+
<LoadingSpinner size="md" text="Loading folder contents..." />
356+
</div>
357+
) : (
358+
<FileList
359+
files={displayFiles}
360+
currentPath={currentPath}
361+
onFileSelect={handleFileSelect}
362+
onFileDoubleClick={handleFileDoubleClick}
363+
selectedFileIds={selectedFileIds}
364+
/>
365+
)}
366+
</main>
367+
</div>
368+
{selectedFileForPermissions && (
369+
<PermissionsDialog
370+
isOpen={permissionsDialogOpen}
371+
onClose={() => {
372+
setPermissionsDialogOpen(false);
373+
setSelectedFileForPermissions(null);
374+
}}
375+
fileName={selectedFileForPermissions.name}
376+
permissions={permissions}
377+
onAddPermission={handleAddPermission}
378+
onRemovePermission={handleRemovePermission}
379+
onUpdatePermission={handleUpdatePermission}
380+
/>
381+
)}
382+
</div>
383+
</AuthWrapper>
384+
);
385+
}
386+

0 commit comments

Comments
 (0)