From 8a68cecdb9df0d320da8df072883a32d80b2c3ba Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 22 May 2020 15:23:42 -0400 Subject: [PATCH] Add ToggleStar to SongContextMenu (WIP) --- conf/configuration.go | 1 + model/album.go | 13 +++---- model/annotation.go | 12 ++++++ model/artist.go | 15 +++----- model/mediafile.go | 13 +++---- persistence/album_repository.go | 10 +++-- persistence/artist_repository.go | 11 ++++-- persistence/mediafile_repository.go | 25 +++++++++++-- persistence/persistence_suite_test.go | 2 +- persistence/sql_annotations.go | 9 +++++ persistence/sql_base_repository.go | 7 +++- server/app/serve_index.go | 1 + ui/src/config.js | 1 + ui/src/song/SongContextMenu.js | 54 ++++++++++++++++++++++++--- 14 files changed, 132 insertions(+), 42 deletions(-) diff --git a/conf/configuration.go b/conf/configuration.go index d5acfd02..64fe20fd 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -37,6 +37,7 @@ type nd struct { DevLogSourceLine bool `default:"false"` DevAutoCreateAdminPassword string `default:""` DevEnableUIPlaylists bool `default:"true"` + DevEnableUIStarred bool `default:"false"` } var Server = &nd{} diff --git a/model/album.go b/model/album.go index 433b9384..6d0b7428 100644 --- a/model/album.go +++ b/model/album.go @@ -3,6 +3,8 @@ package model import "time" type Album struct { + Annotations + ID string `json:"id" orm:"column(id)"` Name string `json:"name"` CoverArtPath string `json:"coverArtPath"` @@ -25,13 +27,6 @@ type Album struct { OrderAlbumArtistName string `json:"orderAlbumArtistName"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` - - // Annotations - PlayCount int64 `json:"playCount" orm:"-"` - PlayDate time.Time `json:"playDate" orm:"-"` - Rating int `json:"rating" orm:"-"` - Starred bool `json:"starred" orm:"-"` - StarredAt time.Time `json:"starredAt" orm:"-"` } type Albums []Album @@ -48,3 +43,7 @@ type AlbumRepository interface { Refresh(ids ...string) error AnnotatedRepository } + +func (a Album) GetAnnotations() Annotations { + return a.Annotations +} diff --git a/model/annotation.go b/model/annotation.go index 505aefd8..46283e93 100644 --- a/model/annotation.go +++ b/model/annotation.go @@ -2,6 +2,18 @@ package model import "time" +type Annotations struct { + PlayCount int64 `json:"playCount"` + PlayDate time.Time `json:"playDate"` + Rating int `json:"rating"` + Starred bool `json:"starred"` + StarredAt time.Time `json:"starredAt"` +} + +type AnnotatedModel interface { + GetAnnotations() Annotations +} + type AnnotatedRepository interface { IncPlayCount(itemID string, ts time.Time) error SetStar(starred bool, itemIDs ...string) error diff --git a/model/artist.go b/model/artist.go index 1a705c3e..4bbcce1a 100644 --- a/model/artist.go +++ b/model/artist.go @@ -1,8 +1,8 @@ package model -import "time" - type Artist struct { + Annotations + ID string `json:"id" orm:"column(id)"` Name string `json:"name"` AlbumCount int `json:"albumCount"` @@ -10,13 +10,6 @@ type Artist struct { FullText string `json:"fullText"` SortArtistName string `json:"sortArtistName"` OrderArtistName string `json:"orderArtistName"` - - // Annotations - PlayCount int64 `json:"playCount" orm:"-"` - PlayDate time.Time `json:"playDate" orm:"-"` - Rating int `json:"rating" orm:"-"` - Starred bool `json:"starred" orm:"-"` - StarredAt time.Time `json:"starredAt" orm:"-"` } type Artists []Artist @@ -38,3 +31,7 @@ type ArtistRepository interface { GetIndex() (ArtistIndexes, error) AnnotatedRepository } + +func (a Artist) GetAnnotations() Annotations { + return a.Annotations +} diff --git a/model/mediafile.go b/model/mediafile.go index 10d8a6ef..675629da 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -6,6 +6,8 @@ import ( ) type MediaFile struct { + Annotations + ID string `json:"id" orm:"pk;column(id)"` Path string `json:"path"` Title string `json:"title"` @@ -36,13 +38,6 @@ type MediaFile struct { Compilation bool `json:"compilation"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` - - // Annotations - PlayCount int64 `json:"playCount" orm:"-"` - PlayDate time.Time `json:"playDate" orm:"-"` - Rating int `json:"rating" orm:"-"` - Starred bool `json:"starred" orm:"-"` - StarredAt time.Time `json:"starredAt" orm:"-"` } func (mf *MediaFile) ContentType() string { @@ -67,3 +62,7 @@ type MediaFileRepository interface { AnnotatedRepository } + +func (mf MediaFile) GetAnnotations() Annotations { + return mf.Annotations +} diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 339c0e70..19f398c0 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -74,12 +74,14 @@ func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuild func (r *albumRepository) Get(id string) (*model.Album, error) { sq := r.selectAlbum().Where(Eq{"id": id}) - var res model.Album - err := r.queryOne(sq, &res) - if err != nil { + var res model.Albums + if err := r.queryAll(sq, &res); err != nil { return nil, err } - return &res, nil + if len(res) == 0 { + return nil, model.ErrNotFound + } + return &res[0], nil } func (r *albumRepository) FindByArtist(artistId string) (model.Albums, error) { diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go index 4c191ba1..9ba4788c 100644 --- a/persistence/artist_repository.go +++ b/persistence/artist_repository.go @@ -55,9 +55,14 @@ func (r *artistRepository) Put(a *model.Artist) error { func (r *artistRepository) Get(id string) (*model.Artist, error) { sel := r.selectArtist().Where(Eq{"id": id}) - var res model.Artist - err := r.queryOne(sel, &res) - return &res, err + var res model.Artists + if err := r.queryAll(sel, &res); err != nil { + return nil, err + } + if len(res) == 0 { + return nil, model.ErrNotFound + } + return &res[0], nil } func (r *artistRepository) GetAll(options ...model.QueryOptions) (model.Artists, error) { diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 761eac56..d0d728f3 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -54,9 +54,14 @@ func (r mediaFileRepository) selectMediaFile(options ...model.QueryOptions) Sele func (r mediaFileRepository) Get(id string) (*model.MediaFile, error) { sel := r.selectMediaFile().Where(Eq{"id": id}) - var res model.MediaFile - err := r.queryOne(sel, &res) - return &res, err + var res model.MediaFiles + if err := r.queryAll(sel, &res); err != nil { + return nil, err + } + if len(res) == 0 { + return nil, model.ErrNotFound + } + return &res[0], nil } func (r mediaFileRepository) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) { @@ -155,8 +160,20 @@ func (r mediaFileRepository) EntityName() string { } func (r mediaFileRepository) NewInstance() interface{} { - return model.MediaFile{} + return &model.MediaFile{} +} + +func (r mediaFileRepository) Save(entity interface{}) (string, error) { + mf := entity.(*model.MediaFile) + err := r.Put(mf) + return mf.ID, err +} + +func (r mediaFileRepository) Update(entity interface{}, cols ...string) error { + mf := entity.(*model.MediaFile) + return r.Put(mf) } var _ model.MediaFileRepository = (*mediaFileRepository)(nil) var _ model.ResourceRepository = (*mediaFileRepository)(nil) +var _ rest.Persistable = (*mediaFileRepository)(nil) diff --git a/persistence/persistence_suite_test.go b/persistence/persistence_suite_test.go index 0f0381a2..1643622b 100644 --- a/persistence/persistence_suite_test.go +++ b/persistence/persistence_suite_test.go @@ -21,7 +21,7 @@ func TestPersistence(t *testing.T) { tests.Init(t, true) //os.Remove("./test-123.db") - //conf.Server.Path = "./test-123.db" + //conf.Server.DbPath = "./test-123.db" conf.Server.DbPath = "file::memory:?cache=shared" _ = orm.RegisterDataBase("default", db.Driver, conf.Server.DbPath) db.EnsureLatestVersion() diff --git a/persistence/sql_annotations.go b/persistence/sql_annotations.go index 50d092f7..4b8f9801 100644 --- a/persistence/sql_annotations.go +++ b/persistence/sql_annotations.go @@ -96,3 +96,12 @@ func (r sqlRepository) cleanAnnotations() error { } return nil } + +func (r sqlRepository) updateAnnotations(id string, m interface{}) error { + ans := m.(model.AnnotatedModel).GetAnnotations() + err := r.SetStar(ans.Starred, id) + if err != nil { + return err + } + return r.SetRating(ans.Rating, id) +} diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index 6ca6beed..fc42f554 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -112,6 +112,8 @@ func (r sqlRepository) executeSQL(sq Sqlizer) (int64, error) { return res.RowsAffected() } +// Note: Due to a bug in the QueryRow, this method does not map any embedded structs (ex: annotations) +// In this case, use the queryAll method and get the first item of the returned list func (r sqlRepository) queryOne(sq Sqlizer, response interface{}) error { query, args, err := sq.ToSql() if err != nil { @@ -169,7 +171,10 @@ func (r sqlRepository) put(id string, m interface{}) (newId string, err error) { return "", err } if count > 0 { - return id, nil + if _, ok := m.(model.AnnotatedModel); ok { + err = r.updateAnnotations(id, m) + } + return id, err } } // If does not have an id OR could not update (new record with predefined id) diff --git a/server/app/serve_index.go b/server/app/serve_index.go index 4c6a997e..7a43caf8 100644 --- a/server/app/serve_index.go +++ b/server/app/serve_index.go @@ -31,6 +31,7 @@ func ServeIndex(ds model.DataStore, fs http.FileSystem) http.HandlerFunc { "loginBackgroundURL": conf.Server.UILoginBackgroundURL, "enableTranscodingConfig": conf.Server.EnableTranscodingConfig, "enablePlaylists": conf.Server.DevEnableUIPlaylists, + "enableStarred": conf.Server.DevEnableUIStarred, } j, err := json.Marshal(appConfig) if err != nil { diff --git a/ui/src/config.js b/ui/src/config.js index 1c862dcd..bd6adb0f 100644 --- a/ui/src/config.js +++ b/ui/src/config.js @@ -8,6 +8,7 @@ const defaultConfig = { loginBackgroundURL: 'https://source.unsplash.com/random/1600x900?music', enableTranscodingConfig: true, enablePlaylists: true, + enableStarred: true, } let config diff --git a/ui/src/song/SongContextMenu.js b/ui/src/song/SongContextMenu.js index d650e35a..673ac1e3 100644 --- a/ui/src/song/SongContextMenu.js +++ b/ui/src/song/SongContextMenu.js @@ -1,16 +1,29 @@ import React, { useState } from 'react' +import PropTypes from 'prop-types' import { useDispatch } from 'react-redux' -import { useTranslate } from 'react-admin' +import { useUpdate, useTranslate, useRefresh, useNotify } from 'react-admin' import { IconButton, Menu, MenuItem } from '@material-ui/core' +import { makeStyles } from '@material-ui/core/styles' import MoreVertIcon from '@material-ui/icons/MoreVert' +import StarIcon from '@material-ui/icons/Star' +import StarBorderIcon from '@material-ui/icons/StarBorder' +import NestedMenuItem from 'material-ui-nested-menu-item' import { addTracks, setTrack } from '../audioplayer' import { AddToPlaylistMenu } from '../common' -import NestedMenuItem from 'material-ui-nested-menu-item' -import PropTypes from 'prop-types' +import config from '../config' -export const SongContextMenu = ({ record, onAddToPlaylist }) => { +const useStyles = makeStyles({ + noWrap: { + whiteSpace: 'nowrap', + }, +}) + +export const SongContextMenu = ({ className, record, onAddToPlaylist }) => { + const classes = useStyles() const dispatch = useDispatch() const translate = useTranslate() + const notify = useNotify() + const refresh = useRefresh() const [anchorEl, setAnchorEl] = useState(null) const options = { playNow: { @@ -41,10 +54,39 @@ export const SongContextMenu = ({ record, onAddToPlaylist }) => { e.stopPropagation() } + const [toggleStar, { toggling: loading }] = useUpdate( + 'albumSong', + record.id, + record, + { + undoable: false, + onFailure: (error) => { + console.log(error) + notify('ra.page.error', 'warning') + refresh() + }, + } + ) + + const handleToggleStar = (e, record) => { + record.starred = !record.starred + toggleStar() + e.stopPropagation() + } + const open = Boolean(anchorEl) return ( - <> + + {config.enableStarred && ( + handleToggleStar(e, record)} + size={'small'} + disabled={loading} + > + {record.starred ? : } + + )} @@ -70,7 +112,7 @@ export const SongContextMenu = ({ record, onAddToPlaylist }) => { /> - + ) }