@@ -4,8 +4,15 @@ import { useTranslation } from 'react-i18next';
4
4
import { useHistory } from 'react-router' ;
5
5
import { Icon } from 'semantic-ui-react' ;
6
6
7
- import { Playlist } from '@nuclear/core' ;
8
- import { Button , ContextPopup , PopupButton , InputDialog , timestampToTimeString , Tooltip } from '@nuclear/ui' ;
7
+ import { Playlist , PlaylistTrack } from '@nuclear/core' ;
8
+ import {
9
+ Button ,
10
+ ContextPopup ,
11
+ PopupButton ,
12
+ InputDialog ,
13
+ timestampToTimeString ,
14
+ Tooltip
15
+ } from '@nuclear/ui' ;
9
16
import { Track } from '@nuclear/ui/lib/types' ;
10
17
11
18
import artPlaceholder from '../../../resources/media/art_placeholder.png' ;
@@ -26,7 +33,7 @@ export type PlaylistViewProps = {
26
33
onReorderTracks : ( isource : number , idest : number ) => void ;
27
34
isExternal ?: boolean ;
28
35
externalSourceName ?: string ;
29
- }
36
+ } ;
30
37
31
38
const PlaylistView : React . FC < PlaylistViewProps > = ( {
32
39
playlist,
@@ -45,16 +52,42 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
45
52
const { t, i18n } = useTranslation ( 'playlists' ) ;
46
53
const history = useHistory ( ) ;
47
54
48
- const onRenamePlaylist = useCallback ( ( name : string ) => {
49
- const updatedPlaylist = {
50
- ...playlist ,
51
- name
52
- } ;
53
- updatePlaylist ( updatedPlaylist ) ;
54
- } , [ playlist , updatePlaylist ] ) ;
55
+ const onRenamePlaylist = useCallback (
56
+ ( name : string ) => {
57
+ const updatedPlaylist = {
58
+ ...playlist ,
59
+ name
60
+ } ;
61
+ updatePlaylist ( updatedPlaylist ) ;
62
+ } ,
63
+ [ playlist , updatePlaylist ]
64
+ ) ;
65
+ const onUpdateTrack = useCallback (
66
+ ( index : number , updatedTrack : Track ) => {
67
+ const updatedTracks = playlist . tracks . map ( ( track , i ) => {
68
+ if ( i === index ) {
69
+ return {
70
+ ...updatedTrack ,
71
+ stream : ( track as PlaylistTrack ) . stream
72
+ } as PlaylistTrack ;
73
+ }
74
+ return track ;
75
+ } ) ;
76
+
77
+ const updatedPlaylist = {
78
+ ...playlist ,
79
+ tracks : updatedTracks
80
+ } as Playlist ;
55
81
56
- const onAddAll = useCallback ( ( ) => addTracks ( playlist . tracks ) ,
57
- [ addTracks , playlist ] ) ;
82
+ updatePlaylist ( updatedPlaylist ) ;
83
+ } ,
84
+ [ playlist , updatePlaylist ]
85
+ ) ;
86
+
87
+ const onAddAll = useCallback (
88
+ ( ) => addTracks ( playlist . tracks ) ,
89
+ [ addTracks , playlist ]
90
+ ) ;
58
91
59
92
const onPlayAll = useCallback ( ( ) => {
60
93
clearQueue ( ) ;
@@ -64,13 +97,16 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
64
97
} , [ addTracks , clearQueue , playlist , selectSong , startPlayback ] ) ;
65
98
66
99
const onDeleteTrack = ! isExternal
67
- ? useCallback ( ( trackToRemove : Track , trackIndex : number ) => {
68
- const newPlaylist = {
69
- ...playlist ,
70
- tracks : playlist . tracks . filter ( ( _ , index ) => index !== trackIndex )
71
- } ;
72
- updatePlaylist ( newPlaylist ) ;
73
- } , [ playlist , updatePlaylist ] )
100
+ ? useCallback (
101
+ ( trackToRemove : Track , trackIndex : number ) => {
102
+ const newPlaylist = {
103
+ ...playlist ,
104
+ tracks : playlist . tracks . filter ( ( _ , index ) => index !== trackIndex )
105
+ } ;
106
+ updatePlaylist ( newPlaylist ) ;
107
+ } ,
108
+ [ playlist , updatePlaylist ]
109
+ )
74
110
: undefined ;
75
111
76
112
const onDeletePlaylist = useCallback ( ( ) => {
@@ -94,37 +130,42 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
94
130
} , [ exportPlaylist , playlist , t ] ) ;
95
131
96
132
return (
97
- < div
98
- data-testid = 'playlist-view'
99
- className = { styles . playlist_view_container }
100
- >
133
+ < div data-testid = 'playlist-view' className = { styles . playlist_view_container } >
101
134
< div className = { styles . playlist } >
102
135
< div className = { styles . playlist_view_info } >
103
136
< div >
104
137
< img
105
138
className = { styles . playlist_thumbnail }
106
- src = { playlist ?. tracks ?. [ 0 ] ?. thumbnail ?? artPlaceholder as unknown as string }
139
+ src = {
140
+ playlist ?. tracks ?. [ 0 ] ?. thumbnail ??
141
+ ( artPlaceholder as unknown as string )
142
+ }
107
143
/>
108
144
</ div >
109
145
< div className = { styles . playlist_header } >
110
- {
111
- isExternal &&
146
+ { isExternal && (
112
147
< div className = { styles . playlist_header_external_source } >
113
148
< Tooltip
114
149
on = 'hover'
115
- content = { t ( 'external-source-tooltip' , { source : externalSourceName } ) }
150
+ content = { t ( 'external-source-tooltip' , {
151
+ source : externalSourceName
152
+ } ) }
116
153
trigger = {
117
- < div className = { styles . playlist_header_external_source_inner } >
154
+ < div
155
+ className = { styles . playlist_header_external_source_inner }
156
+ >
118
157
< Icon name = 'external square' />
119
158
{ externalSourceName }
120
159
</ div >
121
160
}
122
161
position = 'bottom center'
123
162
/>
124
163
</ div >
125
- }
164
+ ) }
126
165
< div className = { styles . playlist_header_inner } >
127
- < label className = { styles . playlist_header_label } > { t ( 'playlist' ) } </ label >
166
+ < label className = { styles . playlist_header_label } >
167
+ { t ( 'playlist' ) }
168
+ </ label >
128
169
< div className = { styles . playlist_name } >
129
170
{ playlist . name }
130
171
< InputDialog
@@ -135,32 +176,33 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
135
176
initialString = { playlist . name }
136
177
onAccept = { onRenamePlaylist }
137
178
trigger = {
138
- ! isExternal &&
139
- < Button
140
- basic
141
- aria-label = { t ( 'rename' ) }
142
- icon = 'pencil'
143
- data-testid = 'rename-button'
144
- />
179
+ ! isExternal && (
180
+ < Button
181
+ basic
182
+ aria-label = { t ( 'rename' ) }
183
+ icon = 'pencil'
184
+ data-testid = 'rename-button'
185
+ />
186
+ )
145
187
}
146
188
/>
147
189
</ div >
148
190
< div className = { styles . playlist_details } >
149
191
< span >
150
192
{ `${ playlist . tracks . length } ${ t ( 'number-of-tracks' ) } ` }
151
193
</ span >
152
- {
153
- playlist . lastModified &&
194
+ { playlist . lastModified && (
154
195
< >
155
- < span >
156
- ·
157
- </ span >
196
+ < span > ·</ span >
158
197
159
198
< span >
160
- { `${ t ( 'modified-at' ) } ${ timestampToTimeString ( playlist . lastModified , i18n . language ) } ` }
199
+ { `${ t ( 'modified-at' ) } ${ timestampToTimeString (
200
+ playlist . lastModified ,
201
+ i18n . language
202
+ ) } `}
161
203
</ span >
162
204
</ >
163
- }
205
+ ) }
164
206
</ div >
165
207
< div className = { styles . playlist_buttons } >
166
208
< Button
@@ -185,32 +227,33 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
185
227
}
186
228
artist = { null }
187
229
title = { playlist . name }
188
- thumb = { playlist ?. tracks ?. [ 0 ] ?. thumbnail ?? artPlaceholder as unknown as string }
230
+ thumb = {
231
+ playlist ?. tracks ?. [ 0 ] ?. thumbnail ??
232
+ ( artPlaceholder as unknown as string )
233
+ }
189
234
>
190
235
< PopupButton
191
236
onClick = { onAddAll }
192
237
ariaLabel = { t ( 'queue' ) }
193
238
icon = 'plus'
194
239
label = { t ( 'queue' ) }
195
240
/>
196
- {
197
- ! isExternal &&
241
+ { ! isExternal && (
198
242
< PopupButton
199
243
onClick = { onDeletePlaylist }
200
244
ariaLabel = { t ( 'delete' ) }
201
245
icon = 'trash'
202
246
label = { t ( 'delete' ) }
203
247
/>
204
- }
205
- {
206
- isExternal &&
248
+ ) }
249
+ { isExternal && (
207
250
< PopupButton
208
251
onClick = { onSaveExternalPlaylist }
209
252
ariaLabel = { t ( 'save-external-playlist' ) }
210
253
icon = 'save'
211
254
label = { t ( 'save-external-playlist' ) }
212
255
/>
213
- }
256
+ ) }
214
257
< PopupButton
215
258
onClick = { onExportPlaylist }
216
259
ariaLabel = { t ( 'export-button' ) }
@@ -228,6 +271,7 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
228
271
onReorder = { ! isExternal && onReorderTracks }
229
272
displayAlbum = { false }
230
273
displayDeleteButton = { ! isExternal }
274
+ onTrackUpdate = { ! isExternal ? onUpdateTrack : undefined }
231
275
searchable
232
276
/>
233
277
</ div >
0 commit comments