1module UI.Tracks.State exposing (..)
2
3import Alien
4import Base64
5import Common exposing (..)
6import ContextMenu
7import Coordinates exposing (Coordinates)
8import Debouncer.Basic as Debouncer
9import Dict
10import Html.Events.Extra.Mouse as Mouse
11import InfiniteList
12import Json.Decode as Json
13import Json.Encode
14import Keyboard
15import List.Ext as List
16import List.Extra as List
17import Maybe.Extra as Maybe
18import Notifications
19import Playlists exposing (Playlist)
20import Queue
21import Return exposing (andThen, return)
22import Return.Ext as Return
23import Sources
24import Task.Extra as Task
25import Tracks exposing (..)
26import Tracks.Collection as Collection
27import Tracks.Encoding as Encoding
28import Tracks.Favourites as Favourites
29import UI.Common.State as Common exposing (showNotification)
30import UI.DnD as DnD
31import UI.Page
32import UI.Ports as Ports
33import UI.Queue.State as Queue
34import UI.Theme
35import UI.Tracks.ContextMenu as Tracks
36import UI.Tracks.Covers as Covers
37import UI.Tracks.Types as Tracks exposing (..)
38import UI.Types exposing (Manager, Model, Msg(..))
39import UI.User.State.Export as User
40import User.Layer exposing (HypaethralData)
41
42
43
44-- 📣
45
46
47update : Tracks.Msg -> Manager
48update msg =
49 case msg of
50 Download a b ->
51 download a b
52
53 DownloadFinished ->
54 downloadFinished
55
56 Harvest ->
57 harvest
58
59 MarkAsSelected a b ->
60 markAsSelected a b
61
62 ScrollToNowPlaying ->
63 scrollToNowPlaying
64
65 SyncTags a ->
66 syncTags a
67
68 ToggleCachedOnly ->
69 toggleCachedOnly
70
71 ToggleCoverSelectionReducesPool ->
72 toggleCoverSelectionReducesPool
73
74 ToggleFavouritesOnly ->
75 toggleFavouritesOnly
76
77 ToggleHideDuplicates ->
78 toggleHideDuplicates
79
80 -----------------------------------------
81 -- Cache
82 -----------------------------------------
83 ClearCache ->
84 clearCache
85
86 RemoveFromCache a ->
87 removeFromCache a
88
89 StoreInCache a ->
90 storeInCache a
91
92 StoredInCache a b ->
93 storedInCache a b
94
95 ---------
96 -- Covers
97 ---------
98 GotCachedCover a ->
99 gotCachedCover a
100
101 InsertCoverCache a ->
102 insertCoverCache a
103
104 -----------------------------------------
105 -- Collection
106 -----------------------------------------
107 Add a ->
108 add a
109
110 AddFavourites a ->
111 addFavourites a
112
113 Reload a ->
114 reload a
115
116 RemoveByPaths a ->
117 removeByPaths a
118
119 RemoveBySourceId a ->
120 removeBySourceId a
121
122 RemoveFavourites a ->
123 removeFavourites a
124
125 SortBy a ->
126 sortBy a
127
128 ToggleFavourite a ->
129 toggleFavourite a
130
131 -----------------------------------------
132 -- Groups
133 -----------------------------------------
134 DisableGrouping ->
135 disableGrouping
136
137 GroupBy a ->
138 groupBy a
139
140 -----------------------------------------
141 -- Menus
142 -----------------------------------------
143 ShowCoverMenu a b ->
144 showCoverMenu a b
145
146 ShowCoverMenuWithSmallDelay a b ->
147 showCoverMenuWithDelay a b
148
149 ShowTracksMenu a b c ->
150 showTracksMenu a b c
151
152 ShowTracksMenuWithSmallDelay a b c ->
153 showTracksMenuWithDelay a b c
154
155 ShowViewMenu a b ->
156 showViewMenu a b
157
158 -----------------------------------------
159 -- Scenes
160 -----------------------------------------
161 ChangeScene a ->
162 changeScene a
163
164 DeselectCover ->
165 deselectCover
166
167 InfiniteListMsg a ->
168 infiniteListMsg a
169
170 SelectCover a ->
171 selectCover a
172
173 -----------------------------------------
174 -- Search
175 -----------------------------------------
176 ClearSearch ->
177 clearSearch
178
179 Search ->
180 search
181
182 SetSearchResults a ->
183 setSearchResults a
184
185 SetSearchTerm a ->
186 setSearchTerm a
187
188
189
190-- 🔱
191
192
193add : Json.Value -> Manager
194add encodedTracks model =
195 reviseCollection
196 (encodedTracks
197 |> Json.decodeValue (Json.list Encoding.trackDecoder)
198 |> Result.withDefault []
199 |> Collection.add
200 )
201 model
202
203
204addFavourites : List IdentifiedTrack -> Manager
205addFavourites =
206 manageFavourites AddToFavourites
207
208
209afterInitialLoad : Manager
210afterInitialLoad model =
211 Common.toggleLoadingScreen Off model
212
213
214changeScene : Scene -> Manager
215changeScene scene model =
216 (case scene of
217 Covers ->
218 Ports.loadAlbumCovers { list = True, coverView = True }
219
220 List ->
221 Cmd.none
222 )
223 |> return { model | scene = scene, selectedCover = Nothing }
224 |> andThen
225 (if model.coverSelectionReducesPool then
226 Queue.reset
227
228 else
229 Return.singleton
230 )
231 |> andThen Common.forceTracksRerender
232 |> andThen User.saveEnclosedUserData
233
234
235clearCache : Manager
236clearCache model =
237 model.cachedTracks
238 |> Json.Encode.list Json.Encode.string
239 |> Alien.broadcast Alien.RemoveTracksFromCache
240 |> Ports.toBrain
241 |> return { model | cachedTracks = [] }
242 |> andThen harvest
243 |> andThen User.saveEnclosedUserData
244 |> andThen
245 ("Tracks cache was cleared"
246 |> Notifications.casual
247 |> Common.showNotification
248 )
249
250
251clearSearch : Manager
252clearSearch model =
253 { model | searchResults = Nothing, searchTerm = Nothing }
254 |> reviseCollection Collection.harvest
255 |> andThen User.saveEnclosedUserData
256
257
258deselectCover : Manager
259deselectCover model =
260 (if model.coverSelectionReducesPool then
261 Queue.reset
262
263 else
264 Return.singleton
265 )
266 { model | selectedCover = Nothing }
267
268
269download : { prefixTrackNumber : Bool, zipName : String } -> List Track -> Manager
270download { prefixTrackNumber, zipName } tracks model =
271 let
272 notification =
273 Notifications.stickyCasual "Downloading tracks ..."
274
275 downloading =
276 Just { notificationId = Notifications.id notification }
277 in
278 [ ( "prefixTrackNumber", Json.Encode.bool prefixTrackNumber )
279 , ( "trackIds"
280 , tracks
281 |> List.map .id
282 |> Json.Encode.list Json.Encode.string
283 )
284 , ( "zipName", Json.Encode.string zipName )
285 ]
286 |> Json.Encode.object
287 |> Alien.broadcast Alien.DownloadTracks
288 |> Ports.toBrain
289 |> return { model | downloading = downloading }
290 |> andThen (Common.showNotification notification)
291
292
293downloadFinished : Manager
294downloadFinished model =
295 case model.downloading of
296 Just { notificationId } ->
297 Common.dismissNotification
298 { id = notificationId }
299 { model | downloading = Nothing }
300
301 Nothing ->
302 Return.singleton model
303
304
305disableGrouping : Manager
306disableGrouping model =
307 { model | grouping = Nothing }
308 |> reviseCollection Collection.arrange
309 |> andThen User.saveEnclosedUserData
310
311
312failedToStoreInCache : List String -> Manager
313failedToStoreInCache trackIds m =
314 showNotification
315 (Notifications.error "Failed to store track in cache")
316 { m | cachingTracksInProgress = List.without trackIds m.cachingTracksInProgress }
317
318
319finishedStoringInCache : List String -> Manager
320finishedStoringInCache trackIds model =
321 { model
322 | cachedTracks = model.cachedTracks ++ trackIds
323 , cachingTracksInProgress = List.without trackIds model.cachingTracksInProgress
324 }
325 |> (\m ->
326 -- When a context menu of a track is open,
327 -- it should be "rerendered" in case
328 -- the track is no longer being downloaded.
329 case m.contextMenu of
330 Just contextMenu ->
331 let
332 isTrackContextMenu =
333 ContextMenu.anyItem
334 (.label >> (==) "Downloading ...")
335 contextMenu
336
337 coordinates =
338 ContextMenu.coordinates contextMenu
339 in
340 if isTrackContextMenu then
341 showTracksMenu Nothing { alt = False } coordinates m
342
343 else
344 Return.singleton m
345
346 Nothing ->
347 Return.singleton m
348 )
349 |> andThen harvest
350 |> andThen User.saveEnclosedUserData
351
352
353generateCovers : Manager
354generateCovers model =
355 model.tracks
356 |> Covers.generate model.sortBy
357 |> (\c -> { model | covers = c })
358 |> Return.singleton
359
360
361gotCachedCover : Json.Value -> Manager
362gotCachedCover json model =
363 let
364 cachedCovers =
365 Maybe.withDefault Dict.empty model.cachedCovers
366
367 decodedValue =
368 Json.decodeValue
369 (Json.map3
370 (\i k u -> ( i, k, u ))
371 (Json.field "imageType" Json.string)
372 (Json.field "key" Json.string)
373 (Json.field "url" Json.string)
374 )
375 json
376 in
377 decodedValue
378 |> Result.map (\( _, key, url ) -> Dict.insert key url cachedCovers)
379 |> Result.map (\dict -> { model | cachedCovers = Just dict })
380 |> Result.withDefault model
381 |> (\m ->
382 case ( m.nowPlaying, decodedValue ) of
383 ( Just nowPlaying, Ok val ) ->
384 let
385 ( imageType, key, url ) =
386 val
387
388 ( _, track ) =
389 nowPlaying.item.identifiedTrack
390
391 hasntLoadedYet =
392 nowPlaying.coverLoaded == False
393
394 ( keyA, keyB ) =
395 ( Base64.encode (Tracks.coverKey False track)
396 , Base64.encode (Tracks.coverKey True track)
397 )
398
399 keyMatches =
400 keyA == key || keyB == key
401 in
402 if hasntLoadedYet && keyMatches then
403 ( m, Ports.setMediaSessionArtwork { blobUrl = url, imageType = imageType } )
404
405 else
406 Return.singleton m
407
408 _ ->
409 Return.singleton m
410 )
411
412
413groupBy : Tracks.Grouping -> Manager
414groupBy grouping model =
415 { model | grouping = Just grouping }
416 |> reviseCollection Collection.arrange
417 |> andThen User.saveEnclosedUserData
418
419
420harvest : Manager
421harvest =
422 reviseCollection Collection.harvest
423
424
425harvestCovers : Manager
426harvestCovers model =
427 model.covers
428 |> Covers.harvest model.selectedCover model.sortBy model.tracks
429 |> (\( c, s ) -> { model | covers = c, selectedCover = s })
430 |> Return.communicate (Ports.loadAlbumCovers { list = True, coverView = True })
431
432
433infiniteListMsg : InfiniteList.Model -> Manager
434infiniteListMsg infiniteList model =
435 return
436 { model | infiniteList = infiniteList }
437 (Ports.loadAlbumCovers { list = True, coverView = False })
438
439
440insertCoverCache : Json.Value -> Manager
441insertCoverCache json model =
442 json
443 |> Json.decodeValue (Json.dict Json.string)
444 |> Result.map (\dict -> { model | cachedCovers = Just dict })
445 |> Result.withDefault model
446 |> Return.singleton
447
448
449manageFavourites : FavouritesManagementAction -> List IdentifiedTrack -> Manager
450manageFavourites action tracks model =
451 let
452 newFavourites =
453 (case action of
454 AddToFavourites ->
455 Favourites.completeFavouritesList
456
457 RemoveFromFavourites ->
458 Favourites.removeFromFavouritesList
459 )
460 tracks
461 model.favourites
462
463 effect collection =
464 collection
465 |> Collection.map
466 (case action of
467 AddToFavourites ->
468 Favourites.completeTracksList tracks
469
470 RemoveFromFavourites ->
471 Favourites.removeFromTracksList tracks
472 )
473 |> (if model.favouritesOnly then
474 Collection.harvest
475
476 else
477 identity
478 )
479
480 selectedCover =
481 Maybe.map
482 (\cover ->
483 cover.tracks
484 |> (case action of
485 AddToFavourites ->
486 Favourites.completeTracksList tracks
487
488 RemoveFromFavourites ->
489 Favourites.removeFromTracksList tracks
490 )
491 |> (\a -> { cover | tracks = a })
492 )
493 model.selectedCover
494 in
495 { model | favourites = newFavourites, selectedCover = selectedCover }
496 |> reviseCollection effect
497 |> andThen User.saveFavourites
498 |> (if model.scene == Covers then
499 andThen generateCovers >> andThen harvestCovers
500
501 else
502 identity
503 )
504
505
506markAsSelected : Int -> { shiftKey : Bool } -> Manager
507markAsSelected indexInList { shiftKey } model =
508 let
509 selection =
510 if shiftKey then
511 model.selectedTrackIndexes
512 |> List.head
513 |> Maybe.map
514 (\n ->
515 if n > indexInList then
516 List.range indexInList n
517
518 else
519 List.range n indexInList
520 )
521 |> Maybe.withDefault [ indexInList ]
522
523 else
524 [ indexInList ]
525 in
526 Return.singleton { model | selectedTrackIndexes = selection }
527
528
529reload : Json.Value -> Manager
530reload encodedTracks model =
531 reviseCollection
532 (encodedTracks
533 |> Json.decodeValue (Json.list Encoding.trackDecoder)
534 |> Result.withDefault model.tracks.untouched
535 |> Collection.replace
536 )
537 model
538
539
540removeByPaths : Json.Value -> Manager
541removeByPaths encodedParams model =
542 let
543 decoder =
544 Json.map2
545 Tuple.pair
546 (Json.field "filePaths" <| Json.list Json.string)
547 (Json.field "sourceId" Json.string)
548
549 ( paths, sourceId ) =
550 encodedParams
551 |> Json.decodeValue decoder
552 |> Result.withDefault ( [], missingId )
553
554 { kept, removed } =
555 Tracks.removeByPaths
556 { sourceId = sourceId, paths = paths }
557 model.tracks.untouched
558
559 newCollection =
560 { emptyCollection | untouched = kept }
561 in
562 { model | tracks = newCollection }
563 |> reviseCollection Collection.identify
564 |> andThen (removeFromCache removed)
565
566
567removeBySourceId : String -> Manager
568removeBySourceId sourceId model =
569 let
570 { kept, removed } =
571 Tracks.removeBySourceId sourceId model.tracks.untouched
572
573 newCollection =
574 { emptyCollection | untouched = kept }
575 in
576 sourceId
577 |> Json.Encode.string
578 |> Alien.broadcast Alien.RemoveTracksBySourceId
579 |> Ports.toBrain
580 |> return { model | tracks = newCollection }
581 |> andThen (reviseCollection Collection.identify)
582 |> andThen (removeFromCache removed)
583
584
585removeFavourites : List IdentifiedTrack -> Manager
586removeFavourites =
587 manageFavourites RemoveFromFavourites
588
589
590removeFromCache : List Track -> Manager
591removeFromCache tracks model =
592 let
593 trackIds =
594 List.map .id tracks
595 in
596 trackIds
597 |> Json.Encode.list Json.Encode.string
598 |> Alien.broadcast Alien.RemoveTracksFromCache
599 |> Ports.toBrain
600 |> return { model | cachedTracks = List.without trackIds model.cachedTracks }
601 |> andThen harvest
602 |> andThen User.saveEnclosedUserData
603
604
605reviseCollection : (Parcel -> Parcel) -> Manager
606reviseCollection collector model =
607 resolveParcel
608 (model
609 |> makeParcel
610 |> collector
611 )
612 model
613
614
615search : Manager
616search model =
617 case ( model.searchTerm, model.searchResults ) of
618 ( Just term, _ ) ->
619 term
620 |> String.trim
621 |> Json.Encode.string
622 |> Ports.giveBrain Alien.SearchTracks
623 |> return model
624
625 ( Nothing, Just _ ) ->
626 reviseCollection Collection.harvest { model | searchResults = Nothing }
627
628 ( Nothing, Nothing ) ->
629 Return.singleton model
630
631
632selectCover : Cover -> Manager
633selectCover cover model =
634 { model | selectedCover = Just cover }
635 |> (if model.coverSelectionReducesPool then
636 Queue.reset
637
638 else
639 Return.singleton
640 )
641 |> Return.command (Ports.loadAlbumCovers { list = False, coverView = True })
642
643
644setSearchResults : Json.Value -> Manager
645setSearchResults json model =
646 case model.searchTerm of
647 Just _ ->
648 json
649 |> Json.decodeValue (Json.list Json.string)
650 |> Result.withDefault []
651 |> (\results -> { model | searchResults = Just results })
652 |> reviseCollection Collection.harvest
653 |> andThen afterInitialLoad
654
655 Nothing ->
656 Return.singleton model
657
658
659setSearchTerm : String -> Manager
660setSearchTerm term model =
661 (case String.trim term of
662 "" ->
663 { model | searchTerm = Nothing }
664
665 _ ->
666 { model | searchTerm = Just term }
667 )
668 |> Return.communicate
669 (Search
670 |> TracksMsg
671 |> Debouncer.provideInput
672 |> SearchDebounce
673 |> Task.do
674 )
675 |> Return.andThen User.saveEnclosedUserData
676
677
678showCoverMenu : Cover -> Coordinates -> Manager
679showCoverMenu cover coordinates model =
680 let
681 menuDependencies =
682 { cached = model.cachedTracks
683 , cachingInProgress = model.cachingTracksInProgress
684 , currentTime = model.currentTime
685 , selectedPlaylist = model.selectedPlaylist
686 , lastModifiedPlaylistName = model.lastModifiedPlaylist
687 , showAlternativeMenu = False
688 , sources = model.sources
689 }
690 in
691 coordinates
692 |> Tracks.trackMenu menuDependencies cover.tracks
693 |> Common.showContextMenuWithModel model
694
695
696showCoverMenuWithDelay : Cover -> Coordinates -> Manager
697showCoverMenuWithDelay a b model =
698 Tracks.ShowCoverMenu a b
699 |> TracksMsg
700 |> Task.doDelayed 250
701 |> return model
702
703
704showTracksMenu : Maybe Int -> { alt : Bool } -> Coordinates -> Manager
705showTracksMenu maybeTrackIndex { alt } coordinates model =
706 let
707 selection =
708 case maybeTrackIndex of
709 Just trackIndex ->
710 if List.isEmpty model.selectedTrackIndexes then
711 [ trackIndex ]
712
713 else if List.member trackIndex model.selectedTrackIndexes == False then
714 [ trackIndex ]
715
716 else
717 model.selectedTrackIndexes
718
719 Nothing ->
720 model.selectedTrackIndexes
721
722 menuDependencies =
723 { cached = model.cachedTracks
724 , cachingInProgress = model.cachingTracksInProgress
725 , currentTime = model.currentTime
726 , selectedPlaylist = model.selectedPlaylist
727 , lastModifiedPlaylistName = model.lastModifiedPlaylist
728 , showAlternativeMenu = alt
729 , sources = model.sources
730 }
731
732 tracks =
733 List.pickIndexes selection model.tracks.harvested
734 in
735 coordinates
736 |> Tracks.trackMenu menuDependencies tracks
737 |> Common.showContextMenuWithModel
738 { model
739 | dnd = DnD.initialModel
740 , selectedTrackIndexes = selection
741 }
742
743
744showTracksMenuWithDelay : Maybe Int -> { alt : Bool } -> Coordinates -> Manager
745showTracksMenuWithDelay a b c model =
746 Tracks.ShowTracksMenu a b c
747 |> TracksMsg
748 |> Task.doDelayed 250
749 |> return model
750
751
752showViewMenu : Maybe Grouping -> Mouse.Event -> Manager
753showViewMenu maybeGrouping mouseEvent model =
754 mouseEvent.clientPos
755 |> Coordinates.fromTuple
756 |> Tracks.viewMenu model.cachedTracksOnly maybeGrouping
757 |> Common.showContextMenuWithModel model
758
759
760scrollToNowPlaying : Manager
761scrollToNowPlaying model =
762 model.nowPlaying
763 |> Maybe.map
764 (.item >> .identifiedTrack >> Tuple.second >> .id)
765 |> Maybe.andThen
766 (\id ->
767 List.find
768 (Tuple.second >> .id >> (==) id)
769 model.tracks.harvested
770 )
771 |> Maybe.map
772 (\( identifiers, track ) ->
773 case model.scene of
774 Covers ->
775 if List.member Keyboard.Shift model.pressedKeys then
776 return
777 { model | selectedCover = Nothing }
778 (UI.Theme.scrollToNowPlaying Covers ( identifiers, track ) model)
779
780 else
781 model.covers.harvested
782 |> List.find (\cover -> List.member track.id cover.trackIds)
783 |> Maybe.unwrap model (\cover -> { model | selectedCover = Just cover })
784 |> Return.communicate (Ports.loadAlbumCovers { list = True, coverView = True })
785
786 List ->
787 return
788 { model | selectedCover = Nothing }
789 (UI.Theme.scrollToNowPlaying List ( identifiers, track ) model)
790 )
791 |> Maybe.map
792 (UI.Page.Index
793 |> Common.changeUrlUsingPage
794 |> andThen
795 )
796 |> Maybe.withDefault
797 (Return.singleton model)
798
799
800sortBy : SortBy -> Manager
801sortBy property model =
802 let
803 sortDir =
804 if model.sortBy /= property then
805 Asc
806
807 else if model.sortDirection == Asc then
808 Desc
809
810 else
811 Asc
812 in
813 { model | sortBy = property, sortDirection = sortDir }
814 |> reviseCollection Collection.arrange
815 |> andThen User.saveEnclosedUserData
816
817
818storeInCache : List Track -> Manager
819storeInCache tracks model =
820 let
821 trackIds =
822 List.map .id tracks
823
824 notification =
825 case tracks of
826 [ t ] ->
827 ("__" ++ t.tags.title ++ "__ will be stored in the cache")
828 |> Notifications.casual
829
830 list ->
831 list
832 |> List.length
833 |> String.fromInt
834 |> (\s -> "__" ++ s ++ " tracks__ will be stored in the cache")
835 |> Notifications.casual
836 in
837 tracks
838 |> Json.Encode.list
839 (\track ->
840 Json.Encode.object
841 [ ( "trackId"
842 , Json.Encode.string track.id
843 )
844 , ( "url"
845 , track
846 |> Queue.makeTrackUrl
847 model.currentTime
848 model.sources
849 |> Json.Encode.string
850 )
851 ]
852 )
853 |> Alien.broadcast Alien.StoreTracksInCache
854 |> Ports.toBrain
855 |> return { model | cachingTracksInProgress = model.cachingTracksInProgress ++ trackIds }
856 |> andThen (Common.showNotification notification)
857
858
859storedInCache : Json.Value -> Maybe String -> Manager
860storedInCache json maybeError =
861 case
862 ( maybeError
863 , Json.decodeValue (Json.list Json.string) json
864 )
865 of
866 ( Nothing, Ok list ) ->
867 finishedStoringInCache list
868
869 ( Nothing, Err err ) ->
870 err
871 |> Json.errorToString
872 |> Notifications.error
873 |> Common.showNotification
874
875 ( Just _, Ok trackIds ) ->
876 failedToStoreInCache trackIds
877
878 ( Just err, Err _ ) ->
879 err
880 |> Notifications.error
881 |> Common.showNotification
882
883
884syncTags : List Track -> Manager
885syncTags tracks =
886 tracks
887 |> Json.Encode.list
888 (\track ->
889 Json.Encode.object
890 [ ( "path", Json.Encode.string track.path )
891 , ( "sourceId", Json.Encode.string track.sourceId )
892 , ( "trackId", Json.Encode.string track.id )
893 ]
894 )
895 |> Alien.broadcast Alien.SyncTrackTags
896 |> Ports.toBrain
897 |> Return.communicate
898
899
900toggleCachedOnly : Manager
901toggleCachedOnly model =
902 { model | cachedTracksOnly = not model.cachedTracksOnly }
903 |> reviseCollection Collection.harvest
904 |> andThen User.saveEnclosedUserData
905 |> andThen Common.forceTracksRerender
906
907
908toggleCoverSelectionReducesPool : Manager
909toggleCoverSelectionReducesPool model =
910 { model | coverSelectionReducesPool = not model.coverSelectionReducesPool }
911 |> Queue.reset
912 |> andThen User.saveSettings
913
914
915toggleFavourite : Int -> Manager
916toggleFavourite index model =
917 case List.getAt index model.tracks.harvested of
918 Just ( i, t ) ->
919 let
920 newFavourites =
921 Favourites.toggleInFavouritesList ( i, t ) model.favourites
922
923 effect collection =
924 collection
925 |> Collection.map (Favourites.toggleInTracksList t)
926 |> (if model.favouritesOnly then
927 Collection.harvest
928
929 else
930 identity
931 )
932
933 selectedCover =
934 Maybe.map
935 (\cover ->
936 cover.tracks
937 |> Favourites.toggleInTracksList t
938 |> (\a -> { cover | tracks = a })
939 )
940 model.selectedCover
941 in
942 { model | favourites = newFavourites, selectedCover = selectedCover }
943 |> reviseCollection effect
944 |> andThen User.saveFavourites
945 |> (if model.scene == Covers then
946 andThen generateCovers >> andThen harvestCovers
947
948 else
949 identity
950 )
951
952 Nothing ->
953 Return.singleton model
954
955
956toggleFavouritesOnly : Manager
957toggleFavouritesOnly model =
958 { model | favouritesOnly = not model.favouritesOnly }
959 |> reviseCollection Collection.harvest
960 |> andThen User.saveEnclosedUserData
961
962
963toggleHideDuplicates : Manager
964toggleHideDuplicates model =
965 { model | hideDuplicates = not model.hideDuplicates }
966 |> reviseCollection Collection.arrange
967 |> andThen User.saveSettings
968
969
970
971-- 📣 ░░ PARCEL
972
973
974makeParcel : Model -> Parcel
975makeParcel model =
976 ( { cached = model.cachedTracks
977 , cachedOnly = model.cachedTracksOnly
978 , enabledSourceIds = Sources.enabledSourceIds model.sources
979 , favourites = model.favourites
980 , favouritesOnly = model.favouritesOnly
981 , grouping = model.grouping
982 , hideDuplicates = model.hideDuplicates
983 , searchResults = model.searchResults
984 , selectedPlaylist = model.selectedPlaylist
985 , sortBy = model.sortBy
986 , sortDirection = model.sortDirection
987 }
988 , model.tracks
989 )
990
991
992resolveParcel : Parcel -> Manager
993resolveParcel ( deps, newCollection ) model =
994 let
995 scrollObj =
996 Json.Encode.object
997 [ ( "scrollTop", Json.Encode.int 0 ) ]
998
999 scrollEvent =
1000 Json.Encode.object
1001 [ ( "target", scrollObj ) ]
1002
1003 newScrollContext =
1004 scrollContext model
1005
1006 collectionChanged =
1007 Collection.tracksChanged
1008 model.tracks.untouched
1009 newCollection.untouched
1010
1011 arrangementChanged =
1012 if collectionChanged then
1013 True
1014
1015 else
1016 Collection.identifiedTracksChanged
1017 model.tracks.arranged
1018 newCollection.arranged
1019
1020 harvestChanged =
1021 if arrangementChanged then
1022 True
1023
1024 else
1025 Collection.identifiedTracksChanged
1026 model.tracks.harvested
1027 newCollection.harvested
1028
1029 scrollContextChanged =
1030 newScrollContext /= model.tracks.scrollContext
1031
1032 modelWithNewCollection =
1033 (if model.scene == List && scrollContextChanged then
1034 \m -> { m | infiniteList = InfiniteList.updateScroll scrollEvent m.infiniteList }
1035
1036 else
1037 identity
1038 )
1039 { model
1040 | tracks =
1041 { newCollection | scrollContext = newScrollContext }
1042 , selectedTrackIndexes =
1043 if collectionChanged || harvestChanged then
1044 []
1045
1046 else
1047 model.selectedTrackIndexes
1048 }
1049 in
1050 (if collectionChanged then
1051 whenCollectionChanges
1052
1053 else if arrangementChanged then
1054 whenArrangementChanges
1055
1056 else if harvestChanged then
1057 whenHarvestChanges
1058
1059 else
1060 identity
1061 )
1062 ( modelWithNewCollection
1063 -----------------------------------------
1064 -- Command
1065 -----------------------------------------
1066 , if scrollContextChanged then
1067 UI.Theme.scrollTracksToTop model.scene
1068
1069 else
1070 Cmd.none
1071 )
1072
1073
1074whenHarvestChanges =
1075 andThen harvestCovers >> andThen Queue.reset
1076
1077
1078whenArrangementChanges =
1079 andThen generateCovers >> whenHarvestChanges
1080
1081
1082whenCollectionChanges =
1083 andThen search >> andThen Common.generateDirectoryPlaylists >> whenArrangementChanges
1084
1085
1086scrollContext : Model -> String
1087scrollContext model =
1088 String.concat
1089 [ Maybe.withDefault "" <| model.searchTerm
1090 , Maybe.withDefault "" <| Maybe.map .name model.selectedPlaylist
1091 ]
1092
1093
1094
1095-- 📣 ░░ USER DATA
1096
1097
1098importHypaethral : HypaethralData -> Maybe Playlist -> Manager
1099importHypaethral data selectedPlaylist model =
1100 { model
1101 | favourites = data.favourites
1102 , selectedPlaylist = selectedPlaylist
1103 , tracks = { emptyCollection | untouched = data.tracks }
1104 }
1105 |> reviseCollection Collection.identify
1106 |> andThen search
1107 |> (case model.searchTerm of
1108 Just _ ->
1109 identity
1110
1111 Nothing ->
1112 andThen afterInitialLoad
1113 )
1114
1115
1116
1117-- ㊙️
1118
1119
1120type FavouritesManagementAction
1121 = AddToFavourites
1122 | RemoveFromFavourites