···3232import UI.Authentication.State as Authentication
3333import UI.Backdrop as Backdrop
3434import UI.Common.State as Common
3535+import UI.DnD as DnD
3536import UI.Equalizer.State as Equalizer
3637import UI.Equalizer.View as Equalizer
3738import UI.Interface.State as Interface
···4041import UI.Playlists.ContextMenu as Playlists
4142import UI.Playlists.State as Playlists
4243import UI.Ports as Ports
4343-import UI.Queue as Queue
4444-import UI.Queue.ContextMenu as Queue
4444+import UI.Queue.State as Queue
4545+import UI.Queue.Types as Queue
4546import UI.Reply.Translate as Reply
4647import UI.Routing.State as Routing
4748import UI.Services.State as Services
···9495 , currentTime = Time.millisToPosix flags.initialTime
9596 , darkMode = flags.darkMode
9697 , downloading = Nothing
9898+ , dnd = DnD.initialModel
9799 , focusedOnInput = False
98100 , isDragging = False
99101 , isLoading = True
···159161 , playlistToActivate = Nothing
160162161163 -----------------------------------------
162162- -- Children (TODO)
164164+ -- Queue
165165+ -----------------------------------------
166166+ , dontPlay = []
167167+ , nowPlaying = Nothing
168168+ , playedPreviously = []
169169+ , playingNext = []
170170+ , selectedQueueItem = Nothing
171171+172172+ --
173173+ , repeat = False
174174+ , shuffle = False
175175+176176+ -----------------------------------------
177177+ -- 🦉 Nested
163178 -----------------------------------------
164179 , authentication = Authentication.initialModel url
165165- , queue = Queue.initialModel
180180+181181+ -----------------------------------------
182182+ -- Children (TODO)
183183+ -----------------------------------------
166184 , sources = Sources.initialModel
167185 , tracks = Tracks.initialModel
168186 }
···279297280298 Debounce a ->
281299 Interface.debounce update a
300300+301301+ DnD a ->
302302+ Interface.dnd a
282303283304 FocusedOnInput ->
284305 Interface.focusedOnInput
···415436 AuthenticationMsg a ->
416437 Authentication.update a
417438439439+ QueueMsg a ->
440440+ Queue.update a
441441+418442 -----------------------------------------
419443 -- Children (TODO)
420444 -----------------------------------------
421421- QueueMsg sub ->
422422- \model ->
423423- Return3.wieldNested
424424- Reply.translate
425425- { mapCmd = QueueMsg
426426- , mapModel = \child -> { model | queue = child }
427427- , update = Queue.update
428428- }
429429- { model = model.queue
430430- , msg = sub
431431- }
432432-433445 SourcesMsg sub ->
434446 \model ->
435447 Return3.wieldNested
+3-4
src/Applications/UI/Audio/State.elm
···66import Return exposing (return)
77import Return.Ext as Return exposing (communicate)
88import UI.Ports as Ports
99-import UI.Queue as Queue
99+import UI.Queue.State as Queue
1010import UI.Reply as Reply
1111import UI.Types as UI exposing (Manager, Organizer)
1212···40404141playPause : Manager
4242playPause model =
4343- if Maybe.isNothing model.queue.activeItem then
4444- -- TODO!
4545- Return.performance (UI.QueueMsg Queue.Shift) model
4343+ if Maybe.isNothing model.nowPlaying then
4444+ Queue.shift model
46454746 else if model.audioIsPlaying then
4847 communicate (Ports.pause ()) model
-1
src/Applications/UI/Console.elm
···1212import Material.Icons.Types exposing (Coloring(..))
1313import Maybe.Extra as Maybe
1414import Queue
1515-import UI.Queue as Queue
1615import UI.Reply exposing (Reply(..))
17161817
+7-4
src/Applications/UI/DnD.elm
···33import Html exposing (Attribute)
44import Html.Events.Extra.Mouse as Mouse
55import Html.Events.Extra.Pointer as Pointer
66-import UI.Reply as Reply exposing (Reply)
768798···3029 }
313032313232+type alias Response =
3333+ { initiated : Bool }
3434+3535+3336initialModel : Model context
3437initialModel =
3538 NotDragging
···3942-- 📣
404341444242-update : Msg context -> Model context -> ( Model context, List Reply )
4545+update : Msg context -> Model context -> ( Model context, Response )
4346update msg model =
4447 ( ------------------------------------
4548 -- Model
···9699 ------------------------------------
97100 , case msg of
98101 Start context ->
9999- [ Reply.StartedDragging ]
102102+ { initiated = True }
100103101104 _ ->
102102- []
105105+ { initiated = False }
103106 )
104107105108
+39-8
src/Applications/UI/Interface/State.elm
···77import UI.Common.State as Common exposing (modifySingleton)
88import UI.DnD as DnD
99import UI.Page as Page
1010-import UI.Queue as Queue
1110import UI.Queue.State as Queue
1211import UI.Tracks as Tracks
1312import UI.Tracks.Scene.List
···4746 return updatedModel mappedCmd
484749484949+dnd : DnD.Msg Int -> Manager
5050+dnd dragMsg model =
5151+ let
5252+ ( d, { initiated } ) =
5353+ DnD.update dragMsg model.dnd
5454+5555+ m =
5656+ if initiated then
5757+ { model | dnd = d, isDragging = True }
5858+5959+ else
6060+ { model | dnd = d }
6161+ in
6262+ if DnD.hasDropped d then
6363+ case model.page of
6464+ Page.Queue _ ->
6565+ let
6666+ ( from, to ) =
6767+ ( Maybe.withDefault 0 <| DnD.modelSubject d
6868+ , Maybe.withDefault 0 <| DnD.modelTarget d
6969+ )
7070+7171+ newFuture =
7272+ Queue.moveItem
7373+ { from = from, to = to, shuffle = model.shuffle }
7474+ model.playingNext
7575+ in
7676+ Queue.fill { m | playingNext = newFuture }
7777+7878+ _ ->
7979+ Return.singleton m
8080+8181+ else
8282+ Return.singleton m
8383+8484+5085focusedOnInput : Manager
5186focusedOnInput model =
5287 Return.singleton { model | focusedOnInput = True }
···681036910470105removeQueueSelection : Manager
7171-removeQueueSelection =
7272- modifySingleton Queue.lens (\q -> { q | selection = Nothing })
106106+removeQueueSelection model =
107107+ Return.singleton { model | selectedQueueItem = Nothing }
731087410975110removeTrackSelection : Manager
···101136 -- do the appropriate thing.
102137 case model.page of
103138 Page.Queue _ ->
104104- -- TODO!
105105- DnD.stoppedDragging
106106- |> Queue.DragMsg
107107- |> QueueMsg
108108- |> Return.performanceF notDragging
139139+ dnd DnD.stoppedDragging notDragging
109140110141 Page.Index ->
111142 case model.tracks.scene of
-730
src/Applications/UI/Queue.elm
···11-module UI.Queue exposing (Model, Msg(..), initialModel, moveQueueItemToFirst, moveQueueItemToLast, update, view)
22-33-import Chunky exposing (..)
44-import Common
55-import Conditional exposing (..)
66-import Coordinates
77-import Css.Classes as C
88-import Html exposing (Html, text)
99-import Html.Attributes exposing (href)
1010-import Html.Events.Extra.Mouse as Mouse
1111-import Icons
1212-import List.Extra as List
1313-import Material.Icons as Icons
1414-import Material.Icons.Types exposing (Coloring(..))
1515-import Queue exposing (..)
1616-import Return3 exposing (..)
1717-import Time
1818-import Tracks exposing (IdentifiedTrack)
1919-import UI.DnD as DnD
2020-import UI.Kit
2121-import UI.List
2222-import UI.Navigation exposing (..)
2323-import UI.Page as Page
2424-import UI.Ports as Ports
2525-import UI.Queue.Fill as Fill
2626-import UI.Queue.Page as Queue exposing (Page(..))
2727-import UI.Reply exposing (Reply(..))
2828-import UI.Sources.Page
2929-3030-3131-3232--- 🌳
3333-3434-3535-type alias Model =
3636- { activeItem : Maybe Item
3737- , future : List Item
3838- , ignored : List Item
3939- , past : List Item
4040-4141- --
4242- , repeat : Bool
4343- , shuffle : Bool
4444-4545- --
4646- , dnd : DnD.Model Int
4747- , selection : Maybe Item
4848- }
4949-5050-5151-initialModel : Model
5252-initialModel =
5353- { activeItem = Nothing
5454- , future = []
5555- , ignored = []
5656- , past = []
5757-5858- --
5959- , repeat = False
6060- , shuffle = False
6161-6262- --
6363- , dnd = DnD.initialModel
6464- , selection = Nothing
6565- }
6666-6767-6868-6969--- 📣
7070-7171-7272-type Msg
7373- = Select Item
7474- | ShowFutureMenu Item { index : Int } Mouse.Event
7575- | ShowHistoryMenu Item Mouse.Event
7676- ------------------------------------
7777- -- Combos
7878- ------------------------------------
7979- | InjectFirstAndPlay IdentifiedTrack
8080- ------------------------------------
8181- -- Future
8282- ------------------------------------
8383- | InjectFirst { showNotification : Bool } (List IdentifiedTrack)
8484- | InjectLast { showNotification : Bool } (List IdentifiedTrack)
8585- | RemoveItem { index : Int, item : Item }
8686- ------------------------------------
8787- -- Position
8888- ------------------------------------
8989- | Rewind
9090- | Shift
9191- ------------------------------------
9292- -- Contents
9393- ------------------------------------
9494- | Clear
9595- | Reset
9696- | Fill Time.Posix (List IdentifiedTrack)
9797- ------------------------------------
9898- -- Drag & Drop
9999- ------------------------------------
100100- | DragMsg (DnD.Msg Int)
101101- ------------------------------------
102102- -- Settings
103103- ------------------------------------
104104- | ToggleRepeat
105105- | ToggleShuffle
106106-107107-108108-update : Msg -> Model -> Return Model Msg Reply
109109-update msg model =
110110- case msg of
111111- Select item ->
112112- return { model | selection = Just item }
113113-114114- ShowFutureMenu item { index } mouseEvent ->
115115- returnRepliesWithModel
116116- model
117117- [ ShowQueueFutureMenu
118118- (Coordinates.fromTuple mouseEvent.clientPos)
119119- { item = item, itemIndex = index }
120120- ]
121121-122122- ShowHistoryMenu item mouseEvent ->
123123- returnRepliesWithModel
124124- model
125125- [ ShowQueueHistoryMenu
126126- (Coordinates.fromTuple mouseEvent.clientPos)
127127- { item = item }
128128- ]
129129-130130- ------------------------------------
131131- -- Combos
132132- ------------------------------------
133133- InjectFirstAndPlay identifiedTrack ->
134134- [ identifiedTrack ]
135135- |> InjectFirst { showNotification = False }
136136- |> updateWithModel model
137137- |> andThen (update Shift)
138138-139139- ------------------------------------
140140- -- Future
141141- ------------------------------------
142142- -- # InjectFirst
143143- -- > Add an item in front of the queue.
144144- --
145145- InjectFirst { showNotification } identifiedTracks ->
146146- let
147147- ( items, tracks ) =
148148- ( List.map (makeItem True) identifiedTracks
149149- , List.map Tuple.second identifiedTracks
150150- )
151151-152152- cleanedFuture =
153153- List.foldl
154154- (\track future ->
155155- Fill.cleanAutoGenerated model.shuffle track.id future
156156- )
157157- model.future
158158- tracks
159159- in
160160- [ case tracks of
161161- [ t ] ->
162162- ("__" ++ t.tags.title ++ "__ will be played next")
163163- |> ShowSuccessNotification
164164-165165- list ->
166166- list
167167- |> List.length
168168- |> String.fromInt
169169- |> (\s -> "__" ++ s ++ " tracks__ will be played next")
170170- |> ShowSuccessNotification
171171- ]
172172- |> (\list -> ifThenElse showNotification list [])
173173- |> returnRepliesWithModel { model | future = items ++ cleanedFuture }
174174- |> addReply FillQueue
175175-176176- -- # InjectLast
177177- -- > Add an item after the last manual entry
178178- -- (ie. after the last injected item).
179179- --
180180- InjectLast { showNotification } identifiedTracks ->
181181- let
182182- ( items, tracks ) =
183183- ( List.map (makeItem True) identifiedTracks
184184- , List.map Tuple.second identifiedTracks
185185- )
186186-187187- cleanedFuture =
188188- List.foldl
189189- (\track future ->
190190- Fill.cleanAutoGenerated model.shuffle track.id future
191191- )
192192- model.future
193193- tracks
194194-195195- manualItems =
196196- cleanedFuture
197197- |> List.filter (.manualEntry >> (==) True)
198198- |> List.length
199199-200200- newFuture =
201201- []
202202- ++ List.take manualItems cleanedFuture
203203- ++ items
204204- ++ List.drop manualItems cleanedFuture
205205- in
206206- [ case tracks of
207207- [ t ] ->
208208- ("__" ++ t.tags.title ++ "__ was added to the queue")
209209- |> ShowSuccessNotification
210210-211211- list ->
212212- list
213213- |> List.length
214214- |> String.fromInt
215215- |> (\s -> "__" ++ s ++ " tracks__ were added to the queue")
216216- |> ShowSuccessNotification
217217- ]
218218- |> (\list -> ifThenElse showNotification list [])
219219- |> returnRepliesWithModel { model | future = newFuture }
220220- |> addReply FillQueue
221221-222222- -- # RemoveItem
223223- -- > Remove a future item.
224224- --
225225- RemoveItem { index, item } ->
226226- let
227227- newFuture =
228228- List.removeAt index model.future
229229-230230- newIgnored =
231231- if item.manualEntry then
232232- model.ignored
233233-234234- else
235235- item :: model.ignored
236236- in
237237- returnRepliesWithModel
238238- { model | future = newFuture, ignored = newIgnored }
239239- [ FillQueue ]
240240-241241- -----------------------------------------
242242- -- Position
243243- -----------------------------------------
244244- -- # Rewind
245245- -- > Put the next item in the queue as the current one.
246246- --
247247- Rewind ->
248248- changeActiveItem
249249- (List.last model.past)
250250- { model
251251- | future =
252252- model.activeItem
253253- |> Maybe.map (\item -> item :: model.future)
254254- |> Maybe.withDefault model.future
255255- , past =
256256- model.past
257257- |> List.init
258258- |> Maybe.withDefault []
259259- }
260260-261261- -- # Shift
262262- -- > Put the next item in the queue as the current one.
263263- --
264264- Shift ->
265265- changeActiveItem
266266- (List.head model.future)
267267- { model
268268- | future =
269269- model.future
270270- |> List.drop 1
271271- , past =
272272- model.activeItem
273273- |> Maybe.map List.singleton
274274- |> Maybe.map (List.append model.past)
275275- |> Maybe.withDefault model.past
276276- }
277277-278278- ------------------------------------
279279- -- Contents
280280- ------------------------------------
281281- -- # Clear
282282- --
283283- Clear ->
284284- returnRepliesWithModel
285285- { model | future = [], ignored = [] }
286286- [ FillQueue ]
287287-288288- -- # Fill
289289- -- > Fill the queue with items.
290290- --
291291- Fill timestamp tracks ->
292292- returnReplyWithModel
293293- (fillQueue timestamp tracks model)
294294- PreloadNextTrack
295295-296296- -- # Reset
297297- -- > Renew the queue, meaning that the auto-generated items in the queue
298298- -- are removed and new items are added.
299299- --
300300- Reset ->
301301- let
302302- newFuture =
303303- List.filter (.manualEntry >> (==) True) model.future
304304- in
305305- returnRepliesWithModel
306306- { model | future = newFuture, ignored = [] }
307307- [ FillQueue ]
308308-309309- ------------------------------------
310310- -- Drag & Drop
311311- ------------------------------------
312312- DragMsg dragMsg ->
313313- let
314314- ( dnd, replies ) =
315315- DnD.update dragMsg model.dnd
316316- in
317317- if DnD.hasDropped dnd then
318318- let
319319- ( from, to ) =
320320- ( Maybe.withDefault 0 <| DnD.modelSubject dnd
321321- , Maybe.withDefault 0 <| DnD.modelTarget dnd
322322- )
323323-324324- newFuture =
325325- moveItem
326326- { from = from, to = to, shuffle = model.shuffle }
327327- model.future
328328- in
329329- returnRepliesWithModel
330330- { model | dnd = dnd, future = newFuture }
331331- (FillQueue :: replies)
332332-333333- else
334334- returnRepliesWithModel
335335- { model | dnd = dnd }
336336- replies
337337-338338- ------------------------------------
339339- -- Settings
340340- ------------------------------------
341341- ToggleRepeat ->
342342- ( { model | repeat = not model.repeat }
343343- , Ports.setRepeat (not model.repeat)
344344- , [ SaveEnclosedUserData ]
345345- )
346346-347347- ToggleShuffle ->
348348- { model | shuffle = not model.shuffle }
349349- |> update Reset
350350- |> addReply SaveEnclosedUserData
351351-352352-353353-updateWithModel : Model -> Msg -> Return Model Msg Reply
354354-updateWithModel model msg =
355355- update msg model
356356-357357-358358-359359--- 📣 ░░ COMMON
360360-361361-362362-changeActiveItem : Maybe Item -> Model -> Return Model Msg Reply
363363-changeActiveItem maybeItem model =
364364- returnRepliesWithModel
365365- { model | activeItem = maybeItem }
366366- [ ActiveQueueItemChanged maybeItem
367367- , FillQueue
368368- ]
369369-370370-371371-fillQueue : Time.Posix -> List IdentifiedTrack -> Model -> Model
372372-fillQueue timestamp availableTracks model =
373373- let
374374- nonMissingTracks =
375375- List.filter
376376- (Tuple.second >> .id >> (/=) Tracks.missingId)
377377- availableTracks
378378- in
379379- model
380380- |> (\m ->
381381- -- Empty the ignored list when we are ignoring all the tracks
382382- if List.length model.ignored == List.length nonMissingTracks then
383383- { m | ignored = [] }
384384-385385- else
386386- m
387387- )
388388- |> (\m ->
389389- if m.shuffle && List.length model.future >= Fill.queueLength then
390390- m
391391-392392- else
393393- let
394394- fillState =
395395- { activeItem = m.activeItem
396396- , future = m.future
397397- , ignored = m.ignored
398398- , past = m.past
399399- }
400400- in
401401- -- Fill using the appropiate method
402402- case m.shuffle of
403403- False ->
404404- { m | future = Fill.ordered timestamp nonMissingTracks fillState }
405405-406406- True ->
407407- { m | future = Fill.shuffled timestamp nonMissingTracks fillState }
408408- )
409409-410410-411411-moveQueueItemToFirst : Model -> { itemIndex : Int } -> Model
412412-moveQueueItemToFirst model { itemIndex } =
413413- model.future
414414- |> moveItem { from = itemIndex, to = 0, shuffle = model.shuffle }
415415- |> (\f -> { model | future = f })
416416-417417-418418-moveQueueItemToLast : Model -> { itemIndex : Int } -> Model
419419-moveQueueItemToLast model { itemIndex } =
420420- let
421421- to =
422422- model.future
423423- |> List.filter (.manualEntry >> (==) True)
424424- |> List.length
425425- in
426426- model.future
427427- |> moveItem { from = itemIndex, to = to, shuffle = model.shuffle }
428428- |> (\f -> { model | future = f })
429429-430430-431431-moveItem : { from : Int, to : Int, shuffle : Bool } -> List Item -> List Item
432432-moveItem { from, to, shuffle } collection =
433433- let
434434- subjectItem =
435435- collection
436436- |> List.getAt from
437437- |> Maybe.map (\s -> { s | manualEntry = True })
438438-439439- fixedTarget =
440440- if to > from then
441441- to - 1
442442-443443- else
444444- to
445445- in
446446- collection
447447- |> List.removeAt from
448448- |> List.indexedFoldr
449449- (\idx existingItem acc ->
450450- if idx == fixedTarget then
451451- case subjectItem of
452452- Just itemToInsert ->
453453- List.append [ itemToInsert, existingItem ] acc
454454-455455- Nothing ->
456456- existingItem :: acc
457457-458458- else if idx < fixedTarget then
459459- { existingItem | manualEntry = True } :: acc
460460-461461- else
462462- existingItem :: acc
463463- )
464464- []
465465- |> (if shuffle then
466466- identity
467467-468468- else
469469- List.filter (.manualEntry >> (==) True)
470470- )
471471-472472-473473-474474--- 🗺
475475-476476-477477-view : Queue.Page -> Model -> Html Msg
478478-view page model =
479479- UI.Kit.receptacle
480480- { scrolling = not (DnD.isDragging model.dnd) }
481481- (case page of
482482- History ->
483483- historyView model
484484-485485- Index ->
486486- futureView model
487487- )
488488-489489-490490-491491--- 🗺 ░░ FUTURE
492492-493493-494494-futureView : Model -> List (Html Msg)
495495-futureView model =
496496- [ -----------------------------------------
497497- -- Navigation
498498- -----------------------------------------
499499- UI.Navigation.local
500500- [ ( Icon Icons.arrow_back
501501- , Label Common.backToIndex Hidden
502502- , NavigateToPage Page.Index
503503- )
504504- , ( Icon Icons.history
505505- , Label "History" Shown
506506- , NavigateToPage (Page.Queue History)
507507- )
508508- , ( Icon Icons.clear
509509- , Label "Clear" Shown
510510- , PerformMsg Clear
511511- )
512512- , ( Icon Icons.not_interested
513513- , Label "Reset ignored" Shown
514514- , PerformMsg Reset
515515- )
516516- ]
517517-518518- -----------------------------------------
519519- -- Content
520520- -----------------------------------------
521521- , if List.isEmpty model.future then
522522- chunk
523523- [ C.relative ]
524524- [ chunk
525525- [ C.absolute, C.left_0, C.top_0 ]
526526- [ UI.Kit.canister [ UI.Kit.h1 "Up next" ] ]
527527- ]
528528-529529- else
530530- UI.Kit.canister
531531- [ UI.Kit.h1 "Up next"
532532- , model.future
533533- |> List.indexedMap (futureItem model.selection)
534534- |> UI.List.view
535535- (UI.List.Draggable
536536- { model = model.dnd
537537- , toMsg = DragMsg
538538- }
539539- )
540540- |> chunky [ C.mt_3 ]
541541- ]
542542-543543- --
544544- , if List.isEmpty model.future then
545545- UI.Kit.centeredContent
546546- [ slab
547547- Html.a
548548- [ href (Page.toString <| Page.Sources UI.Sources.Page.New) ]
549549- [ C.text_inherit, C.block, C.opacity_30 ]
550550- [ Icons.music_note 64 Inherit ]
551551- , slab
552552- Html.a
553553- [ href (Page.toString <| Page.Sources UI.Sources.Page.New) ]
554554- [ C.text_inherit, C.block, C.leading_normal, C.mt_2, C.opacity_40, C.text_center ]
555555- [ text "Nothing here yet,"
556556- , lineBreak
557557- , text "add some music first."
558558- ]
559559- ]
560560-561561- else
562562- nothing
563563- ]
564564-565565-566566-futureItem : Maybe Item -> Int -> Queue.Item -> UI.List.Item Msg
567567-futureItem selection idx item =
568568- let
569569- ( identifiers, track ) =
570570- item.identifiedTrack
571571-572572- isSelected =
573573- selection
574574- |> Maybe.map (.identifiedTrack >> Tuple.first >> .indexInList)
575575- |> (==) (Just identifiers.indexInList)
576576-577577- iconFn =
578578- if item.manualEntry then
579579- identity
580580-581581- else
582582- Icons.wrapped subtleFutureIconClasses
583583- in
584584- { label =
585585- inline
586586- [ C.block
587587- , C.truncate
588588-589589- --
590590- , if item.manualEntry || isSelected then
591591- C.text_inherit
592592-593593- else
594594- C.text_base05
595595-596596- -- Dark mode
597597- ------------
598598- , if item.manualEntry || isSelected then
599599- C.dark__text_inherit
600600-601601- else
602602- C.dark__text_base04
603603- ]
604604- [ inline
605605- [ C.inline_block
606606- , C.mr_2
607607- , C.opacity_60
608608- , C.text_xs
609609- ]
610610- [ text (String.fromInt <| idx + 1), text "." ]
611611- , text (track.tags.artist ++ " - " ++ track.tags.title)
612612- ]
613613- , actions =
614614- [ -- Remove
615615- ---------
616616- { icon =
617617- if item.manualEntry then
618618- iconFn Icons.remove_circle_outline
619619-620620- else
621621- iconFn Icons.not_interested
622622- , msg = Just (\_ -> RemoveItem { index = idx, item = item })
623623- , title = ifThenElse item.manualEntry "Remove" "Ignore"
624624- }
625625-626626- -- Menu
627627- -------
628628- , { icon = iconFn Icons.more_vert
629629- , msg = Just (ShowFutureMenu item { index = idx })
630630- , title = "Menu"
631631- }
632632- ]
633633- , msg = Just (Select item)
634634- , isSelected = isSelected
635635- }
636636-637637-638638-subtleFutureIconClasses : List String
639639-subtleFutureIconClasses =
640640- [ C.text_gray_500
641641-642642- -- Dark mode
643643- ------------
644644- , C.dark__text_base02
645645- ]
646646-647647-648648-649649--- 🗺 ░░ HISTORY
650650-651651-652652-historyView : Model -> List (Html Msg)
653653-historyView model =
654654- [ -----------------------------------------
655655- -- Navigation
656656- -----------------------------------------
657657- UI.Navigation.local
658658- [ ( Icon Icons.arrow_back
659659- , Label Common.backToIndex Hidden
660660- , NavigateToPage Page.Index
661661- )
662662- , ( Icon Icons.update
663663- , Label "Up next" Shown
664664- , NavigateToPage (Page.Queue Index)
665665- )
666666- ]
667667-668668- -----------------------------------------
669669- -- Content
670670- -----------------------------------------
671671- , if List.isEmpty model.past then
672672- chunk
673673- [ C.relative ]
674674- [ chunk
675675- [ C.absolute, C.left_0, C.top_0 ]
676676- [ UI.Kit.canister [ UI.Kit.h1 "History" ] ]
677677- ]
678678-679679- else
680680- UI.Kit.canister
681681- [ UI.Kit.h1 "History"
682682- , model.past
683683- |> List.reverse
684684- |> List.indexedMap historyItem
685685- |> UI.List.view UI.List.Normal
686686- |> chunky [ C.mt_3 ]
687687- ]
688688-689689- --
690690- , if List.isEmpty model.past then
691691- UI.Kit.centeredContent
692692- [ chunk
693693- [ C.opacity_30 ]
694694- [ Icons.music_note 64 Inherit ]
695695- , chunk
696696- [ C.leading_normal, C.mt_2, C.opacity_40, C.text_center ]
697697- [ text "Nothing here yet,"
698698- , lineBreak
699699- , text "play some music first."
700700- ]
701701- ]
702702-703703- else
704704- nothing
705705- ]
706706-707707-708708-historyItem : Int -> Queue.Item -> UI.List.Item Msg
709709-historyItem idx ({ identifiedTrack, manualEntry } as item) =
710710- let
711711- ( _, track ) =
712712- identifiedTrack
713713- in
714714- { label =
715715- inline
716716- [ C.block, C.truncate ]
717717- [ inline
718718- [ C.inline_block, C.text_xs, C.mr_2 ]
719719- [ text (String.fromInt <| idx + 1), text "." ]
720720- , text (track.tags.artist ++ " - " ++ track.tags.title)
721721- ]
722722- , actions =
723723- [ { icon = Icons.more_vert
724724- , msg = Just (ShowHistoryMenu item)
725725- , title = "Menu"
726726- }
727727- ]
728728- , msg = Nothing
729729- , isSelected = False
730730- }
+450-5
src/Applications/UI/Queue/State.elm
···11module UI.Queue.State exposing (..)
2233+import Coordinates
44+import Dict
55+import Html.Events.Extra.Mouse as Mouse
66+import List.Extra as List
37import Monocle.Lens as Lens exposing (Lens)
88+import Notifications
99+import Queue exposing (..)
1010+import Return exposing (andThen, return)
1111+import Return.Ext as Return
1212+import Tracks exposing (..)
1313+import UI.Common.State as Common
1414+import UI.Ports as Ports
1515+import UI.Queue.ContextMenu as Queue
1616+import UI.Queue.Fill as Fill
1717+import UI.Queue.Types as Queue exposing (..)
1818+import UI.Tracks as Tracks
1919+import UI.Types exposing (..)
2020+import UI.User.State.Export as User exposing (..)
42152262377--- 🌳
2424+-- 📣
2525+2626+2727+update : Queue.Msg -> Manager
2828+update msg =
2929+ case msg of
3030+ Clear ->
3131+ clear
3232+3333+ Reset ->
3434+ reset
3535+3636+ Rewind ->
3737+ rewind
3838+3939+ Shift ->
4040+ shift
4141+4242+ Select a ->
4343+ select a
4444+4545+ ShowFutureMenu a b c ->
4646+ showFutureMenu a b c
4747+4848+ ShowHistoryMenu a b ->
4949+ showHistoryMenu a b
5050+5151+ ToggleRepeat ->
5252+ toggleRepeat
5353+5454+ ToggleShuffle ->
5555+ toggleShuffle
5656+5757+ ------------------------------------
5858+ -- Future
5959+ ------------------------------------
6060+ InjectFirst a b ->
6161+ injectFirst a b
6262+6363+ InjectLast a b ->
6464+ injectLast a b
6565+6666+ InjectFirstAndPlay a ->
6767+ injectFirstAndPlay a
6868+6969+ RemoveItem a ->
7070+ removeItem a
7171+7272+7373+7474+-- 🛠
7575+7676+7777+changeActiveItem : Maybe Item -> Manager
7878+changeActiveItem maybeItem model =
7979+ let
8080+ nowPlaying =
8181+ Maybe.map .identifiedTrack maybeItem
8282+8383+ portCmd =
8484+ maybeItem
8585+ |> Maybe.map
8686+ (.identifiedTrack >> Tuple.second)
8787+ |> Maybe.map
8888+ (Queue.makeEngineItem
8989+ model.currentTime
9090+ model.sources.collection
9191+ model.tracks.cached
9292+ (if model.rememberProgress then
9393+ model.progress
9494+9595+ else
9696+ Dict.empty
9797+ )
9898+ )
9999+ |> Ports.activeQueueItemChanged
100100+ in
101101+ { model | nowPlaying = maybeItem }
102102+ |> Return.performance
103103+ (nowPlaying
104104+ |> Tracks.SetNowPlaying
105105+ |> TracksMsg
106106+ )
107107+ |> andThen fill
108108+ |> Return.command portCmd
109109+110110+111111+clear : Manager
112112+clear model =
113113+ fill { model | playingNext = [], dontPlay = [] }
114114+115115+116116+fill : Manager
117117+fill model =
118118+ let
119119+ ( availableTracks, timestamp ) =
120120+ ( model.tracks.collection.harvested
121121+ , model.currentTime
122122+ )
123123+124124+ nonMissingTracks =
125125+ List.filter
126126+ (Tuple.second >> .id >> (/=) Tracks.missingId)
127127+ availableTracks
128128+ in
129129+ model
130130+ |> (\m ->
131131+ -- Empty the ignored list when we are ignoring all the tracks
132132+ if List.length model.dontPlay == List.length nonMissingTracks then
133133+ { m | dontPlay = [] }
134134+135135+ else
136136+ m
137137+ )
138138+ |> (\m ->
139139+ if m.shuffle && List.length model.playingNext >= Fill.queueLength then
140140+ m
141141+142142+ else
143143+ let
144144+ fillState =
145145+ { activeItem = m.nowPlaying
146146+ , future = m.playingNext
147147+ , ignored = m.dontPlay
148148+ , past = m.playedPreviously
149149+ }
150150+ in
151151+ -- Fill using the appropiate method
152152+ case m.shuffle of
153153+ False ->
154154+ { m | playingNext = Fill.ordered timestamp nonMissingTracks fillState }
155155+156156+ True ->
157157+ { m | playingNext = Fill.shuffled timestamp nonMissingTracks fillState }
158158+ )
159159+ |> preloadNext
160160+161161+162162+preloadNext : Manager
163163+preloadNext model =
164164+ case List.head model.playingNext of
165165+ Just item ->
166166+ item
167167+ |> .identifiedTrack
168168+ |> Tuple.second
169169+ |> Queue.makeEngineItem
170170+ model.currentTime
171171+ model.sources.collection
172172+ model.tracks.cached
173173+ (if model.rememberProgress then
174174+ model.progress
175175+176176+ else
177177+ Dict.empty
178178+ )
179179+ |> Ports.preloadAudio
180180+ |> return model
181181+182182+ Nothing ->
183183+ Return.singleton model
184184+185185+186186+rewind : Manager
187187+rewind model =
188188+ changeActiveItem
189189+ (List.last model.playedPreviously)
190190+ { model
191191+ | playingNext =
192192+ model.nowPlaying
193193+ |> Maybe.map (\item -> item :: model.playingNext)
194194+ |> Maybe.withDefault model.playingNext
195195+ , playedPreviously =
196196+ model.playedPreviously
197197+ |> List.init
198198+ |> Maybe.withDefault []
199199+ }
200200+201201+202202+{-| Renew the queue, meaning that the auto-generated items in the queue are removed and new items are added.
203203+-}
204204+reset : Manager
205205+reset model =
206206+ model.playingNext
207207+ |> List.filter (.manualEntry >> (==) True)
208208+ |> (\f -> { model | playingNext = f, dontPlay = [] })
209209+ |> fill
210210+211211+212212+select : Item -> Manager
213213+select item model =
214214+ Return.singleton { model | selectedQueueItem = Just item }
215215+216216+217217+shift : Manager
218218+shift model =
219219+ changeActiveItem
220220+ (List.head model.playingNext)
221221+ { model
222222+ | playingNext =
223223+ model.playingNext
224224+ |> List.drop 1
225225+ , playedPreviously =
226226+ model.nowPlaying
227227+ |> Maybe.map List.singleton
228228+ |> Maybe.map (List.append model.playedPreviously)
229229+ |> Maybe.withDefault model.playedPreviously
230230+ }
231231+232232+233233+showFutureMenu : Item -> { index : Int } -> Mouse.Event -> Manager
234234+showFutureMenu item { index } mouseEvent model =
235235+ mouseEvent.clientPos
236236+ |> Coordinates.fromTuple
237237+ |> Queue.futureMenu
238238+ { cached = model.tracks.cached
239239+ , cachingInProgress = model.tracks.cachingInProgress
240240+ , itemIndex = index
241241+ }
242242+ item
243243+ |> Just
244244+ |> (\c -> { model | contextMenu = c })
245245+ |> Return.singleton
246246+247247+248248+showHistoryMenu : Item -> Mouse.Event -> Manager
249249+showHistoryMenu item mouseEvent model =
250250+ mouseEvent.clientPos
251251+ |> Coordinates.fromTuple
252252+ |> Queue.historyMenu
253253+ { cached = model.tracks.cached
254254+ , cachingInProgress = model.tracks.cachingInProgress
255255+ }
256256+ item
257257+ |> Just
258258+ |> (\c -> { model | contextMenu = c })
259259+ |> Return.singleton
260260+261261+262262+toggleRepeat : Manager
263263+toggleRepeat model =
264264+ { model | repeat = not model.repeat }
265265+ |> saveEnclosedUserData
266266+ |> Return.effect_ (.repeat >> Ports.setRepeat)
267267+268268+269269+toggleShuffle : Manager
270270+toggleShuffle model =
271271+ { model | shuffle = not model.shuffle }
272272+ |> reset
273273+ |> andThen saveEnclosedUserData
274274+275275+276276+277277+-- 🛠 ░░ FUTURE
278278+279279+280280+{-| Add an item in front of the queue.
281281+-}
282282+injectFirst : { showNotification : Bool } -> List IdentifiedTrack -> Manager
283283+injectFirst { showNotification } identifiedTracks model =
284284+ let
285285+ ( items, tracks ) =
286286+ ( List.map (makeItem True) identifiedTracks
287287+ , List.map Tuple.second identifiedTracks
288288+ )
289289+290290+ cleanedFuture =
291291+ List.foldl
292292+ (.id >> Fill.cleanAutoGenerated model.shuffle)
293293+ model.playingNext
294294+ tracks
295295+296296+ notification =
297297+ case tracks of
298298+ [ t ] ->
299299+ ("__" ++ t.tags.title ++ "__ will be played next")
300300+ |> Notifications.success
301301+302302+ list ->
303303+ list
304304+ |> List.length
305305+ |> String.fromInt
306306+ |> (\s -> "__" ++ s ++ " tracks__ will be played next")
307307+ |> Notifications.success
308308+ in
309309+ { model | playingNext = items ++ cleanedFuture }
310310+ |> (if showNotification then
311311+ Common.showNotification notification
312312+313313+ else
314314+ Return.singleton
315315+ )
316316+ |> andThen fill
317317+318318+319319+injectFirstAndPlay : IdentifiedTrack -> Manager
320320+injectFirstAndPlay identifiedTrack model =
321321+ model
322322+ |> injectFirst { showNotification = False } [ identifiedTrack ]
323323+ |> andThen shift
324324+325325+326326+{-| Add an item after the last manual entry
327327+(ie. after the last injected item).
328328+-}
329329+injectLast : { showNotification : Bool } -> List IdentifiedTrack -> Manager
330330+injectLast { showNotification } identifiedTracks model =
331331+ let
332332+ ( items, tracks ) =
333333+ ( List.map (makeItem True) identifiedTracks
334334+ , List.map Tuple.second identifiedTracks
335335+ )
336336+337337+ cleanedFuture =
338338+ List.foldl
339339+ (.id >> Fill.cleanAutoGenerated model.shuffle)
340340+ model.playingNext
341341+ tracks
342342+343343+ manualItems =
344344+ cleanedFuture
345345+ |> List.filter (.manualEntry >> (==) True)
346346+ |> List.length
347347+348348+ newFuture =
349349+ []
350350+ ++ List.take manualItems cleanedFuture
351351+ ++ items
352352+ ++ List.drop manualItems cleanedFuture
353353+354354+ notification =
355355+ case tracks of
356356+ [ t ] ->
357357+ ("__" ++ t.tags.title ++ "__ was added to the queue")
358358+ |> Notifications.success
359359+360360+ list ->
361361+ list
362362+ |> List.length
363363+ |> String.fromInt
364364+ |> (\s -> "__" ++ s ++ " tracks__ were added to the queue")
365365+ |> Notifications.success
366366+ in
367367+ { model | playingNext = newFuture }
368368+ |> (if showNotification then
369369+ Common.showNotification notification
370370+371371+ else
372372+ Return.singleton
373373+ )
374374+ |> andThen fill
375375+376376+377377+moveQueueItemToFirst : { itemIndex : Int } -> Manager
378378+moveQueueItemToFirst { itemIndex } model =
379379+ model.playingNext
380380+ |> moveItem { from = itemIndex, to = 0, shuffle = model.shuffle }
381381+ |> (\f -> { model | playingNext = f })
382382+ |> fill
383383+384384+385385+moveQueueItemToLast : { itemIndex : Int } -> Manager
386386+moveQueueItemToLast { itemIndex } model =
387387+ let
388388+ to =
389389+ model.playingNext
390390+ |> List.filter (.manualEntry >> (==) True)
391391+ |> List.length
392392+ in
393393+ model.playingNext
394394+ |> moveItem { from = itemIndex, to = to, shuffle = model.shuffle }
395395+ |> (\f -> { model | playingNext = f })
396396+ |> fill
397397+398398+399399+removeItem : { index : Int, item : Item } -> Manager
400400+removeItem { index, item } model =
401401+ let
402402+ newFuture =
403403+ List.removeAt index model.playingNext
404404+405405+ newIgnored =
406406+ if item.manualEntry then
407407+ model.dontPlay
8408409409+ else
410410+ item :: model.dontPlay
411411+ in
412412+ fill { model | playingNext = newFuture, dontPlay = newIgnored }
94131010-lens =
1111- { get = .queue
1212- , set = \queue ui -> { ui | queue = queue }
1313- }
414414+415415+416416+-- ⚗️
417417+418418+419419+moveItem : { from : Int, to : Int, shuffle : Bool } -> List Item -> List Item
420420+moveItem { from, to, shuffle } collection =
421421+ let
422422+ subjectItem =
423423+ collection
424424+ |> List.getAt from
425425+ |> Maybe.map (\s -> { s | manualEntry = True })
426426+427427+ fixedTarget =
428428+ if to > from then
429429+ to - 1
430430+431431+ else
432432+ to
433433+ in
434434+ collection
435435+ |> List.removeAt from
436436+ |> List.indexedFoldr
437437+ (\idx existingItem acc ->
438438+ if idx == fixedTarget then
439439+ case subjectItem of
440440+ Just itemToInsert ->
441441+ List.append [ itemToInsert, existingItem ] acc
442442+443443+ Nothing ->
444444+ existingItem :: acc
445445+446446+ else if idx < fixedTarget then
447447+ { existingItem | manualEntry = True } :: acc
448448+449449+ else
450450+ existingItem :: acc
451451+ )
452452+ []
453453+ |> (if shuffle then
454454+ identity
455455+456456+ else
457457+ List.filter (.manualEntry >> (==) True)
458458+ )