diff --git a/model/genres.go b/model/genre.go
similarity index 68%
rename from model/genres.go
rename to model/genre.go
index f7da78bcb..50f945ae4 100644
--- a/model/genres.go
+++ b/model/genre.go
@@ -2,9 +2,9 @@ package model
type Genre struct {
ID string `json:"id" orm:"column(id)"`
- Name string
- SongCount int `json:"-"`
- AlbumCount int `json:"-"`
+ Name string `json:"name"`
+ SongCount int `json:"-"`
+ AlbumCount int `json:"-"`
}
type Genres []Genre
diff --git a/persistence/album_repository.go b/persistence/album_repository.go
index f0b7e16a5..12a147c65 100644
--- a/persistence/album_repository.go
+++ b/persistence/album_repository.go
@@ -82,7 +82,12 @@ func artistFilter(field string, value interface{}) Sqlizer {
}
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) {
diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go
index 651f3e353..b00f71587 100644
--- a/persistence/artist_repository.go
+++ b/persistence/artist_repository.go
@@ -49,7 +49,11 @@ func (r *artistRepository) selectArtist(options ...model.QueryOptions) SelectBui
}
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) {
diff --git a/persistence/genre_repository.go b/persistence/genre_repository.go
index 4ea6f67cf..c5c27a3ab 100644
--- a/persistence/genre_repository.go
+++ b/persistence/genre_repository.go
@@ -22,6 +22,9 @@ func NewGenreRepository(ctx context.Context, o orm.Ormer) model.GenreRepository
r.ctx = ctx
r.ormer = o
r.tableName = "genre"
+ r.filterMappings = map[string]filterFunc{
+ "name": containsFilter,
+ }
return r
}
diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go
index c2a659e80..f9307a1e1 100644
--- a/persistence/mediafile_repository.go
+++ b/persistence/mediafile_repository.go
@@ -38,7 +38,11 @@ func NewMediaFileRepository(ctx context.Context, o orm.Ormer) *mediaFileReposito
}
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) {
diff --git a/persistence/persistence.go b/persistence/persistence.go
index 953fbd878..9c816dd16 100644
--- a/persistence/persistence.go
+++ b/persistence/persistence.go
@@ -88,6 +88,8 @@ func (s *SQLStore) Resource(ctx context.Context, m interface{}) model.ResourceRe
return s.Album(ctx).(model.ResourceRepository)
case model.MediaFile:
return s.MediaFile(ctx).(model.ResourceRepository)
+ case model.Genre:
+ return s.Genre(ctx).(model.ResourceRepository)
case model.Playlist:
return s.Playlist(ctx).(model.ResourceRepository)
case model.Share:
diff --git a/server/nativeapi/native_api.go b/server/nativeapi/native_api.go
index 7c2ffc3af..2f39dd733 100644
--- a/server/nativeapi/native_api.go
+++ b/server/nativeapi/native_api.go
@@ -37,6 +37,7 @@ func (n *Router) routes() http.Handler {
n.R(r, "/song", model.MediaFile{}, true)
n.R(r, "/album", model.Album{}, 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, "/playlist", model.Playlist{}, true)
n.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig)
diff --git a/ui/src/App.js b/ui/src/App.js
index 00f2e05c0..e1a300bb7 100644
--- a/ui/src/App.js
+++ b/ui/src/App.js
@@ -108,6 +108,7 @@ const Admin = (props) => {
),
,
+ ,
,
,
,
diff --git a/ui/src/album/AlbumList.js b/ui/src/album/AlbumList.js
index 5d2e464cf..f45fbbb6a 100644
--- a/ui/src/album/AlbumList.js
+++ b/ui/src/album/AlbumList.js
@@ -42,6 +42,15 @@ const AlbumFilter = (props) => {
>
+ ({ name: [searchText] })}
+ >
+
+
{config.enableFavourites && (
diff --git a/ui/src/artist/ArtistList.js b/ui/src/artist/ArtistList.js
index 6e0c3d34c..0a93d72c3 100644
--- a/ui/src/artist/ArtistList.js
+++ b/ui/src/artist/ArtistList.js
@@ -1,11 +1,14 @@
import React, { useMemo } from 'react'
import { useHistory } from 'react-router-dom'
import {
+ AutocompleteInput,
Datagrid,
Filter,
NumberField,
+ ReferenceInput,
SearchInput,
TextField,
+ useTranslate,
} from 'react-admin'
import { useMediaQuery, withWidth } from '@material-ui/core'
import FavoriteIcon from '@material-ui/icons/Favorite'
@@ -49,18 +52,30 @@ const useStyles = makeStyles({
},
})
-const ArtistFilter = (props) => (
-
-
- {config.enableFavourites && (
- }
- defaultValue={true}
- />
- )}
-
-)
+const ArtistFilter = (props) => {
+ const translate = useTranslate()
+ return (
+
+
+ ({ name: [searchText] })}
+ >
+
+
+ {config.enableFavourites && (
+ }
+ defaultValue={true}
+ />
+ )}
+
+ )
+}
const ArtistListView = ({ hasShow, hasEdit, hasList, width, ...rest }) => {
const classes = useStyles()
diff --git a/ui/src/artist/ArtistListActions.js b/ui/src/artist/ArtistListActions.js
index 946557670..54fe31765 100644
--- a/ui/src/artist/ArtistListActions.js
+++ b/ui/src/artist/ArtistListActions.js
@@ -1,13 +1,29 @@
-import React from 'react'
+import React, { cloneElement } from 'react'
import { sanitizeListRestProps, TopToolbar } from 'react-admin'
import { useMediaQuery } from '@material-ui/core'
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'))
return (
+ {filters &&
+ cloneElement(filters, {
+ resource,
+ showFilter,
+ displayedFilters,
+ filterValues,
+ context: 'button',
+ })}
{isNotSmall && }
)
diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json
index 7fa83749a..e7069a582 100644
--- a/ui/src/i18n/en.json
+++ b/ui/src/i18n/en.json
@@ -76,6 +76,7 @@
"albumCount": "Album Count",
"songCount": "Song Count",
"playCount": "Plays",
+ "genre": "Genre",
"rating": "Rating"
}
},
diff --git a/ui/src/song/SongList.js b/ui/src/song/SongList.js
index 83aca4652..7605013b8 100644
--- a/ui/src/song/SongList.js
+++ b/ui/src/song/SongList.js
@@ -1,10 +1,13 @@
import React from 'react'
import {
+ AutocompleteInput,
Filter,
FunctionField,
NumberField,
+ ReferenceInput,
SearchInput,
TextField,
+ useTranslate,
} from 'react-admin'
import { useMediaQuery } from '@material-ui/core'
import FavoriteIcon from '@material-ui/icons/Favorite'
@@ -55,18 +58,30 @@ const useStyles = makeStyles({
},
})
-const SongFilter = (props) => (
-
-
- {config.enableFavourites && (
- }
- defaultValue={true}
- />
- )}
-
-)
+const SongFilter = (props) => {
+ const translate = useTranslate()
+ return (
+
+
+ ({ name: [searchText] })}
+ >
+
+
+ {config.enableFavourites && (
+ }
+ defaultValue={true}
+ />
+ )}
+
+ )
+}
const SongList = (props) => {
const classes = useStyles()