+2
-1
CHANGELOG.md
+2
-1
CHANGELOG.md
···
3
3
## 3.5.0
4
4
5
5
- **Improve audio playback, processing and error handling**.
6
-
- **No longer resets shuffle history when the collection or search changes**.
6
+
- **Added collections, an alternative to playlists**. Tracks can only appear once in a collection and cannot be ordered.
7
7
- **Basic setup for themes**.
8
+
- No longer resets shuffle history when the collection or search changes.
8
9
- WebDAV improvements.
9
10
- Removed Fission/Webnative user layer (discontinued).
10
11
- Minor improvements/fixes to the artwork downloading process.
+89
-20
src/Core/Themes/Sunrise/Playlists/View.elm
+89
-20
src/Core/Themes/Sunrise/Playlists/View.elm
···
46
46
Index ->
47
47
index playlists selectedPlaylist bgColor authMethodSupportsPublicData
48
48
49
-
New ->
50
-
new
49
+
NewCollection ->
50
+
newCollection
51
+
52
+
NewPlaylist ->
53
+
newPlaylist
51
54
)
52
55
53
56
···
61
64
isSelected playlist =
62
65
Maybe.map (\s -> ( s.autoGenerated, s.name )) selectedPlaylist == Just ( playlist.autoGenerated, playlist.name )
63
66
64
-
customPlaylists =
67
+
( customCollections, customPlaylists ) =
65
68
playlists
66
69
|> List.filterNot (.autoGenerated >> Maybe.isJust)
67
70
|> List.sortBy lowercaseName
71
+
|> List.partition .collection
68
72
69
73
customPlaylistListItem playlist =
70
74
if isSelected playlist then
···
104
108
, isSelected = False
105
109
}
106
110
107
-
directoryPlaylists =
111
+
directoryCollections =
108
112
playlists
109
113
|> List.filter (.autoGenerated >> Maybe.isJust)
110
114
|> List.sortBy lowercaseName
···
135
139
, NavigateToPage Page.Index
136
140
)
137
141
, ( Icon Icons.add
142
+
, Label "Create a new collection" Shown
143
+
, NavigateToPage (Page.Playlists NewCollection)
144
+
)
145
+
, ( Icon Icons.add
138
146
, Label "Create a new playlist" Shown
139
-
, NavigateToPage (Page.Playlists New)
147
+
, NavigateToPage (Page.Playlists NewPlaylist)
140
148
)
141
149
]
142
150
···
153
161
154
162
else
155
163
Kit.canister
156
-
[ Kit.h1 "Playlists"
164
+
[ Kit.h1 "Collections & Playlists"
157
165
158
166
-- Intro
159
167
--------
160
168
, intro
161
169
170
+
-- Custom Collections
171
+
---------------------
172
+
, if List.isEmpty customCollections then
173
+
nothing
174
+
175
+
else
176
+
raw
177
+
[ category "Your Collections"
178
+
, Themes.Sunrise.List.view
179
+
Themes.Sunrise.List.Normal
180
+
(List.map customPlaylistListItem customCollections)
181
+
]
182
+
162
183
-- Custom Playlists
163
184
-------------------
164
185
, if List.isEmpty customPlaylists then
···
172
193
(List.map customPlaylistListItem customPlaylists)
173
194
]
174
195
175
-
-- Directory Playlists
176
-
----------------------
177
-
, if List.isEmpty directoryPlaylists then
196
+
-- Directory Collections
197
+
------------------------
198
+
, if List.isEmpty directoryCollections then
178
199
nothing
179
200
180
201
else
181
202
raw
182
-
[ category "Autogenerated Directory Playlists"
203
+
[ category "Autogenerated Directory Collections"
183
204
, Themes.Sunrise.List.view
184
205
Themes.Sunrise.List.Normal
185
-
(List.map directoryPlaylistListItem directoryPlaylists)
206
+
(List.map directoryPlaylistListItem directoryCollections)
186
207
]
187
208
]
188
209
···
191
212
Kit.centeredContent
192
213
[ slab
193
214
Html.a
194
-
[ href (Page.toString <| Page.Playlists New) ]
215
+
[ href (Page.toString <| Page.Playlists NewPlaylist) ]
195
216
[ "block"
196
217
, "opacity-30"
197
218
, "text-inherit"
···
199
220
[ Icons.waves 64 Inherit ]
200
221
, slab
201
222
Html.a
202
-
[ href (Page.toString <| Page.Playlists New) ]
223
+
[ href (Page.toString <| Page.Playlists NewPlaylist) ]
203
224
[ "block"
204
225
, "leading-normal"
205
226
, "mt-2"
···
209
230
]
210
231
[ text "No playlists found, create one"
211
232
, lineBreak
212
-
, text "or enable directory playlists."
233
+
, text "or enable directory collections."
213
234
]
214
235
]
215
236
···
220
241
221
242
intro : Html Msg
222
243
intro =
223
-
[ text "Playlists are not tied to the sources of its tracks, "
224
-
, text "same goes for favourites."
244
+
[ text "Collections and playlists are not tied to the sources of its tracks, "
245
+
, text "same goes for favourites. "
225
246
, lineBreak
226
-
, text "There's also directory playlists, which are playlists derived from root directories."
247
+
, text "There's also directory collections, which are derived from root directories."
227
248
]
228
249
|> raw
229
250
|> Kit.intro
···
283
304
-- NEW
284
305
285
306
286
-
new : List (Html Msg)
287
-
new =
307
+
newCollection : List (Html Msg)
308
+
newCollection =
288
309
[ -----------------------------------------
289
310
-- Navigation
290
311
-----------------------------------------
···
298
319
-----------------------------------------
299
320
-- Content
300
321
-----------------------------------------
301
-
, [ Kit.h2 "Name your playlist"
322
+
, [ Kit.h2 "Name your collection"
302
323
303
324
--
304
325
, [ onInput SetPlaylistCreationContext
305
326
, placeholder "The Classics"
327
+
]
328
+
|> Kit.textField
329
+
|> chunky [ "max-w-md", "mx-auto" ]
330
+
331
+
-- Button
332
+
---------
333
+
, chunk
334
+
[ "mt-10" ]
335
+
[ Kit.button
336
+
Normal
337
+
Bypass
338
+
(text "Create collection")
339
+
]
340
+
]
341
+
|> Kit.canisterForm
342
+
|> List.singleton
343
+
|> Kit.centeredContent
344
+
|> List.singleton
345
+
|> slab
346
+
Html.form
347
+
[ onSubmit CreateCollection ]
348
+
[ "flex"
349
+
, "flex-grow"
350
+
, "text-center"
351
+
]
352
+
]
353
+
354
+
355
+
newPlaylist : List (Html Msg)
356
+
newPlaylist =
357
+
[ -----------------------------------------
358
+
-- Navigation
359
+
-----------------------------------------
360
+
Navigation.local
361
+
[ ( Icon Icons.arrow_back
362
+
, Label "Back to list" Hidden
363
+
, NavigateToPage (Page.Playlists Index)
364
+
)
365
+
]
366
+
367
+
-----------------------------------------
368
+
-- Content
369
+
-----------------------------------------
370
+
, [ Kit.h2 "Name your playlist"
371
+
372
+
--
373
+
, [ onInput SetPlaylistCreationContext
374
+
, placeholder "Sunset"
306
375
]
307
376
|> Kit.textField
308
377
|> chunky [ "max-w-md", "mx-auto" ]
+5
-6
src/Core/Themes/Sunrise/Tracks/View.elm
+5
-6
src/Core/Themes/Sunrise/Tracks/View.elm
···
110
110
model.selectedPlaylist
111
111
|> Maybe.andThen
112
112
(\playlist ->
113
-
case playlist.autoGenerated of
114
-
Just _ ->
115
-
Nothing
113
+
if playlist.collection || Maybe.isJust playlist.autoGenerated then
114
+
Nothing
116
115
117
-
Nothing ->
118
-
Just model.dnd
116
+
else
117
+
Just model.dnd
119
118
)
120
119
|> Themes.Sunrise.Tracks.Scene.List.view
121
120
{ bgColor = model.extractedBackdropColor
···
364
363
Navigation.localWithTabindex
365
364
tabindex_
366
365
[ ( Icon Icons.waves
367
-
, Label "Playlists" Hidden
366
+
, Label "Collections & Playlists" Hidden
368
367
, if pressedShift then
369
368
PerformMsg AssistWithSelectingPlaylist
370
369
+7
-1
src/Core/UI.elm
+7
-1
src/Core/UI.elm
···
427
427
AddTracksToPlaylist a ->
428
428
Playlists.addTracksToPlaylist a
429
429
430
+
AssistWithAddingTracksToCollection a ->
431
+
Playlists.assistWithAddingTracksToCollection a
432
+
430
433
AssistWithAddingTracksToPlaylist a ->
431
434
Playlists.assistWithAddingTracksToPlaylist a
432
435
433
436
AssistWithSelectingPlaylist ->
434
437
Playlists.assistWithSelectingPlaylist
435
438
439
+
CreateCollection ->
440
+
Playlists.createCollection
441
+
436
442
CreatePlaylist ->
437
-
Playlists.create
443
+
Playlists.createPlaylist
438
444
439
445
DeactivatePlaylist ->
440
446
Playlists.deactivate
+10
src/Core/UI/Commands/Alfred.elm
+10
src/Core/UI/Commands/Alfred.elm
···
96
96
97
97
--
98
98
, { icon = Just (Icons.queue_music 16)
99
+
, title = "Add current track to collection"
100
+
, value = Command (UI.AssistWithAddingTracksToCollection <| [ identifiedTrack ])
101
+
}
102
+
103
+
--
104
+
, { icon = Just (Icons.queue_music 16)
99
105
, title = "Add current track to playlist"
100
106
, value = Command (UI.AssistWithAddingTracksToPlaylist <| [ identifiedTrack ])
101
107
}
···
176
182
tracks ->
177
183
List.concat
178
184
[ [ { icon = Just (Icons.queue_music 16)
185
+
, title = "Add current selection to collection"
186
+
, value = Command (UI.AssistWithAddingTracksToCollection tracks)
187
+
}
188
+
, { icon = Just (Icons.queue_music 16)
179
189
, title = "Add current selection to playlist"
180
190
, value = Command (UI.AssistWithAddingTracksToPlaylist tracks)
181
191
}
+8
-3
src/Core/UI/Page.elm
+8
-3
src/Core/UI/Page.elm
···
1
1
module UI.Page exposing (Page(..), fromUrl, rewriteUrl, sameBase, sources, toString)
2
2
3
+
import Material.Icons exposing (collections)
3
4
import Maybe.Extra as Maybe
4
5
import Sources exposing (Service(..))
5
6
import UI.Playlists.Page as Playlists
···
78
79
Playlists Playlists.Index ->
79
80
"playlists"
80
81
81
-
Playlists Playlists.New ->
82
-
"playlists/new"
82
+
Playlists Playlists.NewCollection ->
83
+
"playlists/new/collection"
84
+
85
+
Playlists Playlists.NewPlaylist ->
86
+
"playlists/new/playlist"
83
87
84
88
Playlists (Playlists.Edit playlistName) ->
85
89
"playlists/edit/" ++ playlistName
···
188
192
-----------------------------------------
189
193
, map (Playlists Playlists.Index) (s "playlists")
190
194
, map (Playlists << Playlists.Edit) (s "playlists" </> s "edit" </> string)
191
-
, map (Playlists Playlists.New) (s "playlists" </> s "new")
195
+
, map (Playlists Playlists.NewCollection) (s "playlists" </> s "new" </> s "collection")
196
+
, map (Playlists Playlists.NewPlaylist) (s "playlists" </> s "new" </> s "playlist")
192
197
193
198
-----------------------------------------
194
199
-- Queue
+14
-9
src/Core/UI/Playlists/Alfred.elm
+14
-9
src/Core/UI/Playlists/Alfred.elm
···
15
15
-- CREATE
16
16
17
17
18
-
create : List IdentifiedTrack -> List Playlist -> Alfred UI.Msg
19
-
create tracks playlists =
18
+
create : { collectionMode : Bool } -> List IdentifiedTrack -> List Playlist -> Alfred UI.Msg
19
+
create { collectionMode } tracks playlists =
20
20
let
21
21
index =
22
22
makeIndex playlists
23
+
24
+
subject =
25
+
ifThenElse collectionMode "collection" "playlist"
23
26
in
24
27
Alfred.create
25
-
{ action = createAction tracks
28
+
{ action = createAction collectionMode tracks
26
29
, index = index
27
30
, message =
28
31
if List.length tracks == 1 then
29
-
"Choose or create a playlist to add this track to."
32
+
"Choose or create a " ++ subject ++ " to add this track to."
30
33
31
34
else
32
-
"Choose or create a playlist to add these tracks to."
35
+
"Choose or create a " ++ subject ++ " to add these tracks to."
33
36
, operation = QueryOrMutation
34
37
}
35
38
36
39
37
-
createAction : List IdentifiedTrack -> Alfred.Action UI.Msg
38
-
createAction tracks ctx =
40
+
createAction : Bool -> List IdentifiedTrack -> Alfred.Action UI.Msg
41
+
createAction collectionMode tracks ctx =
39
42
let
40
43
playlistTracks =
41
44
Tracks.toPlaylistTracks tracks
···
47
50
case Alfred.stringValue result.value of
48
51
Just playlistName ->
49
52
[ UI.AddTracksToPlaylist
50
-
{ playlistName = playlistName
53
+
{ collection = collectionMode
54
+
, playlistName = playlistName
51
55
, tracks = playlistTracks
52
56
}
53
57
]
···
62
66
case ctx.searchTerm of
63
67
Just searchTerm ->
64
68
[ UI.AddTracksToPlaylist
65
-
{ playlistName = searchTerm
69
+
{ collection = collectionMode
70
+
, playlistName = searchTerm
66
71
, tracks = playlistTracks
67
72
}
68
73
]
+5
-4
src/Core/UI/Playlists/ContextMenu.elm
+5
-4
src/Core/UI/Playlists/ContextMenu.elm
···
62
62
Just _ ->
63
63
ContextMenu
64
64
[ addToQueue identifiedTracksFromPlaylist
65
-
, convertToRegularPlaylist tracksFromPlaylist playlist
65
+
, convertToRegularCollection tracksFromPlaylist playlist
66
66
, downloadAsZip tracksFromPlaylist playlist
67
67
, storeInCache tracksFromPlaylist
68
68
]
···
97
97
}
98
98
99
99
100
-
convertToRegularPlaylist tracksFromPlaylist playlist =
100
+
convertToRegularCollection tracksFromPlaylist playlist =
101
101
Item
102
102
{ icon = Icons.waves
103
-
, label = "Convert to regular playlist"
103
+
, label = "Save as regular collection"
104
104
, msg =
105
105
AddTracksToPlaylist
106
-
{ playlistName = playlist.name
106
+
{ collection = True
107
+
, playlistName = playlist.name
107
108
, tracks = List.map Tracks.playlistTrackFromTrack tracksFromPlaylist
108
109
}
109
110
+5
-3
src/Core/UI/Playlists/Directory.elm
+5
-3
src/Core/UI/Playlists/Directory.elm
···
1
1
module UI.Playlists.Directory exposing (generate)
2
2
3
3
import Dict exposing (Dict)
4
+
import List.Extra as List
4
5
import Playlists exposing (..)
5
6
import Set exposing (Set)
6
7
import Sources exposing (Source)
7
8
import String.Ext as String
8
9
import Tracks exposing (Track)
9
-
import List.Extra as List
10
+
10
11
11
12
12
13
-- 🔱
···
42
43
|> List.map
43
44
(\n ->
44
45
let
45
-
s =
46
-
String.split "/" n
46
+
s =
47
+
String.split "/" n
47
48
in
48
49
{ autoGenerated = Just { level = List.length s - 1 }
50
+
, collection = True
49
51
, name = Maybe.withDefault n (List.last s)
50
52
, public = False
51
53
, tracks = []
+2
-1
src/Core/UI/Playlists/Page.elm
+2
-1
src/Core/UI/Playlists/Page.elm
+173
-50
src/Core/UI/Playlists/State.elm
+173
-50
src/Core/UI/Playlists/State.elm
···
33
33
|> andThen (Common.changeUrlUsingPage Page.Index)
34
34
35
35
36
-
addTracksToPlaylist : { playlistName : String, tracks : List PlaylistTrack } -> Manager
37
-
addTracksToPlaylist { playlistName, tracks } model =
36
+
addTracksToPlaylist : { collection : Bool, playlistName : String, tracks : List PlaylistTrackWithoutMetadata } -> Manager
37
+
addTracksToPlaylist { collection, playlistName, tracks } model =
38
38
let
39
39
properPlaylistName =
40
40
String.trim playlistName
···
44
44
(\p -> Maybe.isNothing p.autoGenerated && p.name == properPlaylistName)
45
45
model.playlists
46
46
47
-
newCollection =
47
+
( tracksAlreadyInPlaylist, newTracks ) =
48
+
playlistIndex
49
+
|> Maybe.andThen
50
+
(\a ->
51
+
if collection then
52
+
Just a
53
+
54
+
else
55
+
Nothing
56
+
)
57
+
|> Maybe.andThen (\idx -> List.getAt idx model.playlists)
58
+
|> Maybe.map
59
+
(\p ->
60
+
List.foldl
61
+
(\track ( a, b, c ) ->
62
+
case
63
+
List.findIndex
64
+
(\x ->
65
+
track.title == x.title && track.album == x.album && track.artist == x.artist
66
+
)
67
+
c
68
+
of
69
+
Just idx ->
70
+
( track :: a, b, List.removeAt idx c )
71
+
72
+
Nothing ->
73
+
( a, track :: b, c )
74
+
)
75
+
( [], [], p.tracks )
76
+
tracks
77
+
)
78
+
|> Maybe.map (\( a, b, _ ) -> ( a, b ))
79
+
|> Maybe.withDefault ( [], tracks )
80
+
|> Tuple.mapSecond
81
+
(List.map
82
+
(\track ->
83
+
let
84
+
newTrack : PlaylistTrack
85
+
newTrack =
86
+
{ album = track.album
87
+
, artist = track.artist
88
+
, title = track.title
89
+
90
+
--
91
+
, insertedAt = model.currentTime
92
+
}
93
+
in
94
+
newTrack
95
+
)
96
+
)
97
+
98
+
newInventory =
48
99
case playlistIndex of
49
100
Just idx ->
50
101
List.updateAt
51
102
idx
52
-
(\p -> { p | tracks = p.tracks ++ tracks })
103
+
(\p -> { p | tracks = p.tracks ++ newTracks })
53
104
model.playlists
54
105
55
106
Nothing ->
56
107
{ autoGenerated = Nothing
108
+
, collection = collection
57
109
, name = properPlaylistName
58
110
, public = False
59
-
, tracks = tracks
111
+
, tracks = newTracks
60
112
}
61
113
:: model.playlists
62
114
63
115
newModel =
64
116
{ model
65
-
| playlists = newCollection
66
-
, lastModifiedPlaylist = Just properPlaylistName
117
+
| playlists = newInventory
118
+
, lastModifiedPlaylist =
119
+
Just
120
+
{ collection = collection
121
+
, name = properPlaylistName
122
+
}
67
123
}
124
+
125
+
subject =
126
+
ifThenElse collection "collection" "playlist"
68
127
in
69
-
(case tracks of
70
-
[ t ] ->
71
-
"Added __" ++ t.title ++ "__"
128
+
case newTracks of
129
+
[] ->
130
+
if collection then
131
+
(case tracksAlreadyInPlaylist of
132
+
[ t ] ->
133
+
"__" ++ t.title ++ "__ was"
72
134
73
-
l ->
74
-
"Added __" ++ String.fromInt (List.length l) ++ " tracks__"
75
-
)
76
-
|> (\s -> s ++ " to the __" ++ properPlaylistName ++ "__ playlist")
77
-
|> Notifications.success
78
-
|> Common.showNotificationWithModel newModel
79
-
|> andThen User.savePlaylists
135
+
l ->
136
+
"__" ++ String.fromInt (List.length l) ++ " tracks__ were"
137
+
)
138
+
|> (\s -> s ++ " already added to the __" ++ properPlaylistName ++ "__ collection")
139
+
|> Notifications.casual
140
+
|> Common.showNotificationWithModel model
141
+
142
+
else
143
+
Return.singleton model
144
+
145
+
_ ->
146
+
(case newTracks of
147
+
[ t ] ->
148
+
"Added __" ++ t.title ++ "__"
149
+
150
+
l ->
151
+
"Added __" ++ String.fromInt (List.length l) ++ " tracks__"
152
+
)
153
+
|> (\s -> s ++ " to the __" ++ properPlaylistName ++ "__ " ++ subject)
154
+
|> Notifications.success
155
+
|> Common.showNotificationWithModel newModel
156
+
|> andThen User.savePlaylists
157
+
158
+
159
+
assistWithAddingTracksToCollection : List IdentifiedTrack -> Manager
160
+
assistWithAddingTracksToCollection tracks model =
161
+
model.playlists
162
+
|> List.filter (\p -> p.autoGenerated == Nothing && p.collection == True)
163
+
|> UI.Playlists.Alfred.create { collectionMode = True } tracks
164
+
|> (\a -> Alfred.assign a model)
80
165
81
166
82
167
assistWithAddingTracksToPlaylist : List IdentifiedTrack -> Manager
83
168
assistWithAddingTracksToPlaylist tracks model =
84
169
model.playlists
85
-
|> List.filterNot (.autoGenerated >> Maybe.isJust)
86
-
|> UI.Playlists.Alfred.create tracks
170
+
|> List.filter (\p -> p.autoGenerated == Nothing && p.collection == False)
171
+
|> UI.Playlists.Alfred.create { collectionMode = False } tracks
87
172
|> (\a -> Alfred.assign a model)
88
173
89
174
···
94
179
|> (\a -> Alfred.assign a model)
95
180
96
181
97
-
create : Manager
98
-
create model =
182
+
create : { collection : Bool } -> Manager
183
+
create { collection } model =
99
184
case model.newPlaylistContext of
100
185
Just playlistName ->
101
186
let
102
187
alreadyExists =
103
-
List.any
188
+
List.find
104
189
(.name >> (==) playlistName)
105
190
(List.filterNot (.autoGenerated >> Maybe.isJust) model.playlists)
106
191
107
192
playlist =
108
193
{ autoGenerated = Nothing
194
+
, collection = collection
109
195
, name = playlistName
110
196
, public = False
111
197
, tracks = []
112
198
}
113
199
in
114
-
if alreadyExists then
115
-
"There's already a playlist with this name"
116
-
|> Notifications.error
117
-
|> Common.showNotificationWithModel model
200
+
case alreadyExists of
201
+
Just existingPlaylist ->
202
+
(if existingPlaylist.collection then
203
+
"There's already a collection using this name"
118
204
119
-
else
120
-
{ model
121
-
| lastModifiedPlaylist = Just playlist.name
122
-
, newPlaylistContext = Nothing
123
-
, playlists = playlist :: model.playlists
124
-
}
125
-
|> User.savePlaylists
126
-
|> andThen redirectToPlaylistIndexPage
205
+
else
206
+
"There's already a playlist using this name"
207
+
)
208
+
|> Notifications.error
209
+
|> Common.showNotificationWithModel model
210
+
211
+
Nothing ->
212
+
{ model
213
+
| lastModifiedPlaylist =
214
+
Just
215
+
{ collection = playlist.collection
216
+
, name = playlist.name
217
+
}
218
+
, newPlaylistContext = Nothing
219
+
, playlists = playlist :: model.playlists
220
+
}
221
+
|> User.savePlaylists
222
+
|> andThen redirectToPlaylistIndexPage
127
223
128
224
Nothing ->
129
225
Return.singleton model
130
226
131
227
228
+
createCollection : Manager
229
+
createCollection =
230
+
create { collection = True }
231
+
232
+
233
+
createPlaylist : Manager
234
+
createPlaylist =
235
+
create { collection = False }
236
+
237
+
132
238
deactivate : Manager
133
239
deactivate =
134
240
deselect
···
195
301
List.partition (.autoGenerated >> Maybe.isJust) model.playlists
196
302
197
303
alreadyExists =
198
-
List.any
304
+
List.find
199
305
(.name >> (==) properName)
200
306
notAutoGenerated
201
307
···
204
310
(\p -> ifThenElse (p.name == oldName) { p | name = properName } p)
205
311
notAutoGenerated
206
312
in
207
-
if alreadyExists then
208
-
"There's already a playlist with this name"
209
-
|> Notifications.error
210
-
|> Common.showNotificationWithModel
211
-
{ model | editPlaylistContext = Nothing }
313
+
case alreadyExists of
314
+
Just existingPlaylist ->
315
+
(if existingPlaylist.collection then
316
+
"There's already a collection using this name"
317
+
318
+
else
319
+
"There's already a playlist using this name"
320
+
)
321
+
|> Notifications.error
322
+
|> Common.showNotificationWithModel
323
+
{ model | editPlaylistContext = Nothing }
324
+
325
+
Nothing ->
326
+
if validName then
327
+
{ model
328
+
| editPlaylistContext = Nothing
329
+
, lastModifiedPlaylist =
330
+
case model.lastModifiedPlaylist of
331
+
Just l ->
332
+
if l.name == oldName then
333
+
Just { l | name = newName }
334
+
335
+
else
336
+
Just l
212
337
213
-
else if validName then
214
-
{ model
215
-
| editPlaylistContext = Nothing
216
-
, lastModifiedPlaylist = Just properName
217
-
, playlists = newCollection ++ autoGenerated
218
-
}
219
-
|> User.savePlaylists
220
-
|> andThen redirectToPlaylistIndexPage
338
+
Nothing ->
339
+
Nothing
340
+
, playlists = newCollection ++ autoGenerated
341
+
}
342
+
|> User.savePlaylists
343
+
|> andThen redirectToPlaylistIndexPage
221
344
222
-
else
223
-
redirectToPlaylistIndexPage model
345
+
else
346
+
redirectToPlaylistIndexPage model
224
347
225
348
Nothing ->
226
349
redirectToPlaylistIndexPage model
+21
src/Core/UI/Queue/ContextMenu.elm
+21
src/Core/UI/Queue/ContextMenu.elm
···
48
48
}
49
49
, Item
50
50
{ icon = Icons.waves
51
+
, label = "Add to collection"
52
+
, msg = AssistWithAddingTracksToCollection tracks
53
+
, active = False
54
+
}
55
+
, Item
56
+
{ icon = Icons.waves
51
57
, label = "Add to playlist"
52
58
, msg = AssistWithAddingTracksToPlaylist tracks
53
59
, active = False
···
76
82
else
77
83
[ Item
78
84
{ icon = Icons.waves
85
+
, label = "Add queue picks to collection"
86
+
, msg =
87
+
manualEntries
88
+
|> List.map .identifiedTrack
89
+
|> AssistWithAddingTracksToCollection
90
+
, active = False
91
+
}
92
+
, Item
93
+
{ icon = Icons.waves
79
94
, label = "Add queue picks to playlist"
80
95
, msg =
81
96
manualEntries
···
120
135
|> QueueMsg
121
136
122
137
--
138
+
, active = False
139
+
}
140
+
, Item
141
+
{ icon = Icons.waves
142
+
, label = "Add to collection"
143
+
, msg = AssistWithAddingTracksToCollection tracks
123
144
, active = False
124
145
}
125
146
, Item
+44
-12
src/Core/UI/Tracks/ContextMenu.elm
+44
-12
src/Core/UI/Tracks/ContextMenu.elm
···
23
23
{ cached : List String
24
24
, cachingInProgress : List String
25
25
, currentTime : Time.Posix
26
-
, lastModifiedPlaylistName : Maybe String
26
+
, lastModifiedPlaylistName : Maybe { collection : Bool, name : String }
27
27
, selectedPlaylist : Maybe Playlist
28
28
, showAlternativeMenu : Bool
29
29
, sources : List Source
···
152
152
153
153
playlistActions :
154
154
{ selectedPlaylist : Maybe Playlist
155
-
, lastModifiedPlaylistName : Maybe String
155
+
, lastModifiedPlaylistName : Maybe { collection : Bool, name : String }
156
156
}
157
157
-> List IdentifiedTrack
158
158
-> List (ContextMenu.Item Msg)
···
170
170
)
171
171
selectedPlaylist
172
172
173
-
maybeAddToLastModifiedPlaylist =
173
+
maybeAddToLastModifiedPlaylist { collection } =
174
174
Maybe.andThen
175
-
(\n ->
176
-
if Maybe.map .name selectedPlaylist /= Just n then
175
+
(\l ->
176
+
if Maybe.map .name selectedPlaylist /= Just l.name && l.collection == collection then
177
177
justAnItem
178
178
{ icon = Icons.waves
179
-
, label = "Add to \"" ++ n ++ "\""
179
+
, label = "Add to \"" ++ l.name ++ "\""
180
180
, msg =
181
181
AddTracksToPlaylist
182
-
{ playlistName = n
182
+
{ collection = collection
183
+
, playlistName = l.name
183
184
, tracks = Tracks.toPlaylistTracks tracks
184
185
}
185
186
···
198
199
-----------------------------------------
199
200
Just playlist ->
200
201
Maybe.values
201
-
[ justAnItem
202
+
[ maybeAddToLastModifiedPlaylist { collection = True }
203
+
, justAnItem
202
204
{ icon = Icons.waves
203
-
, label = "Remove from playlist"
205
+
, label =
206
+
if playlist.collection then
207
+
"Remove from collection"
208
+
209
+
else
210
+
"Remove from playlist"
204
211
, msg = RemoveTracksFromPlaylist playlist tracks
205
212
206
213
--
207
214
, active = False
208
215
}
209
-
, maybeAddToLastModifiedPlaylist
210
216
, justAnItem
211
217
{ icon = Icons.waves
212
-
, label = "Add to another playlist"
218
+
, label =
219
+
if playlist.collection then
220
+
"Add to another collection"
221
+
222
+
else
223
+
"Add to playlist"
224
+
, msg = AssistWithAddingTracksToCollection tracks
225
+
226
+
--
227
+
, active = False
228
+
}
229
+
, maybeAddToLastModifiedPlaylist { collection = False }
230
+
, justAnItem
231
+
{ icon = Icons.waves
232
+
, label =
233
+
if playlist.collection then
234
+
"Add to playlist"
235
+
236
+
else
237
+
"Add to another playlist"
213
238
, msg = AssistWithAddingTracksToPlaylist tracks
214
239
215
240
--
···
222
247
-----------------------------------------
223
248
_ ->
224
249
Maybe.values
225
-
[ maybeAddToLastModifiedPlaylist
250
+
[ maybeAddToLastModifiedPlaylist { collection = True }
251
+
, justAnItem
252
+
{ icon = Icons.waves
253
+
, label = "Add to collection"
254
+
, msg = AssistWithAddingTracksToCollection tracks
255
+
, active = False
256
+
}
257
+
, maybeAddToLastModifiedPlaylist { collection = False }
226
258
, justAnItem
227
259
{ icon = Icons.waves
228
260
, label = "Add to playlist"
+5
-3
src/Core/UI/Types.elm
+5
-3
src/Core/UI/Types.elm
···
20
20
import LastFm
21
21
import Management
22
22
import Notifications exposing (Notification)
23
-
import Playlists exposing (Playlist, PlaylistTrack)
23
+
import Playlists exposing (Playlist, PlaylistTrack, PlaylistTrackWithoutMetadata)
24
24
import Queue
25
25
import Random
26
26
import Sources exposing (Source)
···
124
124
-- Playlists
125
125
-----------------------------------------
126
126
, editPlaylistContext : Maybe { oldName : String, newName : String }
127
-
, lastModifiedPlaylist : Maybe String
127
+
, lastModifiedPlaylist : Maybe { collection : Bool, name : String }
128
128
, newPlaylistContext : Maybe String
129
129
, playlists : List Playlist
130
130
, playlistToActivate : Maybe String
···
256
256
-- Playlists
257
257
-----------------------------------------
258
258
| ActivatePlaylist Playlist
259
-
| AddTracksToPlaylist { playlistName : String, tracks : List PlaylistTrack }
259
+
| AddTracksToPlaylist { collection : Bool, playlistName : String, tracks : List PlaylistTrackWithoutMetadata }
260
+
| AssistWithAddingTracksToCollection (List IdentifiedTrack)
260
261
| AssistWithAddingTracksToPlaylist (List IdentifiedTrack)
261
262
| AssistWithSelectingPlaylist
263
+
| CreateCollection
262
264
| CreatePlaylist
263
265
| DeactivatePlaylist
264
266
| DeletePlaylist { playlistName : String }
+15
src/Library/Playlists.elm
+15
src/Library/Playlists.elm
···
1
1
module Playlists exposing (..)
2
2
3
+
import Time
4
+
5
+
6
+
3
7
-- 🌳
4
8
5
9
6
10
type alias Playlist =
7
11
{ autoGenerated : Maybe { level : Int }
12
+
, collection : Bool
8
13
, name : String
9
14
, public : Bool
10
15
, tracks : List PlaylistTrack
11
16
}
12
17
13
18
19
+
type alias PlaylistTrackWithoutMetadata =
20
+
{ album : Maybe String
21
+
, artist : Maybe String
22
+
, title : String
23
+
}
24
+
25
+
14
26
type alias PlaylistTrack =
15
27
{ album : Maybe String
16
28
, artist : Maybe String
17
29
, title : String
30
+
31
+
--
32
+
, insertedAt : Time.Posix
18
33
}
19
34
20
35
+10
-3
src/Library/Playlists/Encoding.elm
+10
-3
src/Library/Playlists/Encoding.elm
···
4
4
import Json.Decode.Ext as Decode
5
5
import Json.Encode as Encode
6
6
import Json.Encode.Ext exposing (..)
7
-
import Maybe.Extra as Maybe exposing (isJust)
7
+
import Maybe.Extra as Maybe
8
8
import Playlists exposing (..)
9
+
import Time.Ext as Time
9
10
10
11
11
12
···
24
25
Nothing ->
25
26
Encode.null
26
27
)
28
+
, ( "collection", Encode.bool playlist.collection )
27
29
, ( "name", Encode.string playlist.name )
28
30
, ( "public", Encode.bool playlist.public )
29
31
, ( "tracks", Encode.list encodePlaylistTrack playlist.tracks )
···
36
38
[ ( "album", encodeMaybe playlistTrack.album Encode.string )
37
39
, ( "artist", encodeMaybe playlistTrack.artist Encode.string )
38
40
, ( "title", Encode.string playlistTrack.title )
41
+
42
+
--
43
+
, ( "insertAt", Time.encode playlistTrack.insertedAt )
39
44
]
40
45
41
46
···
45
50
46
51
decoder : Decode.Decoder Playlist
47
52
decoder =
48
-
Decode.map4 Playlist
53
+
Decode.map5 Playlist
49
54
(Decode.field "autoGenerated" <| Decode.maybe <| Decode.map (\l -> { level = l }) <| Decode.field "level" Decode.int)
55
+
(Decode.optionalField "collection" Decode.bool False)
50
56
(Decode.field "name" Decode.string)
51
57
(Decode.optionalField "public" Decode.bool False)
52
58
(Decode.field "tracks" <| Decode.list playlistTrackDecoder)
···
54
60
55
61
playlistTrackDecoder : Decode.Decoder PlaylistTrack
56
62
playlistTrackDecoder =
57
-
Decode.map3 PlaylistTrack
63
+
Decode.map4 PlaylistTrack
58
64
(Decode.maybe <| Decode.field "album" Decode.string)
59
65
(Decode.maybe <| Decode.field "artist" Decode.string)
60
66
(Decode.field "title" Decode.string)
67
+
(Decode.optionalField "insertedAt" Time.decoder Time.default)
+2
-2
src/Library/Playlists/Matching.elm
+2
-2
src/Library/Playlists/Matching.elm
···
13
13
List.foldl
14
14
(\( i, t ) ( identifiedTracks, remainingPlaylistTracks ) ->
15
15
let
16
-
imaginaryPlaylistTrack =
16
+
im =
17
17
{ album = t.tags.album
18
18
, artist = t.tags.artist
19
19
, title = t.tags.title
···
22
22
( matches, remainingPlaylistTracksWithoutMatches ) =
23
23
List.foldl
24
24
(\( pi, pt ) ->
25
-
if imaginaryPlaylistTrack == pt then
25
+
if im.title == pt.title && im.album == pt.album && im.artist == pt.artist then
26
26
Tuple.mapBoth
27
27
((::) ( playlistTrackIdentifiers i pi, t ))
28
28
identity
+3
-3
src/Library/Tracks.elm
+3
-3
src/Library/Tracks.elm
···
3
3
import Base64
4
4
import List.Extra as List
5
5
import Maybe.Extra as Maybe
6
-
import Playlists exposing (Playlist, PlaylistTrack)
6
+
import Playlists exposing (Playlist, PlaylistTrack, PlaylistTrackWithoutMetadata)
7
7
import String.Ext as String
8
8
import Time
9
9
import Time.Ext as Time
···
442
442
|> Maybe.withDefault False
443
443
444
444
445
-
playlistTrackFromTrack : Track -> PlaylistTrack
445
+
playlistTrackFromTrack : Track -> PlaylistTrackWithoutMetadata
446
446
playlistTrackFromTrack track =
447
447
{ album = track.tags.album
448
448
, artist = track.tags.artist
···
450
450
}
451
451
452
452
453
-
toPlaylistTracks : List IdentifiedTrack -> List PlaylistTrack
453
+
toPlaylistTracks : List IdentifiedTrack -> List PlaylistTrackWithoutMetadata
454
454
toPlaylistTracks =
455
455
List.map (Tuple.second >> playlistTrackFromTrack)
456
456