Add Genre filters to UI

This commit is contained in:
Deluan 2021-07-16 19:41:49 -04:00 committed by Deluan Quintão
parent c56c7c865e
commit 20b7e5c49b
13 changed files with 108 additions and 32 deletions

View File

@ -2,9 +2,9 @@ package model
type Genre struct { type Genre struct {
ID string `json:"id" orm:"column(id)"` ID string `json:"id" orm:"column(id)"`
Name string Name string `json:"name"`
SongCount int `json:"-"` SongCount int `json:"-"`
AlbumCount int `json:"-"` AlbumCount int `json:"-"`
} }
type Genres []Genre type Genres []Genre

View File

@ -82,7 +82,12 @@ func artistFilter(field string, value interface{}) Sqlizer {
} }
func (r *albumRepository) CountAll(options ...model.QueryOptions) (int64, error) { func (r *albumRepository) CountAll(options ...model.QueryOptions) (int64, error) {
return r.count(r.selectAlbum(), options...) sql := r.selectAlbum()
sql = sql.LeftJoin("album_genres ag on album.id = ag.album_id").
LeftJoin("genre on ag.genre_id = genre.id").
GroupBy("album.id")
return r.count(sql, options...)
} }
func (r *albumRepository) Exists(id string) (bool, error) { func (r *albumRepository) Exists(id string) (bool, error) {

View File

@ -49,7 +49,11 @@ func (r *artistRepository) selectArtist(options ...model.QueryOptions) SelectBui
} }
func (r *artistRepository) CountAll(options ...model.QueryOptions) (int64, error) { func (r *artistRepository) CountAll(options ...model.QueryOptions) (int64, error) {
return r.count(r.newSelectWithAnnotation("artist.id"), options...) sql := r.newSelectWithAnnotation("artist.id")
sql = sql.LeftJoin("artist_genres ag on artist.id = ag.artist_id").
LeftJoin("genre on ag.genre_id = genre.id").
GroupBy("artist.id")
return r.count(sql, options...)
} }
func (r *artistRepository) Exists(id string) (bool, error) { func (r *artistRepository) Exists(id string) (bool, error) {

View File

@ -22,6 +22,9 @@ func NewGenreRepository(ctx context.Context, o orm.Ormer) model.GenreRepository
r.ctx = ctx r.ctx = ctx
r.ormer = o r.ormer = o
r.tableName = "genre" r.tableName = "genre"
r.filterMappings = map[string]filterFunc{
"name": containsFilter,
}
return r return r
} }

View File

@ -38,7 +38,11 @@ func NewMediaFileRepository(ctx context.Context, o orm.Ormer) *mediaFileReposito
} }
func (r *mediaFileRepository) CountAll(options ...model.QueryOptions) (int64, error) { func (r *mediaFileRepository) CountAll(options ...model.QueryOptions) (int64, error) {
return r.count(r.newSelectWithAnnotation("media_file.id"), options...) sql := r.newSelectWithAnnotation("media_file.id")
sql = sql.LeftJoin("media_file_genres mfg on media_file.id = mfg.media_file_id").
LeftJoin("genre on mfg.genre_id = genre.id").
GroupBy("media_file.id")
return r.count(sql, options...)
} }
func (r *mediaFileRepository) Exists(id string) (bool, error) { func (r *mediaFileRepository) Exists(id string) (bool, error) {

View File

@ -88,6 +88,8 @@ func (s *SQLStore) Resource(ctx context.Context, m interface{}) model.ResourceRe
return s.Album(ctx).(model.ResourceRepository) return s.Album(ctx).(model.ResourceRepository)
case model.MediaFile: case model.MediaFile:
return s.MediaFile(ctx).(model.ResourceRepository) return s.MediaFile(ctx).(model.ResourceRepository)
case model.Genre:
return s.Genre(ctx).(model.ResourceRepository)
case model.Playlist: case model.Playlist:
return s.Playlist(ctx).(model.ResourceRepository) return s.Playlist(ctx).(model.ResourceRepository)
case model.Share: case model.Share:

View File

@ -37,6 +37,7 @@ func (n *Router) routes() http.Handler {
n.R(r, "/song", model.MediaFile{}, true) n.R(r, "/song", model.MediaFile{}, true)
n.R(r, "/album", model.Album{}, true) n.R(r, "/album", model.Album{}, true)
n.R(r, "/artist", model.Artist{}, true) n.R(r, "/artist", model.Artist{}, true)
n.R(r, "/genre", model.Genre{}, true)
n.R(r, "/player", model.Player{}, true) n.R(r, "/player", model.Player{}, true)
n.R(r, "/playlist", model.Playlist{}, true) n.R(r, "/playlist", model.Playlist{}, true)
n.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig) n.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig)

View File

@ -108,6 +108,7 @@ const Admin = (props) => {
<Resource name="transcoding" /> <Resource name="transcoding" />
), ),
<Resource name="translation" />, <Resource name="translation" />,
<Resource name="genre" />,
<Resource name="playlistTrack" />, <Resource name="playlistTrack" />,
<Resource name="keepalive" />, <Resource name="keepalive" />,
<Player />, <Player />,

View File

@ -42,6 +42,15 @@ const AlbumFilter = (props) => {
> >
<AutocompleteInput emptyText="-- None --" /> <AutocompleteInput emptyText="-- None --" />
</ReferenceInput> </ReferenceInput>
<ReferenceInput
label={translate('resources.album.fields.genre')}
source="genre_id"
reference="genre"
sort={{ field: 'name', order: 'ASC' }}
filterToQuery={(searchText) => ({ name: [searchText] })}
>
<AutocompleteInput emptyText="-- None --" />
</ReferenceInput>
<NullableBooleanInput source="compilation" /> <NullableBooleanInput source="compilation" />
<NumberInput source="year" /> <NumberInput source="year" />
{config.enableFavourites && ( {config.enableFavourites && (

View File

@ -1,11 +1,14 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { import {
AutocompleteInput,
Datagrid, Datagrid,
Filter, Filter,
NumberField, NumberField,
ReferenceInput,
SearchInput, SearchInput,
TextField, TextField,
useTranslate,
} from 'react-admin' } from 'react-admin'
import { useMediaQuery, withWidth } from '@material-ui/core' import { useMediaQuery, withWidth } from '@material-ui/core'
import FavoriteIcon from '@material-ui/icons/Favorite' import FavoriteIcon from '@material-ui/icons/Favorite'
@ -49,18 +52,30 @@ const useStyles = makeStyles({
}, },
}) })
const ArtistFilter = (props) => ( const ArtistFilter = (props) => {
<Filter {...props} variant={'outlined'}> const translate = useTranslate()
<SearchInput source="name" alwaysOn /> return (
{config.enableFavourites && ( <Filter {...props} variant={'outlined'}>
<QuickFilter <SearchInput source="name" alwaysOn />
source="starred" <ReferenceInput
label={<FavoriteIcon fontSize={'small'} />} label={translate('resources.artist.fields.genre')}
defaultValue={true} source="genre_id"
/> reference="genre"
)} sort={{ field: 'name', order: 'ASC' }}
</Filter> filterToQuery={(searchText) => ({ name: [searchText] })}
) >
<AutocompleteInput emptyText="-- None --" />
</ReferenceInput>
{config.enableFavourites && (
<QuickFilter
source="starred"
label={<FavoriteIcon fontSize={'small'} />}
defaultValue={true}
/>
)}
</Filter>
)
}
const ArtistListView = ({ hasShow, hasEdit, hasList, width, ...rest }) => { const ArtistListView = ({ hasShow, hasEdit, hasList, width, ...rest }) => {
const classes = useStyles() const classes = useStyles()

View File

@ -1,13 +1,29 @@
import React from 'react' import React, { cloneElement } from 'react'
import { sanitizeListRestProps, TopToolbar } from 'react-admin' import { sanitizeListRestProps, TopToolbar } from 'react-admin'
import { useMediaQuery } from '@material-ui/core' import { useMediaQuery } from '@material-ui/core'
import { ToggleFieldsMenu } from '../common' import { ToggleFieldsMenu } from '../common'
const ArtistListActions = ({ className, ...rest }) => { const ArtistListActions = ({
className,
filters,
resource,
showFilter,
displayedFilters,
filterValues,
...rest
}) => {
const isNotSmall = useMediaQuery((theme) => theme.breakpoints.up('sm')) const isNotSmall = useMediaQuery((theme) => theme.breakpoints.up('sm'))
return ( return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}> <TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{filters &&
cloneElement(filters, {
resource,
showFilter,
displayedFilters,
filterValues,
context: 'button',
})}
{isNotSmall && <ToggleFieldsMenu resource="artist" />} {isNotSmall && <ToggleFieldsMenu resource="artist" />}
</TopToolbar> </TopToolbar>
) )

View File

@ -76,6 +76,7 @@
"albumCount": "Album Count", "albumCount": "Album Count",
"songCount": "Song Count", "songCount": "Song Count",
"playCount": "Plays", "playCount": "Plays",
"genre": "Genre",
"rating": "Rating" "rating": "Rating"
} }
}, },

View File

@ -1,10 +1,13 @@
import React from 'react' import React from 'react'
import { import {
AutocompleteInput,
Filter, Filter,
FunctionField, FunctionField,
NumberField, NumberField,
ReferenceInput,
SearchInput, SearchInput,
TextField, TextField,
useTranslate,
} from 'react-admin' } from 'react-admin'
import { useMediaQuery } from '@material-ui/core' import { useMediaQuery } from '@material-ui/core'
import FavoriteIcon from '@material-ui/icons/Favorite' import FavoriteIcon from '@material-ui/icons/Favorite'
@ -55,18 +58,30 @@ const useStyles = makeStyles({
}, },
}) })
const SongFilter = (props) => ( const SongFilter = (props) => {
<Filter {...props} variant={'outlined'}> const translate = useTranslate()
<SearchInput source="title" alwaysOn /> return (
{config.enableFavourites && ( <Filter {...props} variant={'outlined'}>
<QuickFilter <SearchInput source="title" alwaysOn />
source="starred" <ReferenceInput
label={<FavoriteIcon fontSize={'small'} />} label={translate('resources.song.fields.genre')}
defaultValue={true} source="genre_id"
/> reference="genre"
)} sort={{ field: 'name', order: 'ASC' }}
</Filter> filterToQuery={(searchText) => ({ name: [searchText] })}
) >
<AutocompleteInput emptyText="-- None --" />
</ReferenceInput>
{config.enableFavourites && (
<QuickFilter
source="starred"
label={<FavoriteIcon fontSize={'small'} />}
defaultValue={true}
/>
)}
</Filter>
)
}
const SongList = (props) => { const SongList = (props) => {
const classes = useStyles() const classes = useStyles()