From 8a709c489ae9bb9a66e7da707b6db7c110e1b082 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 11 May 2020 21:27:00 -0400 Subject: [PATCH] Add playlist views --- persistence/playlist_repository.go | 56 +++++++++++---- persistence/playlist_tracks_repository.go | 12 ++++ ui/src/App.js | 1 + .../httpClient.js} | 11 +-- ui/src/dataProvider/index.js | 3 + ui/src/dataProvider/wrapperDataProvider.js | 68 +++++++++++++++++++ ui/src/playlist/PlaylistCreate.js | 20 ++++++ ui/src/playlist/PlaylistEdit.js | 20 ++++++ ui/src/playlist/PlaylistList.js | 11 +-- ui/src/playlist/index.js | 4 ++ 10 files changed, 175 insertions(+), 31 deletions(-) rename ui/src/{dataProvider.js => dataProvider/httpClient.js} (70%) create mode 100644 ui/src/dataProvider/index.js create mode 100644 ui/src/dataProvider/wrapperDataProvider.js create mode 100644 ui/src/playlist/PlaylistCreate.js create mode 100644 ui/src/playlist/PlaylistEdit.js diff --git a/persistence/playlist_repository.go b/persistence/playlist_repository.go index ecf92da46..e6c528a4f 100644 --- a/persistence/playlist_repository.go +++ b/persistence/playlist_repository.go @@ -77,15 +77,6 @@ func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playli return res, err } -func (r *playlistRepository) Tracks(playlistId string) model.PlaylistTracksRepository { - p := &playlistTracksRepository{} - p.playlistId = playlistId - p.ctx = r.ctx - p.ormer = r.ormer - p.tableName = "playlist_tracks" - return p -} - func (r *playlistRepository) updateTracks(id string, tracks model.MediaFiles) error { // Remove old tracks del := Delete("playlist_tracks").Where(Eq{"playlist_id": id}) @@ -94,10 +85,27 @@ func (r *playlistRepository) updateTracks(id string, tracks model.MediaFiles) er return err } - // Add new tracks - for i, t := range tracks { - ins := Insert("playlist_tracks").Columns("playlist_id", "media_file_id", "id"). - Values(id, t.ID, i) + // Break the track list in chunks to avoid hitting SQLITE_MAX_FUNCTION_ARG limit + numTracks := len(tracks) + const chunkSize = 50 + var chunks [][]model.MediaFile + for i := 0; i < numTracks; i += chunkSize { + end := i + chunkSize + if end > numTracks { + end = numTracks + } + + chunks = append(chunks, tracks[i:end]) + } + + // Add new tracks, chunk by chunk + pos := 0 + for i := range chunks { + ins := Insert("playlist_tracks").Columns("playlist_id", "media_file_id", "id") + for _, t := range chunks[i] { + ins = ins.Values(id, t.ID, pos) + pos++ + } _, err = r.executeSQL(ins) if err != nil { return err @@ -159,5 +167,25 @@ func (r *playlistRepository) NewInstance() interface{} { return &model.Playlist{} } +func (r *playlistRepository) Save(entity interface{}) (string, error) { + pls := entity.(*model.Playlist) + pls.Owner = loggedUser(r.ctx).UserName + err := r.Put(pls) + if err != nil { + return "", err + } + return pls.ID, err +} + +func (r *playlistRepository) Update(entity interface{}, cols ...string) error { + pls := entity.(*model.Playlist) + err := r.Put(pls) + if err == model.ErrNotFound { + return rest.ErrNotFound + } + return err +} + var _ model.PlaylistRepository = (*playlistRepository)(nil) -var _ model.ResourceRepository = (*playlistRepository)(nil) +var _ rest.Repository = (*playlistRepository)(nil) +var _ rest.Persistable = (*playlistRepository)(nil) diff --git a/persistence/playlist_tracks_repository.go b/persistence/playlist_tracks_repository.go index 5459cd639..af2ff5041 100644 --- a/persistence/playlist_tracks_repository.go +++ b/persistence/playlist_tracks_repository.go @@ -12,6 +12,18 @@ type playlistTracksRepository struct { playlistId string } +func (r *playlistRepository) Tracks(playlistId string) model.PlaylistTracksRepository { + p := &playlistTracksRepository{} + p.playlistId = playlistId + p.ctx = r.ctx + p.ormer = r.ormer + p.tableName = "playlist_tracks" + p.sortMappings = map[string]string{ + "id": "playlist_tracks.id", + } + return p +} + func (r *playlistTracksRepository) Count(options ...rest.QueryOptions) (int64, error) { return r.count(Select().Where(Eq{"playlist_id": r.playlistId}), r.parseRestOptions(options...)) } diff --git a/ui/src/App.js b/ui/src/App.js index 9235bbece..22fa38ded 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -71,6 +71,7 @@ const App = () => ( ), , , + , , ]} diff --git a/ui/src/dataProvider.js b/ui/src/dataProvider/httpClient.js similarity index 70% rename from ui/src/dataProvider.js rename to ui/src/dataProvider/httpClient.js index 9a9a06784..e35b44c6d 100644 --- a/ui/src/dataProvider.js +++ b/ui/src/dataProvider/httpClient.js @@ -1,14 +1,11 @@ import { fetchUtils } from 'react-admin' -import jsonServerProvider from 'ra-data-json-server' -import baseUrl from './utils/baseUrl' -import config from './config' +import baseUrl from '../utils/baseUrl' +import config from '../config' -const restUrl = '/app/api' const customAuthorizationHeader = 'X-ND-Authorization' const httpClient = (url, options = {}) => { url = baseUrl(url) - url = url.replace(restUrl + '/albumSong', restUrl + '/song') if (!options.headers) { options.headers = new Headers({ Accept: 'application/json' }) } @@ -27,6 +24,4 @@ const httpClient = (url, options = {}) => { }) } -const dataProvider = jsonServerProvider(restUrl, httpClient) - -export default dataProvider +export default httpClient diff --git a/ui/src/dataProvider/index.js b/ui/src/dataProvider/index.js new file mode 100644 index 000000000..6828ab2c5 --- /dev/null +++ b/ui/src/dataProvider/index.js @@ -0,0 +1,3 @@ +import wrapperDataProvider from './wrapperDataProvider' + +export default wrapperDataProvider diff --git a/ui/src/dataProvider/wrapperDataProvider.js b/ui/src/dataProvider/wrapperDataProvider.js new file mode 100644 index 000000000..3ece0f882 --- /dev/null +++ b/ui/src/dataProvider/wrapperDataProvider.js @@ -0,0 +1,68 @@ +import jsonServerProvider from 'ra-data-json-server' +import httpClient from './httpClient' + +const restUrl = '/app/api' + +const dataProvider = jsonServerProvider(restUrl, httpClient) + +const mapResource = (resource, params) => { + console.log('R: ', resource, 'P: ', params) + switch (resource) { + case 'albumSong': + return ['song', params] + + case 'playlistTrack': + // /api/playlistTrack?playlist_id=123 => /api/playlist/123/tracks + let plsId = '0' + if (params.filter) { + plsId = params.filter.playlist_id + delete params.filter.playlist_id + } + return [`playlist/${plsId}/tracks`, params] + + default: + return [resource, params] + } +} + +const wrapperDataProvider = { + ...dataProvider, + getList: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.getList(r, p) + }, + getOne: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.getOne(r, p) + }, + getMany: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.getMany(r, p) + }, + getManyReference: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.getManyReference(r, p) + }, + update: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.update(r, p) + }, + updateMany: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.updateMany(r, p) + }, + create: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.create(r, p) + }, + delete: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.delete(r, p) + }, + deleteMany: (resource, params) => { + const [r, p] = mapResource(resource, params) + return dataProvider.deleteMany(r, p) + }, +} + +export default wrapperDataProvider diff --git a/ui/src/playlist/PlaylistCreate.js b/ui/src/playlist/PlaylistCreate.js new file mode 100644 index 000000000..a3e1f88b8 --- /dev/null +++ b/ui/src/playlist/PlaylistCreate.js @@ -0,0 +1,20 @@ +import React from 'react' +import { + Create, + SimpleForm, + TextInput, + BooleanInput, + required, +} from 'react-admin' + +const PlaylistCreate = (props) => ( + + + + + + + +) + +export default PlaylistCreate diff --git a/ui/src/playlist/PlaylistEdit.js b/ui/src/playlist/PlaylistEdit.js new file mode 100644 index 000000000..d58b8ce26 --- /dev/null +++ b/ui/src/playlist/PlaylistEdit.js @@ -0,0 +1,20 @@ +import React from 'react' +import { + Edit, + SimpleForm, + TextInput, + BooleanInput, + required, +} from 'react-admin' + +const PlaylistEdit = (props) => ( + + + + + + + +) + +export default PlaylistEdit diff --git a/ui/src/playlist/PlaylistList.js b/ui/src/playlist/PlaylistList.js index 680283e77..6b4b2f56e 100644 --- a/ui/src/playlist/PlaylistList.js +++ b/ui/src/playlist/PlaylistList.js @@ -1,22 +1,15 @@ import React from 'react' import { - List, Datagrid, TextField, BooleanField, NumberField, DateField, } from 'react-admin' -import { DurationField, Title } from '../common' +import { DurationField, List } from '../common' const PlaylistList = (props) => ( - - } - exporter={false} - > + diff --git a/ui/src/playlist/index.js b/ui/src/playlist/index.js index e518832c3..d97692910 100644 --- a/ui/src/playlist/index.js +++ b/ui/src/playlist/index.js @@ -1,7 +1,11 @@ import PlaylistIcon from '../icons/Playlist' import PlaylistList from './PlaylistList' +import PlaylistEdit from './PlaylistEdit' +import PlaylistCreate from './PlaylistCreate' export default { icon: PlaylistIcon, list: PlaylistList, + create: PlaylistCreate, + edit: PlaylistEdit, }