diff --git a/src/Applications/Brain.elm b/src/Applications/Brain.elm index cee3bcd8..19c5fdf5 100644 --- a/src/Applications/Brain.elm +++ b/src/Applications/Brain.elm @@ -12,7 +12,7 @@ import Brain.Tracks as Tracks import Json.Decode as Json import Json.Decode.Pipeline exposing (optional) import Json.Encode -import Replying exposing (andThen, return) +import Replying exposing (andThen2, return) import Sources.Encoding as Sources import Sources.Processing.Encoding as Processing import Tracks.Encoding as Tracks @@ -112,7 +112,7 @@ update msg model = encodedTracks = Json.Encode.list Tracks.encodeTrack decodedData.tracks in - andThen + andThen2 (updateSearchIndex encodedTracks) ( { model | hypaethralUserData = decodedData } , Brain.Ports.toUI (Alien.broadcast Alien.LoadHypaethralUserData value) @@ -138,7 +138,7 @@ update msg model = |> Result.withDefault model.hypaethralUserData.tracks |> hypaethralLenses.setTracks model |> updateSearchIndex value - |> andThen saveHypaethralData + |> andThen2 saveHypaethralData updateSearchIndex : Json.Value -> Model -> ( Model, Cmd Msg ) diff --git a/src/Applications/UI.elm b/src/Applications/UI.elm index d6180123..2fee0d58 100644 --- a/src/Applications/UI.elm +++ b/src/Applications/UI.elm @@ -7,7 +7,7 @@ import Browser.Navigation as Nav import Chunky exposing (..) import Color import Color.Ext as Color -import Common +import Common exposing (Switch(..)) import Css exposing (url) import Css.Global import File @@ -18,7 +18,7 @@ import Html.Styled.Attributes exposing (id, style) import Html.Styled.Lazy as Lazy import Json.Decode import Json.Encode -import Replying exposing (andThen, do, return) +import Replying as N5 exposing (do, return) import Return2 as R2 import Sources import Sources.Encoding @@ -29,7 +29,7 @@ import Tracks.Encoding import UI.Authentication as Authentication import UI.Backdrop as Backdrop import UI.Console -import UI.Core as Core exposing (Flags, Model, Msg(..), Switch(..)) +import UI.Core as Core exposing (Flags, Model, Msg(..)) import UI.Kit import UI.Navigation as Navigation import UI.Page as Page @@ -117,12 +117,12 @@ update msg model = LoadEnclosedUserData json -> model |> UserData.importEnclosed json - |> Replying.reducto update translateReply + |> N5.reducto update translateReply LoadHypaethralUserData json -> - { model | isAuthenticated = True, isLoading = False } + { model | isAuthenticated = True } |> UserData.importHypaethral json - |> Replying.reducto update translateReply + |> N5.reducto update translateReply SetCurrentTime time -> let @@ -136,10 +136,10 @@ update msg model = , Cmd.none ) - ToggleLoadingScreen On -> + Core.ToggleLoadingScreen On -> R2.withNoCmd { model | isLoading = True } - ToggleLoadingScreen Off -> + Core.ToggleLoadingScreen Off -> R2.withNoCmd { model | isLoading = False } ----------------------------------------- @@ -357,9 +357,9 @@ update msg model = |> Result.withDefault Json.Encode.null |> LoadHypaethralUserData ) - |> andThen (update Core.SaveFavourites) - |> andThen (update Core.SaveSources) - |> andThen (update Core.SaveTracks) + |> N5.andThen2 (update Core.SaveFavourites) + |> N5.andThen2 (update Core.SaveSources) + |> N5.andThen2 (update Core.SaveTracks) -- TODO: -- Show notication relating to import |> R2.addCmd (do <| ChangeUrlUsingPage Page.Index) @@ -447,9 +447,12 @@ translateReply reply = Reply.SaveTracks -> Core.SaveTracks + Reply.ToggleLoadingScreen state -> + Core.ToggleLoadingScreen state + updateChild = - Replying.updateChild update translateReply + N5.updateChild update translateReply @@ -489,7 +492,7 @@ translateAlienEvent event = SourcesMsg Sources.FinishedProcessing Just Alien.HideLoadingScreen -> - ToggleLoadingScreen Off + Core.ToggleLoadingScreen Off Just Alien.LoadEnclosedUserData -> LoadEnclosedUserData event.data diff --git a/src/Applications/UI/Core.elm b/src/Applications/UI/Core.elm index 5ac78352..b073c7e1 100644 --- a/src/Applications/UI/Core.elm +++ b/src/Applications/UI/Core.elm @@ -1,8 +1,9 @@ -module UI.Core exposing (Flags, Model, Msg(..), Switch(..)) +module UI.Core exposing (Flags, Model, Msg(..)) import Alien import Browser import Browser.Navigation as Nav +import Common exposing (Switch(..)) import File exposing (File) import Json.Encode as Json import Queue @@ -120,8 +121,3 @@ type Msg | ChangeUrlUsingPage Page | LinkClicked Browser.UrlRequest | UrlChanged Url - - -type Switch - = On - | Off diff --git a/src/Applications/UI/Reply.elm b/src/Applications/UI/Reply.elm index f637ddf6..4c8057df 100644 --- a/src/Applications/UI/Reply.elm +++ b/src/Applications/UI/Reply.elm @@ -1,6 +1,7 @@ module UI.Reply exposing (Reply(..)) import Alien +import Common exposing (Switch(..)) import Json.Decode as Json import Queue import Sources exposing (Source) @@ -27,3 +28,4 @@ type Reply | SaveFavourites | SaveSources | SaveTracks + | ToggleLoadingScreen Switch diff --git a/src/Applications/UI/Tracks.elm b/src/Applications/UI/Tracks.elm index 7528dbcf..016f0381 100644 --- a/src/Applications/UI/Tracks.elm +++ b/src/Applications/UI/Tracks.elm @@ -4,6 +4,7 @@ import Alien import Chunky exposing (..) import Color import Color.Ext as Color +import Common exposing (Switch(..)) import Css import Html.Styled as Html exposing (Html, text) import Html.Styled.Attributes exposing (css, placeholder, tabindex, title, value) @@ -18,7 +19,8 @@ import Material.Icons.Av as Icons import Material.Icons.Content as Icons import Material.Icons.Editor as Icons import Maybe.Extra as Maybe -import Replying exposing (R3D3) +import Replying as N5 exposing (R3D3) +import Return2 as R2 import Return3 as R3 import Tachyons.Classes as T import Tracks exposing (..) @@ -89,7 +91,7 @@ update msg model = in { model | sortBy = property, sortDirection = sortDir } |> reviseCollection arrange - |> R3.mapReply (Maybe.map <| (::) SaveEnclosedUserData) + |> N5.addReply SaveEnclosedUserData ----------------------------------------- -- Collection @@ -135,17 +137,17 @@ update msg model = -- Favourites ----------------------------------------- ToggleFavouritesOnly -> - reviseCollection - harvest - { model | favouritesOnly = not model.favouritesOnly } + { model | favouritesOnly = not model.favouritesOnly } + |> reviseCollection harvest + |> N5.addReply SaveEnclosedUserData ----------------------------------------- -- Search ----------------------------------------- ClearSearch -> - reviseCollection - harvest - { model | searchResults = Nothing, searchTerm = Nothing } + { model | searchResults = Nothing, searchTerm = Nothing } + |> reviseCollection harvest + |> N5.addReply SaveEnclosedUserData Search -> case ( model.searchTerm, model.searchResults ) of @@ -162,22 +164,22 @@ update msg model = R3.withNothing model SetSearchResults json -> - reviseCollection - harvest - (json - |> Json.decodeValue (Json.list Json.string) - |> Result.withDefault [] - |> (\results -> { model | searchResults = Just results }) - ) + json + |> Json.decodeValue (Json.list Json.string) + |> Result.withDefault [] + |> (\results -> { model | searchResults = Just results }) + |> reviseCollection harvest + |> N5.addReply (ToggleLoadingScreen Off) SetSearchTerm term -> - R3.withNothing + R3.withReply + [ SaveEnclosedUserData ] (case String.trim term of "" -> - { model | searchTerm = Nothing } + R2.withNoCmd { model | searchTerm = Nothing } _ -> - { model | searchTerm = Just term } + R2.withNoCmd { model | searchTerm = Just term } ) diff --git a/src/Applications/UI/UserData.elm b/src/Applications/UI/UserData.elm index 1abd8d51..eb147d26 100644 --- a/src/Applications/UI/UserData.elm +++ b/src/Applications/UI/UserData.elm @@ -4,7 +4,7 @@ import Authentication exposing (..) import Json.Decode as Json import Json.Decode.Pipeline exposing (..) import Json.Encode -import Replying exposing (R3D3) +import Replying as N5 exposing (R3D3) import Return3 as R3 import Sources import Sources.Encoding as Sources @@ -95,6 +95,7 @@ importTracks model data = |> Tracks.makeParcel |> Tracks.identify |> Tracks.resolveParcel adjustedModel + |> N5.andThen3 (Tracks.update Tracks.Search) @@ -105,7 +106,9 @@ exportEnclosed : UI.Core.Model -> Json.Value exportEnclosed model = encodeEnclosed { backgroundImage = model.backdrop.chosen + , onlyShowFavourites = model.tracks.favouritesOnly , repeat = model.queue.repeat + , searchTerm = model.tracks.searchTerm , shuffle = model.queue.shuffle , sortBy = model.tracks.sortBy , sortDirection = model.tracks.sortDirection @@ -128,7 +131,7 @@ importEnclosed value model = { queue | repeat = data.repeat, shuffle = data.shuffle } newTracks = - { tracks | sortBy = data.sortBy, sortDirection = data.sortDirection } + { tracks | favouritesOnly = data.onlyShowFavourites, searchTerm = data.searchTerm, sortBy = data.sortBy, sortDirection = data.sortDirection } in R3.withNothing { model diff --git a/src/Library/Authentication.elm b/src/Library/Authentication.elm index be99dbe6..3adaf9b2 100644 --- a/src/Library/Authentication.elm +++ b/src/Library/Authentication.elm @@ -22,7 +22,9 @@ type Method type alias EnclosedUserData = { backgroundImage : Maybe String + , onlyShowFavourites : Bool , repeat : Bool + , searchTerm : Maybe String , shuffle : Bool , sortBy : Tracks.SortBy , sortDirection : Tracks.SortDirection @@ -86,17 +88,21 @@ enclosedDecoder : Json.Decoder EnclosedUserData enclosedDecoder = Json.succeed EnclosedUserData |> required "backgroundImage" (Json.maybe Json.string) + |> optional "onlyShowFavourites" Json.bool False |> optional "repeat" Json.bool False + |> required "searchTerm" (Json.maybe Json.string) |> optional "shuffle" Json.bool False |> optional "sortBy" Tracks.sortByDecoder Tracks.Artist |> optional "sortDirection" Tracks.sortDirectionDecoder Tracks.Asc encodeEnclosed : EnclosedUserData -> Json.Value -encodeEnclosed { backgroundImage, repeat, shuffle, sortBy, sortDirection } = +encodeEnclosed { backgroundImage, onlyShowFavourites, repeat, searchTerm, shuffle, sortBy, sortDirection } = Json.Encode.object - [ ( "backgroundImage", Json.Encode.string (Maybe.withDefault "" backgroundImage) ) + [ ( "backgroundImage", Maybe.unwrap Json.Encode.null Json.Encode.string backgroundImage ) + , ( "onlyShowFavourites", Json.Encode.bool onlyShowFavourites ) , ( "repeat", Json.Encode.bool repeat ) + , ( "searchTerm", Maybe.unwrap Json.Encode.null Json.Encode.string searchTerm ) , ( "shuffle", Json.Encode.bool shuffle ) , ( "sortBy", Tracks.encodeSortBy sortBy ) , ( "sortDirection", Tracks.encodeSortDirection sortDirection ) diff --git a/src/Library/Common.elm b/src/Library/Common.elm index a41ca2d0..35ae3e6b 100644 --- a/src/Library/Common.elm +++ b/src/Library/Common.elm @@ -1,9 +1,18 @@ -module Common exposing (urlOrigin) +module Common exposing (Switch(..), urlOrigin) import Url exposing (Protocol(..), Url) +-- 🌳 + + +type Switch + = On + | Off + + + -- 🔱 diff --git a/src/Library/Replying.elm b/src/Library/Replying.elm index 5d35108b..cb7e6c6e 100644 --- a/src/Library/Replying.elm +++ b/src/Library/Replying.elm @@ -1,4 +1,4 @@ -module Replying exposing (R3D3, Updator, andThen, do, reducto, return, updateChild) +module Replying exposing (R3D3, Updator, addReply, andThen2, andThen3, do, reducto, return, updateChild) import Return2 import Return3 @@ -21,6 +21,68 @@ type alias Updator msg model = -- 🔱 +{-| Add a reply. +-} +addReply : reply -> R3D3 model msg reply -> R3D3 model msg reply +addReply reply ( model, cmd, maybeReplies ) = + ( model + , cmd + , maybeReplies + |> Maybe.map (\l -> l ++ [ reply ]) + |> Maybe.withDefault [ reply ] + |> Just + ) + + +{-| Add a list of replies in front of the existing replies. +-} +addRepliesInFront : List reply -> R3D3 model msg reply -> R3D3 model msg reply +addRepliesInFront replies ( model, cmd, maybeReplies ) = + ( model + , cmd + , maybeReplies + |> Maybe.map (\l -> replies ++ l) + |> Maybe.withDefault replies + |> Just + ) + + +{-| Chain R2D2 `update` calls. +-} +andThen2 : (model -> ( model, Cmd msg )) -> ( model, Cmd msg ) -> ( model, Cmd msg ) +andThen2 fn ( model, cmd ) = + Return2.addCmd cmd (fn model) + + +{-| Chain R3D3 `update` calls. +-} +andThen3 : (model -> R3D3 model msg reply) -> R3D3 model msg reply -> R3D3 model msg reply +andThen3 fn ( model, cmd, maybeReplies ) = + case maybeReplies of + Just replies -> + Return3.addCmd cmd (fn model) |> addRepliesInFront replies + + Nothing -> + Return3.addCmd cmd (fn model) + + +{-| Reduce a `R3D3` to a `R2D2`. +-} +reducto : Updator msg model -> (reply -> msg) -> R3D3 model msg reply -> ( model, Cmd msg ) +reducto updator translator ( model, cmd, maybeReplies ) = + maybeReplies + |> Maybe.withDefault [] + |> List.map translator + |> List.foldl (andThenUpdate updator) ( model, cmd ) + + +{-| Convenience function for returning the standard ( model, Cmd msg ) tuple. +-} +return : model -> Cmd msg -> ( model, Cmd msg ) +return model msg = + ( model, msg ) + + {-| Handle the state of a child. NOTE: Replies are performed from left to right. @@ -44,32 +106,6 @@ updateChild update translateReply context data = |> reducto update translateReply -{-| Convenience function for returning the standard ( model, Cmd msg ) tuple. --} -return : model -> Cmd msg -> ( model, Cmd msg ) -return model msg = - ( model, msg ) - - -{-| Chain `update` calls. --} -andThen : (model -> ( model, Cmd msg )) -> ( model, Cmd msg ) -> ( model, Cmd msg ) -andThen fn ( model, cmd ) = - model - |> fn - |> Return2.addCmd cmd - - -{-| Reduce a `R3D3` to a `R2D2`. --} -reducto : Updator msg model -> (reply -> msg) -> R3D3 model msg reply -> ( model, Cmd msg ) -reducto updator translator ( model, cmd, maybeReplies ) = - maybeReplies - |> Maybe.withDefault [] - |> List.map translator - |> List.foldl (andThenUpdate updator) ( model, cmd ) - - -- 🔱 ░░ TASKS