Skip to content

Commit 4d89e63

Browse files
committed
add thumbnail with caching system and make settings pages so they can be reloaded without having to re-navigate to them
1 parent 4c9720c commit 4d89e63

14 files changed

Lines changed: 203 additions & 37 deletions

File tree

app/_components/FeatureComponents/FilesPage/MobileSidebarWrapper.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ export default function MobileSidebarWrapper({
5757
touchStartY.current = null;
5858
};
5959

60-
document.addEventListener("touchstart", handleTouchStart, { passive: true });
60+
document.addEventListener("touchstart", handleTouchStart, {
61+
passive: true,
62+
});
6163
document.addEventListener("touchmove", handleTouchMove, { passive: true });
6264
document.addEventListener("touchend", handleTouchEnd);
6365

@@ -104,7 +106,8 @@ export default function MobileSidebarWrapper({
104106

105107
<aside
106108
ref={sidebarRef}
107-
className={`fixed inset-y-0 left-0 w-[80%] bg-sidebar z-50 transform transition-transform duration-300 medium:hidden overflow-hidden ${isSidebarOpen ? "translate-x-0" : "-translate-x-full"
109+
className={`fixed inset-y-0 left-0 w-[80%] bg-sidebar z-50 transform transition-transform duration-300 medium:hidden overflow-hidden ${
110+
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
108111
}`}
109112
>
110113
<div className="h-full flex flex-col">

app/_components/FeatureComponents/SettingsPage/PreferencesTab.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function PreferencesTab() {
1212
particlesEnabled: initialParticles,
1313
wandCursorEnabled: initialWand,
1414
pokemonThemesEnabled: initialPokemonThemes,
15+
showThumbnails: initialShowThumbnails,
1516
torrentPreferences,
1617
user,
1718
} = usePreferences();
@@ -25,6 +26,9 @@ export default function PreferencesTab() {
2526
const [pokemonThemesEnabled, setPokemonThemesEnabled] = useState(
2627
initialPokemonThemes ?? false
2728
);
29+
const [showThumbnails, setShowThumbnails] = useState(
30+
initialShowThumbnails ?? false
31+
);
2832
const [torrentsEnabled, setTorrentsEnabled] = useState(
2933
torrentPreferences?.enabled ?? false
3034
);
@@ -65,6 +69,15 @@ export default function PreferencesTab() {
6569
router.refresh();
6670
};
6771

72+
const handleShowThumbnailsToggle = async () => {
73+
const newValue = !showThumbnails;
74+
setShowThumbnails(newValue);
75+
await updateUserPreferences(user?.username ?? "", {
76+
showThumbnails: newValue,
77+
});
78+
router.refresh();
79+
};
80+
6881
const handleTorrentsEnabledToggle = async () => {
6982
const newValue = !torrentsEnabled;
7083
setTorrentsEnabled(newValue);
@@ -126,6 +139,19 @@ export default function PreferencesTab() {
126139
</div>
127140
</div>
128141

142+
<div>
143+
<h2 className="text-xl font-medium text-on-surface mb-6">Files</h2>
144+
<div className="p-6 bg-surface-container rounded-lg space-y-6">
145+
<Switch
146+
id="show-thumbnails"
147+
checked={showThumbnails}
148+
onChange={handleShowThumbnailsToggle}
149+
label="Image Thumbnails"
150+
description="Show image previews instead of file icons in the file list. Thumbnails are cached server-side."
151+
/>
152+
</div>
153+
</div>
154+
129155
<div>
130156
<h2 className="text-xl font-medium text-on-surface mb-6">Torrents (beta)</h2>
131157
<div className="p-6 bg-surface-container rounded-lg space-y-6">

app/_components/FeatureComponents/SettingsPage/SettingsPage.tsx

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

33
import { useState, useEffect } from "react";
4-
import { useSearchParams } from "next/navigation";
4+
import { usePathname, useRouter } from "next/navigation";
55
import Link from "next/link";
66
import ProfileTab from "@/app/_components/FeatureComponents/SettingsPage/ProfileTab";
77
import PreferencesTab from "@/app/_components/FeatureComponents/SettingsPage/PreferencesTab";
@@ -35,8 +35,8 @@ type Tab =
3535

3636
function SettingsPageContent() {
3737
const { user, torrentPreferences } = usePreferences();
38-
const searchParams = useSearchParams();
39-
const [activeTab, setActiveTab] = useState<Tab>("profile");
38+
const pathname = usePathname();
39+
const router = useRouter();
4040
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
4141
const { toggleSidebar } = useSidebar();
4242
const torrentsEnabled = torrentPreferences?.enabled ?? false;
@@ -45,31 +45,25 @@ function SettingsPageContent() {
4545
setIsUploadModalOpen(true);
4646
};
4747

48+
const validTabs: Tab[] = [
49+
"profile",
50+
"preferences",
51+
"upload",
52+
"encryption",
53+
...(torrentsEnabled ? ["torrents" as Tab] : []),
54+
...(user?.isAdmin ? ["users" as Tab, "audit-logs" as Tab] : []),
55+
];
56+
57+
const tabFromPath = pathname.split("/")[2] as Tab | undefined;
58+
const activeTab: Tab =
59+
tabFromPath && validTabs.includes(tabFromPath) ? tabFromPath : "profile";
60+
4861
useEffect(() => {
4962
if (activeTab === "torrents" && !torrentsEnabled) {
50-
setActiveTab("profile");
63+
router.replace("/settings/profile");
5164
}
5265
}, [torrentsEnabled, activeTab]);
5366

54-
useEffect(() => {
55-
const tabParam = searchParams.get("tab");
56-
if (tabParam) {
57-
const validTabs: Tab[] = [
58-
"profile",
59-
"preferences",
60-
"encryption",
61-
"users",
62-
"audit-logs",
63-
...(torrentsEnabled ? ["torrents" as Tab] : []),
64-
];
65-
if (validTabs.includes(tabParam as Tab)) {
66-
setActiveTab(tabParam as Tab);
67-
} else if (tabParam === "torrents" && !torrentsEnabled) {
68-
setActiveTab("profile");
69-
}
70-
}
71-
}, [searchParams, torrentsEnabled]);
72-
7367
if (!user) {
7468
return null;
7569
}
@@ -116,7 +110,7 @@ function SettingsPageContent() {
116110
<SettingsSidebar
117111
tabs={tabs}
118112
activeTab={activeTab}
119-
onTabChange={setActiveTab}
113+
onTabChange={(tab) => router.push(`/settings/${tab}`)}
120114
/>
121115
}
122116
>
@@ -125,7 +119,7 @@ function SettingsPageContent() {
125119
<div className="lg:hidden mb-6">
126120
<Select
127121
value={activeTab}
128-
onChange={(e) => setActiveTab(e.target.value as Tab)}
122+
onChange={(e) => router.push(`/settings/${e.target.value}`)}
129123
>
130124
{tabs.map((tab) => (
131125
<option key={tab.id} value={tab.id}>

app/_components/FeatureComponents/SettingsPage/SettingsSidebar.tsx

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

3+
import Link from "next/link";
34
import Icon from "@/app/_components/GlobalComponents/Icons/Icon";
45

56
type Tab =
@@ -20,22 +21,21 @@ interface SettingsSidebarProps {
2021
export default function SettingsSidebar({
2122
tabs,
2223
activeTab,
23-
onTabChange,
2424
}: SettingsSidebarProps) {
2525
return (
2626
<nav className="px-2 pb-2 pt-6 space-y-1">
2727
{tabs.map((tab) => (
28-
<button
28+
<Link
2929
key={tab.id}
30-
onClick={() => onTabChange(tab.id)}
30+
href={`/settings/${tab.id}`}
3131
className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors ${activeTab === tab.id
3232
? "bg-sidebar-active text-on-surface font-medium"
3333
: "text-on-surface hover:bg-surface-variant/20"
3434
}`}
3535
>
3636
<Icon icon={tab.icon} size="sm" />
3737
{tab.label}
38-
</button>
38+
</Link>
3939
))}
4040
</nav>
4141
);

app/_components/GlobalComponents/Cards/FileCard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface FileCardProps {
2424
allUsers?: User[];
2525
recursive?: boolean;
2626
hasTorrent?: boolean;
27+
showThumbnails?: boolean;
2728
}
2829

2930
export default function FileCard({
@@ -45,6 +46,7 @@ export default function FileCard({
4546
allUsers = [],
4647
recursive = false,
4748
hasTorrent = false,
49+
showThumbnails = false,
4850
}: FileCardProps) {
4951
if (viewMode === "list") {
5052
return (
@@ -66,6 +68,7 @@ export default function FileCard({
6668
allUsers={allUsers}
6769
recursive={recursive}
6870
hasTorrent={hasTorrent}
71+
showThumbnails={showThumbnails}
6972
/>
7073
);
7174
}
@@ -89,6 +92,7 @@ export default function FileCard({
8992
allUsers={allUsers}
9093
recursive={recursive}
9194
hasTorrent={hasTorrent}
95+
showThumbnails={showThumbnails}
9296
/>
9397
);
9498
}

app/_components/GlobalComponents/Cards/GridCard.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface GridCardProps {
3131
allUsers?: User[];
3232
recursive?: boolean;
3333
hasTorrent?: boolean;
34+
showThumbnails?: boolean;
3435
}
3536

3637
export default function GridCard({
@@ -51,6 +52,7 @@ export default function GridCard({
5152
allUsers = [],
5253
recursive = false,
5354
hasTorrent = false,
55+
showThumbnails = false,
5456
}: GridCardProps) {
5557
const { showContextMenu } = useContextMenu();
5658
const { encryptPath } = usePathEncryption();
@@ -356,12 +358,19 @@ export default function GridCard({
356358
);
357359
})()
358360
: (() => {
359-
const iconInfo = getFileIconInfo(
360-
(item as FileMetadata).originalName
361-
);
361+
const fileItem = item as FileMetadata;
362+
const iconInfo = getFileIconInfo(fileItem.originalName);
363+
const isImage = fileItem.mimeType?.startsWith("image/");
362364
return (
363365
<div className="relative mb-3">
364-
{iconInfo.extension ? (
366+
{showThumbnails && isImage ? (
367+
<img
368+
src={`/api/thumbnail/${encodeURIComponent(fileItem.id)}`}
369+
alt={fileItem.originalName}
370+
loading="lazy"
371+
className="w-16 h-16 object-cover rounded transition-transform group-hover:scale-105"
372+
/>
373+
) : iconInfo.extension ? (
365374
<FileIconComponent
366375
extension={iconInfo.extension}
367376
size="2xl"

app/_components/GlobalComponents/Cards/ListCard.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface ListCardProps {
3131
allUsers?: User[];
3232
recursive?: boolean;
3333
hasTorrent?: boolean;
34+
showThumbnails?: boolean;
3435
}
3536

3637
export default function ListCard({
@@ -51,6 +52,7 @@ export default function ListCard({
5152
allUsers = [],
5253
recursive = false,
5354
hasTorrent = false,
55+
showThumbnails = false,
5456
}: ListCardProps) {
5557
const { showContextMenu } = useContextMenu();
5658
const { encryptPath } = usePathEncryption();
@@ -200,7 +202,19 @@ export default function ListCard({
200202
);
201203
}
202204

203-
const iconInfo = getFileIconInfo((item as FileMetadata).originalName);
205+
const fileItem = item as FileMetadata;
206+
const iconInfo = getFileIconInfo(fileItem.originalName);
207+
const isImage = fileItem.mimeType?.startsWith("image/");
208+
if (showThumbnails && isImage) {
209+
return (
210+
<img
211+
src={`/api/thumbnail/${encodeURIComponent(fileItem.id)}`}
212+
alt={fileItem.originalName}
213+
loading="lazy"
214+
className="w-10 h-10 object-cover rounded flex-shrink-0"
215+
/>
216+
);
217+
}
204218
return iconInfo.extension ? (
205219
<FileIconComponent
206220
extension={iconInfo.extension}

app/_components/GlobalComponents/Files/FileListClient.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export default function FileListClient({
118118
});
119119

120120
const { hasTorrent, refresh: refreshTorrents } = useFileTorrents();
121-
const { torrentPreferences } = usePreferences();
121+
const { torrentPreferences, showThumbnails } = usePreferences();
122122
const torrentsEnabled = torrentPreferences?.enabled ?? false;
123123

124124
if (allFiles.length === 0 && folders.length === 0 && !isLoadingMore) {
@@ -218,6 +218,7 @@ export default function FileListClient({
218218
onToggleSelect={() => toggleFileSelection(file.id)}
219219
recursive={isRecursive}
220220
hasTorrent={hasTorrent(filePath)}
221+
showThumbnails={showThumbnails}
221222
/>
222223
);
223224
})}

app/_lib/preferences.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface UserPreferences {
1212
pokemonThemesEnabled?: boolean;
1313
customKeysPath?: string;
1414
e2eEncryptionOnTransfer?: boolean;
15+
showThumbnails?: boolean;
1516
torrentPreferences?: TorrentPreferences;
1617
dropzones?: {
1718
enabled?: boolean;
@@ -158,6 +159,8 @@ export const updateUserPreferences = async (
158159
updates.e2eEncryptionOnTransfer ??
159160
existing?.e2eEncryptionOnTransfer ??
160161
true,
162+
showThumbnails:
163+
updates.showThumbnails ?? existing?.showThumbnails ?? false,
161164
torrentPreferences: updates.torrentPreferences
162165
? {
163166
...defaultTorrentPrefs,

app/_providers/PreferencesProvider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface PreferencesContextType {
1212
encryptionKey: string | null;
1313
customKeysPath?: string;
1414
e2eEncryptionOnTransfer?: boolean;
15+
showThumbnails?: boolean;
1516
torrentPreferences?: TorrentPreferences;
1617
dropzones?: UserPreferences["dropzones"];
1718
}
@@ -24,6 +25,7 @@ const PreferencesContext = createContext<PreferencesContextType>({
2425
encryptionKey: null,
2526
customKeysPath: undefined,
2627
e2eEncryptionOnTransfer: true,
28+
showThumbnails: false,
2729
torrentPreferences: {
2830
seedRatio: 1.0,
2931
autoStartTorrents: true,

0 commit comments

Comments
 (0)