Improve gallery video UX and add upload-to-publish media workflow.
Stage raw files in upload/, publish with make sync-media/publish, and polish the lightbox: autoplay, remembered volume, Escape to close, and image/video icons without poster or caption clutter. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,9 +33,7 @@ templ GalleryGrid(images []gallery.Image) {
|
||||
} else {
|
||||
<span class="gallery-placeholder" aria-hidden="true"></span>
|
||||
}
|
||||
if img.IsVideo {
|
||||
<span class="gallery-video-badge" aria-hidden="true">Video</span>
|
||||
}
|
||||
@GalleryMediaIcon(img.IsVideo)
|
||||
if img.Collection != "" {
|
||||
<span class="gallery-album">{ img.Collection }</span>
|
||||
} else if img.Album != "" {
|
||||
@@ -49,6 +47,20 @@ templ GalleryGrid(images []gallery.Image) {
|
||||
}
|
||||
}
|
||||
|
||||
templ GalleryMediaIcon(isVideo bool) {
|
||||
<span class="gallery-media-icon" aria-hidden="true">
|
||||
if isVideo {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"></path>
|
||||
</svg>
|
||||
} else {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path>
|
||||
</svg>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
|
||||
templ ImageModal(img gallery.Image, prevPath, nextPath string) {
|
||||
<div
|
||||
class="modal-backdrop"
|
||||
@@ -65,21 +77,12 @@ templ ImageModal(img gallery.Image, prevPath, nextPath string) {
|
||||
</button>
|
||||
<figure class="modal-figure">
|
||||
if img.IsVideo {
|
||||
<div class="modal-video">
|
||||
<video controls playsinline preload="auto" poster={ img.ThumbURL }>
|
||||
<source src={ img.URL } type={ videoMIME(img.Filename) }/>
|
||||
</video>
|
||||
<p class="video-unavailable" hidden>
|
||||
Playback is not supported in this browser.
|
||||
<a href={ img.URL } download={ img.Filename }>Download the video</a>
|
||||
</p>
|
||||
<div class="modal-video-stack">
|
||||
<video src={ img.URL } controls autoplay playsinline preload="auto"></video>
|
||||
</div>
|
||||
} else {
|
||||
<img src={ img.URL } alt={ img.Filename }/>
|
||||
}
|
||||
if !img.Date.IsZero() {
|
||||
<figcaption>{ modalCaption(img) }</figcaption>
|
||||
}
|
||||
</figure>
|
||||
<nav class="modal-nav" aria-label="Gallery navigation">
|
||||
if prevPath != "" {
|
||||
@@ -124,13 +127,3 @@ func modalAriaLabel(img gallery.Image) string {
|
||||
}
|
||||
return "Image viewer"
|
||||
}
|
||||
|
||||
func modalCaption(img gallery.Image) string {
|
||||
if img.Collection != "" {
|
||||
return img.Collection + " — " + formatDate(img.Date)
|
||||
}
|
||||
if img.Album != "" {
|
||||
return gallery.AlbumLabel(img.Album) + " — " + formatDate(img.Date)
|
||||
}
|
||||
return formatDate(img.Date)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
import "time"
|
||||
|
||||
func currentYear() string {
|
||||
return time.Now().Format("2006")
|
||||
}
|
||||
|
||||
func videoMIME(filename string) string {
|
||||
switch strings.ToLower(filepath.Ext(filename)) {
|
||||
case ".webm":
|
||||
return "video/webm"
|
||||
case ".mov":
|
||||
return "video/quicktime"
|
||||
default:
|
||||
return "video/mp4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,27 +68,63 @@ templ Layout(title string, preloadImage string, content templ.Component) {
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
hydrateLazyThumbs(document);
|
||||
var modalRoot = document.getElementById("modal-root");
|
||||
if (!modalRoot) return;
|
||||
modalRoot.addEventListener("volumechange", function (event) {
|
||||
if (event.target.tagName !== "VIDEO") return;
|
||||
saveVideoVolume(event.target.volume);
|
||||
});
|
||||
});
|
||||
|
||||
function initModalVideos(root) {
|
||||
root.querySelectorAll(".modal-video video").forEach(function (video) {
|
||||
var wrap = video.closest(".modal-video");
|
||||
var notice = wrap && wrap.querySelector(".video-unavailable");
|
||||
video.addEventListener(
|
||||
"error",
|
||||
function () {
|
||||
video.classList.add("video-broken");
|
||||
if (notice) notice.hidden = false;
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
video.load();
|
||||
});
|
||||
function closeModal() {
|
||||
var modal = document.getElementById("modal-root");
|
||||
if (modal) modal.innerHTML = "";
|
||||
}
|
||||
|
||||
var galleryVideoVolumeKey = "gallery-video-volume";
|
||||
|
||||
function getSavedVideoVolume() {
|
||||
try {
|
||||
var stored = localStorage.getItem(galleryVideoVolumeKey);
|
||||
if (stored === null) return null;
|
||||
var v = parseFloat(stored);
|
||||
if (!isFinite(v) || v < 0 || v > 1) return null;
|
||||
return v;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveVideoVolume(volume) {
|
||||
try {
|
||||
localStorage.setItem(galleryVideoVolumeKey, String(volume));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function playModalVideo() {
|
||||
var video = document.querySelector("#modal-root video");
|
||||
if (!video) return;
|
||||
var saved = getSavedVideoVolume();
|
||||
if (saved !== null) video.volume = saved;
|
||||
var p = video.play();
|
||||
if (p && typeof p.catch === "function") {
|
||||
p.catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key !== "Escape") return;
|
||||
var modal = document.getElementById("modal-root");
|
||||
if (!modal || !modal.firstElementChild) return;
|
||||
event.preventDefault();
|
||||
closeModal();
|
||||
});
|
||||
|
||||
document.body.addEventListener("htmx:afterSwap", function (event) {
|
||||
hydrateLazyThumbs(event.target);
|
||||
initModalVideos(event.target);
|
||||
if (event.detail && event.detail.target && event.detail.target.id === "modal-root") {
|
||||
playModalVideo();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user