feat: Native previews for video, audio, and PDF files
Use presigned R2 URLs with native HTML elements instead of generic icons for all previewable file types on the download page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
18442a7564
commit
e938f6d9a9
|
|
@ -57,15 +57,11 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine thumbnail type
|
// Determine preview type and generate presigned URL for inline rendering
|
||||||
thumbType := "" // "image", "video", "audio", "pdf", or ""
|
thumbType := "" // "image", "video", "audio", "pdf", or ""
|
||||||
thumbnailURL := ""
|
previewURL := ""
|
||||||
if strings.HasPrefix(rec.ContentType, "image/") {
|
if strings.HasPrefix(rec.ContentType, "image/") {
|
||||||
thumbType = "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/") {
|
} else if strings.HasPrefix(rec.ContentType, "video/") {
|
||||||
thumbType = "video"
|
thumbType = "video"
|
||||||
} else if strings.HasPrefix(rec.ContentType, "audio/") {
|
} else if strings.HasPrefix(rec.ContentType, "audio/") {
|
||||||
|
|
@ -73,6 +69,11 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
} else if rec.ContentType == "application/pdf" {
|
} else if rec.ContentType == "application/pdf" {
|
||||||
thumbType = "pdf"
|
thumbType = "pdf"
|
||||||
}
|
}
|
||||||
|
if thumbType != "" {
|
||||||
|
if url, err := h.r2.PresignGet(r.Context(), rec.R2Key, rec.Filename, true); err == nil {
|
||||||
|
previewURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"ID": rec.ID,
|
"ID": rec.ID,
|
||||||
|
|
@ -84,8 +85,8 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
"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,
|
"ThumbType": thumbType,
|
||||||
"ThumbnailURL": thumbnailURL,
|
"PreviewURL": previewURL,
|
||||||
}
|
}
|
||||||
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")
|
||||||
|
|
|
||||||
|
|
@ -11,30 +11,13 @@
|
||||||
<div class="file-card">
|
<div class="file-card">
|
||||||
<div class="file-preview">
|
<div class="file-preview">
|
||||||
{{if eq .ThumbType "image"}}
|
{{if eq .ThumbType "image"}}
|
||||||
<a href="{{.ViewURL}}"><img src="{{.ThumbnailURL}}" alt="{{.Filename}}" class="thumbnail"></a>
|
<a href="{{.ViewURL}}"><img src="{{.PreviewURL}}" alt="{{.Filename}}" class="thumbnail"></a>
|
||||||
{{else if eq .ThumbType "video"}}
|
{{else if eq .ThumbType "video"}}
|
||||||
<div class="thumb-icon">
|
<video src="{{.PreviewURL}}" class="thumbnail" preload="metadata" controls muted playsinline></video>
|
||||||
<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"}}
|
{{else if eq .ThumbType "audio"}}
|
||||||
<div class="thumb-icon">
|
<audio src="{{.PreviewURL}}" class="audio-preview" preload="metadata" controls></audio>
|
||||||
<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"}}
|
{{else if eq .ThumbType "pdf"}}
|
||||||
<div class="thumb-icon">
|
<iframe src="{{.PreviewURL}}" class="pdf-preview" title="{{.Filename}}"></iframe>
|
||||||
<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}}
|
||||||
<div class="thumb-icon">
|
<div class="thumb-icon">
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,21 @@ footer a:hover { color: var(--accent); }
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audio-preview {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 360px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
.thumb-icon {
|
.thumb-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue