diff options
author | Gabriel Arakaki Giovanini <mail@gabrielgio.me> | 2023-08-13 21:36:42 +0200 |
---|---|---|
committer | Gabriel Arakaki Giovanini <mail@gabrielgio.me> | 2023-08-13 21:36:42 +0200 |
commit | c51fb8cc8b850b4915e083d0dd2c30d79f8b632e (patch) | |
tree | 527ba9e4f897bc2faf997ff648aeb85cc9c247eb | |
parent | f99f2bc94808d784c92ec4e58c660a8be3ed7fce (diff) | |
download | lens-c51fb8cc8b850b4915e083d0dd2c30d79f8b632e.tar.gz lens-c51fb8cc8b850b4915e083d0dd2c30d79f8b632e.tar.bz2 lens-c51fb8cc8b850b4915e083d0dd2c30d79f8b632e.zip |
feat: Add (yet again) crude album implementation
This is a initial UI album implementation. This should cover the most
basic album navigation.
This is still plenty to do :)
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | cmd/server/main.go | 1 | ||||
-rw-r--r-- | pkg/database/repository/media.go | 13 | ||||
-rw-r--r-- | pkg/database/sql/media.go | 46 | ||||
-rw-r--r-- | pkg/view/album.go | 102 | ||||
-rw-r--r-- | pkg/view/media.go | 26 | ||||
-rw-r--r-- | pkg/worker/scanner/album_scanner.go | 1 | ||||
-rw-r--r-- | templates/album.qtpl | 50 | ||||
-rw-r--r-- | templates/base.qtpl | 6 |
9 files changed, 227 insertions, 20 deletions
@@ -30,7 +30,7 @@ compress: build compress_into_oblivion: build upx --best --ultra-brute $(OUT) -run: sass +run: sass tmpl $(GO_RUN) $(SERVER) \ --db-type=$(DB_TYPE) \ --db-con="$(DB_CON)" \ diff --git a/cmd/server/main.go b/cmd/server/main.go index 035d00a..daf5356 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -109,6 +109,7 @@ func main() { view.NewFileSystemView(*fileSystemController, settingsRepository), view.NewSettingsView(settingsRepository, userController), view.NewMediaView(mediaRepository, userRepository, settingsRepository), + view.NewAlbumView(mediaRepository, userRepository, settingsRepository), } { v.SetMyselfIn(extRouter) } diff --git a/pkg/database/repository/media.go b/pkg/database/repository/media.go index d6addbf..9915c90 100644 --- a/pkg/database/repository/media.go +++ b/pkg/database/repository/media.go @@ -35,7 +35,9 @@ type ( } Album struct { - ID uint + ID uint + Name string + Path string } MediaThumbnail struct { @@ -43,9 +45,10 @@ type ( } Pagination struct { - Page int - Size int - Path string + Page int + Size int + AlbumID *uint + Path string } CreateMedia struct { @@ -83,8 +86,10 @@ type ( CreateThumbnail(context.Context, uint, *MediaThumbnail) error ListEmptyAlbums(context.Context, *Pagination) ([]*Media, error) + ListAlbums(context.Context, uint) ([]*Album, error) ExistsAlbumByAbsolutePath(context.Context, string) (bool, error) GetAlbumByAbsolutePath(context.Context, string) (*Album, error) + GetAlbum(context.Context, uint) (*Album, error) CreateAlbum(context.Context, *CreateAlbum) (*Album, error) CreateAlbumFile(context.Context, *CreateAlbumFile) error } diff --git a/pkg/database/sql/media.go b/pkg/database/sql/media.go index 59e39ee..4b48608 100644 --- a/pkg/database/sql/media.go +++ b/pkg/database/sql/media.go @@ -23,7 +23,7 @@ type ( gorm.Model Width *float64 Height *float64 - MediaID uint + MediaID uint `gorm:"not null"` Media Media Description *string Camera *string @@ -43,8 +43,8 @@ type ( MediaThumbnail struct { gorm.Model - Path string - MediaID uint + Path string `gorm:"not null;unique"` + MediaID uint `gorm:"not null"` Media Media } @@ -53,7 +53,7 @@ type ( ParentID *uint Parent *MediaAlbum Name string - Path string + Path string `gorm:"not null; unique"` } MediaAlbumFile struct { @@ -104,7 +104,9 @@ func (m *MediaEXIF) ToModel() *repository.MediaEXIF { func (a *MediaAlbum) ToModel() *repository.Album { return &repository.Album{ - ID: a.ID, + ID: a.ID, + Name: a.Name, + Path: a.Path, } } @@ -407,6 +409,22 @@ func (r *MediaRepository) GetAlbumByAbsolutePath(ctx context.Context, path strin return m.ToModel(), nil } +func (r *MediaRepository) GetAlbum(ctx context.Context, albumID uint) (*repository.Album, error) { + m := &MediaAlbum{} + result := r.db. + WithContext(ctx). + Model(&MediaAlbum{}). + Where("id = ?", albumID). + Limit(1). + Take(m) + + if result.Error != nil { + return nil, result.Error + } + + return m.ToModel(), nil +} + func (m *MediaRepository) CreateAlbum(ctx context.Context, createAlbum *repository.CreateAlbum) (*repository.Album, error) { album := &MediaAlbum{ ParentID: createAlbum.ParentID, @@ -439,3 +457,21 @@ func (m *MediaRepository) CreateAlbumFile(ctx context.Context, createAlbumFile * return nil } + +func (m *MediaRepository) ListAlbums(ctx context.Context, albumID uint) ([]*repository.Album, error) { + albums := make([]*MediaAlbum, 0) + + result := m.db. + WithContext(ctx). + Model(&MediaAlbum{}). + Where("parent_id = ?", albumID). + Find(&albums) + + if result.Error != nil { + return nil, result.Error + } + + return list.Map(albums, func(a *MediaAlbum) *repository.Album { + return a.ToModel() + }), nil +} diff --git a/pkg/view/album.go b/pkg/view/album.go new file mode 100644 index 0000000..a96b9bd --- /dev/null +++ b/pkg/view/album.go @@ -0,0 +1,102 @@ +package view + +import ( + "net/http" + + "git.sr.ht/~gabrielgio/img/pkg/database/repository" + "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/templates" +) + +type ( + AlbumView struct { + mediaRepository repository.MediaRepository + userRepository repository.UserRepository + settingsRepository repository.SettingsRepository + } +) + +func NewAlbumView( + mediaRepository repository.MediaRepository, + userRepository repository.UserRepository, + settingsRepository repository.SettingsRepository, +) *AlbumView { + return &AlbumView{ + mediaRepository: mediaRepository, + userRepository: userRepository, + settingsRepository: settingsRepository, + } +} + +func (self *AlbumView) Index(w http.ResponseWriter, r *http.Request) error { + p := getPagination(r) + token := ext.GetTokenFromCtx(w, r) + + // TODO: optmize call, GetPathFromUserID may no be necessary + userPath, err := self.userRepository.GetPathFromUserID(r.Context(), token.UserID) + if err != nil { + return err + } + + var albums []*repository.Album + var album *repository.Album + + if p.AlbumID == nil { + // use user path as default value + p.Path = userPath + + album, err = self.mediaRepository.GetAlbumByAbsolutePath(r.Context(), p.Path) + if err != nil { + return err + } + + albums, err = self.mediaRepository.ListAlbums(r.Context(), album.ID) + if err != nil { + return err + } + } else { + album, err = self.mediaRepository.GetAlbum(r.Context(), *p.AlbumID) + if err != nil { + return err + } + + // TODO: User can enter a album out of its bounderies + p.Path = album.Path + + albums, err = self.mediaRepository.ListAlbums(r.Context(), *p.AlbumID) + if err != nil { + return err + } + + } + + medias, err := self.mediaRepository.List(r.Context(), p) + if err != nil { + return err + } + + settings, err := self.settingsRepository.Load(r.Context()) + if err != nil { + return err + } + + page := &templates.AlbumPage{ + Medias: medias, + Albums: albums, + Name: album.Name, + Next: &repository.Pagination{ + Size: p.Size, + Page: p.Page + 1, + }, + Settings: settings, + } + + templates.WritePageTemplate(w, page) + + return nil +} + +func (self *AlbumView) SetMyselfIn(r *ext.Router) { + r.GET("/album/", self.Index) + r.POST("/album/", self.Index) +} diff --git a/pkg/view/media.go b/pkg/view/media.go index c7d84ec..3124119 100644 --- a/pkg/view/media.go +++ b/pkg/view/media.go @@ -17,12 +17,14 @@ type ( } ) -func getPagination(w http.ResponseWriter, r *http.Request) *repository.Pagination { +func getPagination(r *http.Request) *repository.Pagination { var ( - size int - page int - sizeStr = r.FormValue("size") - pageStr = r.FormValue("page") + size int + page int + albumID *uint + sizeStr = r.FormValue("size") + pageStr = r.FormValue("page") + albumIDStr = r.FormValue("albumId") ) if sizeStr == "" { @@ -41,9 +43,17 @@ func getPagination(w http.ResponseWriter, r *http.Request) *repository.Paginatio page = p } + if albumIDStr == "" { + page = 0 + } else if p, err := strconv.Atoi(albumIDStr); err == nil { + id := uint(p) + albumID = &id + } + return &repository.Pagination{ - Page: page, - Size: size, + Page: page, + Size: size, + AlbumID: albumID, } } @@ -60,7 +70,7 @@ func NewMediaView( } func (self *MediaView) Index(w http.ResponseWriter, r *http.Request) error { - p := getPagination(w, r) + p := getPagination(r) token := ext.GetTokenFromCtx(w, r) userPath, err := self.userRepository.GetPathFromUserID(r.Context(), token.UserID) diff --git a/pkg/worker/scanner/album_scanner.go b/pkg/worker/scanner/album_scanner.go index 618a184..04af9bc 100644 --- a/pkg/worker/scanner/album_scanner.go +++ b/pkg/worker/scanner/album_scanner.go @@ -92,6 +92,7 @@ func FanInwards(paths []string) []string { result := make([]string, 0, len(paths)) for i := (len(paths) - 1); i >= 0; i-- { subPaths := paths[0:i] + subPaths = append([]string{"/"}, subPaths...) result = append(result, path.Join(subPaths...)) } return result diff --git a/templates/album.qtpl b/templates/album.qtpl new file mode 100644 index 0000000..ce8111e --- /dev/null +++ b/templates/album.qtpl @@ -0,0 +1,50 @@ +{% import "git.sr.ht/~gabrielgio/img/pkg/database/repository" %} + +{% code +type AlbumPage struct { + Medias []*repository.Media + Next *repository.Pagination + Settings *repository.Settings + Albums []*repository.Album + Name string +} + +func (m *AlbumPage) PreloadAttr() string { + if m.Settings.PreloadVideoMetadata { + return "metadata" + } + return "none" +} +%} + +{% func (p *AlbumPage) Title() %}Media{% endfunc %} + +{% func (p *AlbumPage) Content() %} +<h1 class="title">{%s p.Name %}</h1> +<div class="tags are-large"> +{% for _, a := range p.Albums %} + <a href="/album/?albumId={%s FromUInttoString(&a.ID) %}" class="tag">{%s a.Name %}</a> +{% endfor %} +</div> +<div class="columns is-multiline"> +{% for _, media := range p.Medias %} + <div class="card-image"> + {% if media.IsVideo() %} + <video class="image is-fit" controls muted="true" poster="/media/thumbnail/?path_hash={%s media.PathHash %}" preload="{%s p.PreloadAttr() %}"> + <source src="/media/image/?path_hash={%s media.PathHash %}" type="{%s media.MIMEType %}"> + </video> + {% else %} + <figure class="image is-fit"> + <img src="/media/thumbnail/?path_hash={%s media.PathHash %}"> + </figure> + {% endif %} + </div> +{% endfor %} +</div> +<div class="row"> + <a href="/media/?page={%d p.Next.Page %}" class="button is-pulled-right">next</a> +</div> +{% endfunc %} + +{% func (p *AlbumPage) Script() %} +{% endfunc %} diff --git a/templates/base.qtpl b/templates/base.qtpl index 5a7c3b7..772167d 100644 --- a/templates/base.qtpl +++ b/templates/base.qtpl @@ -11,8 +11,7 @@ Page { %} -{% code - func FromUInttoString(u *uint) string { +{% code func FromUInttoString(u *uint) string { if u != nil { return strconv.FormatUint(uint64(*u), 10) } @@ -40,6 +39,9 @@ Page prints a page implementing Page interface. <a href="/media/" class="navbar-item"> media </a> + <a href="/album/" class="navbar-item"> + album + </a> <a href="/settings/" class="navbar-item"> settings </a> |