diff --git a/resources/i18n/pt.json b/resources/i18n/pt.json index b313d60ed..f1c1fcf09 100644 --- a/resources/i18n/pt.json +++ b/resources/i18n/pt.json @@ -116,7 +116,8 @@ }, "actions": { "selectPlaylist": "Selecione a playlist:", - "addNewPlaylist": "Criar \"%{name}\"" + "addNewPlaylist": "Criar \"%{name}\"", + "export": "Exportar" } } }, diff --git a/server/app/playlists.go b/server/app/playlists.go index 6aa4747b1..a480118ca 100644 --- a/server/app/playlists.go +++ b/server/app/playlists.go @@ -56,16 +56,24 @@ func handleExportPlaylist(ds model.DataStore) http.HandlerFunc { return } + log.Debug(ctx, "Exporting playlist as M3U", "playlistId", plsId, "name", pls.Name) w.Header().Set("Content-Type", "audio/x-mpegurl") + disposition := fmt.Sprintf("attachment; filename=\"%s.m3u\"", pls.Name) + w.Header().Set("Content-Disposition", disposition) // TODO: Move this and the import playlist logic to `core` - w.Write([]byte("#EXTM3U\n")) + _, err = w.Write([]byte("#EXTM3U\n")) + if err != nil { + log.Error(ctx, "Error sending playlist", "name", pls.Name) + return + } for _, t := range pls.Tracks { header := fmt.Sprintf("#EXTINF:%.f,%s - %s\n", t.Duration, t.Artist, t.Title) line := t.Path + "\n" - _, err := w.Write([]byte(header + line)) + _, err = w.Write([]byte(header + line)) if err != nil { log.Error(ctx, "Error sending playlist", "name", pls.Name) + return } } } diff --git a/ui/src/consts.js b/ui/src/consts.js new file mode 100644 index 000000000..77d107519 --- /dev/null +++ b/ui/src/consts.js @@ -0,0 +1,3 @@ +export const REST_URL = '/app/api' + +export const M3U_MIME_TYPE = 'audio/x-mpegurl' diff --git a/ui/src/dataProvider/index.js b/ui/src/dataProvider/index.js index 6828ab2c5..e9ceea761 100644 --- a/ui/src/dataProvider/index.js +++ b/ui/src/dataProvider/index.js @@ -1,3 +1,6 @@ +import httpClient from './httpClient' import wrapperDataProvider from './wrapperDataProvider' +export { httpClient } + export default wrapperDataProvider diff --git a/ui/src/dataProvider/wrapperDataProvider.js b/ui/src/dataProvider/wrapperDataProvider.js index c7ea249ed..827ed1c85 100644 --- a/ui/src/dataProvider/wrapperDataProvider.js +++ b/ui/src/dataProvider/wrapperDataProvider.js @@ -1,9 +1,8 @@ import jsonServerProvider from 'ra-data-json-server' import httpClient from './httpClient' +import { REST_URL } from '../consts' -const restUrl = '/app/api' - -const dataProvider = jsonServerProvider(restUrl, httpClient) +const dataProvider = jsonServerProvider(REST_URL, httpClient) const mapResource = (resource, params) => { switch (resource) { diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index f0e0a03be..f8618f297 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -85,7 +85,8 @@ }, "actions": { "selectPlaylist": "Select a playlist:", - "addNewPlaylist": "Create \"%{name}\"" + "addNewPlaylist": "Create \"%{name}\"", + "export": "Export" } }, "user": { diff --git a/ui/src/playlist/PlaylistActions.js b/ui/src/playlist/PlaylistActions.js index 803ff9677..c82de43da 100644 --- a/ui/src/playlist/PlaylistActions.js +++ b/ui/src/playlist/PlaylistActions.js @@ -9,7 +9,10 @@ import { import PlayArrowIcon from '@material-ui/icons/PlayArrow' import ShuffleIcon from '@material-ui/icons/Shuffle' import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined' +import QueueMusicIcon from '@material-ui/icons/QueueMusic' +import { httpClient } from '../dataProvider' import { playTracks, shuffleTracks } from '../audioplayer' +import { M3U_MIME_TYPE, REST_URL } from '../consts' import subsonic from '../subsonic' const PlaylistActions = ({ @@ -18,38 +21,68 @@ const PlaylistActions = ({ data, exporter, permanentFilter, - playlistId, + record, ...rest }) => { const dispatch = useDispatch() const translate = useTranslate() + const handlePlay = React.useCallback(() => { + dispatch(playTracks(data, ids)) + }, [dispatch, data, ids]) + + const handleShuffle = React.useCallback(() => { + dispatch(shuffleTracks(data, ids)) + }, [dispatch, data, ids]) + + const handleDownload = React.useCallback(() => { + subsonic.download(record.id) + }, [record]) + + const handleExport = React.useCallback( + () => + httpClient(`${REST_URL}/playlist/${record.id}/tracks`, { + headers: new Headers({ Accept: M3U_MIME_TYPE }), + }).then((res) => { + console.log(res) + const blob = new Blob([res.body], { type: M3U_MIME_TYPE }) + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = `${record.name}.m3u` + document.body.appendChild(link) + link.click() + link.parentNode.removeChild(link) + }), + [record] + ) + return ( + ) } diff --git a/ui/src/playlist/PlaylistShow.js b/ui/src/playlist/PlaylistShow.js index b15945e13..a4499a565 100644 --- a/ui/src/playlist/PlaylistShow.js +++ b/ui/src/playlist/PlaylistShow.js @@ -26,7 +26,7 @@ const PlaylistShow = (props) => { playlistId={props.id} readOnly={isReadOnly(record && record.owner)} title={} - actions={<PlaylistActions playlistId={props.id} />} + actions={<PlaylistActions record={record} />} filter={{ playlist_id: props.id }} resource={'playlistTrack'} exporter={false}