Remove tracks from playlist

This commit is contained in:
Deluan 2020-05-16 18:10:08 -04:00 committed by Deluan Quintão
parent 5c95eed517
commit 12cf2f1104
6 changed files with 72 additions and 9 deletions

View File

@ -2,8 +2,6 @@ package model
import ( import (
"time" "time"
"github.com/deluan/rest"
) )
type Playlist struct { type Playlist struct {
@ -41,7 +39,8 @@ type PlaylistTrack struct {
type PlaylistTracks []PlaylistTrack type PlaylistTracks []PlaylistTrack
type PlaylistTrackRepository interface { type PlaylistTrackRepository interface {
rest.Repository ResourceRepository
Add(mediaFileIds []string) error Add(mediaFileIds []string) error
Update(mediaFileIds []string) error Update(mediaFileIds []string) error
Delete(id string) error
} }

View File

@ -123,12 +123,16 @@ func (r *playlistTrackRepository) Update(mediaFileIds []string) error {
} }
} }
return r.updateStats()
}
func (r *playlistTrackRepository) updateStats() error {
// Get total playlist duration and count // Get total playlist duration and count
statsSql := Select("sum(duration) as duration", "count(*) as count").From("media_file"). statsSql := Select("sum(duration) as duration", "count(*) as count").From("media_file").
Join("playlist_tracks f on f.media_file_id = media_file.id"). Join("playlist_tracks f on f.media_file_id = media_file.id").
Where(Eq{"playlist_id": r.playlistId}) Where(Eq{"playlist_id": r.playlistId})
var res struct{ Duration, Count float32 } var res struct{ Duration, Count float32 }
err = r.queryOne(statsSql, &res) err := r.queryOne(statsSql, &res)
if err != nil { if err != nil {
return err return err
} }
@ -142,5 +146,12 @@ func (r *playlistTrackRepository) Update(mediaFileIds []string) error {
return err return err
} }
func (r *playlistTrackRepository) Delete(id string) error {
err := r.delete(And{Eq{"playlist_id": r.playlistId}, Eq{"id": id}})
if err != nil {
return err
}
return r.updateStats()
}
var _ model.PlaylistTrackRepository = (*playlistTrackRepository)(nil) var _ model.PlaylistTrackRepository = (*playlistTrackRepository)(nil)
var _ model.ResourceRepository = (*playlistTrackRepository)(nil)

View File

@ -109,6 +109,9 @@ func (app *Router) addPlaylistTrackRoute(r chi.Router) {
r.Route("/{id}", func(r chi.Router) { r.Route("/{id}", func(r chi.Router) {
r.Use(UrlParams) r.Use(UrlParams)
r.Get("/", wrapper(rest.Get)) r.Get("/", wrapper(rest.Get))
r.Delete("/", func(w http.ResponseWriter, r *http.Request) {
deleteFromPlaylist(app.ds)(w, r)
})
}) })
r.With(UrlParams).Post("/", func(w http.ResponseWriter, r *http.Request) { r.With(UrlParams).Post("/", func(w http.ResponseWriter, r *http.Request) {
addToPlaylist(app.ds)(w, r) addToPlaylist(app.ds)(w, r)

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model" "github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/utils" "github.com/deluan/navidrome/utils"
) )
@ -13,6 +14,29 @@ type addTracksPayload struct {
Ids []string `json:"ids"` Ids []string `json:"ids"`
} }
func deleteFromPlaylist(ds model.DataStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
playlistId := utils.ParamString(r, ":playlistId")
id := r.URL.Query().Get(":id")
tracksRepo := ds.Playlist(r.Context()).Tracks(playlistId)
err := tracksRepo.Delete(id)
if err == model.ErrNotFound {
log.Warn("Track not found in playlist", "playlistId", playlistId, "id", id)
http.Error(w, "not found", http.StatusNotFound)
return
}
if err != nil {
log.Error("Error deleting track from playlist", "playlistId", playlistId, "id", id, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = w.Write([]byte("{}"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func addToPlaylist(ds model.DataStore) http.HandlerFunc { func addToPlaylist(ds model.DataStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
playlistId := utils.ParamString(r, ":playlistId") playlistId := utils.ParamString(r, ":playlistId")
@ -32,7 +56,7 @@ func addToPlaylist(ds model.DataStore) http.HandlerFunc {
// Must return an object with an ID, to satisfy ReactAdmin `create` call // Must return an object with an ID, to satisfy ReactAdmin `create` call
_, err = w.Write([]byte(fmt.Sprintf(`{"id":"%s"}`, playlistId))) _, err = w.Write([]byte(fmt.Sprintf(`{"id":"%s"}`, playlistId)))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
} }
} }

View File

@ -1,12 +1,17 @@
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux'
import { useGetOne } from 'react-admin' import { useGetOne } from 'react-admin'
import PlaylistDetails from './PlaylistDetails' import PlaylistDetails from './PlaylistDetails'
import { Title } from '../common' import { Title } from '../common'
import PlaylistSongs from './PlaylistSongs' import PlaylistSongs from './PlaylistSongs'
import PlaylistActions from './PlaylistActions' import PlaylistActions from './PlaylistActions'
import PlaylistSongBulkActions from './PlaylistSongBulkActions'
const PlaylistShow = (props) => { const PlaylistShow = (props) => {
const { data: record, loading, error } = useGetOne('playlist', props.id) const viewVersion = useSelector((s) => s.admin.ui && s.admin.ui.viewVersion)
const { data: record, loading, error } = useGetOne('playlist', props.id, {
v: viewVersion,
})
if (loading) { if (loading) {
return null return null
@ -29,8 +34,7 @@ const PlaylistShow = (props) => {
exporter={false} exporter={false}
perPage={-1} perPage={-1}
pagination={null} pagination={null}
bulkActionButtons={false} bulkActionButtons={<PlaylistSongBulkActions playlistId={props.id} />}
// bulkActionButtons={<AlbumSongBulkActions />}
/> />
</> </>
) )

View File

@ -0,0 +1,22 @@
import React, { Fragment, useEffect } from 'react'
import { BulkDeleteButton, useUnselectAll } from 'react-admin'
import PropTypes from 'prop-types'
const PlaylistSongBulkActions = ({ playlistId, ...rest }) => {
const unselectAll = useUnselectAll()
useEffect(() => {
unselectAll('playlistTrack')
// eslint-disable-next-line
}, [])
return (
<Fragment>
<BulkDeleteButton {...rest} resource={`playlist/${playlistId}/tracks`} />
</Fragment>
)
}
PlaylistSongBulkActions.propTypes = {
playlistId: PropTypes.string.isRequired,
}
export default PlaylistSongBulkActions