Add ToggleStar to SongContextMenu (WIP)

This commit is contained in:
Deluan 2020-05-22 15:23:42 -04:00
parent e21262675e
commit 8a68cecdb9
14 changed files with 132 additions and 42 deletions

View File

@ -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{}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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)

View File

@ -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()

View File

@ -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)
}

View File

@ -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)

View File

@ -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 {

View File

@ -8,6 +8,7 @@ const defaultConfig = {
loginBackgroundURL: 'https://source.unsplash.com/random/1600x900?music',
enableTranscodingConfig: true,
enablePlaylists: true,
enableStarred: true,
}
let config

View File

@ -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 (
<>
<span className={`${classes.noWrap} ${className}`}>
{config.enableStarred && (
<IconButton
onClick={(e) => handleToggleStar(e, record)}
size={'small'}
disabled={loading}
>
{record.starred ? <StarIcon /> : <StarBorderIcon />}
</IconButton>
)}
<IconButton onClick={handleClick} size={'small'}>
<MoreVertIcon />
</IconButton>
@ -70,7 +112,7 @@ export const SongContextMenu = ({ record, onAddToPlaylist }) => {
/>
</NestedMenuItem>
</Menu>
</>
</span>
)
}