A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove UI.Reply and everything related

+918 -1031
+48 -10
src/Applications/UI.elm
··· 43 43 import UI.Ports as Ports 44 44 import UI.Queue.State as Queue 45 45 import UI.Queue.Types as Queue 46 - import UI.Reply.Translate as Reply 47 46 import UI.Routing.State as Routing 48 47 import UI.Services.State as Services 49 48 import UI.Sources.ContextMenu as Sources ··· 233 232 Bypass -> 234 233 Return.singleton 235 234 236 - Reply reply -> 237 - Reply.translate reply 238 - 239 235 ----------------------------------------- 240 236 -- Alfred 241 237 ----------------------------------------- ··· 254 250 NoteProgress a -> 255 251 Audio.noteProgress a 256 252 253 + Seek a -> 254 + Audio.seek a 255 + 257 256 SetAudioDuration a -> 258 257 Audio.setDuration a 259 258 ··· 274 273 275 274 TogglePlay -> 276 275 Audio.playPause 276 + 277 + ToggleRememberProgress -> 278 + Audio.toggleRememberProgress 277 279 278 280 ----------------------------------------- 279 281 -- Authentication (TODO: Move) ··· 323 325 Blur -> 324 326 Interface.blur 325 327 328 + ContextMenuConfirmation a b -> 329 + Interface.contextMenuConfirmation a b 330 + 331 + CopyToClipboard a -> 332 + Interface.copyToClipboard a 333 + 326 334 Debounce a -> 327 335 Interface.debounce update a 336 + 337 + DismissNotification a -> 338 + Common.dismissNotification a 328 339 329 340 DnD a -> 330 341 Interface.dnd a ··· 334 345 335 346 HideOverlay -> 336 347 Interface.hideOverlay 348 + 349 + MsgViaContextMenu a -> 350 + Interface.msgViaContextMenu a 337 351 338 352 PreferredColorSchemaChanged a -> 339 353 Interface.preferredColorSchemaChanged a 340 354 355 + RemoveNotification a -> 356 + Interface.removeNotification a 357 + 341 358 RemoveQueueSelection -> 342 359 Interface.removeQueueSelection 343 360 ··· 356 373 StoppedDragging -> 357 374 Interface.stoppedDragging 358 375 359 - UI.ToggleLoadingScreen a -> 376 + ToggleLoadingScreen a -> 360 377 Common.toggleLoadingScreen a 361 378 362 379 ----------------------------------------- ··· 365 382 ActivatePlaylist a -> 366 383 Playlists.activate a 367 384 368 - UI.AddTracksToPlaylist a -> 385 + AddTracksToPlaylist a -> 369 386 Playlists.addTracksToPlaylist a 370 387 371 388 CreatePlaylist -> ··· 384 401 Playlists.modify 385 402 386 403 MoveTrackInSelectedPlaylist a -> 387 - Playlists.moveTrackInSelectedPlaylist a 404 + Playlists.moveTrackInSelected a 405 + 406 + RemoveTracksFromPlaylist a b -> 407 + Playlists.removeTracks a b 388 408 389 - SelectPlaylist a -> 390 - Playlists.select a 409 + RequestAssistanceForPlaylists a -> 410 + Playlists.requestAssistance a 391 411 392 412 SetPlaylistCreationContext a -> 393 413 Playlists.setCreationContext a ··· 416 436 ----------------------------------------- 417 437 -- Services 418 438 ----------------------------------------- 439 + ConnectLastFm -> 440 + Services.connectLastFm 441 + 442 + DisconnectLastFm -> 443 + Services.disconnectLastFm 444 + 419 445 GotLastFmSession a -> 420 446 Services.gotLastFmSession a 421 447 ··· 437 463 ----------------------------------------- 438 464 -- User 439 465 ----------------------------------------- 466 + Export -> 467 + User.export 468 + 440 469 ImportFile a -> 441 470 User.importFile a 442 471 443 472 ImportJson a -> 444 473 User.importJson a 445 474 475 + ImportLegacyData -> 476 + User.importLegacyData 477 + 478 + InsertDemo -> 479 + User.insertDemo 480 + 446 481 LoadEnclosedUserData a -> 447 482 User.loadEnclosedUserData a 448 483 449 484 LoadHypaethralUserData a -> 450 485 User.loadHypaethralUserData a 451 486 452 - UI.SaveEnclosedUserData -> 487 + RequestImport -> 488 + User.requestImport 489 + 490 + SaveEnclosedUserData -> 453 491 User.saveEnclosedUserData 454 492 455 493 -----------------------------------------
+11 -9
src/Applications/UI/Adjunct.elm
··· 5 5 import Return 6 6 import Return.Ext as Return 7 7 import UI.Alfred.State as Alfred 8 + import UI.Audio.State as Audio 8 9 import UI.Authentication.Types as Authentication 9 10 import UI.Interface.State exposing (hideOverlay) 10 - import UI.Reply as Reply exposing (Reply) 11 + import UI.Queue.State as Queue 12 + import UI.Tracks.State as Tracks 11 13 import UI.Types as UI exposing (..) 12 14 13 15 ··· 54 56 hideOverlay m 55 57 56 58 [ Keyboard.ArrowLeft ] -> 57 - Return.performance (Reply Reply.RewindQueue) m 59 + Queue.rewind m 58 60 59 61 [ Keyboard.ArrowRight ] -> 60 - Return.performance (Reply Reply.ShiftQueue) m 62 + Queue.shift m 61 63 62 64 [ Keyboard.ArrowUp ] -> 63 - Return.performance (Reply (Reply.Seek <| (m.audioPosition - 10) / m.audioDuration)) m 65 + Audio.seek ((m.audioPosition - 10) / m.audioDuration) m 64 66 65 67 [ Keyboard.ArrowDown ] -> 66 - Return.performance (Reply (Reply.Seek <| (m.audioPosition + 10) / m.audioDuration)) m 68 + Audio.seek ((m.audioPosition + 10) / m.audioDuration) m 67 69 68 70 [ Keyboard.Character "N" ] -> 69 - Return.performance (Reply Reply.ScrollToNowPlaying) m 71 + Tracks.scrollToNowPlaying m 70 72 71 73 [ Keyboard.Character "P" ] -> 72 - Return.performance (Reply Reply.TogglePlayPause) m 74 + Audio.playPause m 73 75 74 76 [ Keyboard.Character "R" ] -> 75 - Return.performance (Reply Reply.ToggleRepeat) m 77 + Queue.toggleRepeat m 76 78 77 79 [ Keyboard.Character "S" ] -> 78 - Return.performance (Reply Reply.ToggleShuffle) m 80 + Queue.toggleShuffle m 79 81 80 82 _ -> 81 83 skip
+12 -5
src/Applications/UI/Audio/State.elm
··· 7 7 import Return.Ext as Return exposing (communicate) 8 8 import UI.Ports as Ports 9 9 import UI.Queue.State as Queue 10 - import UI.Reply as Reply 11 10 import UI.Types as UI exposing (Manager, Organizer) 11 + import UI.User.State.Export as User 12 12 13 13 14 14 ··· 29 29 Dict.insert trackId progress model.progress 30 30 in 31 31 if model.rememberProgress then 32 - -- TODO! 33 - Return.performance 34 - (UI.Reply Reply.SaveProgress) 35 - { model | progress = updatedProgressTable } 32 + User.saveProgress { model | progress = updatedProgressTable } 36 33 37 34 else 38 35 Return.singleton model ··· 50 47 communicate (Ports.play ()) model 51 48 52 49 50 + seek : Float -> Manager 51 + seek percentage = 52 + Return.communicate (Ports.seek percentage) 53 + 54 + 53 55 setDuration : Float -> Manager 54 56 setDuration duration model = 55 57 let ··· 91 93 stop : Manager 92 94 stop = 93 95 communicate (Ports.pause ()) 96 + 97 + 98 + toggleRememberProgress : Manager 99 + toggleRememberProgress model = 100 + User.saveSettings { model | rememberProgress = not model.rememberProgress }
+5 -4
src/Applications/UI/Authentication/ContextMenu.elm
··· 3 3 import ContextMenu exposing (..) 4 4 import Coordinates exposing (Coordinates) 5 5 import Svg 6 - import UI.Reply exposing (Reply(..)) 6 + import UI.Authentication.Types as Authentication 7 7 import UI.Svg.Elements 8 + import UI.Types exposing (Msg(..)) 8 9 9 10 10 11 11 12 -- 🔱 12 13 13 14 14 - moreOptionsMenu : Coordinates -> ContextMenu Reply 15 + moreOptionsMenu : Coordinates -> ContextMenu Msg 15 16 moreOptionsMenu = 16 17 ContextMenu 17 18 [ Item 18 19 { icon = \_ _ -> Svg.map never UI.Svg.Elements.ipfsLogo 19 20 , label = "IPFS (using the Mutable File System)" 20 - , msg = PingIpfsForAuth 21 + , msg = AuthenticationMsg Authentication.PingIpfs 21 22 , active = False 22 23 } 23 24 , Item 24 25 { icon = \_ _ -> Svg.map never UI.Svg.Elements.textileLogo 25 26 , label = "Textile (Experimental)" 26 - , msg = PingTextileForAuth 27 + , msg = AuthenticationMsg Authentication.PingTextile 27 28 , active = False 28 29 } 29 30 ]
+64 -16
src/Applications/UI/Authentication/State.elm
··· 23 23 import Return.Ext as Return 24 24 import SHA 25 25 import String.Ext as String 26 + import Tracks 27 + import UI.Authentication.ContextMenu as Authentication 26 28 import UI.Authentication.Types as Authentication exposing (..) 27 29 import UI.Backdrop as Backdrop 28 30 import UI.Common.State as Common exposing (showNotification, showNotificationWithModel) 29 31 import UI.Ports as Ports 30 - import UI.Reply as Reply exposing (Reply(..)) 31 - import UI.Reply.Translate as Reply 32 32 import UI.Types as UI exposing (..) 33 - import Url exposing (Url) 33 + import Url exposing (Protocol(..), Url) 34 34 import Url.Ext as Url 35 35 import User.Layer exposing (..) 36 36 import User.Layer.Methods.RemoteStorage as RemoteStorage ··· 127 127 SignedIn a -> 128 128 signedIn a 129 129 130 + SignOut -> 131 + signOut 132 + 130 133 TriggerExternalAuth a b -> 131 134 externalAuth a b 132 135 ··· 142 145 ShowNewEncryptionKeyScreen a -> 143 146 showNewEncryptionKeyScreen a 144 147 145 - Authentication.ShowUpdateEncryptionKeyScreen a -> 148 + ShowUpdateEncryptionKeyScreen a -> 146 149 showUpdateEncryptionKeyScreen a 147 150 148 151 UpdateEncryptionKey a b -> ··· 236 239 ) 237 240 |> Lens.adjust lens model 238 241 |> Return.singleton 239 - |> Return.andThen (Reply.translate Reply.ForceTracksRerender) 240 242 241 243 242 244 externalAuth : Method -> String -> Manager ··· 327 329 328 330 329 331 showMoreOptions : Mouse.Event -> Manager 330 - showMoreOptions mouseEvent = 332 + showMoreOptions mouseEvent model = 331 333 ( mouseEvent.clientPos 332 334 , mouseEvent.offsetPos 333 335 ) ··· 336 338 , y = b - d + 12 337 339 } 338 340 ) 339 - |> ShowMoreAuthenticationOptions 340 - |> Reply 341 - |> Return.performance 341 + |> Authentication.moreOptionsMenu 342 + |> Common.showContextMenuWithModel model 342 343 343 344 344 345 signedIn : Method -> Manager ··· 378 379 |> Return.andThen (Common.toggleLoadingScreen On) 379 380 380 381 382 + signOut : Manager 383 + signOut model = 384 + { model 385 + | authentication = Authentication.Unauthenticated 386 + , playlists = [] 387 + , playlistToActivate = Nothing 388 + 389 + -- Queue 390 + -------- 391 + , dontPlay = [] 392 + , nowPlaying = Nothing 393 + , playedPreviously = [] 394 + , playingNext = [] 395 + , selectedQueueItem = Nothing 396 + 397 + -- 398 + , repeat = False 399 + , shuffle = False 400 + 401 + -- Sources 402 + ---------- 403 + , processingContext = [] 404 + , sources = [] 405 + 406 + -- Tracks 407 + --------- 408 + , favourites = [] 409 + , hideDuplicates = False 410 + , searchResults = Nothing 411 + , tracks = Tracks.emptyCollection 412 + } 413 + |> Backdrop.setDefault 414 + |> Return.command (Ports.toBrain <| Alien.trigger Alien.SignOut) 415 + |> Return.command (Ports.toBrain <| Alien.trigger Alien.StopProcessing) 416 + |> Return.command (Ports.activeQueueItemChanged Nothing) 417 + |> Return.command (Nav.pushUrl model.navKey "#/") 418 + 419 + 381 420 startFlow : Manager 382 421 startFlow = 383 422 replaceState Unauthenticated ··· 412 451 -- 413 452 |> Return.return (lens.set (Authenticated method) model) 414 453 |> Return.andThen (Common.showNotification <| Notifications.success "Saving data without encryption ...") 415 - |> Return.andThen (Reply.translate ForceTracksRerender) 416 454 417 455 418 456 showNewEncryptionKeyScreen : Method -> Manager ··· 441 479 -- 442 480 |> Return.return (lens.set (Authenticated method) model) 443 481 |> Return.andThen (Common.showNotification <| Notifications.success "Encrypting data with new passphrase ...") 444 - |> Return.andThen (Reply.translate ForceTracksRerender) 445 482 446 483 447 484 ··· 450 487 451 488 pingIpfs : Manager 452 489 pingIpfs model = 453 - { url = "//localhost:5001/api/v0/id" 454 - , expect = Http.expectWhatever (AuthenticationMsg << PingIpfsCallback) 455 - } 456 - |> Http.get 457 - |> return model 490 + case model.url.protocol of 491 + Https -> 492 + """ 493 + Unfortunately the local IPFS API doesn't work with HTTPS. 494 + Install the [IPFS Companion](https://github.com/ipfs-shipyard/ipfs-companion#release-channel) browser extension to get around this issue 495 + (and make sure it redirects to the local gateway). 496 + """ 497 + |> Notifications.error 498 + |> Common.showNotificationWithModel model 499 + 500 + Http -> 501 + { url = "//localhost:5001/api/v0/id" 502 + , expect = Http.expectWhatever (AuthenticationMsg << PingIpfsCallback) 503 + } 504 + |> Http.get 505 + |> return model 458 506 459 507 460 508 pingIpfsCallback : Result Http.Error () -> Manager
+2
src/Applications/UI/Authentication/Types.elm
··· 31 31 32 32 type Msg 33 33 = Bypass 34 + -- 34 35 | CancelFlow 35 36 | GetStarted 36 37 | ShowMoreOptions Mouse.Event 37 38 | SignIn Method 38 39 | SignInWithPassphrase Method String 39 40 | SignedIn Method 41 + | SignOut 40 42 | TriggerExternalAuth Method String 41 43 ----------------------------------------- 42 44 -- Encryption
-2
src/Applications/UI/Authentication/View.elm
··· 16 16 import Material.Icons as Icons 17 17 import Material.Icons.Types exposing (Coloring(..)) 18 18 import Maybe.Extra as Maybe 19 - import Return3 exposing (..) 20 19 import String.Ext as String 21 20 import Svg exposing (Svg) 22 21 import UI.Authentication.Types as Authentication exposing (..) 23 22 import UI.Kit 24 - import UI.Reply exposing (Reply(..)) 25 23 import UI.Svg.Elements 26 24 import UI.Types as UI exposing (..) 27 25 import User.Layer exposing (..)
+2 -4
src/Applications/UI/Backdrop.elm
··· 11 11 import Return exposing (return) 12 12 import Return.Ext as Return 13 13 import UI.Ports as Ports 14 - import UI.Reply as Reply exposing (Reply(..)) 15 14 import UI.Types exposing (..) 15 + import UI.User.State.Export as User 16 16 17 17 18 18 ··· 61 61 62 62 chooseBackdrop : String -> Manager 63 63 chooseBackdrop backdrop model = 64 - Return.performance 65 - (Reply SaveSettings) 66 - { model | chosenBackdrop = Just backdrop } 64 + User.saveSettings { model | chosenBackdrop = Just backdrop } 67 65 68 66 69 67 loadBackdrop : String -> Manager
+4 -7
src/Applications/UI/Common/State.elm
··· 10 10 import UI.Notifications 11 11 import UI.Page as Page exposing (Page) 12 12 import UI.Playlists.Directory 13 - import UI.Reply exposing (Reply) 14 - import UI.Types as UI exposing (Manager) 13 + import UI.Types as UI exposing (Manager, Msg) 15 14 16 15 17 16 ··· 31 30 options 32 31 |> UI.Notifications.dismiss model.notifications 33 32 |> Return.map (\n -> { model | notifications = n }) 34 - |> Return.mapCmd UI.Reply 35 33 36 34 37 35 generateDirectoryPlaylists : Manager ··· 55 53 |> Return.singleton 56 54 57 55 58 - showContextMenuWithModel : UI.Model -> ContextMenu Reply -> ( UI.Model, Cmd UI.Msg ) 56 + showContextMenuWithModel : UI.Model -> ContextMenu Msg -> ( UI.Model, Cmd UI.Msg ) 59 57 showContextMenuWithModel model contextMenu = 60 58 Return.singleton { model | contextMenu = Just contextMenu } 61 59 62 60 63 - showNotification : Notification Reply -> Manager 61 + showNotification : Notification Msg -> Manager 64 62 showNotification notification model = 65 63 model.notifications 66 64 |> UI.Notifications.show notification 67 65 |> Return.map (\n -> { model | isLoading = False, notifications = n }) 68 - |> Return.mapCmd UI.Reply 69 66 70 67 71 - showNotificationWithModel : UI.Model -> Notification Reply -> ( UI.Model, Cmd UI.Msg ) 68 + showNotificationWithModel : UI.Model -> Notification Msg -> ( UI.Model, Cmd UI.Msg ) 72 69 showNotificationWithModel model notification = 73 70 showNotification notification model 74 71
+37 -8
src/Applications/UI/Console.elm
··· 12 12 import Material.Icons.Types exposing (Coloring(..)) 13 13 import Maybe.Extra as Maybe 14 14 import Queue 15 - import UI.Reply exposing (Reply(..)) 15 + import UI.Queue.Types as Queue 16 + import UI.Tracks.Types as Tracks 17 + import UI.Types exposing (Msg(..)) 16 18 17 19 18 20 19 21 -- 🗺 20 22 21 23 22 - view : Maybe Queue.Item -> Bool -> Bool -> { stalled : Bool, loading : Bool, playing : Bool } -> ( Float, Float ) -> Html Reply 24 + view : Maybe Queue.Item -> Bool -> Bool -> { stalled : Bool, loading : Bool, playing : Bool } -> ( Float, Float ) -> Html Msg 23 25 view activeQueueItem repeat shuffle { stalled, loading, playing } ( position, duration ) = 24 26 chunk 25 27 [ C.antialiased ··· 50 52 case Maybe.map .identifiedTrack activeQueueItem of 51 53 Just ( _, { tags } ) -> 52 54 Html.span 53 - [ onClick ScrollToNowPlaying 55 + [ onClick (TracksMsg Tracks.ScrollToNowPlaying) 54 56 , class C.cursor_pointer 55 57 , title "Scroll to track" 56 58 ] ··· 110 112 -- 111 113 , C.sm__justify_center 112 114 ] 113 - [ button "Toggle repeat" (smallLight repeat) (icon Icons.repeat 18) ToggleRepeat 114 - , button "Play previous track" lightPlaceHolder (icon Icons.fast_rewind 20) RewindQueue 115 - , button "" (largeLight playing) play TogglePlayPause 116 - , button "Play next track" lightPlaceHolder (icon Icons.fast_forward 20) ShiftQueue 117 - , button "Toggle shuffle" (smallLight shuffle) (icon Icons.shuffle 18) ToggleShuffle 115 + [ button "Toggle repeat" 116 + (smallLight repeat) 117 + (icon Icons.repeat 18) 118 + (QueueMsg Queue.ToggleRepeat) 119 + 120 + -- 121 + , button 122 + "Play previous track" 123 + lightPlaceHolder 124 + (icon Icons.fast_rewind 20) 125 + (QueueMsg Queue.Rewind) 126 + 127 + -- 128 + , button 129 + "" 130 + (largeLight playing) 131 + play 132 + TogglePlay 133 + 134 + -- 135 + , button 136 + "Play next track" 137 + lightPlaceHolder 138 + (icon Icons.fast_forward 20) 139 + (QueueMsg Queue.Shift) 140 + 141 + -- 142 + , button 143 + "Toggle shuffle" 144 + (smallLight shuffle) 145 + (icon Icons.shuffle 18) 146 + (QueueMsg Queue.ToggleShuffle) 118 147 ] 119 148 ] 120 149
+4 -4
src/Applications/UI/ContextMenu.elm
··· 9 9 import Html.Events exposing (custom) 10 10 import Json.Decode 11 11 import Material.Icons.Types exposing (Coloring(..)) 12 - import UI.Reply exposing (Reply) 12 + import UI.Types as UI exposing (Msg) 13 13 14 14 15 15 16 16 -- 🗺 17 17 18 18 19 - view : Maybe (ContextMenu Reply) -> Html Reply 19 + view : Maybe (ContextMenu Msg) -> Html Msg 20 20 view m = 21 21 case m of 22 22 Just (ContextMenu items coordinates) -> ··· 61 61 nothing 62 62 63 63 64 - itemView : ContextMenu.ItemProperties Reply -> Html Reply 64 + itemView : ContextMenu.ItemProperties Msg -> Html Msg 65 65 itemView { icon, label, msg, active } = 66 66 brick 67 67 [ custom 68 68 "tap" 69 69 (Json.Decode.succeed 70 - { message = UI.Reply.ReplyViaContextMenu msg 70 + { message = UI.MsgViaContextMenu msg 71 71 , stopPropagation = True 72 72 , preventDefault = True 73 73 }
+1 -1
src/Applications/UI/DnD.elm
··· 95 95 _ -> 96 96 NotDragging 97 97 ------------------------------------ 98 - -- Reply 98 + -- Response 99 99 ------------------------------------ 100 100 , case msg of 101 101 Start context ->
-1
src/Applications/UI/Equalizer/State.elm
··· 5 5 import Html.Events.Extra.Pointer as Pointer 6 6 import Return exposing (andThen, return) 7 7 import UI.Ports as Ports 8 - import UI.Reply exposing (Reply(..)) 9 8 import UI.Types as UI exposing (..) 10 9 import UI.User.State.Export as User 11 10
+38 -1
src/Applications/UI/Interface/State.elm
··· 2 2 3 3 import Common exposing (Switch(..)) 4 4 import Debouncer.Basic as Debouncer 5 + import Notifications 5 6 import Return exposing (return) 6 7 import Return.Ext as Return 7 8 import UI.DnD as DnD 8 9 import UI.Page as Page 9 10 import UI.Playlists.State as Playlists 11 + import UI.Ports as Ports 10 12 import UI.Queue.State as Queue 11 13 import UI.Tracks.Types as Tracks 12 14 import UI.Types as UI exposing (..) ··· 22 24 Return.singleton { model | focusedOnInput = False } 23 25 24 26 27 + contextMenuConfirmation : String -> Msg -> Manager 28 + contextMenuConfirmation conf msg model = 29 + return 30 + { model | confirmation = Just conf } 31 + (Return.task msg) 32 + 33 + 34 + copyToClipboard : String -> Manager 35 + copyToClipboard string = 36 + string 37 + |> Ports.copyToClipboard 38 + |> Return.communicate 39 + 40 + 25 41 debounce : (Msg -> Model -> ( Model, Cmd Msg )) -> Debouncer.Msg Msg -> Manager 26 42 debounce update debouncerMsg model = 27 43 let ··· 76 92 Page.Index -> 77 93 case model.scene of 78 94 Tracks.List -> 79 - Playlists.moveTrackInSelectedPlaylist 95 + Playlists.moveTrackInSelected 80 96 { to = Maybe.withDefault 0 (DnD.modelTarget d) } 81 97 m 82 98 ··· 105 121 preferredColorSchemaChanged : { dark : Bool } -> Manager 106 122 preferredColorSchemaChanged { dark } model = 107 123 Return.singleton { model | darkMode = dark } 124 + 125 + 126 + msgViaContextMenu : Msg -> Manager 127 + msgViaContextMenu msg model = 128 + return 129 + (case msg of 130 + ContextMenuConfirmation _ _ -> 131 + model 132 + 133 + _ -> 134 + { model | confirmation = Nothing, contextMenu = Nothing } 135 + ) 136 + (Return.task msg) 137 + 138 + 139 + removeNotification : { id : Int } -> Manager 140 + removeNotification { id } model = 141 + model.notifications 142 + |> List.filter (Notifications.id >> (/=) id) 143 + |> (\n -> { model | notifications = n }) 144 + |> Return.singleton 108 145 109 146 110 147 removeQueueSelection : Manager
+11 -7
src/Applications/UI/Notifications.elm
··· 12 12 import Notifications exposing (..) 13 13 import Process 14 14 import Task 15 - import UI.Reply exposing (Reply(..)) 15 + import UI.Types exposing (Msg(..)) 16 16 17 17 18 18 ··· 20 20 21 21 22 22 type alias Model = 23 - List (Notification Reply) 23 + List (Notification Msg) 24 24 25 25 26 26 27 27 -- 📣 28 28 29 29 30 - dismiss : Model -> { id : Int } -> ( Model, Cmd Reply ) 30 + dismiss : Model -> { id : Int } -> ( Model, Cmd Msg ) 31 31 dismiss collection { id } = 32 32 ( List.map 33 33 (\notification -> ··· 44 44 ) 45 45 46 46 47 - show : Notification Reply -> Model -> ( Model, Cmd Reply ) 47 + show : Notification Msg -> Model -> ( Model, Cmd Msg ) 48 48 show notification collection = 49 49 let 50 50 existingNotificationIds = ··· 70 70 ) 71 71 72 72 73 - showWithModel : Model -> Notification Reply -> ( Model, Cmd Reply ) 73 + showWithModel : Model -> Notification Msg -> ( Model, Cmd Msg ) 74 74 showWithModel model notification = 75 75 show notification model 76 76 ··· 79 79 -- 🗺 80 80 81 81 82 - view : Model -> Html Reply 82 + view : Model -> Html Msg 83 83 view collection = 84 84 collection 85 85 |> List.reverse ··· 101 101 |> Html.Styled.toUnstyled 102 102 103 103 104 - notificationView : Notification Reply -> Html.Styled.Html Reply 104 + 105 + -- TODO: Remove .Styled.Html 106 + 107 + 108 + notificationView : Notification Msg -> Html.Styled.Html Msg 105 109 notificationView notification = 106 110 let 107 111 kind =
+32 -9
src/Applications/UI/Playlists/ContextMenu.elm
··· 2 2 3 3 import ContextMenu exposing (..) 4 4 import Coordinates exposing (Coordinates) 5 + import Html.Events.Extra.Mouse 5 6 import Material.Icons as Icons 6 7 import Playlists exposing (Playlist) 7 8 import Playlists.Matching 8 9 import Tracks exposing (IdentifiedTrack) 9 10 import UI.Page 10 11 import UI.Playlists.Page 11 - import UI.Reply exposing (Reply(..)) 12 + import UI.Tracks.Types as Tracks 13 + import UI.Types exposing (Msg(..)) 12 14 import Url 13 15 14 16 ··· 16 18 -- 🔱 17 19 18 20 19 - listMenu : Playlist -> List IdentifiedTrack -> Maybe String -> Coordinates -> ContextMenu Reply 21 + listMenu : Playlist -> List IdentifiedTrack -> Maybe String -> Coordinates -> ContextMenu Msg 20 22 listMenu playlist allTracks confirmation coordinates = 21 23 let 22 24 ( identifiedTracksFromPlaylist, _ ) = ··· 30 32 playlistId = 31 33 "Playlist - " ++ playlist.name 32 34 33 - menuReply = 34 - ShowPlaylistListMenu coordinates playlist 35 + menuMsg = 36 + ShowPlaylistListMenu 37 + playlist 38 + { button = Html.Events.Extra.Mouse.MainButton 39 + , clientPos = Coordinates.toTuple coordinates 40 + , keys = { alt = False, ctrl = False, shift = False } 41 + , offsetPos = ( 0, 0 ) 42 + , pagePos = ( 0, 0 ) 43 + , screenPos = ( 0, 0 ) 44 + } 35 45 36 46 askForConfirmation = 37 47 confirmation == Just playlistId ··· 40 50 [ Item 41 51 { icon = Icons.archive 42 52 , label = "Download as zip file" 43 - , msg = DownloadTracks playlist.name tracksFromPlaylist 53 + , msg = 54 + tracksFromPlaylist 55 + |> Tracks.Download playlist.name 56 + |> TracksMsg 57 + 58 + -- 44 59 , active = False 45 60 } 61 + 62 + -- 46 63 , Item 47 64 { icon = Icons.font_download 48 65 , label = "Rename playlist" ··· 51 68 |> Url.percentEncode 52 69 |> UI.Playlists.Page.Edit 53 70 |> UI.Page.Playlists 54 - |> GoToPage 71 + |> ChangeUrlUsingPage 72 + 73 + -- 55 74 , active = False 56 75 } 76 + 77 + -- 57 78 , Item 58 79 { icon = Icons.delete 59 80 , label = ··· 64 85 "Remove playlist" 65 86 , msg = 66 87 if askForConfirmation then 67 - RemovePlaylistFromCollection { playlistName = playlist.name } 88 + DeletePlaylist { playlistName = playlist.name } 68 89 69 90 else 70 - ContextMenuConfirmation playlistId menuReply 91 + ContextMenuConfirmation playlistId menuMsg 71 92 , active = 72 93 askForConfirmation 73 94 } 95 + 96 + -- 74 97 , Item 75 98 { icon = Icons.offline_bolt 76 99 , label = "Store in cache" 77 - , msg = StoreTracksInCache tracksFromPlaylist 100 + , msg = TracksMsg (Tracks.StoreInCache tracksFromPlaylist) 78 101 , active = False 79 102 } 80 103 ]
+34 -4
src/Applications/UI/Playlists/State.elm
··· 12 12 import Playlists.Encoding as Playlists 13 13 import Return exposing (andThen, return) 14 14 import Return.Ext as Return 15 + import Tracks exposing (IdentifiedTrack) 15 16 import Tracks.Collection 17 + import UI.Alfred.State as Alfred 16 18 import UI.Common.State as Common 17 19 import UI.Page as Page 20 + import UI.Playlists.Alfred 18 21 import UI.Playlists.ContextMenu as Playlists 19 22 import UI.Playlists.Page exposing (..) 20 23 import UI.Ports as Ports 21 - import UI.Reply as Reply 22 24 import UI.Tracks.State as Tracks 23 25 import UI.Tracks.Types as Tracks 24 26 import UI.Types as UI exposing (..) ··· 189 191 redirectToIndexPage model 190 192 191 193 192 - moveTrackInSelectedPlaylist : { to : Int } -> Manager 193 - moveTrackInSelectedPlaylist { to } model = 194 + moveTrackInSelected : { to : Int } -> Manager 195 + moveTrackInSelected { to } model = 194 196 case model.selectedPlaylist of 195 197 Just playlist -> 196 198 let ··· 213 215 , selectedPlaylist = Just updatedPlaylist 214 216 } 215 217 |> Tracks.reviseCollection Tracks.Collection.arrange 216 - |> andThen (Return.performance <| Reply Reply.SavePlaylists) 218 + |> andThen User.savePlaylists 217 219 218 220 Nothing -> 219 221 Return.singleton model 222 + 223 + 224 + removeTracks : Playlist -> List IdentifiedTrack -> Manager 225 + removeTracks playlist tracks model = 226 + let 227 + updatedPlaylist = 228 + Tracks.removeFromPlaylist tracks playlist 229 + in 230 + model.playlists 231 + |> List.map 232 + (\p -> 233 + if p.name == playlist.name then 234 + updatedPlaylist 235 + 236 + else 237 + p 238 + ) 239 + |> (\c -> { model | playlists = c }) 240 + |> select updatedPlaylist 241 + |> andThen User.savePlaylists 242 + 243 + 244 + requestAssistance : List IdentifiedTrack -> Manager 245 + requestAssistance tracks model = 246 + model.playlists 247 + |> List.filterNot .autoGenerated 248 + |> UI.Playlists.Alfred.create tracks 249 + |> (\a -> Alfred.assign a model) 220 250 221 251 222 252 save : Manager
+28 -7
src/Applications/UI/Queue/ContextMenu.elm
··· 4 4 import Coordinates exposing (Coordinates) 5 5 import Material.Icons as Icons 6 6 import Queue 7 - import UI.Reply exposing (Reply(..)) 7 + import UI.Queue.Types as Queue 8 8 import UI.Tracks.ContextMenu 9 + import UI.Types exposing (Msg(..)) 9 10 10 11 11 12 ··· 16 17 { cached : List String, cachingInProgress : List String, itemIndex : Int } 17 18 -> Queue.Item 18 19 -> Coordinates 19 - -> ContextMenu Reply 20 + -> ContextMenu Msg 20 21 futureMenu { cached, cachingInProgress, itemIndex } item = 21 22 let 22 23 tracks = ··· 26 27 [ Item 27 28 { icon = Icons.update 28 29 , label = "Move to the top" 29 - , msg = MoveQueueItemToFirst { itemIndex = itemIndex } 30 + , msg = 31 + { index = itemIndex } 32 + |> Queue.MoveItemToFirst 33 + |> QueueMsg 34 + 35 + -- 30 36 , active = False 31 37 } 32 38 , Item 33 39 { icon = Icons.update 34 40 , label = "Move to the end of my picks" 35 - , msg = MoveQueueItemToLast { itemIndex = itemIndex } 41 + , msg = 42 + { index = itemIndex } 43 + |> Queue.MoveItemToLast 44 + |> QueueMsg 45 + 46 + -- 36 47 , active = False 37 48 } 38 49 , Item ··· 51 62 { cached : List String, cachingInProgress : List String } 52 63 -> Queue.Item 53 64 -> Coordinates 54 - -> ContextMenu Reply 65 + -> ContextMenu Msg 55 66 historyMenu { cached, cachingInProgress } item = 56 67 let 57 68 tracks = ··· 61 72 [ Item 62 73 { icon = Icons.update 63 74 , label = "Play next" 64 - , msg = AddToQueue { inFront = True, tracks = tracks } 75 + , msg = 76 + { inFront = True, tracks = tracks } 77 + |> Queue.AddTracks 78 + |> QueueMsg 79 + 80 + -- 65 81 , active = False 66 82 } 67 83 , Item 68 84 { icon = Icons.update 69 85 , label = "Add to queue" 70 - , msg = AddToQueue { inFront = False, tracks = tracks } 86 + , msg = 87 + { inFront = False, tracks = tracks } 88 + |> Queue.AddTracks 89 + |> QueueMsg 90 + 91 + -- 71 92 , active = False 72 93 } 73 94 , Item
+27 -6
src/Applications/UI/Queue/State.elm
··· 55 55 ------------------------------------ 56 56 -- Future 57 57 ------------------------------------ 58 + AddTracks a -> 59 + addTracks a 60 + 58 61 InjectFirst a b -> 59 62 injectFirst a b 60 63 ··· 63 66 64 67 InjectFirstAndPlay a -> 65 68 injectFirstAndPlay a 69 + 70 + MoveItemToFirst a -> 71 + moveItemToFirst a 72 + 73 + MoveItemToLast a -> 74 + moveItemToLast a 66 75 67 76 RemoveItem a -> 68 77 removeItem a ··· 263 272 -- 🛠 ░░ FUTURE 264 273 265 274 275 + addTracks : { inFront : Bool, tracks : List IdentifiedTrack } -> Manager 276 + addTracks { inFront, tracks } = 277 + (if inFront then 278 + injectFirst 279 + 280 + else 281 + injectLast 282 + ) 283 + { showNotification = True } 284 + tracks 285 + 286 + 266 287 {-| Add an item in front of the queue. 267 288 -} 268 289 injectFirst : { showNotification : Bool } -> List IdentifiedTrack -> Manager ··· 360 381 |> andThen fill 361 382 362 383 363 - moveQueueItemToFirst : { itemIndex : Int } -> Manager 364 - moveQueueItemToFirst { itemIndex } model = 384 + moveItemToFirst : { index : Int } -> Manager 385 + moveItemToFirst { index } model = 365 386 model.playingNext 366 - |> moveItem { from = itemIndex, to = 0, shuffle = model.shuffle } 387 + |> moveItem { from = index, to = 0, shuffle = model.shuffle } 367 388 |> (\f -> { model | playingNext = f }) 368 389 |> fill 369 390 370 391 371 - moveQueueItemToLast : { itemIndex : Int } -> Manager 372 - moveQueueItemToLast { itemIndex } model = 392 + moveItemToLast : { index : Int } -> Manager 393 + moveItemToLast { index } model = 373 394 let 374 395 to = 375 396 model.playingNext ··· 377 398 |> List.length 378 399 in 379 400 model.playingNext 380 - |> moveItem { from = itemIndex, to = to, shuffle = model.shuffle } 401 + |> moveItem { from = index, to = to, shuffle = model.shuffle } 381 402 |> (\f -> { model | playingNext = f }) 382 403 |> fill 383 404
+3
src/Applications/UI/Queue/Types.elm
··· 23 23 ------------------------------------ 24 24 -- Future 25 25 ------------------------------------ 26 + | AddTracks { inFront : Bool, tracks : List IdentifiedTrack } 26 27 | InjectFirst { showNotification : Bool } (List IdentifiedTrack) 27 28 | InjectLast { showNotification : Bool } (List IdentifiedTrack) 28 29 | InjectFirstAndPlay IdentifiedTrack 30 + | MoveItemToFirst { index : Int } 31 + | MoveItemToLast { index : Int } 29 32 | RemoveItem { index : Int, item : Item }
-1
src/Applications/UI/Queue/View.elm
··· 19 19 import UI.Page as Page 20 20 import UI.Queue.Page as Queue exposing (Page(..)) 21 21 import UI.Queue.Types as Queue exposing (..) 22 - import UI.Reply exposing (Reply(..)) 23 22 import UI.Sources.Page 24 23 import UI.Types as UI exposing (..) 25 24
-113
src/Applications/UI/Reply.elm
··· 1 - module UI.Reply exposing (Reply(..)) 2 - 3 - import Common exposing (Switch(..)) 4 - import Coordinates exposing (Coordinates) 5 - import Playlists exposing (Playlist, PlaylistTrack) 6 - import Sources exposing (Source) 7 - import Tracks exposing (IdentifiedTrack, Track) 8 - import UI.Page exposing (Page) 9 - import User.Layer 10 - 11 - 12 - 13 - -- 🌳 14 - 15 - 16 - type Reply 17 - = Shunt 18 - -- 19 - | CopyToClipboard String 20 - | GoToPage Page 21 - | ToggleLoadingScreen Switch 22 - ----------------------------------------- 23 - -- Audio 24 - ----------------------------------------- 25 - | Seek Float 26 - | TogglePlayPause 27 - | ToggleRememberProgress 28 - ----------------------------------------- 29 - -- Authentication 30 - ----------------------------------------- 31 - | ImportLegacyData 32 - | PingIpfsForAuth 33 - | PingTextileForAuth 34 - | ShowUpdateEncryptionKeyScreen User.Layer.Method 35 - | SignOut 36 - ----------------------------------------- 37 - -- Context Menu 38 - ----------------------------------------- 39 - | ContextMenuConfirmation String Reply 40 - | ReplyViaContextMenu Reply 41 - | ShowMoreAuthenticationOptions Coordinates 42 - | ShowPlaylistListMenu Coordinates Playlist 43 - ----------------------------------------- 44 - -- Last.fm 45 - ----------------------------------------- 46 - | ConnectLastFm 47 - | DisconnectLastFm 48 - ----------------------------------------- 49 - -- Notifications 50 - ----------------------------------------- 51 - | DismissNotification { id : Int } 52 - | RemoveNotification { id : Int } 53 - | ShowErrorNotification String 54 - | ShowStickyErrorNotification String 55 - | ShowStickyErrorNotificationWithCode String String 56 - | ShowSuccessNotification String 57 - | ShowStickySuccessNotification String 58 - | ShowWarningNotification String 59 - | ShowStickyWarningNotification String 60 - ----------------------------------------- 61 - -- Playlists 62 - ----------------------------------------- 63 - | ActivatePlaylist Playlist 64 - | AddTracksToPlaylist { playlistName : String, tracks : List PlaylistTrack } 65 - | DeactivatePlaylist 66 - | RemoveFromSelectedPlaylist Playlist (List IdentifiedTrack) 67 - | RemovePlaylistFromCollection { playlistName : String } 68 - | RequestAssistanceForPlaylists (List IdentifiedTrack) 69 - ----------------------------------------- 70 - -- Queue 71 - ----------------------------------------- 72 - | AddToQueue { inFront : Bool, tracks : List IdentifiedTrack } 73 - | MoveQueueItemToFirst { itemIndex : Int } 74 - | MoveQueueItemToLast { itemIndex : Int } 75 - | RewindQueue 76 - | ShiftQueue 77 - | ToggleRepeat 78 - | ToggleShuffle 79 - ----------------------------------------- 80 - -- Sources & Tracks 81 - ----------------------------------------- 82 - | AddSourceToCollection Source 83 - | ClearTracksCache 84 - | DisableTracksGrouping 85 - | DownloadTracks String (List Track) 86 - | ExternalSourceAuthorization (String -> String) 87 - | ForceTracksRerender 88 - | GroupTracksBy Tracks.Grouping 89 - | ProcessSources (List Source) 90 - | RemoveSourceFromCollection { sourceId : String } 91 - | RemoveTracksFromCache (List Track) 92 - | RemoveTracksWithSourceId String 93 - | ScrollToNowPlaying 94 - | StoreTracksInCache (List Track) 95 - | ToggleCachedTracksOnly 96 - | ToggleDirectoryPlaylists { sourceId : String } 97 - | ToggleHideDuplicates 98 - | ToggleProcessAutomatically 99 - ----------------------------------------- 100 - -- User Data 101 - ----------------------------------------- 102 - | ChooseBackdrop String 103 - | Export 104 - | InsertDemo 105 - | LoadDefaultBackdrop 106 - | RequestImport 107 - | SaveEnclosedUserData 108 - | SaveFavourites 109 - | SavePlaylists 110 - | SaveProgress 111 - | SaveSettings 112 - | SaveSources 113 - | SaveTracks
-595
src/Applications/UI/Reply/Translate.elm
··· 1 - module UI.Reply.Translate exposing (..) 2 - 3 - import Alien 4 - import Browser.Dom 5 - import Browser.Navigation as Nav 6 - import Chunky exposing (..) 7 - import Common exposing (Switch(..)) 8 - import Conditional exposing (..) 9 - import File.Download 10 - import File.Select 11 - import Html.Attributes exposing (id) 12 - import Json.Encode 13 - import LastFm 14 - import List.Ext as List 15 - import List.Extra as List 16 - import Notifications 17 - import Playlists.Encoding as Playlists 18 - import Queue 19 - import Return exposing (andThen, return) 20 - import Return.Ext as Return 21 - import Settings 22 - import Sources 23 - import Sources.Encoding as Sources 24 - import String.Ext as String 25 - import Task 26 - import Tracks 27 - import Tracks.Encoding as Tracks 28 - import UI.Audio.State as Audio 29 - import UI.Authentication.ContextMenu as Authentication 30 - import UI.Authentication.Types as Authentication 31 - import UI.Backdrop as Backdrop 32 - import UI.Common.State as Common exposing (showNotification, showNotificationWithModel) 33 - import UI.Demo as Demo 34 - import UI.Notifications 35 - import UI.Playlists.Alfred 36 - import UI.Playlists.ContextMenu as Playlists 37 - import UI.Ports as Ports 38 - import UI.Queue.ContextMenu as Queue 39 - import UI.Queue.State as Queue 40 - import UI.Queue.Types as Queue 41 - import UI.Reply as Reply exposing (Reply(..)) 42 - import UI.Settings as Settings 43 - import UI.Settings.State as Settings 44 - import UI.Sources.ContextMenu as Sources 45 - import UI.Sources.State as Sources 46 - import UI.Sources.Types as Sources 47 - import UI.Tracks.ContextMenu as Tracks 48 - import UI.Tracks.Scene.List 49 - import UI.Tracks.Types as Tracks 50 - import UI.Types as UI exposing (..) 51 - import Url exposing (Protocol(..)) 52 - import Url.Ext as Url 53 - import User.Layer exposing (..) 54 - import User.Layer.Methods.RemoteStorage as RemoteStorage 55 - 56 - 57 - 58 - -- 📣 ░░ REPLIES 59 - 60 - 61 - translate : Reply -> Manager 62 - translate reply model = 63 - case reply of 64 - Shunt -> 65 - Return.singleton model 66 - 67 - -- 68 - CopyToClipboard string -> 69 - string 70 - |> Ports.copyToClipboard 71 - |> return model 72 - 73 - GoToPage page -> 74 - Common.changeUrlUsingPage page model 75 - 76 - Reply.ToggleLoadingScreen a -> 77 - Common.toggleLoadingScreen a model 78 - 79 - ----------------------------------------- 80 - -- Audio 81 - ----------------------------------------- 82 - Seek percentage -> 83 - return model (Ports.seek percentage) 84 - 85 - TogglePlayPause -> 86 - Audio.playPause model 87 - 88 - ToggleRememberProgress -> 89 - translate 90 - SaveSettings 91 - { model | rememberProgress = not model.rememberProgress } 92 - 93 - ----------------------------------------- 94 - -- Authentication 95 - ----------------------------------------- 96 - ImportLegacyData -> 97 - Alien.ImportLegacyData 98 - |> Alien.trigger 99 - |> Ports.toBrain 100 - |> return model 101 - |> andThen 102 - (""" 103 - I'll try to import data from Diffuse version one. 104 - If this was successful, you'll get a notification. 105 - """ 106 - |> Notifications.warning 107 - |> showNotification 108 - ) 109 - 110 - PingIpfsForAuth -> 111 - case model.url.protocol of 112 - Https -> 113 - """ 114 - Unfortunately the local IPFS API doesn't work with HTTPS. 115 - Install the [IPFS Companion](https://github.com/ipfs-shipyard/ipfs-companion#release-channel) browser extension to get around this issue 116 - (and make sure it redirects to the local gateway). 117 - """ 118 - |> Notifications.error 119 - |> showNotificationWithModel model 120 - 121 - Http -> 122 - Authentication.PingIpfs 123 - |> AuthenticationMsg 124 - |> Return.performanceF model 125 - 126 - PingTextileForAuth -> 127 - Authentication.PingTextile 128 - |> AuthenticationMsg 129 - |> Return.performanceF model 130 - 131 - ShowUpdateEncryptionKeyScreen authMethod -> 132 - authMethod 133 - |> Authentication.ShowUpdateEncryptionKeyScreen 134 - |> AuthenticationMsg 135 - |> Return.performanceF model 136 - 137 - SignOut -> 138 - { model 139 - | authentication = Authentication.Unauthenticated 140 - , playlists = [] 141 - , playlistToActivate = Nothing 142 - 143 - -- Queue 144 - -------- 145 - , dontPlay = [] 146 - , nowPlaying = Nothing 147 - , playedPreviously = [] 148 - , playingNext = [] 149 - , selectedQueueItem = Nothing 150 - 151 - -- 152 - , repeat = False 153 - , shuffle = False 154 - 155 - -- Sources 156 - ---------- 157 - , processingContext = [] 158 - , sources = [] 159 - 160 - -- Tracks 161 - --------- 162 - , favourites = [] 163 - , hideDuplicates = False 164 - , searchResults = Nothing 165 - , tracks = Tracks.emptyCollection 166 - } 167 - |> Backdrop.setDefault 168 - |> Return.command (Ports.toBrain <| Alien.trigger Alien.SignOut) 169 - |> Return.command (Ports.toBrain <| Alien.trigger Alien.StopProcessing) 170 - |> Return.command (Ports.activeQueueItemChanged Nothing) 171 - |> Return.command (Nav.pushUrl model.navKey "#/") 172 - 173 - ----------------------------------------- 174 - -- Context Menu 175 - ----------------------------------------- 176 - ContextMenuConfirmation conf r -> 177 - { model | confirmation = Just conf } 178 - |> Return.singleton 179 - |> andThen (translate r) 180 - 181 - ReplyViaContextMenu r -> 182 - case r of 183 - ContextMenuConfirmation _ _ -> 184 - translate r model 185 - 186 - _ -> 187 - translate r { model | contextMenu = Nothing } 188 - 189 - ShowMoreAuthenticationOptions coordinates -> 190 - Return.singleton { model | contextMenu = Just (Authentication.moreOptionsMenu coordinates) } 191 - 192 - Reply.ShowPlaylistListMenu coordinates playlist -> 193 - Return.singleton { model | contextMenu = Just (Playlists.listMenu playlist model.tracks.identified model.confirmation coordinates) } 194 - 195 - ----------------------------------------- 196 - -- Last.fm 197 - ----------------------------------------- 198 - ConnectLastFm -> 199 - model.url 200 - |> Common.urlOrigin 201 - |> String.addSuffix "?action=authenticate/lastfm" 202 - |> Url.percentEncode 203 - |> String.append "&cb=" 204 - |> String.append 205 - (String.append 206 - "http://www.last.fm/api/auth/?api_key=" 207 - LastFm.apiKey 208 - ) 209 - |> Nav.load 210 - |> return model 211 - 212 - DisconnectLastFm -> 213 - translate 214 - SaveSettings 215 - { model | lastFm = LastFm.disconnect model.lastFm } 216 - 217 - ----------------------------------------- 218 - -- Notifications 219 - ----------------------------------------- 220 - DismissNotification options -> 221 - options 222 - |> UI.Notifications.dismiss model.notifications 223 - |> Return.map (\n -> { model | notifications = n }) 224 - |> Return.mapCmd Reply 225 - 226 - RemoveNotification { id } -> 227 - model.notifications 228 - |> List.filter (Notifications.id >> (/=) id) 229 - |> (\n -> { model | notifications = n }) 230 - |> Return.singleton 231 - 232 - ShowErrorNotification string -> 233 - showNotificationWithModel model (Notifications.error string) 234 - 235 - ShowStickyErrorNotification string -> 236 - showNotificationWithModel model (Notifications.stickyError string) 237 - 238 - ShowStickyErrorNotificationWithCode string code -> 239 - showNotificationWithModel model (Notifications.errorWithCode string code []) 240 - 241 - ShowSuccessNotification string -> 242 - showNotificationWithModel model (Notifications.success string) 243 - 244 - ShowStickySuccessNotification string -> 245 - showNotificationWithModel model (Notifications.stickySuccess string) 246 - 247 - ShowWarningNotification string -> 248 - showNotificationWithModel model (Notifications.warning string) 249 - 250 - ShowStickyWarningNotification string -> 251 - showNotificationWithModel model (Notifications.stickyWarning string) 252 - 253 - ----------------------------------------- 254 - -- Playlists 255 - ----------------------------------------- 256 - Reply.ActivatePlaylist playlist -> 257 - playlist 258 - |> SelectPlaylist 259 - |> Return.performanceF model 260 - 261 - Reply.AddTracksToPlaylist a -> 262 - Return.performance (UI.AddTracksToPlaylist a) model 263 - 264 - Reply.DeactivatePlaylist -> 265 - Return.performanceF model DeselectPlaylist 266 - 267 - RemoveFromSelectedPlaylist playlist tracks -> 268 - let 269 - updatedPlaylist = 270 - Tracks.removeFromPlaylist tracks playlist 271 - 272 - tracksModel = 273 - model.tracks 274 - in 275 - model.playlists 276 - |> List.map 277 - (\p -> 278 - if p.name == playlist.name then 279 - updatedPlaylist 280 - 281 - else 282 - p 283 - ) 284 - |> (\c -> { model | playlists = c }) 285 - |> Return.performance (SelectPlaylist updatedPlaylist) 286 - |> andThen (translate SavePlaylists) 287 - 288 - RemovePlaylistFromCollection args -> 289 - args 290 - |> DeletePlaylist 291 - |> Return.performanceF { model | confirmation = Nothing } 292 - 293 - RequestAssistanceForPlaylists tracks -> 294 - model.playlists 295 - |> List.filterNot .autoGenerated 296 - |> UI.Playlists.Alfred.create tracks 297 - |> AssignAlfred 298 - |> Return.performanceF model 299 - 300 - ----------------------------------------- 301 - -- Queue 302 - ----------------------------------------- 303 - AddToQueue { inFront, tracks } -> 304 - (if inFront then 305 - Queue.InjectFirst 306 - 307 - else 308 - Queue.InjectLast 309 - ) 310 - |> (\msg -> msg { showNotification = True } tracks) 311 - |> QueueMsg 312 - |> Return.performanceF model 313 - 314 - MoveQueueItemToFirst args -> 315 - Queue.moveQueueItemToFirst args model 316 - 317 - MoveQueueItemToLast args -> 318 - Queue.moveQueueItemToLast args model 319 - 320 - RewindQueue -> 321 - Return.performance (QueueMsg Queue.Rewind) model 322 - 323 - ShiftQueue -> 324 - Return.performance (QueueMsg Queue.Shift) model 325 - 326 - ToggleRepeat -> 327 - Return.performance (QueueMsg Queue.ToggleRepeat) model 328 - 329 - ToggleShuffle -> 330 - Return.performance (QueueMsg Queue.ToggleShuffle) model 331 - 332 - ----------------------------------------- 333 - -- Sources & Tracks 334 - ----------------------------------------- 335 - AddSourceToCollection source -> 336 - source 337 - |> Sources.AddToCollection 338 - |> SourcesMsg 339 - |> Return.performanceF model 340 - 341 - ClearTracksCache -> 342 - model.cachedTracks 343 - |> Json.Encode.list Json.Encode.string 344 - |> Alien.broadcast Alien.RemoveTracksFromCache 345 - |> Ports.toBrain 346 - |> return { model | cachedTracks = [] } 347 - |> andThen (Return.performance <| TracksMsg Tracks.Harvest) 348 - |> andThen (translate <| Reply.SaveEnclosedUserData) 349 - |> andThen (translate <| ShowWarningNotification "Tracks cache was cleared") 350 - 351 - DisableTracksGrouping -> 352 - Tracks.DisableGrouping 353 - |> TracksMsg 354 - |> Return.performanceF model 355 - 356 - DownloadTracks zipName tracks -> 357 - let 358 - notification = 359 - Notifications.stickyWarning "Downloading tracks ..." 360 - 361 - downloading = 362 - Just { notificationId = Notifications.id notification } 363 - in 364 - [ ( "zipName", Json.Encode.string zipName ) 365 - , ( "trackIds" 366 - , tracks 367 - |> List.map .id 368 - |> Json.Encode.list Json.Encode.string 369 - ) 370 - ] 371 - |> Json.Encode.object 372 - |> Alien.broadcast Alien.DownloadTracks 373 - |> Ports.toBrain 374 - |> return { model | downloading = downloading } 375 - |> andThen (showNotification notification) 376 - 377 - ExternalSourceAuthorization urlBuilder -> 378 - model.url 379 - |> Common.urlOrigin 380 - |> urlBuilder 381 - |> Nav.load 382 - |> return model 383 - 384 - ForceTracksRerender -> 385 - ( model 386 - , Task.attempt 387 - (always Bypass) 388 - (Browser.Dom.setViewportOf UI.Tracks.Scene.List.containerId 0 1) 389 - ) 390 - 391 - GroupTracksBy grouping -> 392 - grouping 393 - |> Tracks.GroupBy 394 - |> TracksMsg 395 - |> Return.performanceF model 396 - 397 - ProcessSources sources -> 398 - Sources.process model 399 - 400 - RemoveSourceFromCollection args -> 401 - args 402 - |> Sources.RemoveFromCollection 403 - |> SourcesMsg 404 - |> Return.performanceF model 405 - 406 - RemoveTracksFromCache tracks -> 407 - let 408 - trackIds = 409 - List.map .id tracks 410 - in 411 - trackIds 412 - |> Json.Encode.list Json.Encode.string 413 - |> Alien.broadcast Alien.RemoveTracksFromCache 414 - |> Ports.toBrain 415 - |> return { model | cachedTracks = List.without trackIds model.cachedTracks } 416 - |> andThen (Return.performance <| TracksMsg Tracks.Harvest) 417 - |> andThen (translate Reply.SaveEnclosedUserData) 418 - 419 - RemoveTracksWithSourceId sourceId -> 420 - let 421 - cmd = 422 - sourceId 423 - |> Json.Encode.string 424 - |> Alien.broadcast Alien.RemoveTracksBySourceId 425 - |> Ports.toBrain 426 - in 427 - sourceId 428 - |> Tracks.RemoveBySourceId 429 - |> TracksMsg 430 - |> Return.performanceF model 431 - |> Return.command cmd 432 - 433 - ScrollToNowPlaying -> 434 - Return.performance (TracksMsg Tracks.ScrollToNowPlaying) model 435 - 436 - StoreTracksInCache tracks -> 437 - let 438 - trackIds = 439 - List.map .id tracks 440 - 441 - notification = 442 - case tracks of 443 - [ t ] -> 444 - ("__" ++ t.tags.title ++ "__ will be stored in the cache") 445 - |> Notifications.success 446 - 447 - list -> 448 - list 449 - |> List.length 450 - |> String.fromInt 451 - |> (\s -> "__" ++ s ++ " tracks__ will be stored in the cache") 452 - |> Notifications.success 453 - in 454 - tracks 455 - |> Json.Encode.list 456 - (\track -> 457 - Json.Encode.object 458 - [ ( "trackId" 459 - , Json.Encode.string track.id 460 - ) 461 - , ( "url" 462 - , track 463 - |> Queue.makeTrackUrl 464 - model.currentTime 465 - model.sources 466 - |> Json.Encode.string 467 - ) 468 - ] 469 - ) 470 - |> Alien.broadcast Alien.StoreTracksInCache 471 - |> Ports.toBrain 472 - |> return { model | cachingTracksInProgress = model.cachingTracksInProgress ++ trackIds } 473 - |> andThen (showNotification notification) 474 - 475 - ToggleCachedTracksOnly -> 476 - Return.performance (TracksMsg Tracks.ToggleCachedOnly) model 477 - 478 - ToggleDirectoryPlaylists args -> 479 - Return.performance (SourcesMsg <| Sources.ToggleDirectoryPlaylists args) model 480 - 481 - ToggleHideDuplicates -> 482 - Return.performance (TracksMsg Tracks.ToggleHideDuplicates) model 483 - 484 - ToggleProcessAutomatically -> 485 - translate SaveSettings { model | processAutomatically = not model.processAutomatically } 486 - 487 - ----------------------------------------- 488 - -- User Data 489 - ----------------------------------------- 490 - Reply.ChooseBackdrop filename -> 491 - filename 492 - |> UI.ChooseBackdrop 493 - |> Return.performanceF model 494 - 495 - Export -> 496 - { favourites = model.favourites 497 - , playlists = List.filterNot .autoGenerated model.playlists 498 - , progress = model.progress 499 - , settings = Just (Settings.gatherSettings model) 500 - , sources = model.sources 501 - , tracks = model.tracks.untouched 502 - } 503 - |> encodeHypaethralData 504 - |> Json.Encode.encode 2 505 - |> File.Download.string "diffuse.json" "application/json" 506 - |> return model 507 - 508 - InsertDemo -> 509 - model.currentTime 510 - |> Demo.tape 511 - |> LoadHypaethralUserData 512 - |> Return.performanceF model 513 - |> saveAllHypaethralData 514 - 515 - LoadDefaultBackdrop -> 516 - Backdrop.setDefault model 517 - 518 - RequestImport -> 519 - ImportFile 520 - |> File.Select.file [ "application/json" ] 521 - |> return model 522 - 523 - Reply.SaveEnclosedUserData -> 524 - Return.performance UI.SaveEnclosedUserData model 525 - 526 - SaveFavourites -> 527 - model.favourites 528 - |> Json.Encode.list Tracks.encodeFavourite 529 - |> Alien.broadcast Alien.SaveFavourites 530 - |> Ports.toBrain 531 - |> return model 532 - 533 - SavePlaylists -> 534 - model.playlists 535 - |> List.filterNot .autoGenerated 536 - |> Json.Encode.list Playlists.encode 537 - |> Alien.broadcast Alien.SavePlaylists 538 - |> Ports.toBrain 539 - |> return model 540 - 541 - SaveProgress -> 542 - model.progress 543 - |> Json.Encode.dict identity Json.Encode.float 544 - |> Alien.broadcast Alien.SaveProgress 545 - |> Ports.toBrain 546 - |> return model 547 - 548 - SaveSettings -> 549 - Settings.save model 550 - 551 - SaveSources -> 552 - model.sources 553 - |> Json.Encode.list Sources.encode 554 - |> Alien.broadcast Alien.SaveSources 555 - |> Ports.toBrain 556 - |> Return.return model 557 - 558 - SaveTracks -> 559 - model.tracks.untouched 560 - |> Json.Encode.list Tracks.encodeTrack 561 - |> Alien.broadcast Alien.SaveTracks 562 - |> Ports.toBrain 563 - |> return model 564 - 565 - 566 - translateWithModel : Model -> Reply -> ( Model, Cmd Msg ) 567 - translateWithModel model reply = 568 - translate reply model 569 - 570 - 571 - saveAllHypaethralData : ( Model, Cmd Msg ) -> ( Model, Cmd Msg ) 572 - saveAllHypaethralData return = 573 - List.foldl 574 - (\( _, bit ) -> 575 - case bit of 576 - Favourites -> 577 - andThen (translate SaveFavourites) 578 - 579 - Playlists -> 580 - andThen (translate SavePlaylists) 581 - 582 - Progress -> 583 - andThen (translate SaveProgress) 584 - 585 - Settings -> 586 - andThen (translate SaveSettings) 587 - 588 - Sources -> 589 - andThen (translate SaveSources) 590 - 591 - Tracks -> 592 - andThen (translate SaveTracks) 593 - ) 594 - return 595 - hypaethralBit.list
+28 -3
src/Applications/UI/Services/State.elm
··· 1 1 module UI.Services.State exposing (..) 2 2 3 + import Browser.Navigation as Nav 4 + import Common 3 5 import Http 4 6 import LastFm 5 7 import Notifications 6 - import Return exposing (andThen) 8 + import Return exposing (andThen, return) 7 9 import Return.Ext as Return 10 + import String.Ext as String 8 11 import UI.Common.State as Common exposing (showNotification) 9 - import UI.Reply exposing (Reply(..)) 10 12 import UI.Types as UI exposing (Manager, Msg(..)) 13 + import UI.User.State.Export as User 14 + import Url 11 15 12 16 13 17 14 18 -- 🔱 15 19 16 20 21 + connectLastFm : Manager 22 + connectLastFm model = 23 + model.url 24 + |> Common.urlOrigin 25 + |> String.addSuffix "?action=authenticate/lastfm" 26 + |> Url.percentEncode 27 + |> String.append "&cb=" 28 + |> String.append 29 + (String.append 30 + "http://www.last.fm/api/auth/?api_key=" 31 + LastFm.apiKey 32 + ) 33 + |> Nav.load 34 + |> return model 35 + 36 + 37 + disconnectLastFm : Manager 38 + disconnectLastFm model = 39 + User.saveSettings { model | lastFm = LastFm.disconnect model.lastFm } 40 + 41 + 17 42 gotLastFmSession : Result Http.Error String -> Manager 18 43 gotLastFmSession result model = 19 44 case result of ··· 27 52 |> showNotification 28 53 (Notifications.success "Connected successfully with Last.fm") 29 54 |> andThen 30 - (Return.performance <| Reply SaveSettings) 55 + User.saveSettings 31 56 32 57 33 58 scrobble : { duration : Int, timestamp : Int, trackId : String } -> Manager
+18 -12
src/Applications/UI/Settings.elm
··· 11 11 import Material.Icons as Icons 12 12 import Material.Icons.Types exposing (Coloring(..)) 13 13 import Settings 14 + import UI.Authentication.Types as Authentication 14 15 import UI.Backdrop as Backdrop exposing (backgroundPositioning) 15 16 import UI.Kit 16 17 import UI.Navigation exposing (..) 17 18 import UI.Page as Page 18 - import UI.Reply exposing (Reply(..)) 19 19 import UI.Settings.ImportExport 20 20 import UI.Settings.Page as Settings exposing (..) 21 + import UI.Sources.Types as Sources 22 + import UI.Tracks.Types as Tracks 23 + import UI.Types exposing (Msg(..)) 21 24 import User.Layer exposing (Method(..)) 22 25 23 26 ··· 35 38 } 36 39 37 40 38 - view : Settings.Page -> Dependencies -> Html Reply 41 + view : Settings.Page -> Dependencies -> Html Msg 39 42 view page deps = 40 43 case page of 41 44 ImportExport -> ··· 49 52 -- INDEX 50 53 51 54 52 - index : Dependencies -> List (Html Reply) 55 + index : Dependencies -> List (Html Msg) 53 56 index deps = 54 57 [ ----------------------------------------- 55 58 -- Navigation ··· 65 68 ) 66 69 , ( Icon Icons.exit_to_app 67 70 , Label "Sign out" Shown 68 - , PerformMsg SignOut 71 + , PerformMsg (AuthenticationMsg Authentication.SignOut) 69 72 ) 70 73 ] 71 74 ··· 80 83 ] 81 84 82 85 83 - content : Dependencies -> List (Html Reply) 86 + content : Dependencies -> List (Html Msg) 84 87 content deps = 85 88 [ ----------------------------------------- 86 89 -- Title ··· 161 164 , UI.Kit.buttonWithColor 162 165 UI.Kit.Gray 163 166 UI.Kit.Normal 164 - ClearTracksCache 167 + (TracksMsg Tracks.ClearCache) 165 168 (text "Clear cache") 166 169 ] 167 170 ··· 183 186 UI.Kit.buttonWithColor 184 187 UI.Kit.Gray 185 188 UI.Kit.Normal 186 - Shunt 189 + Bypass 187 190 (text "Connecting") 188 191 189 192 ( False, Nothing ) -> ··· 205 208 [ label "Hide Duplicates" 206 209 , UI.Kit.checkbox 207 210 { checked = deps.hideDuplicateTracks 208 - , toggleMsg = ToggleHideDuplicates 211 + , toggleMsg = TracksMsg Tracks.ToggleHideDuplicates 209 212 } 210 213 ] 211 214 , chunk ··· 213 216 [ label "Process sources automatically" 214 217 , UI.Kit.checkbox 215 218 { checked = deps.processAutomatically 216 - , toggleMsg = ToggleProcessAutomatically 219 + , toggleMsg = SourcesMsg Sources.ToggleProcessAutomatically 217 220 } 218 221 ] 219 222 ] ··· 246 249 -- AUTHENTICATION 247 250 248 251 249 - changePassphrase : User.Layer.Method -> Html Reply 252 + changePassphrase : User.Layer.Method -> Html Msg 250 253 changePassphrase method = 251 254 inline 252 255 [] ··· 254 257 , text "If you want to, you can " 255 258 , UI.Kit.textButton 256 259 { label = "change your passphrase" 257 - , onClick = ShowUpdateEncryptionKeyScreen method 260 + , onClick = 261 + method 262 + |> Authentication.ShowUpdateEncryptionKeyScreen 263 + |> AuthenticationMsg 258 264 } 259 265 , text "." 260 266 ] ··· 264 270 -- BACKGROUND IMAGE 265 271 266 272 267 - backgroundImage : Maybe String -> Html Reply 273 + backgroundImage : Maybe String -> Html Msg 268 274 backgroundImage chosenBackground = 269 275 chunk 270 276 [ C.flex, C.flex_wrap ]
+3 -3
src/Applications/UI/Settings/ImportExport.elm
··· 7 7 import UI.Kit exposing (ButtonType(..)) 8 8 import UI.Navigation exposing (..) 9 9 import UI.Page 10 - import UI.Reply exposing (Reply(..)) 11 10 import UI.Settings.Page 11 + import UI.Types exposing (Msg(..)) 12 12 import User.Layer exposing (Method(..)) 13 13 14 14 ··· 16 16 -- 🗺 17 17 18 18 19 - view : Maybe Method -> Html Reply 19 + view : Maybe Method -> Html Msg 20 20 view userLayerMethod = 21 21 UI.Kit.receptacle 22 22 { scrolling = True } ··· 78 78 ] 79 79 80 80 81 - otherImportOptions : Html Reply 81 + otherImportOptions : Html Msg 82 82 otherImportOptions = 83 83 raw 84 84 [ chunk
-32
src/Applications/UI/Settings/State.elm
··· 1 - module UI.Settings.State exposing (..) 2 - 3 - import Alien 4 - import Return exposing (return) 5 - import Settings exposing (Settings) 6 - import UI.Ports as Ports 7 - import UI.Types exposing (..) 8 - 9 - 10 - 11 - -- 🔱 12 - -- TODO: Move to User.State.Export 13 - 14 - 15 - gatherSettings : Model -> Settings 16 - gatherSettings { chosenBackdrop, hideDuplicates, lastFm, processAutomatically, rememberProgress } = 17 - { backgroundImage = chosenBackdrop 18 - , hideDuplicates = hideDuplicates 19 - , lastFm = lastFm.sessionKey 20 - , processAutomatically = processAutomatically 21 - , rememberProgress = rememberProgress 22 - } 23 - 24 - 25 - save : Manager 26 - save model = 27 - model 28 - |> gatherSettings 29 - |> Settings.encode 30 - |> Alien.broadcast Alien.SaveSettings 31 - |> Ports.toBrain 32 - |> return model
+33 -7
src/Applications/UI/Sources/ContextMenu.elm
··· 6 6 import Material.Icons as Icons 7 7 import Sources exposing (Source) 8 8 import UI.Page 9 - import UI.Reply exposing (Reply(..)) 10 9 import UI.Sources.Page 10 + import UI.Sources.Types as Sources 11 + import UI.Types exposing (Msg(..)) 11 12 12 13 13 14 14 15 -- 🔱 15 16 16 17 17 - sourceMenu : Source -> Coordinates -> ContextMenu Reply 18 + sourceMenu : Source -> Coordinates -> ContextMenu Msg 18 19 sourceMenu source = 19 20 ContextMenu 20 21 [ Item 21 22 { icon = ifThenElse source.directoryPlaylists Icons.folder Icons.folder_open 22 23 , label = ifThenElse source.directoryPlaylists "Disable Directory Playlists" "Enable Directory Playlists" 23 - , msg = ToggleDirectoryPlaylists { sourceId = source.id } 24 + , msg = 25 + { sourceId = source.id } 26 + |> Sources.ToggleDirectoryPlaylists 27 + |> SourcesMsg 28 + 29 + -- 24 30 , active = False 25 31 } 32 + 33 + -- 26 34 , Item 27 35 { icon = Icons.edit 28 36 , label = "Edit source" ··· 30 38 source.id 31 39 |> UI.Sources.Page.Edit 32 40 |> UI.Page.Sources 33 - |> GoToPage 41 + |> ChangeUrlUsingPage 42 + 43 + -- 34 44 , active = False 35 45 } 46 + 47 + -- 36 48 , Item 37 49 { icon = Icons.sync 38 50 , label = "Process source" 39 - , msg = ProcessSources [ source ] 51 + , msg = 52 + [ source ] 53 + |> Sources.ProcessSpecific 54 + |> SourcesMsg 55 + 56 + -- 40 57 , active = False 41 58 } 59 + 60 + -- 42 61 , Item 43 62 { icon = Icons.delete 44 63 , label = "Remove source" 45 - , msg = RemoveSourceFromCollection { sourceId = source.id } 64 + , msg = 65 + { sourceId = source.id } 66 + |> Sources.RemoveFromCollection 67 + |> SourcesMsg 46 68 , active = False 47 69 } 70 + 71 + -- 48 72 , Item 49 73 { icon = Icons.font_download 50 74 , label = "Rename source" ··· 52 76 source.id 53 77 |> UI.Sources.Page.Rename 54 78 |> UI.Page.Sources 55 - |> GoToPage 79 + |> ChangeUrlUsingPage 80 + 81 + -- 56 82 , active = False 57 83 } 58 84 ]
-2
src/Applications/UI/Sources/Form.elm
··· 12 12 import List.Extra as List 13 13 import Material.Icons as Icons 14 14 import Material.Icons.Types exposing (Coloring(..)) 15 - import Return3 exposing (..) 16 15 import Sources exposing (..) 17 16 import Sources.Services as Services 18 17 import UI.Kit exposing (ButtonType(..), select) 19 18 import UI.Navigation exposing (..) 20 19 import UI.Page as Page 21 - import UI.Reply exposing (Reply(..)) 22 20 import UI.Sources.Page as Sources 23 21 import UI.Sources.Types exposing (..) 24 22 import UI.Types exposing (Model)
+70 -47
src/Applications/UI/Sources/State.elm
··· 1 1 module UI.Sources.State exposing (..) 2 2 3 3 import Alien 4 + import Browser.Navigation as Nav 4 5 import Common 5 6 import Conditional exposing (ifThenElse) 6 7 import Coordinates ··· 21 22 import UI.Common.State as Common 22 23 import UI.Page as Page 23 24 import UI.Ports as Ports 24 - import UI.Reply as Reply 25 25 import UI.Sources.ContextMenu as Sources 26 26 import UI.Sources.Form as Form 27 27 import UI.Sources.Page as Sources 28 28 import UI.Sources.Types exposing (..) 29 + import UI.Tracks.State as Tracks 29 30 import UI.Types as UI exposing (Manager, Model) 30 31 import UI.User.State.Export as User 32 + import Url 31 33 32 34 33 35 ··· 76 78 Process -> 77 79 process 78 80 81 + ProcessSpecific a -> 82 + processSpecific a 83 + 79 84 ReportProcessingError a -> 80 85 reportProcessingError a 81 86 ··· 136 141 ToggleDirectoryPlaylists a -> 137 142 toggleDirectoryPlaylists a 138 143 144 + ToggleProcessAutomatically -> 145 + toggleProcessAutomatically 146 + 139 147 140 148 141 149 -- 🔱 ··· 168 176 Return.singleton model 169 177 170 178 toProcess -> 171 - let 172 - notification = 173 - Notifications.stickyWarning "Processing sources ..." 179 + processSpecific toProcess model 174 180 175 - notificationId = 176 - Notifications.id notification 181 + 182 + processSpecific : List Source -> Manager 183 + processSpecific toProcess model = 184 + let 185 + notification = 186 + Notifications.stickyWarning "Processing sources ..." 187 + 188 + notificationId = 189 + Notifications.id notification 177 190 178 - newNotifications = 179 - List.filter 180 - (\n -> Notifications.kind n /= Notifications.Error) 181 - model.notifications 191 + newNotifications = 192 + List.filter 193 + (\n -> Notifications.kind n /= Notifications.Error) 194 + model.notifications 182 195 183 - processingContext = 184 - toProcess 185 - |> List.sortBy (.data >> Dict.fetch "name" "") 186 - |> List.map (\{ id } -> ( id, 0 )) 196 + processingContext = 197 + toProcess 198 + |> List.sortBy (.data >> Dict.fetch "name" "") 199 + |> List.map (\{ id } -> ( id, 0 )) 187 200 188 - newModel = 189 - { model 190 - | notifications = newNotifications 191 - , processingContext = processingContext 192 - , processingError = Nothing 193 - , processingNotificationId = Just notificationId 194 - } 195 - in 196 - [ ( "origin" 197 - , Json.Encode.string (Common.urlOrigin model.url) 198 - ) 199 - , ( "sources" 200 - , Json.Encode.list Sources.encode toProcess 201 - ) 202 - ] 203 - |> Json.Encode.object 204 - |> Alien.broadcast Alien.ProcessSources 205 - |> Ports.toBrain 206 - |> return newModel 207 - |> andThen (Common.showNotification notification) 201 + newModel = 202 + { model 203 + | notifications = newNotifications 204 + , processingContext = processingContext 205 + , processingError = Nothing 206 + , processingNotificationId = Just notificationId 207 + } 208 + in 209 + [ ( "origin" 210 + , Json.Encode.string (Common.urlOrigin model.url) 211 + ) 212 + , ( "sources" 213 + , Json.Encode.list Sources.encode toProcess 214 + ) 215 + ] 216 + |> Json.Encode.object 217 + |> Alien.broadcast Alien.ProcessSources 218 + |> Ports.toBrain 219 + |> return newModel 220 + |> andThen (Common.showNotification notification) 208 221 209 222 210 223 reportProcessingError : Json.Value -> Manager ··· 299 312 unsuitableSource 300 313 in 301 314 { model | sources = model.sources ++ [ source ] } 302 - |> Return.performance (UI.Reply Reply.SaveSources) 315 + |> User.saveSources 303 316 |> andThen process 304 317 305 318 ··· 309 322 |> List.filter (.id >> (/=) sourceId) 310 323 |> (\c -> { model | sources = c }) 311 324 |> Return.singleton 312 - |> andThen (Return.performance <| UI.Reply Reply.SaveSources) 313 - |> andThen (Return.performance <| UI.Reply <| Reply.RemoveTracksWithSourceId sourceId) 325 + |> andThen User.saveSources 326 + |> andThen (Tracks.removeBySourceId sourceId) 314 327 315 328 316 329 updateSourceData : Json.Value -> Manager ··· 331 344 ) 332 345 |> Maybe.map (\col -> { model | sources = col }) 333 346 |> Maybe.withDefault model 334 - |> Return.performance (UI.Reply Reply.SaveSources) 347 + |> User.saveSources 335 348 336 349 337 350 ··· 417 430 ( How, Dropbox ) -> 418 431 form.context.data 419 432 |> Sources.Services.Dropbox.authorizationUrl 420 - |> Reply.ExternalSourceAuthorization 421 - |> UI.Reply 422 - |> Return.performanceF model 433 + |> externalAuthorization model 423 434 424 435 ( How, Google ) -> 425 436 form.context.data 426 437 |> Sources.Services.Google.authorizationUrl 427 - |> Reply.ExternalSourceAuthorization 428 - |> UI.Reply 429 - |> Return.performanceF model 438 + |> externalAuthorization model 430 439 431 440 _ -> 432 441 model ··· 463 472 source 464 473 ) 465 474 |> (\collection -> { model | sources = collection }) 466 - |> Return.performance (UI.Reply Reply.SaveSources) 475 + |> User.saveSources 467 476 468 477 469 478 toggleDirectoryPlaylists : { sourceId : String } -> Manager ··· 478 487 source 479 488 ) 480 489 |> (\collection -> { model | sources = collection }) 481 - |> Return.performance (UI.Reply Reply.SaveSources) 490 + |> User.saveSources 482 491 |> andThen Common.generateDirectoryPlaylists 483 492 484 493 494 + toggleProcessAutomatically : Manager 495 + toggleProcessAutomatically model = 496 + User.saveSettings { model | processAutomatically = not model.processAutomatically } 497 + 498 + 485 499 486 500 -- ⚗️ 487 501 488 502 503 + externalAuthorization : Model -> (String -> String) -> ( Model, Cmd UI.Msg ) 504 + externalAuthorization model urlBuilder = 505 + model.url 506 + |> Common.urlOrigin 507 + |> urlBuilder 508 + |> Nav.load 509 + |> return model 510 + 511 + 489 512 replaceSourceInCollection : Source -> Manager 490 513 replaceSourceInCollection source model = 491 514 model.sources 492 515 |> List.map (\s -> ifThenElse (s.id == source.id) source s) 493 516 |> (\s -> { model | sources = s }) 494 - |> Return.performance (UI.Reply Reply.SaveSources) 517 + |> User.saveSources 495 518 496 519 497 520 sourcesToProcess : Model -> List Source
+2
src/Applications/UI/Sources/Types.elm
··· 31 31 | FinishedProcessingSource { sourceId : String } 32 32 | FinishedProcessing 33 33 | Process 34 + | ProcessSpecific (List Source) 34 35 | ReportProcessingError Json.Value 35 36 | ReportProcessingProgress Json.Value 36 37 | StopProcessing ··· 57 58 | SourceContextMenu Source Mouse.Event 58 59 | ToggleActivation { sourceId : String } 59 60 | ToggleDirectoryPlaylists { sourceId : String } 61 + | ToggleProcessAutomatically
-2
src/Applications/UI/Sources/View.elm
··· 10 10 import List.Extra as List 11 11 import Material.Icons as Icons 12 12 import Material.Icons.Types exposing (Coloring(..)) 13 - import Return3 as Return exposing (..) 14 13 import Sources exposing (..) 15 14 import UI.Kit exposing (ButtonType(..)) 16 15 import UI.List 17 16 import UI.Navigation exposing (..) 18 17 import UI.Page as Page 19 - import UI.Reply exposing (Reply(..)) 20 18 import UI.Sources.Form as Form 21 19 import UI.Sources.Page as Sources exposing (..) 22 20 import UI.Sources.Types exposing (Msg(..))
+66 -24
src/Applications/UI/Tracks/ContextMenu.elm
··· 10 10 import Sources exposing (Source) 11 11 import Time 12 12 import Tracks exposing (Grouping(..), IdentifiedTrack) 13 - import UI.Reply exposing (Reply(..)) 13 + import UI.Queue.Types as Queue 14 + import UI.Tracks.Types as Tracks 15 + import UI.Types exposing (Msg(..)) 14 16 15 17 16 18 ··· 28 30 } 29 31 -> List IdentifiedTrack 30 32 -> Coordinates 31 - -> ContextMenu Reply 33 + -> ContextMenu Msg 32 34 trackMenu { cached, cachingInProgress, currentTime, selectedPlaylist, lastModifiedPlaylistName, showAlternativeMenu, sources } tracks = 33 35 if showAlternativeMenu then 34 36 [ temporaryUrlActions ··· 65 67 cacheAction : 66 68 { cached : List String, cachingInProgress : List String } 67 69 -> List IdentifiedTrack 68 - -> ContextMenu.Item Reply 70 + -> ContextMenu.Item Msg 69 71 cacheAction { cached, cachingInProgress } tracks = 70 72 case tracks of 71 73 [ ( i, t ) as track ] -> ··· 73 75 Item 74 76 { icon = Icons.offline_bolt 75 77 , label = "Remove from cache" 76 - , msg = RemoveTracksFromCache (List.map Tuple.second tracks) 78 + , msg = 79 + tracks 80 + |> List.map Tuple.second 81 + |> Tracks.RemoveFromCache 82 + |> TracksMsg 83 + 84 + -- 77 85 , active = False 78 86 } 79 87 ··· 81 89 Item 82 90 { icon = Icons.offline_bolt 83 91 , label = "Downloading ..." 84 - , msg = Shunt 92 + , msg = Bypass 85 93 , active = True 86 94 } 87 95 ··· 89 97 Item 90 98 { icon = Icons.offline_bolt 91 99 , label = "Store in cache" 92 - , msg = StoreTracksInCache (List.map Tuple.second tracks) 100 + , msg = 101 + tracks 102 + |> List.map Tuple.second 103 + |> Tracks.StoreInCache 104 + |> TracksMsg 105 + 106 + -- 93 107 , active = False 94 108 } 95 109 ··· 97 111 Item 98 112 { icon = Icons.offline_bolt 99 113 , label = "Store in cache" 100 - , msg = StoreTracksInCache (List.map Tuple.second tracks) 114 + , msg = 115 + tracks 116 + |> List.map Tuple.second 117 + |> Tracks.StoreInCache 118 + |> TracksMsg 119 + 120 + -- 101 121 , active = False 102 122 } 103 123 ··· 107 127 , lastModifiedPlaylistName : Maybe String 108 128 } 109 129 -> List IdentifiedTrack 110 - -> List (ContextMenu.Item Reply) 130 + -> List (ContextMenu.Item Msg) 111 131 playlistActions { selectedPlaylist, lastModifiedPlaylistName } tracks = 112 132 let 113 133 maybeCustomPlaylist = ··· 124 144 , label = "Add to \"" ++ n ++ "\"" 125 145 , msg = 126 146 AddTracksToPlaylist 127 - { playlistName = n, tracks = Tracks.toPlaylistTracks tracks } 147 + { playlistName = n 148 + , tracks = Tracks.toPlaylistTracks tracks 149 + } 150 + 151 + -- 128 152 , active = False 129 153 } 130 154 ··· 142 166 [ justAnItem 143 167 { icon = Icons.waves 144 168 , label = "Remove from playlist" 145 - , msg = RemoveFromSelectedPlaylist playlist tracks 169 + , msg = RemoveTracksFromPlaylist playlist tracks 170 + 171 + -- 146 172 , active = False 147 173 } 148 174 , maybeAddToLastModifiedPlaylist ··· 150 176 { icon = Icons.waves 151 177 , label = "Add to another playlist" 152 178 , msg = RequestAssistanceForPlaylists tracks 179 + 180 + -- 153 181 , active = False 154 182 } 155 183 ] ··· 169 197 ] 170 198 171 199 172 - queueActions : List IdentifiedTrack -> List (ContextMenu.Item Reply) 200 + queueActions : List IdentifiedTrack -> List (ContextMenu.Item Msg) 173 201 queueActions identifiedTracks = 174 202 [ Item 175 203 { icon = Icons.update 176 204 , label = "Play next" 177 - , msg = AddToQueue { inFront = True, tracks = identifiedTracks } 205 + , msg = 206 + { inFront = True, tracks = identifiedTracks } 207 + |> Queue.AddTracks 208 + |> QueueMsg 209 + 210 + -- 178 211 , active = False 179 212 } 180 213 , Item 181 214 { icon = Icons.update 182 215 , label = "Add to queue" 183 - , msg = AddToQueue { inFront = False, tracks = identifiedTracks } 216 + , msg = 217 + { inFront = False, tracks = identifiedTracks } 218 + |> Queue.AddTracks 219 + |> QueueMsg 220 + 221 + -- 184 222 , active = False 185 223 } 186 224 ] 187 225 188 226 189 - temporaryUrlActions : Time.Posix -> List Source -> List IdentifiedTrack -> List (ContextMenu.Item Reply) 227 + temporaryUrlActions : 228 + Time.Posix 229 + -> List Source 230 + -> List IdentifiedTrack 231 + -> List (ContextMenu.Item Msg) 190 232 temporaryUrlActions timestamp sources tracks = 191 233 case tracks of 192 234 [ ( i, t ) ] -> ··· 206 248 -- VIEW MENU 207 249 208 250 209 - viewMenu : Bool -> Maybe Grouping -> Coordinates -> ContextMenu Reply 251 + viewMenu : Bool -> Maybe Grouping -> Coordinates -> ContextMenu Msg 210 252 viewMenu onlyCachedTracks maybeGrouping = 211 253 ContextMenu 212 254 [ groupByDirectory (maybeGrouping == Just Directory) ··· 219 261 { icon = Icons.filter_list 220 262 , label = "Cached tracks only" 221 263 , active = onlyCachedTracks 222 - , msg = ToggleCachedTracksOnly 264 + , msg = TracksMsg Tracks.ToggleCachedOnly 223 265 } 224 266 ] 225 267 ··· 233 275 -- 234 276 , msg = 235 277 if isActive then 236 - DisableTracksGrouping 278 + TracksMsg Tracks.DisableGrouping 237 279 238 280 else 239 - GroupTracksBy Directory 281 + TracksMsg (Tracks.GroupBy Directory) 240 282 } 241 283 242 284 ··· 249 291 -- 250 292 , msg = 251 293 if isActive then 252 - DisableTracksGrouping 294 + TracksMsg Tracks.DisableGrouping 253 295 254 296 else 255 - GroupTracksBy FirstAlphaCharacter 297 + TracksMsg (Tracks.GroupBy FirstAlphaCharacter) 256 298 } 257 299 258 300 ··· 265 307 -- 266 308 , msg = 267 309 if isActive then 268 - DisableTracksGrouping 310 + TracksMsg Tracks.DisableGrouping 269 311 270 312 else 271 - GroupTracksBy AddedOn 313 + TracksMsg (Tracks.GroupBy AddedOn) 272 314 } 273 315 274 316 ··· 281 323 -- 282 324 , msg = 283 325 if isActive then 284 - DisableTracksGrouping 326 + TracksMsg Tracks.DisableGrouping 285 327 286 328 else 287 - GroupTracksBy TrackYear 329 + TracksMsg (Tracks.GroupBy TrackYear) 288 330 }
-1
src/Applications/UI/Tracks/Scene/List.elm
··· 20 20 import Material.Icons.Types exposing (Coloring(..)) 21 21 import Maybe.Extra as Maybe 22 22 import Queue 23 - import Return3 exposing (..) 24 23 import Task 25 24 import Tracks exposing (..) 26 25 import UI.DnD as DnD
+123 -10
src/Applications/UI/Tracks/State.elm
··· 13 13 import Maybe.Extra as Maybe 14 14 import Notifications 15 15 import Playlists exposing (Playlist) 16 + import Queue 16 17 import Return exposing (andThen, return) 17 18 import Return.Ext as Return 18 19 import Sources ··· 25 26 import UI.Common.State as Common exposing (showNotification) 26 27 import UI.DnD as DnD 27 28 import UI.Page 28 - import UI.Ports 29 + import UI.Ports as Ports 29 30 import UI.Queue.State as Queue 30 - import UI.Reply as Reply 31 - import UI.Settings.State as Settings 32 31 import UI.Tracks.ContextMenu as Tracks 33 32 import UI.Tracks.Scene.List 34 33 import UI.Tracks.Types as Tracks exposing (..) ··· 44 43 update : Tracks.Msg -> Manager 45 44 update msg = 46 45 case msg of 46 + Download a b -> 47 + download a b 48 + 47 49 Harvest -> 48 50 harvest 49 51 ··· 61 63 62 64 ToggleHideDuplicates -> 63 65 toggleHideDuplicates 66 + 67 + ----------------------------------------- 68 + -- Cache 69 + ----------------------------------------- 70 + ClearCache -> 71 + clearCache 72 + 73 + RemoveFromCache a -> 74 + removeFromCache a 75 + 76 + StoreInCache a -> 77 + storeInCache a 64 78 65 79 ----------------------------------------- 66 80 -- Collection ··· 137 151 ) 138 152 139 153 154 + clearCache : Manager 155 + clearCache model = 156 + model.cachedTracks 157 + |> Json.Encode.list Json.Encode.string 158 + |> Alien.broadcast Alien.RemoveTracksFromCache 159 + |> Ports.toBrain 160 + |> return { model | cachedTracks = [] } 161 + |> andThen harvest 162 + |> andThen User.saveEnclosedUserData 163 + |> andThen 164 + ("Tracks cache was cleared" 165 + |> Notifications.warning 166 + |> Common.showNotification 167 + ) 168 + 169 + 140 170 clearSearch : Manager 141 171 clearSearch model = 142 172 { model | searchResults = Nothing, searchTerm = Nothing } 143 173 |> reviseCollection Collection.harvest 144 174 |> andThen User.saveEnclosedUserData 175 + 176 + 177 + download : String -> List Track -> Manager 178 + download zipName tracks model = 179 + let 180 + notification = 181 + Notifications.stickyWarning "Downloading tracks ..." 182 + 183 + downloading = 184 + Just { notificationId = Notifications.id notification } 185 + in 186 + [ ( "zipName", Json.Encode.string zipName ) 187 + , ( "trackIds" 188 + , tracks 189 + |> List.map .id 190 + |> Json.Encode.list Json.Encode.string 191 + ) 192 + ] 193 + |> Json.Encode.object 194 + |> Alien.broadcast Alien.DownloadTracks 195 + |> Ports.toBrain 196 + |> return { model | downloading = downloading } 197 + |> andThen (Common.showNotification notification) 145 198 146 199 147 200 downloadTracksFinished : Manager ··· 268 321 in 269 322 { model | tracks = newCollection } 270 323 |> reviseCollection Collection.identify 271 - |> andThen (Return.performance <| Reply <| Reply.RemoveTracksFromCache removed) 324 + |> andThen (removeFromCache removed) 272 325 273 326 274 327 removeBySourceId : String -> Manager ··· 280 333 newCollection = 281 334 { emptyCollection | untouched = kept } 282 335 in 283 - { model | tracks = newCollection } 284 - |> reviseCollection Collection.identify 285 - |> andThen (Return.performance <| Reply <| Reply.RemoveTracksFromCache removed) 336 + sourceId 337 + |> Json.Encode.string 338 + |> Alien.broadcast Alien.RemoveTracksBySourceId 339 + |> Ports.toBrain 340 + |> return { model | tracks = newCollection } 341 + |> andThen (reviseCollection Collection.identify) 342 + |> andThen (removeFromCache removed) 343 + 344 + 345 + removeFromCache : List Track -> Manager 346 + removeFromCache tracks model = 347 + let 348 + trackIds = 349 + List.map .id tracks 350 + in 351 + trackIds 352 + |> Json.Encode.list Json.Encode.string 353 + |> Alien.broadcast Alien.RemoveTracksFromCache 354 + |> Ports.toBrain 355 + |> return { model | cachedTracks = List.without trackIds model.cachedTracks } 356 + |> andThen harvest 357 + |> andThen User.saveEnclosedUserData 286 358 287 359 288 360 reviseCollection : (Parcel -> Parcel) -> Manager ··· 302 374 term 303 375 |> String.trim 304 376 |> Json.Encode.string 305 - |> UI.Ports.giveBrain Alien.SearchTracks 377 + |> Ports.giveBrain Alien.SearchTracks 306 378 |> return model 307 379 308 380 ( Nothing, Just _ ) -> ··· 439 511 |> andThen User.saveEnclosedUserData 440 512 441 513 514 + storeInCache : List Track -> Manager 515 + storeInCache tracks model = 516 + let 517 + trackIds = 518 + List.map .id tracks 519 + 520 + notification = 521 + case tracks of 522 + [ t ] -> 523 + ("__" ++ t.tags.title ++ "__ will be stored in the cache") 524 + |> Notifications.success 525 + 526 + list -> 527 + list 528 + |> List.length 529 + |> String.fromInt 530 + |> (\s -> "__" ++ s ++ " tracks__ will be stored in the cache") 531 + |> Notifications.success 532 + in 533 + tracks 534 + |> Json.Encode.list 535 + (\track -> 536 + Json.Encode.object 537 + [ ( "trackId" 538 + , Json.Encode.string track.id 539 + ) 540 + , ( "url" 541 + , track 542 + |> Queue.makeTrackUrl 543 + model.currentTime 544 + model.sources 545 + |> Json.Encode.string 546 + ) 547 + ] 548 + ) 549 + |> Alien.broadcast Alien.StoreTracksInCache 550 + |> Ports.toBrain 551 + |> return { model | cachingTracksInProgress = model.cachingTracksInProgress ++ trackIds } 552 + |> andThen (Common.showNotification notification) 553 + 554 + 442 555 toggleCachedOnly : Manager 443 556 toggleCachedOnly model = 444 557 { model | cachedTracksOnly = not model.cachedTracksOnly } ··· 463 576 in 464 577 { model | favourites = newFavourites } 465 578 |> reviseCollection effect 466 - |> andThen (Return.performance <| Reply Reply.SaveFavourites) 579 + |> andThen User.saveFavourites 467 580 468 581 Nothing -> 469 582 Return.singleton model ··· 480 593 toggleHideDuplicates model = 481 594 { model | hideDuplicates = not model.hideDuplicates } 482 595 |> reviseCollection Collection.arrange 483 - |> andThen Settings.save 596 + |> andThen User.saveSettings 484 597 485 598 486 599
+8 -1
src/Applications/UI/Tracks/Types.elm
··· 20 20 21 21 22 22 type Msg 23 - = Harvest 23 + = Download String (List Track) 24 + | Harvest 24 25 | MarkAsSelected Int { shiftKey : Bool } 25 26 | ScrollToNowPlaying 26 27 | ToggleCachedOnly 27 28 | ToggleFavouritesOnly 28 29 | ToggleHideDuplicates 30 + ----------------------------------------- 31 + -- Cache 32 + ----------------------------------------- 33 + | ClearCache 34 + | RemoveFromCache (List Track) 35 + | StoreInCache (List Track) 29 36 ----------------------------------------- 30 37 -- Collection 31 38 -----------------------------------------
+1 -3
src/Applications/UI/Tracks/View.elm
··· 19 19 import Material.Icons.Types exposing (Coloring(..)) 20 20 import Maybe.Extra as Maybe 21 21 import Playlists exposing (Playlist) 22 - import Return3 as Return exposing (..) 23 22 import Sources 24 23 import Tracks exposing (..) 25 24 import Tracks.Collection as Collection exposing (..) ··· 28 27 import UI.Page 29 28 import UI.Playlists.Page 30 29 import UI.Queue.Page 31 - import UI.Reply as UI exposing (Reply(..)) 32 30 import UI.Sources.Page as Sources 33 31 import UI.Tracks.Scene.List 34 32 import UI.Tracks.Types as Tracks exposing (..) ··· 312 310 [ UI.Kit.buttonWithColor 313 311 UI.Kit.Gray 314 312 UI.Kit.Normal 315 - (Reply InsertDemo) 313 + InsertDemo 316 314 (buttonContents 317 315 [ UI.Kit.inlineIcon Icons.music_note 318 316 , text "Insert demo"
+18 -9
src/Applications/UI/Types.elm
··· 32 32 import Tracks.Encoding as Tracks 33 33 import UI.Authentication.Types as Authentication 34 34 import UI.DnD as DnD 35 - import UI.Notifications 36 35 import UI.Page as Page exposing (Page) 37 36 import UI.Queue.Types as Queue 38 - import UI.Reply as Reply exposing (Reply(..)) 39 - import UI.Sources.ContextMenu as Sources 40 37 import UI.Sources.Types as Sources 41 - import UI.Tracks.ContextMenu as Tracks 42 38 import UI.Tracks.Types as Tracks exposing (Scene) 43 39 import Url exposing (Protocol(..), Url) 44 40 import User.Layer exposing (..) ··· 116 112 -- Instances 117 113 ----------------------------------------- 118 114 , alfred : Maybe (Alfred Msg) 119 - , contextMenu : Maybe (ContextMenu Reply) 120 - , notifications : UI.Notifications.Model 115 + , contextMenu : Maybe (ContextMenu Msg) 116 + , notifications : List (Notification Msg) 121 117 122 118 ----------------------------------------- 123 119 -- Playlists ··· 187 183 188 184 type Msg 189 185 = Bypass 190 - | Reply Reply 191 186 ----------------------------------------- 192 187 -- Alfred 193 188 ----------------------------------------- ··· 198 193 -- Audio 199 194 ----------------------------------------- 200 195 | NoteProgress { trackId : String, progress : Float } 196 + | Seek Float 201 197 | SetAudioDuration Float 202 198 | SetAudioHasStalled Bool 203 199 | SetAudioIsLoading Bool ··· 205 201 | SetAudioPosition Float 206 202 | Stop 207 203 | TogglePlay 204 + | ToggleRememberProgress 208 205 ----------------------------------------- 209 206 -- Authentication (TODO: Move to Auth.Types) 210 207 ----------------------------------------- ··· 229 226 -- Interface 230 227 ----------------------------------------- 231 228 | Blur 229 + | ContextMenuConfirmation String Msg 230 + | CopyToClipboard String 232 231 | Debounce (Debouncer.Msg Msg) 232 + | DismissNotification { id : Int } 233 233 | DnD (DnD.Msg Int) 234 234 | FocusedOnInput 235 235 | HideOverlay 236 + | MsgViaContextMenu Msg 236 237 | PreferredColorSchemaChanged { dark : Bool } 238 + | RemoveNotification { id : Int } 237 239 | RemoveQueueSelection 238 240 | RemoveTrackSelection 239 241 | ResizedWindow ( Int, Int ) 240 - | ShowNotification (Notification Reply) 242 + | ShowNotification (Notification Msg) 241 243 | SetIsTouchDevice Bool 242 244 | StoppedDragging 243 245 | ToggleLoadingScreen Switch ··· 252 254 | DeselectPlaylist 253 255 | ModifyPlaylist 254 256 | MoveTrackInSelectedPlaylist { to : Int } 255 - | SelectPlaylist Playlist 257 + | RemoveTracksFromPlaylist Playlist (List IdentifiedTrack) 258 + | RequestAssistanceForPlaylists (List IdentifiedTrack) 256 259 | SetPlaylistCreationContext String 257 260 | SetPlaylistModificationContext String String 258 261 | ShowPlaylistListMenu Playlist Mouse.Event ··· 266 269 ----------------------------------------- 267 270 -- Services 268 271 ----------------------------------------- 272 + | ConnectLastFm 273 + | DisconnectLastFm 269 274 | GotLastFmSession (Result Http.Error String) 270 275 | Scrobble { duration : Int, timestamp : Int, trackId : String } 271 276 ----------------------------------------- ··· 277 282 ----------------------------------------- 278 283 -- User 279 284 ----------------------------------------- 285 + | Export 280 286 | ImportFile File 281 287 | ImportJson String 288 + | ImportLegacyData 289 + | InsertDemo 282 290 | LoadEnclosedUserData Json.Decode.Value 283 291 | LoadHypaethralUserData Json.Decode.Value 292 + | RequestImport 284 293 | SaveEnclosedUserData 285 294 ----------------------------------------- 286 295 -- ⚗️ Adjunct
+88 -1
src/Applications/UI/User/State/Export.elm
··· 1 1 module UI.User.State.Export exposing (..) 2 2 3 3 import Alien 4 - import Return 4 + import File.Download 5 + import Json.Encode 6 + import List.Extra as List 7 + import Playlists.Encoding as Playlists 8 + import Return exposing (return) 9 + import Settings exposing (Settings) 10 + import Sources.Encoding as Sources 11 + import Tracks.Encoding as Tracks 5 12 import UI.Ports as Ports 6 13 import UI.Types as UI exposing (..) 7 14 import User.Layer exposing (..) ··· 11 18 -- 🔱 12 19 13 20 21 + export model = 22 + { favourites = model.favourites 23 + , playlists = List.filterNot .autoGenerated model.playlists 24 + , progress = model.progress 25 + , settings = Just (gatherSettings model) 26 + , sources = model.sources 27 + , tracks = model.tracks.untouched 28 + } 29 + |> encodeHypaethralData 30 + |> Json.Encode.encode 2 31 + |> File.Download.string "diffuse.json" "application/json" 32 + |> return model 33 + 34 + 35 + gatherSettings : Model -> Settings 36 + gatherSettings { chosenBackdrop, hideDuplicates, lastFm, processAutomatically, rememberProgress } = 37 + { backgroundImage = chosenBackdrop 38 + , hideDuplicates = hideDuplicates 39 + , lastFm = lastFm.sessionKey 40 + , processAutomatically = processAutomatically 41 + , rememberProgress = rememberProgress 42 + } 43 + 44 + 14 45 saveEnclosedUserData : Manager 15 46 saveEnclosedUserData model = 16 47 { cachedTracks = model.cachedTracks ··· 29 60 |> Alien.broadcast Alien.SaveEnclosedUserData 30 61 |> Ports.toBrain 31 62 |> Return.return model 63 + 64 + 65 + saveFavourites : Manager 66 + saveFavourites model = 67 + model.favourites 68 + |> Json.Encode.list Tracks.encodeFavourite 69 + |> Alien.broadcast Alien.SaveFavourites 70 + |> Ports.toBrain 71 + |> return model 72 + 73 + 74 + savePlaylists : Manager 75 + savePlaylists model = 76 + model.playlists 77 + |> List.filterNot .autoGenerated 78 + |> Json.Encode.list Playlists.encode 79 + |> Alien.broadcast Alien.SavePlaylists 80 + |> Ports.toBrain 81 + |> return model 82 + 83 + 84 + saveProgress : Manager 85 + saveProgress model = 86 + model.progress 87 + |> Json.Encode.dict identity Json.Encode.float 88 + |> Alien.broadcast Alien.SaveProgress 89 + |> Ports.toBrain 90 + |> return model 91 + 92 + 93 + saveSettings : Manager 94 + saveSettings model = 95 + model 96 + |> gatherSettings 97 + |> Settings.encode 98 + |> Alien.broadcast Alien.SaveSettings 99 + |> Ports.toBrain 100 + |> return model 101 + 102 + 103 + saveSources : Manager 104 + saveSources model = 105 + model.sources 106 + |> Json.Encode.list Sources.encode 107 + |> Alien.broadcast Alien.SaveSources 108 + |> Ports.toBrain 109 + |> Return.return model 110 + 111 + 112 + saveTracks : Manager 113 + saveTracks model = 114 + model.tracks.untouched 115 + |> Json.Encode.list Tracks.encodeTrack 116 + |> Alien.broadcast Alien.SaveTracks 117 + |> Ports.toBrain 118 + |> return model
+67 -13
src/Applications/UI/User/State/Import.elm
··· 1 1 module UI.User.State.Import exposing (..) 2 2 3 + import Alien 3 4 import File exposing (File) 5 + import File.Select 4 6 import Json.Decode 5 7 import Json.Encode 6 8 import LastFm ··· 10 12 import Process 11 13 import Return exposing (andThen, return) 12 14 import Return.Ext as Return exposing (communicate) 13 - import Return3 14 15 import Task 15 16 import UI.Backdrop as Backdrop 16 17 import UI.Common.State as Common exposing (showNotification) 18 + import UI.Demo as Demo 17 19 import UI.Equalizer.State as Equalizer 18 20 import UI.Page as Page 19 21 import UI.Playlists.Directory 20 22 import UI.Ports as Ports 21 - import UI.Reply exposing (..) 22 - import UI.Reply.Translate as Reply 23 23 import UI.Sources.State as Sources 24 24 import UI.Tracks.State as Tracks 25 25 import UI.Types as UI exposing (..) 26 + import UI.User.State.Export as User 26 27 import Url.Ext as Url 27 28 import User.Layer exposing (..) 28 29 ··· 54 55 |> showNotification 55 56 ) 56 57 -- Clear tracks cache 57 - |> andThen (Reply.translate ClearTracksCache) 58 + |> andThen Tracks.clearCache 58 59 -- Redirect to index page 59 60 |> andThen (Common.changeUrlUsingPage Page.Index) 60 61 ----------------------------- 61 62 -- Save all the imported data 62 63 ----------------------------- 63 - |> Reply.saveAllHypaethralData 64 + |> saveAllHypaethralData 64 65 65 66 66 - loadEnclosedUserData : Json.Decode.Value -> Manager 67 - loadEnclosedUserData json model = 67 + importLegacyData : Manager 68 + importLegacyData model = 69 + Alien.ImportLegacyData 70 + |> Alien.trigger 71 + |> Ports.toBrain 72 + |> return model 73 + |> andThen 74 + (""" 75 + I'll try to import data from Diffuse version one. 76 + If this was successful, you'll get a notification. 77 + """ 78 + |> Notifications.warning 79 + |> Common.showNotification 80 + ) 81 + 82 + 83 + insertDemo : Manager 84 + insertDemo model = 68 85 model 69 - |> importEnclosed json 70 - |> Return3.wield Reply.translate 86 + |> loadHypaethralUserData (Demo.tape model.currentTime) 87 + |> saveAllHypaethralData 88 + 89 + 90 + loadEnclosedUserData : Json.Decode.Value -> Manager 91 + loadEnclosedUserData = 92 + importEnclosed 71 93 72 94 73 95 loadHypaethralUserData : Json.Decode.Value -> Manager ··· 111 133 ) 112 134 113 135 136 + requestImport : Manager 137 + requestImport model = 138 + ImportFile 139 + |> File.Select.file [ "application/json" ] 140 + |> return model 141 + 142 + 114 143 115 144 -- ⚗️ ░░ HYPAETHRAL DATA 116 145 ··· 160 189 |> Common.showNotificationWithModel model 161 190 162 191 192 + saveAllHypaethralData : ( Model, Cmd Msg ) -> ( Model, Cmd Msg ) 193 + saveAllHypaethralData return = 194 + List.foldl 195 + (\( _, bit ) -> 196 + case bit of 197 + Favourites -> 198 + andThen User.saveFavourites 199 + 200 + Playlists -> 201 + andThen User.savePlaylists 202 + 203 + Progress -> 204 + andThen User.saveProgress 205 + 206 + Settings -> 207 + andThen User.saveSettings 208 + 209 + Sources -> 210 + andThen User.saveSources 211 + 212 + Tracks -> 213 + andThen User.saveTracks 214 + ) 215 + return 216 + hypaethralBit.list 217 + 218 + 163 219 164 220 -- ⚗️ ░░ ENCLOSED DATA 165 221 166 222 167 - importEnclosed : Json.Decode.Value -> Model -> Return3.Return Model Msg Reply 223 + importEnclosed : Json.Decode.Value -> Manager 168 224 importEnclosed value model = 169 225 let 170 226 equalizerSettings = ··· 201 257 [ Equalizer.adjustAllKnobs newEqualizerSettings 202 258 , Ports.setRepeat data.repeat 203 259 ] 204 - -- 205 - , [] 206 260 ) 207 261 208 262 Err err -> 209 - Return3.return model 263 + Return.singleton model
+23 -31
src/Applications/UI/View.elm
··· 38 38 import UI.Playlists.ContextMenu as Playlists 39 39 import UI.Playlists.View as Playlists 40 40 import UI.Queue.View as Queue 41 - import UI.Reply exposing (Reply(..)) 42 41 import UI.Settings as Settings 43 42 import UI.Settings.Page 44 43 import UI.Sources.ContextMenu as Sources ··· 104 103 ----------------------------------------- 105 104 -- Context Menu 106 105 ----------------------------------------- 107 - , model.contextMenu 108 - |> Lazy.lazy UI.ContextMenu.view 109 - |> Html.map Reply 106 + , Lazy.lazy UI.ContextMenu.view model.contextMenu 110 107 111 108 ----------------------------------------- 112 109 -- Notifications 113 110 ----------------------------------------- 114 - , model.notifications 115 - |> Lazy.lazy UI.Notifications.view 116 - |> Html.map Reply 111 + , Lazy.lazy UI.Notifications.view model.notifications 117 112 118 113 ----------------------------------------- 119 114 -- Overlay 120 115 ----------------------------------------- 121 - , model.contextMenu 122 - |> Lazy.lazy2 overlay model.alfred 116 + , Lazy.lazy2 overlay model.alfred model.contextMenu 123 117 124 118 ----------------------------------------- 125 119 -- Content ··· 191 185 Queue.view subPage model 192 186 193 187 Page.Settings subPage -> 194 - { authenticationMethod = Authentication.extractMethod model.authentication 195 - , chosenBackgroundImage = model.chosenBackdrop 196 - , hideDuplicateTracks = model.hideDuplicates 197 - , lastFm = model.lastFm 198 - , processAutomatically = model.processAutomatically 199 - , rememberProgress = model.rememberProgress 200 - } 201 - |> Lazy.lazy2 Settings.view subPage 202 - |> Html.map Reply 188 + Lazy.lazy2 Settings.view 189 + subPage 190 + { authenticationMethod = Authentication.extractMethod model.authentication 191 + , chosenBackgroundImage = model.chosenBackdrop 192 + , hideDuplicateTracks = model.hideDuplicates 193 + , lastFm = model.lastFm 194 + , processAutomatically = model.processAutomatically 195 + , rememberProgress = model.rememberProgress 196 + } 203 197 204 198 Page.Sources subPage -> 205 199 Sources.view subPage model ··· 208 202 ----------------------------------------- 209 203 -- Controls 210 204 ----------------------------------------- 211 - , Html.map Reply 212 - (UI.Console.view 213 - model.nowPlaying 214 - model.repeat 215 - model.shuffle 216 - { stalled = model.audioHasStalled 217 - , loading = model.audioIsLoading 218 - , playing = model.audioIsPlaying 219 - } 220 - ( model.audioPosition 221 - , model.audioDuration 222 - ) 205 + , UI.Console.view 206 + model.nowPlaying 207 + model.repeat 208 + model.shuffle 209 + { stalled = model.audioHasStalled 210 + , loading = model.audioIsLoading 211 + , playing = model.audioIsPlaying 212 + } 213 + ( model.audioPosition 214 + , model.audioDuration 223 215 ) 224 216 ] 225 217 ··· 286 278 Html.map never UI.Svg.Elements.loading 287 279 288 280 289 - overlay : Maybe (Alfred Msg) -> Maybe (ContextMenu Reply) -> Html Msg 281 + overlay : Maybe (Alfred Msg) -> Maybe (ContextMenu Msg) -> Html Msg 290 282 overlay maybeAlfred maybeContextMenu = 291 283 let 292 284 isShown =
+6 -1
src/Library/Coordinates.elm
··· 1 - module Coordinates exposing (Coordinates, Viewport, fromTuple) 1 + module Coordinates exposing (..) 2 2 3 3 -- 🌳 4 4 ··· 22 22 { x = x 23 23 , y = y 24 24 } 25 + 26 + 27 + toTuple : Coordinates -> ( Float, Float ) 28 + toTuple { x, y } = 29 + ( x, y )
-14
src/Library/Return/Ext.elm
··· 12 12 ( m, c ) 13 13 14 14 15 - {-| TODO: Remove when finished with refactor 16 - -} 17 - performance : msg -> model -> ( model, Cmd msg ) 18 - performance msg model = 19 - ( model, task msg ) 20 - 21 - 22 - {-| TODO: Remove when finished with refactor 23 - -} 24 - performanceF : model -> msg -> ( model, Cmd msg ) 25 - performanceF model msg = 26 - performance msg model 27 - 28 - 29 15 task : msg -> Cmd msg 30 16 task msg = 31 17 msg
+1 -1
src/Library/User/Layer.elm
··· 1 - module User.Layer exposing (EnclosedData, HypaethralBit(..), HypaethralData, Method(..), decodeEnclosedData, decodeHypaethralData, decodeMethod, emptyHypaethralData, enclosedDataDecoder, encodeEnclosedData, encodeHypaethralBit, encodeHypaethralData, encodeMethod, hypaethralBit, hypaethralBitFileName, hypaethralBitKey, hypaethralDataDecoder, methodFromString, methodSeparator, methodToString, putHypaethralJsonBitsTogether) 1 + module User.Layer exposing (..) 2 2 3 3 {-| User Layer. 4 4