1diff --git a/pkg/view/media.go b/pkg/view/media.go
2index 8a10fe0792a75afa70e81bd0cc05b080012c4c80..88ecaf2e3247dbfa0b54afe1fb165282928e063f 100644
3--- a/pkg/view/media.go
4+++ b/pkg/view/media.go
5@@ -3,6 +3,7 @@
6 import (
7 "net/http"
8 "strconv"
9+ "strings"
10
11 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
12 "git.sr.ht/~gabrielgio/img/pkg/ext"
13@@ -103,6 +104,41 @@
14 return nil
15 }
16
17+func (self *MediaView) Detail(w http.ResponseWriter, r *http.Request) error {
18+ user := ext.GetUserFromCtx(r)
19+
20+ userPath, err := self.userRepository.GetPathFromUserID(r.Context(), user.ID)
21+ if err != nil {
22+ return err
23+ }
24+
25+ pathHash := r.FormValue("path_hash")
26+
27+ media, err := self.mediaRepository.Get(r.Context(), pathHash)
28+ if err != nil {
29+ return err
30+ }
31+
32+ if !strings.Contains(media.Path, userPath) {
33+ ext.NotFound(w)
34+ return nil
35+ }
36+
37+ settings, err := self.settingsRepository.Load(r.Context())
38+ if err != nil {
39+ return err
40+ }
41+
42+ page := &templates.DetailPage{
43+ Settings: settings,
44+ Media: media,
45+ }
46+
47+ templates.WritePageTemplate(w, page, user.IsAdmin)
48+
49+ return nil
50+}
51+
52 func (self *MediaView) GetImage(w http.ResponseWriter, r *http.Request) error {
53 pathHash := r.FormValue("path_hash")
54
55@@ -134,6 +170,9 @@
56 func (self *MediaView) SetMyselfIn(r *ext.Router) {
57 r.GET("/media", self.Index)
58 r.POST("/media", self.Index)
59+
60+ r.GET("/detail", self.Detail)
61+ r.POST("/detail", self.Detail)
62
63 r.GET("/media/image", self.GetImage)
64 r.GET("/media/thumbnail", self.GetThumbnail)
65diff --git a/templates/detail.qtpl b/templates/detail.qtpl
66new file mode 100644
67index 0000000000000000000000000000000000000000..a981be9a83f2dcf3837a3286d3dbf11402390fc4
68--- /dev/null
69+++ b/templates/detail.qtpl
70@@ -0,0 +1,34 @@
71+{% import "git.sr.ht/~gabrielgio/img/pkg/database/repository" %}
72+
73+{% code
74+type DetailPage struct {
75+ Media *repository.Media
76+ Settings *repository.Settings
77+}
78+
79+func (m *DetailPage) PreloadAttr() string {
80+ if m.Settings.PreloadVideoMetadata {
81+ return "metadata"
82+ }
83+ return "none"
84+}
85+%}
86+
87+{% func (p *DetailPage) Title() %}Media{% endfunc %}
88+
89+{% func (p *DetailPage) Content() %}
90+<div class="card-image">
91+ {% if p.Media.IsVideo() %}
92+ <video class="image is-fit" controls muted="true" poster="/media/thumbnail?path_hash={%s p.Media.PathHash %}" preload="{%s p.PreloadAttr() %}">
93+ <source src="/media/image?path_hash={%s p.Media.PathHash %}" type="{%s p.Media.MIMEType %}">
94+ </video>
95+ {% else %}
96+ <figure class="image is-fit">
97+ <img src="/media/image?path_hash={%s p.Media.PathHash %}">
98+ </figure>
99+ {% endif %}
100+</div>
101+{% endfunc %}
102+
103+{% func (p *DetailPage) Script() %}
104+{% endfunc %}
105diff --git a/templates/mosaic.qtpl b/templates/mosaic.qtpl
106index 18dbcba489fef78faa1326522e834762f3efadba..21a8bae9560e8e3c9b07bfd4aa75a66ec2465360 100644
107--- a/templates/mosaic.qtpl
108+++ b/templates/mosaic.qtpl
109@@ -7,14 +7,16 @@ {% for _, c := range list.Distribuite(medias, 6) %}
110 <div class="column is-2">
111 {% for _, media := range c %}
112 <div class="card-image">
113+ <a href="/detail?path_hash={%s media.PathHash %}">
114 {% if media.IsVideo() %}
115 <video class="image is-fit" controls muted="true" poster="/media/thumbnail?path_hash={%s media.PathHash %}" preload="{%s preloadAttr %}">
116 <source src="/media/image?path_hash={%s media.PathHash %}" type="{%s media.MIMEType %}">
117 </video>
118 {% else %}
119- <figure class="image is-fit">
120- <img src="/media/thumbnail?path_hash={%s media.PathHash %}">
121- </figure>
122+ <figure class="image is-fit">
123+ <img src="/media/thumbnail?path_hash={%s media.PathHash %}">
124+ </figure>
125+ </a>
126 {% endif %}
127 </div>
128 {% endfor %}