Skip to content

Commit 46df242

Browse files
Switched to using solid-client for storage browsing, implemented double click to browse and open storage. code base cleanup/refactor
1 parent 5cee2fc commit 46df242

13 files changed

Lines changed: 367 additions & 245 deletions

app/components/AuthWrapper.tsx

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,13 @@ export default function AuthWrapper({ children }: AuthWrapperProps) {
3131
// Get the session instance after handling redirect
3232
const session = getDefaultSession();
3333

34-
// development logs (I will remove later)
35-
console.log("=== Session Check ===");
36-
console.log("Redirect Info:", redirectInfo);
37-
console.log("Session Info:", session.info);
38-
console.log("Is Logged In:", session.info.isLoggedIn);
39-
console.log("WebID:", session.info.webId);
40-
console.log("Session ID:", session.info.sessionId);
41-
if (session.info.expirationDate) {
42-
const expDate = new Date(session.info.expirationDate);
43-
console.log("Expiration Date:", expDate.toISOString());
44-
console.log("Is Expired:", expDate <= new Date());
45-
}
46-
4734
let isLoggedIn = session.info.isLoggedIn && !!session.info.webId;
4835

4936
// Check expiration if session exists
5037
if (isLoggedIn && session.info.expirationDate) {
5138
const expirationDate = new Date(session.info.expirationDate);
5239
const now = new Date();
5340
if (expirationDate <= now) {
54-
console.log("Session expired, user needs to re-login");
5541
isLoggedIn = false;
5642
}
5743
}
@@ -60,7 +46,6 @@ export default function AuthWrapper({ children }: AuthWrapperProps) {
6046

6147
setIsAuthenticated(isLoggedIn);
6248
} catch (err) {
63-
console.error("Auth check failed:", err);
6449
const errorMessage =
6550
err instanceof Error ? err : new Error("Authentication check failed");
6651
setError(errorMessage);
@@ -83,20 +68,11 @@ export default function AuthWrapper({ children }: AuthWrapperProps) {
8368
});
8469
const session = getDefaultSession();
8570
if (session.info.isLoggedIn) {
86-
// more logs (i will remove them later)
87-
console.log("=== Authentication Response (from polling) ===");
88-
console.log("Redirect Info:", redirectInfo);
89-
console.log("Session Info:", session.info);
90-
console.log("WebID:", session.info.webId);
91-
console.log("Is Logged In:", session.info.isLoggedIn);
92-
console.log("Session ID:", session.info.sessionId);
93-
console.log("==============================================");
94-
9571
setIsAuthenticated(true);
9672
setError(null);
9773
}
9874
} catch (err) {
99-
console.error("Auth polling failed:", err);
75+
// Silent fail for polling
10076
}
10177
}, 1000);
10278

app/components/Breadcrumb.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ export default function Breadcrumb({ items, onNavigate }: BreadcrumbProps) {
3636
)}
3737
{displayItems.slice(items.length > 2 ? 1 : 0).map((item, index) => {
3838
const actualIndex = items.length > 2 ? items.length - 2 + index : index;
39+
// Use a combination of path and index to ensure unique keys
3940
return (
40-
<li key={item.path} className="flex items-center gap-1 sm:gap-2">
41+
<li key={`${item.path}-${actualIndex}`} className="flex items-center gap-1 sm:gap-2">
4142
{actualIndex > 0 && (
4243
<ChevronRightIcon className="h-4 w-4 flex-shrink-0 text-gray-400" />
4344
)}

app/components/FileItem.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useRef } from "react";
44
import Button from "./shared/Button";
55
import { EllipsisVerticalIcon } from "@heroicons/react/24/outline";
66
import { getFileIcon, formatFileSize, formatDate, type FileType } from "../lib/helpers";
@@ -33,6 +33,31 @@ export default function FileItem({
3333
isSelected = false,
3434
}: FileItemProps) {
3535
const [isHovered, setIsHovered] = useState(false);
36+
const clickCountRef = useRef(0);
37+
const clickTimeoutRef = useRef<NodeJS.Timeout | null>(null);
38+
39+
const handleClick = (e: React.MouseEvent) => {
40+
clickCountRef.current += 1;
41+
42+
if (clickCountRef.current === 1) {
43+
clickTimeoutRef.current = setTimeout(() => {
44+
if (clickCountRef.current === 1) {
45+
onSelect(file);
46+
}
47+
clickCountRef.current = 0;
48+
clickTimeoutRef.current = null;
49+
}, 300);
50+
} else if (clickCountRef.current === 2) {
51+
if (clickTimeoutRef.current) {
52+
clearTimeout(clickTimeoutRef.current);
53+
clickTimeoutRef.current = null;
54+
}
55+
clickCountRef.current = 0;
56+
e.preventDefault();
57+
e.stopPropagation();
58+
onDoubleClick(file);
59+
}
60+
};
3661

3762
if (view === "grid") {
3863
return (
@@ -43,8 +68,7 @@ export default function FileItem({
4368
}`}
4469
onMouseEnter={() => setIsHovered(true)}
4570
onMouseLeave={() => setIsHovered(false)}
46-
onClick={() => onSelect(file)}
47-
onDoubleClick={() => onDoubleClick(file)}
71+
onClick={handleClick}
4872
role="button"
4973
tabIndex={0}
5074
aria-label={`${file.type === "folder" ? "Folder" : "File"}: ${file.name}`}
@@ -66,8 +90,7 @@ export default function FileItem({
6690
}`}
6791
onMouseEnter={() => setIsHovered(true)}
6892
onMouseLeave={() => setIsHovered(false)}
69-
onClick={() => onSelect(file)}
70-
onDoubleClick={() => onDoubleClick(file)}
93+
onClick={handleClick}
7194
role="button"
7295
tabIndex={0}
7396
aria-label={`${file.type === "folder" ? "Folder" : "File"}: ${file.name}`}
@@ -91,7 +114,6 @@ export default function FileItem({
91114
aria-label="More options"
92115
onClick={(e) => {
93116
e.stopPropagation();
94-
// Handle more options
95117
}}
96118
>
97119
<EllipsisVerticalIcon className="h-4 w-4 sm:h-5 sm:w-5" />

app/components/Header.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ import ProfileIcon from "./ProfileIcon";
88
import {
99
Bars3Icon,
1010
MagnifyingGlassIcon,
11-
ShareIcon,
1211
PlusIcon,
1312
} from "@heroicons/react/24/outline";
1413

1514
interface HeaderProps {
16-
selectedFileCount?: number;
17-
onShareClick?: () => void;
1815
onMenuClick?: () => void;
1916
}
2017

21-
export default function Header({ selectedFileCount = 0, onShareClick, onMenuClick }: HeaderProps) {
18+
export default function Header({ onMenuClick }: HeaderProps) {
2219
const [searchQuery, setSearchQuery] = useState("");
2320

2421
return (
@@ -54,18 +51,6 @@ export default function Header({ selectedFileCount = 0, onShareClick, onMenuClic
5451

5552
{/* Action Buttons */}
5653
<div className="ml-auto flex items-center gap-1 sm:gap-2">
57-
{selectedFileCount > 0 && onShareClick && (
58-
<Button
59-
variant="primary"
60-
size="sm"
61-
onClick={onShareClick}
62-
className="flex h-9 items-center gap-1 sm:gap-2 sm:px-3"
63-
aria-label="Share selected files"
64-
>
65-
<ShareIcon className="h-4 w-4" />
66-
<span className="hidden sm:inline">Share</span>
67-
</Button>
68-
)}
6954
<Button
7055
variant="secondary"
7156
size="sm"

app/lib/helpers/breadcrumbUtils.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Breadcrumb utility functions for building navigation breadcrumbs
3+
*/
4+
5+
export interface BreadcrumbItem {
6+
name: string;
7+
path: string;
8+
}
9+
10+
/**
11+
* Builds breadcrumb items for navigation within a storage
12+
* @param selectedStorageId - The ID of the currently selected storage
13+
* @param selectedStorageUrl - The URL of the currently selected storage
14+
* @param selectedStorageName - The name of the currently selected storage
15+
* @param currentPath - The current path within the storage
16+
* @returns Array of breadcrumb items
17+
*/
18+
export function buildBreadcrumbItems(
19+
selectedStorageId: string | null,
20+
selectedStorageUrl: string | undefined,
21+
selectedStorageName: string | undefined,
22+
currentPath: string
23+
): BreadcrumbItem[] {
24+
if (!selectedStorageId) {
25+
return [{ name: "My Solid Storages", path: "/" }];
26+
}
27+
28+
const items: BreadcrumbItem[] = [
29+
{ name: "My Solid Storages", path: "/" },
30+
{ name: selectedStorageName || "Storage", path: selectedStorageUrl || "/" },
31+
];
32+
33+
if (currentPath !== "/" && currentPath !== selectedStorageUrl) {
34+
const storageUrl = selectedStorageUrl || "";
35+
const storagePath = new URL(storageUrl).pathname;
36+
const currentPathObj = new URL(currentPath);
37+
const relativePath = currentPathObj.pathname
38+
.replace(storagePath, "")
39+
.replace(/^\/|\/$/g, "");
40+
const segments = relativePath.split("/").filter(Boolean);
41+
42+
segments.forEach((segment, index) => {
43+
const pathSegments = segments.slice(0, index + 1);
44+
const path = storageUrl + pathSegments.join("/") + "/";
45+
items.push({ name: decodeURIComponent(segment), path });
46+
});
47+
}
48+
49+
return items;
50+
}
51+

app/lib/helpers/fileFilters.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
/**
66
* Checks if a URL or name represents a profile-related item that should be hidden from the file manager
7+
* Only filters out the actual profile card document, not the profile folder
78
* @param url - The URL to check
89
* @param name - Optional name to check (for additional filtering)
910
* @returns true if the item should be filtered out (is profile-related)
@@ -12,12 +13,13 @@ export function isProfileItem(url: string, name?: string): boolean {
1213
const urlLower = url.toLowerCase();
1314
const nameLower = name?.toLowerCase() || "";
1415

16+
// Only filter out the profile card document, not the profile folder
17+
// Profile card is typically at /profile/card or ends with /card
1518
return (
16-
urlLower.includes('/profile/') ||
17-
urlLower.includes('/card') ||
18-
urlLower.endsWith('/profile') ||
19-
urlLower.includes('profile/card') ||
20-
nameLower.includes('card')
19+
urlLower.includes('/profile/card') ||
20+
urlLower.endsWith('/card') ||
21+
(urlLower.includes('/card') && !urlLower.endsWith('/')) ||
22+
nameLower === 'card'
2123
);
2224
}
2325

app/lib/helpers/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
/**
2-
* Central export point for all helper functions
3-
* This allows importing multiple helpers from a single location
4-
*/
5-
61
export * from "./fileUtils";
72
export * from "./dateUtils";
83
export * from "./fileIcons";
94
export * from "./fileFilters";
5+
export * from "./urlUtils";
6+
export * from "./breadcrumbUtils";
107

app/lib/helpers/urlUtils.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* URL utility functions for processing and extracting information from URLs
3+
*/
4+
5+
/**
6+
* Extracts a display name from a URL
7+
* @param url - The URL to extract the name from
8+
* @returns The decoded name extracted from the URL path
9+
*/
10+
export function extractNameFromUrl(url: string): string {
11+
try {
12+
const urlObj = new URL(url);
13+
const pathParts = urlObj.pathname.split("/").filter(Boolean);
14+
let name = pathParts[pathParts.length - 1] || urlObj.hostname;
15+
16+
try {
17+
name = decodeURIComponent(name);
18+
} catch (e) {
19+
// Keep original name if decoding fails
20+
}
21+
22+
return name;
23+
} catch (e) {
24+
// If URL parsing fails, try to extract from the string directly
25+
const parts = url.split("/").filter(Boolean);
26+
const lastPart = parts[parts.length - 1] || url;
27+
try {
28+
return decodeURIComponent(lastPart);
29+
} catch {
30+
return lastPart;
31+
}
32+
}
33+
}
34+
35+
/**
36+
* Resolves a relative URL to an absolute URL
37+
* @param url - The URL to resolve (may be relative or absolute)
38+
* @param baseUrl - The base URL to resolve against
39+
* @returns The absolute URL
40+
*/
41+
export function resolveUrl(url: string, baseUrl: string): string {
42+
try {
43+
return new URL(url, baseUrl).href;
44+
} catch (e) {
45+
return url;
46+
}
47+
}
48+
49+
/**
50+
* Checks if a URL has a file extension that indicates it's likely a file
51+
* @param url - The URL to check
52+
* @returns true if the URL has a known file extension
53+
*/
54+
export function isLikelyFile(url: string): boolean {
55+
const fileExtension = url.split('.').pop()?.toLowerCase();
56+
const knownFileExtensions = ['txt', 'md', 'json', 'xml', 'html', 'css', 'js', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'pdf'];
57+
return fileExtension ? knownFileExtensions.includes(fileExtension) : false;
58+
}
59+

app/lib/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ export { useSolidStorages } from "./useSolidStorages";
1313
export type { SolidStorage } from "./useSolidStorages";
1414
export { useUserProfile } from "./useUserProfile";
1515
export type { UserProfile } from "./useUserProfile";
16+
export { useBrowseStorage } from "./useBrowseStorage";
1617

0 commit comments

Comments
 (0)