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 <noreply@anthropic.com>
This commit is contained in:
parent
df182ca09f
commit
9ab1cb3349
|
|
@ -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{
|
data := map[string]any{
|
||||||
"ID": rec.ID,
|
"ID": rec.ID,
|
||||||
"Filename": rec.Filename,
|
"Filename": rec.Filename,
|
||||||
"Size": rec.SizeBytes,
|
"Size": rec.SizeBytes,
|
||||||
"ContentType": rec.ContentType,
|
"ContentType": rec.ContentType,
|
||||||
"UploadedAt": rec.UploadedAt.Format("Jan 2, 2006 at 3:04 PM"),
|
"UploadedAt": rec.UploadedAt.Format("Jan 2, 2006 at 3:04 PM"),
|
||||||
"Downloads": rec.DownloadCount,
|
"Downloads": rec.DownloadCount,
|
||||||
"Previewable": isPreviewable(rec.ContentType),
|
"Previewable": isPreviewable(rec.ContentType),
|
||||||
"ViewURL": "/f/" + id + "/view",
|
"ViewURL": "/f/" + id + "/view",
|
||||||
"DownloadURL": "/f/" + id + "/dl",
|
"DownloadURL": "/f/" + id + "/dl",
|
||||||
|
"ThumbType": thumbType,
|
||||||
|
"ThumbnailURL": thumbnailURL,
|
||||||
}
|
}
|
||||||
if rec.ExpiresAt != nil {
|
if rec.ExpiresAt != nil {
|
||||||
data["ExpiresAt"] = rec.ExpiresAt.Format("Jan 2, 2006 at 3:04 PM")
|
data["ExpiresAt"] = rec.ExpiresAt.Format("Jan 2, 2006 at 3:04 PM")
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,45 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>{{.Filename}}</h1>
|
|
||||||
|
|
||||||
<div class="file-card">
|
<div class="file-card">
|
||||||
<div class="file-icon">
|
<div class="file-preview">
|
||||||
{{if .Previewable}}
|
{{if eq .ThumbType "image"}}
|
||||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
<a href="{{.ViewURL}}"><img src="{{.ThumbnailURL}}" alt="{{.Filename}}" class="thumbnail"></a>
|
||||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
{{else if eq .ThumbType "video"}}
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<div class="thumb-icon">
|
||||||
</svg>
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<polygon points="5 3 19 12 5 21 5 3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{{else if eq .ThumbType "audio"}}
|
||||||
|
<div class="thumb-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<path d="M9 18V5l12-2v13"/>
|
||||||
|
<circle cx="6" cy="18" r="3"/>
|
||||||
|
<circle cx="18" cy="16" r="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{{else if eq .ThumbType "pdf"}}
|
||||||
|
<div class="thumb-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||||
|
<polyline points="14 2 14 8 20 8"/>
|
||||||
|
<line x1="16" y1="13" x2="8" y2="13"/>
|
||||||
|
<line x1="16" y1="17" x2="8" y2="17"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
<div class="thumb-icon">
|
||||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
<polyline points="13 2 13 9 20 9"/>
|
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
|
||||||
</svg>
|
<polyline points="13 2 13 9 20 9"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 class="file-title">{{.Filename}}</h2>
|
||||||
|
|
||||||
<div class="file-meta">
|
<div class="file-meta">
|
||||||
<div class="meta-row">
|
<div class="meta-row">
|
||||||
<span class="meta-label">Size</span>
|
<span class="meta-label">Size</span>
|
||||||
|
|
|
||||||
|
|
@ -224,24 +224,51 @@ footer a:hover { color: var(--accent); }
|
||||||
|
|
||||||
/* Download page */
|
/* Download page */
|
||||||
.file-card {
|
.file-card {
|
||||||
margin-top: 1.5rem;
|
margin-top: 0;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon {
|
.file-preview {
|
||||||
text-align: center;
|
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);
|
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 {
|
.file-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-row {
|
.meta-row {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue