mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
Merge 9cfa3eaaab21d6020e62eca3218b410e4cc78e3e into 08a027dbcc1ab9d3b6f22982c1954c4353a4521d
This commit is contained in:
commit
4cf30a1e02
@ -81,6 +81,7 @@ type configOptions struct {
|
||||
EnableGravatar bool
|
||||
EnableFavourites bool
|
||||
EnableStarRating bool
|
||||
SkipLowRatingInShuffle bool
|
||||
EnableUserEditing bool
|
||||
EnableArtworkUpload bool
|
||||
MaxImageUploadSize string
|
||||
@ -780,6 +781,7 @@ func setViperDefaults() {
|
||||
viper.SetDefault("enablegravatar", false)
|
||||
viper.SetDefault("enablefavourites", true)
|
||||
viper.SetDefault("enablestarrating", true)
|
||||
viper.SetDefault("skiplowratinginshuffle", false)
|
||||
viper.SetDefault("enableuserediting", true)
|
||||
viper.SetDefault("defaulttheme", "Dark")
|
||||
viper.SetDefault("defaultlanguage", "")
|
||||
|
||||
@ -100,6 +100,7 @@ var mediaFileFilter = sync.OnceValue(func() map[string]filterFunc {
|
||||
"title": fullTextFilter("media_file", "mbz_recording_id", "mbz_release_track_id"),
|
||||
"starred": annotationBoolFilter("starred"),
|
||||
"has_rating": annotationBoolFilter("rating"),
|
||||
"not_disliked": notDislikedFilter,
|
||||
"genre_id": tagIDFilter,
|
||||
"missing": booleanFilter,
|
||||
"artists_id": artistFilter,
|
||||
|
||||
@ -44,6 +44,17 @@ func (r sqlRepository) withAnnotation(query SelectBuilder, idField string) Selec
|
||||
return query
|
||||
}
|
||||
|
||||
func notDislikedFilter(_ string, value any) Sqlizer {
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if strings.ToLower(v) == "true" {
|
||||
return NotEq{"COALESCE(annotation.rating, 0)": 1}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func annotationBoolFilter(field string) func(string, any) Sqlizer {
|
||||
return func(_ string, value any) Sqlizer {
|
||||
v, ok := value.(string)
|
||||
|
||||
@ -51,6 +51,7 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl
|
||||
"enableDownloads": conf.Server.EnableDownloads,
|
||||
"enableFavourites": conf.Server.EnableFavourites,
|
||||
"enableStarRating": conf.Server.EnableStarRating,
|
||||
"skipLowRatingInShuffle": conf.Server.SkipLowRatingInShuffle,
|
||||
"defaultTheme": conf.Server.DefaultTheme,
|
||||
"defaultLanguage": conf.Server.DefaultLanguage,
|
||||
"defaultUIVolume": conf.Server.DefaultUIVolume,
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core/scrobbler"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
@ -242,6 +244,13 @@ func (api *Router) GetRandomSongs(r *http.Request) (*responses.Subsonic, error)
|
||||
}
|
||||
opts := filter.SongsByRandom(genre, fromYear, toYear)
|
||||
opts = filter.ApplyLibraryFilter(opts, musicFolderIds)
|
||||
if conf.Server.SkipLowRatingInShuffle {
|
||||
if opts.Filters != nil {
|
||||
opts.Filters = squirrel.And{opts.Filters, filter.NotDisliked()}
|
||||
} else {
|
||||
opts.Filters = filter.NotDisliked()
|
||||
}
|
||||
}
|
||||
|
||||
songs, err := api.getSongs(r.Context(), 0, size, opts)
|
||||
if err != nil {
|
||||
|
||||
@ -358,6 +358,16 @@ func (api *Router) GetSimilarSongs(r *http.Request) (*responses.Subsonic, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.Server.SkipLowRatingInShuffle {
|
||||
filtered := songs[:0]
|
||||
for _, s := range songs {
|
||||
if s.Rating != 1 {
|
||||
filtered = append(filtered, s)
|
||||
}
|
||||
}
|
||||
songs = filtered
|
||||
}
|
||||
|
||||
response := newResponse()
|
||||
response.SimilarSongs = &responses.SimilarSongs{
|
||||
Song: slice.MapWithArg(songs, ctx, childFromMediaFile),
|
||||
|
||||
@ -108,6 +108,10 @@ func SongsByRandom(genre string, fromYear, toYear int) Options {
|
||||
return addDefaultFilters(options)
|
||||
}
|
||||
|
||||
func NotDisliked() Sqlizer {
|
||||
return NotEq{"COALESCE(annotation.rating, 0)": 1}
|
||||
}
|
||||
|
||||
func SongsByArtistTitleWithLyricsFirst(artist, title string) Options {
|
||||
return addDefaultFilters(Options{
|
||||
Sort: "lyrics, updated_at",
|
||||
|
||||
@ -9,6 +9,7 @@ export const PLAYER_SET_VOLUME = 'PLAYER_SET_VOLUME'
|
||||
export const PLAYER_SET_MODE = 'PLAYER_SET_MODE'
|
||||
export const TRANSCODING_SET_PROFILE = 'TRANSCODING_SET_PROFILE'
|
||||
export const PLAYER_REFRESH_QUEUE = 'PLAYER_REFRESH_QUEUE'
|
||||
export const PLAYER_REMOVE_FROM_QUEUE = 'PLAYER_REMOVE_FROM_QUEUE'
|
||||
|
||||
export const setTrack = (data) => ({
|
||||
type: PLAYER_SET_TRACK,
|
||||
@ -114,3 +115,8 @@ export const refreshQueue = (resolvedUrls) => ({
|
||||
type: PLAYER_REFRESH_QUEUE,
|
||||
data: resolvedUrls,
|
||||
})
|
||||
|
||||
export const removeFromQueue = (trackId) => ({
|
||||
type: PLAYER_REMOVE_FROM_QUEUE,
|
||||
data: trackId,
|
||||
})
|
||||
|
||||
@ -4,6 +4,7 @@ import { useDispatch } from 'react-redux'
|
||||
import ShuffleIcon from '@material-ui/icons/Shuffle'
|
||||
import { playTracks } from '../actions'
|
||||
import PropTypes from 'prop-types'
|
||||
import config from '../config'
|
||||
|
||||
export const ShuffleAllButton = ({ filters }) => {
|
||||
const translate = useTranslate()
|
||||
@ -11,6 +12,9 @@ export const ShuffleAllButton = ({ filters }) => {
|
||||
const dispatch = useDispatch()
|
||||
const notify = useNotify()
|
||||
filters = { ...filters, missing: false }
|
||||
if (config.skipLowRatingInShuffle) {
|
||||
filters = { ...filters, not_disliked: true }
|
||||
}
|
||||
|
||||
const handleOnClick = () => {
|
||||
dataProvider
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { useDataProvider, useNotify } from 'react-admin'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import subsonic from '../subsonic'
|
||||
import { removeFromQueue, addTracks } from '../actions'
|
||||
import config from '../config'
|
||||
|
||||
export const useRating = (resource, record) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const notify = useNotify()
|
||||
const dataProvider = useDataProvider()
|
||||
const dispatch = useDispatch()
|
||||
const queue = useSelector((state) => state.player?.queue)
|
||||
const current = useSelector((state) => state.player?.current)
|
||||
const mountedRef = useRef(false)
|
||||
const rating = record.rating
|
||||
|
||||
@ -56,9 +62,20 @@ export const useRating = (resource, record) => {
|
||||
|
||||
const rate = (val, id) => {
|
||||
setLoading(true)
|
||||
const trackId = record.mediaFileId || record.id
|
||||
subsonic
|
||||
.setRating(id, val)
|
||||
.then(refreshRating)
|
||||
.then(() => {
|
||||
if (config.skipLowRatingInShuffle && queue?.length > 0 && !record.playlistId) {
|
||||
const inQueue = queue.some((item) => item.trackId === trackId)
|
||||
if (val === 1 && inQueue) {
|
||||
dispatch(removeFromQueue(trackId))
|
||||
} else if (rating === 1 && val !== 1 && !inQueue && queue.length < 500) {
|
||||
dispatch(addTracks({ [trackId]: record }))
|
||||
}
|
||||
}
|
||||
refreshRating()
|
||||
})
|
||||
.catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error setting star rating: ', e)
|
||||
|
||||
@ -17,6 +17,7 @@ const defaultConfig = {
|
||||
gaTrackingId: '',
|
||||
devActivityPanel: true,
|
||||
enableStarRating: true,
|
||||
skipLowRatingInShuffle: false,
|
||||
defaultTheme: 'Dark',
|
||||
defaultLanguage: '',
|
||||
defaultUIVolume: 100,
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
PLAYER_SYNC_QUEUE,
|
||||
PLAYER_SET_MODE,
|
||||
PLAYER_REFRESH_QUEUE,
|
||||
PLAYER_REMOVE_FROM_QUEUE,
|
||||
} from '../actions'
|
||||
import config from '../config'
|
||||
|
||||
@ -245,6 +246,12 @@ export const playerReducer = (previousState = initialState, payload) => {
|
||||
previousState.savedPlayIndex >= 0 ? previousState.savedPlayIndex : 0,
|
||||
}
|
||||
}
|
||||
case PLAYER_REMOVE_FROM_QUEUE:
|
||||
return {
|
||||
...previousState,
|
||||
queue: previousState.queue.filter((item) => item.trackId !== payload.data),
|
||||
clear: true,
|
||||
}
|
||||
default:
|
||||
return previousState
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user