From 9ab1cb3349694bca902f601e2efba50956133f0e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Mar 2026 10:47:44 -0700 Subject: [PATCH] feat: Add file thumbnails and fix download page layout - Images show actual thumbnail from R2 (presigned inline URL) - Video/audio/PDF show type-specific icons - Use flexbox gap for consistent spacing (no overlap) - Move filename into card as h2, remove top-level h1 Co-Authored-By: Claude Opus 4.6 --- internal/handler/download.go | 37 ++++++++++++++++++++++------- web/download.html | 46 ++++++++++++++++++++++++++---------- web/static/style.css | 35 +++++++++++++++++++++++---- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/internal/handler/download.go b/internal/handler/download.go index 9573f9c..56220fb 100644 --- a/internal/handler/download.go +++ b/internal/handler/download.go @@ -57,16 +57,35 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) { } } + // Determine thumbnail type + thumbType := "" // "image", "video", "audio", "pdf", or "" + thumbnailURL := "" + if strings.HasPrefix(rec.ContentType, "image/") { + thumbType = "image" + // Generate inline presigned URL for the image thumbnail + if url, err := h.r2.PresignGet(r.Context(), rec.R2Key, rec.Filename, true); err == nil { + thumbnailURL = url + } + } else if strings.HasPrefix(rec.ContentType, "video/") { + thumbType = "video" + } else if strings.HasPrefix(rec.ContentType, "audio/") { + thumbType = "audio" + } else if rec.ContentType == "application/pdf" { + thumbType = "pdf" + } + data := map[string]any{ - "ID": rec.ID, - "Filename": rec.Filename, - "Size": rec.SizeBytes, - "ContentType": rec.ContentType, - "UploadedAt": rec.UploadedAt.Format("Jan 2, 2006 at 3:04 PM"), - "Downloads": rec.DownloadCount, - "Previewable": isPreviewable(rec.ContentType), - "ViewURL": "/f/" + id + "/view", - "DownloadURL": "/f/" + id + "/dl", + "ID": rec.ID, + "Filename": rec.Filename, + "Size": rec.SizeBytes, + "ContentType": rec.ContentType, + "UploadedAt": rec.UploadedAt.Format("Jan 2, 2006 at 3:04 PM"), + "Downloads": rec.DownloadCount, + "Previewable": isPreviewable(rec.ContentType), + "ViewURL": "/f/" + id + "/view", + "DownloadURL": "/f/" + id + "/dl", + "ThumbType": thumbType, + "ThumbnailURL": thumbnailURL, } if rec.ExpiresAt != nil { data["ExpiresAt"] = rec.ExpiresAt.Format("Jan 2, 2006 at 3:04 PM") diff --git a/web/download.html b/web/download.html index 8d0a5c8..72c463d 100644 --- a/web/download.html +++ b/web/download.html @@ -8,23 +8,45 @@
-

{{.Filename}}

-
-
- {{if .Previewable}} - - - - +
+ {{if eq .ThumbType "image"}} + {{.Filename}} + {{else if eq .ThumbType "video"}} +
+ + + +
+ {{else if eq .ThumbType "audio"}} +
+ + + + + +
+ {{else if eq .ThumbType "pdf"}} +
+ + + + + + +
{{else}} - - - - +
+ + + + +
{{end}}
+

{{.Filename}}

+
Size diff --git a/web/static/style.css b/web/static/style.css index 41c7ff7..5d4f459 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -224,24 +224,51 @@ footer a:hover { color: var(--accent); } /* Download page */ .file-card { - margin-top: 1.5rem; + margin-top: 0; padding: 1.5rem; background: var(--surface); border-radius: 12px; border: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 1.25rem; } -.file-icon { +.file-preview { text-align: center; +} + +.thumbnail { + max-width: 100%; + max-height: 280px; + border-radius: 8px; + object-fit: contain; + background: var(--bg); + display: block; + margin: 0 auto; +} + +.thumb-icon { + display: flex; + align-items: center; + justify-content: center; + height: 120px; color: var(--text-dim); - margin-bottom: 1.25rem; + background: var(--bg); + border-radius: 8px; +} + +.file-title { + font-size: 1rem; + font-weight: 600; + word-break: break-all; + line-height: 1.4; } .file-meta { display: flex; flex-direction: column; gap: 0.5rem; - margin-bottom: 1.25rem; } .meta-row {