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

Configure Feed

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

Refactor Return types and handling of nested state

+904 -939
+1 -2
elm.json
··· 7 7 "elm-version": "0.19.0", 8 8 "dependencies": { 9 9 "direct": { 10 - "Chadtech/return": "1.0.2", 11 10 "FabienHenon/elm-infinite-list-view": "3.0.0", 12 11 "Gizra/elm-debouncer": "2.0.0", 13 12 "NoRedInk/elm-json-decode-pipeline": "1.0.0", ··· 51 50 "direct": {}, 52 51 "indirect": {} 53 52 } 54 - } 53 + }
+64 -69
src/Applications/Brain.elm
··· 13 13 import Json.Decode as Json 14 14 import Json.Decode.Pipeline exposing (optional) 15 15 import Json.Encode 16 - import Replying exposing (andThen2, return) 16 + import Return2 exposing (..) 17 + import Return3 17 18 import Sources.Encoding as Sources 18 19 import Sources.Processing.Encoding as Processing 19 20 import Tracks.Encoding as Tracks ··· 71 72 update msg model = 72 73 case msg of 73 74 Bypass -> 74 - ( model 75 - , Cmd.none 76 - ) 75 + return model 77 76 78 77 NotifyUI alienEvent -> 79 - ( model 80 - , Cmd.batch 81 - [ Brain.Ports.toUI alienEvent 78 + [ Brain.Ports.toUI alienEvent 82 79 83 - -- Sometimes the loading screen is still showing, 84 - -- so we hide it here just in case. 85 - , case alienEvent.error of 86 - Just _ -> 87 - Brain.Ports.toUI (Alien.trigger Alien.HideLoadingScreen) 80 + -- Sometimes the loading screen is still showing, 81 + -- so we hide it here just in case. 82 + , case alienEvent.error of 83 + Just _ -> 84 + Brain.Ports.toUI (Alien.trigger Alien.HideLoadingScreen) 88 85 89 - Nothing -> 90 - Cmd.none 91 - ] 92 - ) 86 + Nothing -> 87 + Cmd.none 88 + ] 89 + |> Cmd.batch 90 + |> returnWithModel model 93 91 94 92 NotSoFast debouncerMsg -> 95 - let 96 - ( subModel, subCmd, emittedMsg ) = 97 - Debouncer.update debouncerMsg model.notSoFast 98 - 99 - mappedCmd = 100 - Cmd.map NotSoFast subCmd 101 - 102 - updatedModel = 103 - { model | notSoFast = subModel } 104 - in 105 - case emittedMsg of 106 - Just emitted -> 107 - updatedModel 108 - |> update emitted 109 - |> Tuple.mapSecond (\cmd -> Cmd.batch [ cmd, mappedCmd ]) 110 - 111 - Nothing -> 112 - ( updatedModel, mappedCmd ) 93 + Return3.wieldNested 94 + update 95 + { mapCmd = NotSoFast 96 + , mapModel = \child -> { model | notSoFast = child } 97 + , update = \m -> Debouncer.update m >> Return3.fromDebouncer 98 + } 99 + { model = model.notSoFast 100 + , msg = debouncerMsg 101 + } 113 102 114 103 ToCache alienEvent -> 115 - ( model 116 - , Brain.Ports.toCache alienEvent 117 - ) 104 + alienEvent 105 + |> Brain.Ports.toCache 106 + |> returnWithModel model 118 107 119 108 ----------------------------------------- 120 109 -- Children ··· 156 145 encodedTracks = 157 146 Json.Encode.list Tracks.encodeTrack decodedData.tracks 158 147 in 159 - andThen2 160 - (updateSearchIndex encodedTracks) 161 - ( { model | hypaethralUserData = decodedData } 162 - , Brain.Ports.toUI (Alien.broadcast Alien.LoadHypaethralUserData value) 163 - ) 148 + value 149 + |> Alien.broadcast Alien.LoadHypaethralUserData 150 + |> Brain.Ports.toUI 151 + |> returnWithModel { model | hypaethralUserData = decodedData } 152 + |> andThen (updateSearchIndex encodedTracks) 164 153 165 154 SaveHypaethralData -> 166 155 model.hypaethralUserData 167 156 |> Authentication.encodeHypaethral 168 157 |> Authentication.SaveHypaethralData 169 158 |> AuthenticationMsg 170 - |> (\m -> update m model) 159 + |> updateWithModel model 171 160 172 161 SaveFavourites value -> 173 162 value ··· 196 185 |> Result.withDefault model.hypaethralUserData.tracks 197 186 |> hypaethralLenses.setTracks model 198 187 |> updateSearchIndex value 199 - |> andThen2 saveHypaethralData 188 + |> andThen saveHypaethralData 189 + 190 + 191 + updateWithModel : Model -> Msg -> ( Model, Cmd Msg ) 192 + updateWithModel model msg = 193 + update msg model 200 194 201 195 202 196 updateSearchIndex : Json.Value -> Model -> ( Model, Cmd Msg ) 203 197 updateSearchIndex value model = 204 - update 205 - (value 206 - |> Tracks.UpdateSearchIndex 207 - |> TracksMsg 208 - ) 209 - model 198 + value 199 + |> Tracks.UpdateSearchIndex 200 + |> TracksMsg 201 + |> updateWithModel model 210 202 211 203 212 204 213 205 -- 📣 ░░ REPLIES 214 206 215 207 216 - translateReply : Reply -> Msg 217 - translateReply reply = 208 + translateReply : Reply -> Model -> ( Model, Cmd Msg ) 209 + translateReply reply model = 218 210 case reply of 219 211 FabricatedNewSecretKey -> 220 - SaveHypaethralData 212 + update SaveHypaethralData model 221 213 222 214 ----------------------------------------- 223 215 -- To UI 224 216 ----------------------------------------- 225 217 GiveUI Alien.LoadHypaethralUserData data -> 226 - LoadHypaethralUserData data 218 + update (LoadHypaethralUserData data) model 227 219 228 220 GiveUI tag data -> 229 - NotifyUI (Alien.broadcast tag data) 221 + data 222 + |> Alien.broadcast tag 223 + |> NotifyUI 224 + |> updateWithModel model 230 225 231 226 NudgeUI tag -> 232 - NotifyUI (Alien.trigger tag) 233 - 234 - 235 - updateChild = 236 - Replying.updateChild update translateReply 227 + tag 228 + |> Alien.trigger 229 + |> NotifyUI 230 + |> updateWithModel model 237 231 238 232 239 233 ··· 242 236 243 237 updateAuthentication : Model -> Authentication.Msg -> ( Model, Cmd Msg ) 244 238 updateAuthentication model sub = 245 - updateChild 239 + Return3.wieldNested 240 + translateReply 246 241 { mapCmd = AuthenticationMsg 247 242 , mapModel = \child -> { model | authentication = child } 248 243 , update = Authentication.update ··· 254 249 255 250 updateProcessing : Model -> Processing.Msg -> ( Model, Cmd Msg ) 256 251 updateProcessing model sub = 257 - updateChild 252 + Return3.wieldNested 253 + translateReply 258 254 { mapCmd = ProcessingMsg 259 255 , mapModel = \child -> { model | processing = child } 260 256 , update = Processing.update ··· 266 262 267 263 updateTracks : Model -> Tracks.Msg -> ( Model, Cmd Msg ) 268 264 updateTracks model sub = 269 - updateChild 265 + Return3.wieldNested 266 + translateReply 270 267 { mapCmd = TracksMsg 271 268 , mapModel = \child -> { model | tracks = child } 272 269 , update = Tracks.update ··· 295 292 296 293 saveHypaethralData : Model -> ( Model, Cmd Msg ) 297 294 saveHypaethralData model = 298 - update 299 - (SaveHypaethralData 300 - |> Debouncer.provideInput 301 - |> NotSoFast 302 - ) 303 - model 295 + SaveHypaethralData 296 + |> Debouncer.provideInput 297 + |> NotSoFast 298 + |> updateWithModel model 304 299 305 300 306 301
+72 -82
src/Applications/Brain/Authentication.elm
··· 26 26 import Conditional exposing (..) 27 27 import Json.Decode as Decode 28 28 import Json.Encode as Json 29 - import Replying exposing (R3D3, do) 29 + import Return3 as Return exposing (..) 30 + import Task.Extra exposing (do) 30 31 31 32 32 33 ··· 77 78 | SaveHypaethralData Json.Value 78 79 79 80 80 - update : Msg -> Model -> R3D3 Model Msg Reply 81 + update : Msg -> Model -> Return Model Msg Reply 81 82 update msg model = 82 83 case msg of 83 84 -- 🐤 ··· 87 88 let 88 89 decoder = 89 90 Decode.map2 90 - (\a b -> { maybeMethod = a, maybePassphrase = b }) 91 + Tuple.pair 91 92 (Decode.field "method" <| Decode.map methodFromString Decode.string) 92 93 (Decode.field "passphrase" <| Decode.maybe Decode.string) 93 94 in 94 95 case Decode.decodeValue decoder json of 95 - Ok { maybeMethod, maybePassphrase } -> 96 - case maybePassphrase of 97 - Just passphrase -> 98 - ( { model | method = maybeMethod, performingSignIn = True } 99 - , do (FabricateSecretKey passphrase) 100 - , Nothing 101 - ) 96 + Ok ( maybeMethod, Just passphrase ) -> 97 + update 98 + (FabricateSecretKey passphrase) 99 + { model | method = maybeMethod, performingSignIn = True } 102 100 103 - Nothing -> 104 - ( { model | method = maybeMethod, performingSignIn = True } 105 - , do RetrieveHypaethralData 106 - , Nothing 107 - ) 101 + Ok ( maybeMethod, Nothing ) -> 102 + update 103 + RetrieveHypaethralData 104 + { model | method = maybeMethod, performingSignIn = True } 108 105 109 106 _ -> 110 - ( model 111 - , Cmd.none 112 - , Nothing 113 - ) 107 + return model 114 108 115 109 -- 💀 116 110 -- Unset & remove stored method. 117 111 PerformSignOut -> 118 - ( { model | method = Nothing } 119 - , Cmd.batch 120 - [ Ports.removeCache (Alien.trigger Alien.AuthMethod) 121 - , Ports.removeCache (Alien.trigger Alien.AuthSecretKey) 122 - ] 123 - , Nothing 124 - ) 112 + [ Ports.removeCache (Alien.trigger Alien.AuthMethod) 113 + , Ports.removeCache (Alien.trigger Alien.AuthSecretKey) 114 + ] 115 + |> Cmd.batch 116 + |> Return.commandWithModel { model | method = Nothing } 125 117 126 118 ----------------------------------------- 127 119 -- # 0 128 120 ----------------------------------------- 129 121 FabricateSecretKey passphrase -> 130 - ( model 131 - , passphrase 122 + passphrase 132 123 |> Json.string 133 124 |> Alien.broadcast Alien.FabricateSecretKey 134 125 |> Ports.fabricateSecretKey 135 - , Nothing 136 - ) 126 + |> Return.commandWithModel model 137 127 138 128 SecretKeyFabricated -> 139 129 if model.performingSignIn then 140 - ( model, do RetrieveHypaethralData, Nothing ) 130 + update RetrieveHypaethralData model 141 131 142 132 else 143 - ( model, Cmd.none, Just [ FabricatedNewSecretKey ] ) 133 + returnReplyWithModel model FabricatedNewSecretKey 144 134 145 135 ----------------------------------------- 146 136 -- # 1 147 137 ----------------------------------------- 148 138 RetrieveMethod -> 149 - ( model 150 - , Alien.AuthMethod 139 + Alien.AuthMethod 151 140 |> Alien.trigger 152 141 |> Ports.requestCache 153 - , Nothing 154 - ) 142 + |> Return.commandWithModel model 155 143 156 144 MethodRetrieved json -> 157 145 case decodeMethod json of 158 146 -- 🚀 159 147 Just method -> 160 - ( { model | method = Just method } 161 - , do RetrieveHypaethralData 162 - , Nothing 163 - ) 148 + update 149 + RetrieveHypaethralData 150 + { model | method = Just method } 164 151 165 152 -- ✋ 166 153 _ -> 167 - ( model 168 - , Cmd.none 169 - , terminate NotAuthenticated 170 - ) 154 + addReplies 155 + (terminate NotAuthenticated) 156 + (return model) 171 157 172 158 ----------------------------------------- 173 159 -- # 2 174 160 ----------------------------------------- 175 161 RetrieveHypaethralData -> 176 - ( model 177 - , case model.method of 162 + case model.method of 178 163 -- 🚀 179 164 Just Ipfs -> 180 - Ports.requestIpfs (Alien.trigger Alien.AuthIpfs) 165 + Alien.AuthIpfs 166 + |> Alien.trigger 167 + |> Ports.requestIpfs 168 + |> Return.commandWithModel model 181 169 182 170 Just Local -> 183 - Ports.requestCache (Alien.trigger Alien.AuthAnonymous) 171 + Alien.AuthAnonymous 172 + |> Alien.trigger 173 + |> Ports.requestCache 174 + |> Return.commandWithModel model 184 175 185 176 Just (RemoteStorage { userAddress, token }) -> 186 177 [ ( "token", Json.string token ) ··· 189 180 |> Json.object 190 181 |> Alien.broadcast Alien.AuthRemoteStorage 191 182 |> Ports.requestRemoteStorage 183 + |> Return.commandWithModel model 192 184 193 185 -- ✋ 194 186 Nothing -> 195 - Cmd.none 196 - , Nothing 197 - ) 187 + return model 198 188 199 189 HypaethralDataRetrieved json -> 200 190 ( { model | performingSignIn = False } ··· 209 199 _ -> 210 200 Cmd.none 211 201 -- 212 - , Maybe.andThen 213 - (\method -> terminate <| Authenticated method json) 214 - model.method 202 + , model.method 203 + |> Maybe.map (\method -> Authenticated method json) 204 + |> Maybe.map terminate 205 + |> Maybe.withDefault [] 215 206 ) 216 207 217 208 ----------------------------------------- 218 209 -- Data 219 210 ----------------------------------------- 220 211 RetrieveEnclosedData -> 221 - ( model 222 - , Ports.requestCache (Alien.trigger Alien.AuthEnclosedData) 223 - , Nothing 224 - ) 212 + Alien.AuthEnclosedData 213 + |> Alien.trigger 214 + |> Ports.requestCache 215 + |> Return.commandWithModel model 225 216 226 217 EnclosedDataRetrieved json -> 227 - ( model 228 - , Cmd.none 229 - , Just [ GiveUI Alien.LoadEnclosedUserData json ] 230 - ) 218 + json 219 + |> GiveUI Alien.LoadEnclosedUserData 220 + |> Return.replyWithModel model 231 221 232 222 SaveEnclosedData json -> 233 - ( model 234 - , json 223 + json 235 224 |> Alien.broadcast Alien.AuthEnclosedData 236 225 |> Ports.toCache 237 - , Nothing 238 - ) 226 + |> Return.commandWithModel model 239 227 240 228 SaveHypaethralData json -> 241 - ( model 242 - , case model.method of 229 + case model.method of 243 230 -- 🚀 244 231 Just Ipfs -> 245 - Ports.toIpfs (Alien.broadcast Alien.AuthIpfs json) 232 + json 233 + |> Alien.broadcast Alien.AuthIpfs 234 + |> Ports.toIpfs 235 + |> Return.commandWithModel model 246 236 247 237 Just Local -> 248 - Ports.toCache (Alien.broadcast Alien.AuthAnonymous json) 238 + json 239 + |> Alien.broadcast Alien.AuthAnonymous 240 + |> Ports.toCache 241 + |> Return.commandWithModel model 249 242 250 243 Just (RemoteStorage { userAddress, token }) -> 251 244 [ ( "data", json ) ··· 255 248 |> Json.object 256 249 |> Alien.broadcast Alien.AuthRemoteStorage 257 250 |> Ports.toRemoteStorage 251 + |> Return.commandWithModel model 258 252 259 253 -- ✋ 260 254 Nothing -> 261 - Cmd.none 262 - , Nothing 263 - ) 255 + return model 264 256 265 257 266 258 ··· 272 264 | NotAuthenticated 273 265 274 266 275 - terminate : Termination -> Maybe (List Reply) 267 + terminate : Termination -> List Reply 276 268 terminate t = 277 269 case t of 278 270 Authenticated method hypData -> 279 - Just 280 - [ GiveUI Alien.LoadHypaethralUserData hypData 281 - , GiveUI Alien.AuthMethod (Authentication.encodeMethod method) 282 - ] 271 + [ GiveUI Alien.LoadHypaethralUserData hypData 272 + , GiveUI Alien.AuthMethod (Authentication.encodeMethod method) 273 + ] 283 274 284 275 NotAuthenticated -> 285 - Just 286 - [ NudgeUI Alien.NotAuthenticated 287 - , NudgeUI Alien.HideLoadingScreen 288 - ] 276 + [ NudgeUI Alien.NotAuthenticated 277 + , NudgeUI Alien.HideLoadingScreen 278 + ]
+35 -45
src/Applications/Brain/Sources/Processing.elm
··· 9 9 import Json.Encode as Encode 10 10 import List.Extra as List 11 11 import Maybe.Extra as Maybe 12 - import Replying exposing (R3D3, do) 13 - import Return3 12 + import Return3 as Return exposing (..) 14 13 import Sources.Processing exposing (..) 15 14 import Task 15 + import Task.Extra exposing (do) 16 16 import Time 17 17 import Tracks.Encoding 18 18 ··· 38 38 -- 📣 39 39 40 40 41 - update : Msg -> Model -> R3D3 Model Msg Reply 41 + update : Msg -> Model -> Return Model Msg Reply 42 42 update msg model = 43 43 case msg of 44 44 {- If already processing, do nothing. ··· 46 46 If there are sources, start processing the first source. 47 47 -} 48 48 Process { origin, sources, tracks } -> 49 - if isProcessing model.status || List.isEmpty sources then 50 - Return3.withNothing model 51 - 52 - else 53 - let 54 - filter s = 55 - List.filter (.sourceId >> (==) s.id) tracks 49 + let 50 + filter s = 51 + List.filter (.sourceId >> (==) s.id) tracks 56 52 57 - all = 58 - List.map (\s -> ( s, filter s )) sources 59 - in 60 - case List.uncons all of 61 - Just ( ( s, t ), future ) -> 62 - ( { model | origin = origin, status = Processing ( s, t ) future } 63 - , Steps.takeFirstStep origin model.currentTime s 64 - , Nothing 65 - ) 53 + all = 54 + List.map (\s -> ( s, filter s )) sources 55 + in 56 + case 57 + ( isProcessing model.status || List.isEmpty sources 58 + , List.uncons all 59 + ) 60 + of 61 + ( False, Just ( ( s, t ), future ) ) -> 62 + Return.commandWithModel 63 + { model | origin = origin, status = Processing ( s, t ) future } 64 + (Steps.takeFirstStep origin model.currentTime s) 66 65 67 - Nothing -> 68 - Return3.withNothing model 66 + _ -> 67 + return model 69 68 70 69 {- If not processing, do nothing. 71 70 If there are no sources left, do nothing. ··· 74 73 NextInLine -> 75 74 case model.status of 76 75 Processing _ (( source, tracks ) :: rest) -> 77 - ( { model | status = Processing ( source, tracks ) rest } 78 - , Steps.takeFirstStep model.origin model.currentTime source 79 - , Nothing 80 - ) 76 + Return.commandWithModel 77 + { model | status = Processing ( source, tracks ) rest } 78 + (Steps.takeFirstStep model.origin model.currentTime source) 81 79 82 80 _ -> 83 - ( { model | status = NotProcessing } 84 - , Cmd.none 85 - , Just [ NudgeUI Alien.FinishedProcessingSources ] 86 - ) 81 + Return.repliesWithModel 82 + { model | status = NotProcessing } 83 + [ NudgeUI Alien.FinishedProcessingSources ] 87 84 88 85 ----------------------------------------- 89 86 -- Phase 1 ··· 91 88 ----------------------------------------- 92 89 PrepareStep context (Ok response) -> 93 90 let 94 - ( cmd, maybeReplies ) = 91 + ( cmd, replies ) = 95 92 Steps.takePrepareStep context response model.currentTime 96 93 in 97 94 ( model 98 95 , cmd 99 - , maybeReplies 96 + , replies 100 97 ) 101 98 102 99 PrepareStep context (Err err) -> 103 100 ( model 104 101 , do NextInLine 105 - , Just [ reportHttpError context.source err ] 102 + , [ reportHttpError context.source err ] 106 103 ) 107 104 108 105 ----------------------------------------- ··· 114 111 Processing ( source, tracks ) rest -> 115 112 ( { model | status = Processing ( context.source, tracks ) rest } 116 113 , Steps.takeTreeStep context response tracks model.currentTime 117 - , Nothing 114 + , [] 118 115 ) 119 116 120 117 NotProcessing -> 121 - ( model 122 - , Cmd.none 123 - , Nothing 124 - ) 118 + return model 125 119 126 120 TreeStep context (Err err) -> 127 121 ( model 128 122 , do NextInLine 129 - , Just [ reportHttpError context.source err ] 123 + , [ reportHttpError context.source err ] 130 124 ) 131 125 132 126 TreeStepRemoveTracks sourceId filePaths -> ··· 139 133 in 140 134 ( model 141 135 , Cmd.none 142 - , Just [ GiveUI Alien.RemoveTracksByPath encodedData ] 136 + , [ GiveUI Alien.RemoveTracksByPath encodedData ] 143 137 ) 144 138 145 139 ----------------------------------------- ··· 164 158 -------- 165 159 , case List.isEmpty (List.filter Maybe.isJust tagsContext.receivedTags) of 166 160 True -> 167 - Nothing 161 + [] 168 162 169 163 False -> 170 164 tagsContext ··· 172 166 |> Encode.list Tracks.Encoding.encodeTrack 173 167 |> GiveUI Alien.AddTracks 174 168 |> List.singleton 175 - |> Just 176 169 ) 177 170 178 171 ----------------------------------------- 179 172 -- Bits & Pieces 180 173 ----------------------------------------- 181 174 SetCurrentTime time -> 182 - ( { model | currentTime = time } 183 - , Cmd.none 184 - , Nothing 185 - ) 175 + return { model | currentTime = time } 186 176 187 177 188 178
+5 -6
src/Applications/Brain/Sources/Processing/Steps.elm
··· 26 26 import Brain.Reply exposing (Reply(..)) 27 27 import Brain.Sources.Processing.Common exposing (..) 28 28 import List.Extra as List 29 - import Replying exposing (do) 30 29 import Set 31 30 import Sources exposing (Source) 32 31 import Sources.Encoding 33 32 import Sources.Processing exposing (..) 34 33 import Sources.Services as Services 34 + import Task.Extra exposing (do) 35 35 import Time 36 36 import Tracks exposing (Track) 37 37 ··· 74 74 -- 2nd STEP 75 75 76 76 77 - takePrepareStep : Context -> String -> Time.Posix -> ( Cmd Msg, Maybe (List Reply) ) 77 + takePrepareStep : Context -> String -> Time.Posix -> ( Cmd Msg, List Reply ) 78 78 takePrepareStep context response currentTime = 79 79 context 80 80 |> handlePreparationResponse response ··· 164 164 } 165 165 166 166 167 - intoPreparationCommands : Time.Posix -> Context -> ( Cmd Msg, Maybe (List Reply) ) 167 + intoPreparationCommands : Time.Posix -> Context -> ( Cmd Msg, List Reply ) 168 168 intoPreparationCommands currentTime context = 169 169 case context.preparationMarker of 170 170 TheBeginning -> 171 171 ( Cmd.none 172 - , Nothing 172 + , [] 173 173 ) 174 174 175 175 -- Still preparing, ··· 177 177 -- 178 178 InProgress _ -> 179 179 ( prepare context currentTime 180 - , Nothing 180 + , [] 181 181 ) 182 182 183 183 -- The preparation is completed, ··· 196 196 |> Sources.Encoding.encode 197 197 |> GiveUI Alien.UpdateSourceData 198 198 |> List.singleton 199 - |> Just 200 199 ) 201 200 202 201
+5 -5
src/Applications/Brain/Tracks.elm
··· 4 4 import Brain.Ports as Ports 5 5 import Brain.Reply exposing (Reply(..)) 6 6 import Json.Encode as Json 7 - import Replying exposing (R3D3) 7 + import Return3 as Return exposing (..) 8 8 import Tracks exposing (Track) 9 9 10 10 ··· 31 31 | UpdateSearchIndex Json.Value 32 32 33 33 34 - update : Msg -> Model -> R3D3 Model Msg Reply 34 + update : Msg -> Model -> Return Model Msg Reply 35 35 update msg model = 36 36 case msg of 37 37 Search term -> 38 38 ( model 39 39 , Ports.requestSearch term 40 - , Nothing 40 + , [] 41 41 ) 42 42 43 43 Searched results -> 44 44 ( model 45 45 , Cmd.none 46 - , Just [ GiveUI Alien.SearchTracks <| Json.list Json.string results ] 46 + , [ GiveUI Alien.SearchTracks (Json.list Json.string results) ] 47 47 ) 48 48 49 49 UpdateSearchIndex tracksJson -> 50 50 ( model 51 51 , Ports.updateSearchIndex tracksJson 52 - , Nothing 52 + , [] 53 53 ) 54 54 55 55
+252 -294
src/Applications/UI.elm
··· 28 28 import Maybe.Extra as Maybe 29 29 import Notifications 30 30 import Process 31 - import Replying as N5 exposing (do, return) 32 - import Return2 as R2 31 + import Return2 exposing (..) 32 + import Return3 33 33 import Sources 34 34 import Sources.Encoding 35 35 import Tachyons.Classes as T ··· 37 37 import Time 38 38 import Tracks.Encoding 39 39 import UI.Authentication as Authentication 40 - import UI.Authentication.ContextMenu 40 + import UI.Authentication.ContextMenu as Authentication 41 41 import UI.Backdrop as Backdrop 42 42 import UI.Console 43 43 import UI.ContextMenu ··· 134 134 update msg model = 135 135 case msg of 136 136 Bypass -> 137 - R2.withNoCmd model 137 + return model 138 138 139 139 LoadEnclosedUserData json -> 140 140 model 141 141 |> UserData.importEnclosed json 142 - |> N5.reducto update translateReply 142 + |> Return3.wield translateReply 143 143 144 144 LoadHypaethralUserData json -> 145 145 model 146 146 |> UserData.importHypaethral json 147 - |> N5.reducto update translateReply 147 + |> Return3.wield translateReply 148 148 149 149 SetCurrentTime time -> 150 150 let ··· 159 159 ) 160 160 161 161 Core.ToggleLoadingScreen On -> 162 - R2.withNoCmd { model | isLoading = True } 162 + return { model | isLoading = True } 163 163 164 164 Core.ToggleLoadingScreen Off -> 165 - R2.withNoCmd { model | isLoading = False } 165 + return { model | isLoading = False } 166 166 167 167 ----------------------------------------- 168 168 -- Audio 169 169 ----------------------------------------- 170 170 Pause -> 171 - R2.withCmd (Ports.pause ()) model 171 + returnWithModel model (Ports.pause ()) 172 172 173 173 Play -> 174 - R2.withCmd (Ports.play ()) model 174 + returnWithModel model (Ports.play ()) 175 175 176 176 Seek percentage -> 177 - R2.withCmd (Ports.seek percentage) model 177 + returnWithModel model (Ports.seek percentage) 178 178 179 179 SetAudioDuration duration -> 180 - R2.withNoCmd { model | audioDuration = duration } 180 + return { model | audioDuration = duration } 181 181 182 182 SetAudioHasStalled hasStalled -> 183 - R2.withNoCmd { model | audioHasStalled = hasStalled } 183 + return { model | audioHasStalled = hasStalled } 184 184 185 185 SetAudioIsLoading isLoading -> 186 - R2.withNoCmd { model | audioIsLoading = isLoading } 186 + return { model | audioIsLoading = isLoading } 187 187 188 188 SetAudioIsPlaying isPlayinh -> 189 - R2.withNoCmd { model | audioIsPlaying = isPlayinh } 189 + return { model | audioIsPlaying = isPlayinh } 190 190 191 191 Unstall -> 192 - R2.withCmd (Ports.unstall ()) model 193 - 194 - ----------------------------------------- 195 - -- Authentication 196 - ----------------------------------------- 197 - Core.ExternalAuth (Authentication.RemoteStorage _) input -> 198 - -- TODO: 199 - -- 1. Make request to RemoteStorage.webfingerAddress 200 - -- 2. If proper response -> Valid RemoteStorage 201 - -- If not -> Not a RemoteStorage -> Show notification 202 - -- 3. Extract oauth address from webfinger response 203 - let 204 - origin = 205 - Common.urlOrigin model.url 206 - in 207 - input 208 - |> Authentication.RemoteStorage.parseUserAddress 209 - |> Maybe.map (Authentication.RemoteStorage.oauthAddress { origin = origin }) 210 - |> Maybe.map Nav.load 211 - |> Maybe.withDefault Cmd.none 212 - |> Tuple.pair model 213 - 214 - Core.ExternalAuth _ _ -> 215 - R2.withNoCmd model 216 - 217 - Core.ShowMoreAuthenticationOptions coordinates -> 218 - coordinates 219 - |> UI.Authentication.ContextMenu.moreOptionsMenu 220 - |> Just 221 - |> (\c -> { model | contextMenu = c }) 222 - |> R2.withNoCmd 192 + returnWithModel model (Ports.unstall ()) 223 193 224 194 ----------------------------------------- 225 195 -- Brain 226 196 ----------------------------------------- 227 - Core.ProcessSources -> 228 - let 229 - notification = 230 - Notifications.warning "Processing sources …" 231 - 232 - notificationId = 233 - Notifications.id notification 234 - 235 - sources = 236 - model.sources 237 - 238 - newSources = 239 - { sources | processingNotificationId = Just notificationId } 240 - in 241 - [ ( "origin" 242 - , Json.Encode.string (Common.urlOrigin model.url) 243 - ) 244 - , ( "sources" 245 - , Json.Encode.list Sources.Encoding.encode model.sources.collection 246 - ) 247 - , ( "tracks" 248 - , Json.Encode.list Tracks.Encoding.encodeTrack model.tracks.collection.untouched 249 - ) 250 - ] 251 - |> Json.Encode.object 252 - |> Alien.broadcast Alien.ProcessSources 253 - |> Ports.toBrain 254 - |> R2.withModel { model | sources = newSources } 255 - |> N5.andThen2 (update <| ShowNotification notification) 256 - 257 - Core.SaveEnclosedUserData -> 258 - model 259 - |> UserData.exportEnclosed 260 - |> Alien.broadcast Alien.SaveEnclosedUserData 261 - |> Ports.toBrain 262 - |> R2.withModel model 263 - 264 - Core.SaveFavourites -> 265 - model 266 - |> UserData.encodedFavourites 267 - |> Alien.broadcast Alien.SaveFavourites 268 - |> Ports.toBrain 269 - |> R2.withModel model 270 - 271 - Core.SaveSettings -> 272 - model 273 - |> UserData.gatherSettings 274 - |> Authentication.encodeSettings 275 - |> Alien.broadcast Alien.SaveSettings 276 - |> Ports.toBrain 277 - |> R2.withModel model 278 - 279 - Core.SaveSources -> 280 - let 281 - updateEnabledSourceIdsOnTracks = 282 - model.sources.collection 283 - |> Sources.enabledSourceIds 284 - |> Tracks.SetEnabledSourceIds 285 - |> TracksMsg 286 - |> update 287 - 288 - ( updatedModel, updatedCmd ) = 289 - updateEnabledSourceIdsOnTracks model 290 - in 291 - updatedModel 292 - |> UserData.encodedSources 293 - |> Alien.broadcast Alien.SaveSources 294 - |> Ports.toBrain 295 - |> R2.withModel updatedModel 296 - |> R2.addCmd updatedCmd 297 - 298 - Core.SaveTracks -> 299 - model 300 - |> UserData.encodedTracks 301 - |> Alien.broadcast Alien.SaveTracks 302 - |> Ports.toBrain 303 - |> R2.withModel model 304 - 305 - Core.SignOut -> 306 - let 307 - alienSigningOut = 308 - Alien.SignOut 309 - |> Alien.trigger 310 - |> Ports.toBrain 311 - in 197 + SignOut -> 312 198 { model 313 199 | authentication = Authentication.initialModel model.url 314 200 , sources = Sources.initialModel 315 201 , tracks = Tracks.initialModel 316 202 } 317 203 |> update (BackdropMsg Backdrop.Default) 318 - |> R2.addCmd alienSigningOut 319 - |> R2.addCmd (Nav.pushUrl model.navKey "/") 204 + |> addCommand (Ports.toBrain <| Alien.trigger Alien.SignOut) 205 + |> addCommand (Nav.pushUrl model.navKey "/") 320 206 321 207 ----------------------------------------- 322 208 -- Children 323 209 ----------------------------------------- 324 210 AuthenticationMsg sub -> 325 - updateChild 211 + Return3.wieldNested 212 + translateReply 326 213 { mapCmd = AuthenticationMsg 327 214 , mapModel = \child -> { model | authentication = child } 328 215 , update = Authentication.update ··· 332 219 } 333 220 334 221 BackdropMsg sub -> 335 - updateChild 222 + Return3.wieldNested 223 + translateReply 336 224 { mapCmd = BackdropMsg 337 225 , mapModel = \child -> { model | backdrop = child } 338 226 , update = Backdrop.update ··· 342 230 } 343 231 344 232 EqualizerMsg sub -> 345 - updateChild 233 + Return3.wieldNested 234 + translateReply 346 235 { mapCmd = EqualizerMsg 347 236 , mapModel = \child -> { model | equalizer = child } 348 237 , update = Equalizer.update ··· 352 241 } 353 242 354 243 QueueMsg sub -> 355 - updateChild 244 + Return3.wieldNested 245 + translateReply 356 246 { mapCmd = QueueMsg 357 247 , mapModel = \child -> { model | queue = child } 358 248 , update = Queue.update ··· 362 252 } 363 253 364 254 SourcesMsg sub -> 365 - updateChild 255 + Return3.wieldNested 256 + translateReply 366 257 { mapCmd = SourcesMsg 367 258 , mapModel = \child -> { model | sources = child } 368 259 , update = Sources.update ··· 372 263 } 373 264 374 265 TracksMsg sub -> 375 - updateChild 266 + Return3.wieldNested 267 + translateReply 376 268 { mapCmd = TracksMsg 377 269 , mapModel = \child -> { model | tracks = child } 378 270 , update = Tracks.update ··· 382 274 } 383 275 384 276 ----------------------------------------- 385 - -- Children, Pt. 2 386 - ----------------------------------------- 387 - Core.ActiveQueueItemChanged maybeQueueItem -> 388 - let 389 - nowPlaying = 390 - Maybe.map .identifiedTrack maybeQueueItem 391 - 392 - portCmd = 393 - maybeQueueItem 394 - |> Maybe.map .identifiedTrack 395 - |> Maybe.map 396 - (UI.Queue.Common.makeEngineItem 397 - model.currentTime 398 - model.sources.collection 399 - ) 400 - |> Ports.activeQueueItemChanged 401 - in 402 - model 403 - |> update (TracksMsg <| Tracks.SetNowPlaying nowPlaying) 404 - |> R2.addCmd portCmd 405 - 406 - Core.FillQueue -> 407 - update 408 - (model.tracks.collection.harvested 409 - |> Queue.Fill model.currentTime 410 - |> QueueMsg 411 - ) 412 - model 413 - 414 - ----------------------------------------- 415 277 -- Context Menu 416 278 ----------------------------------------- 417 - Core.HideContextMenu -> 418 - ( { model | contextMenu = Nothing } 419 - , Cmd.none 420 - ) 421 - 422 - Core.ShowTracksContextMenu coordinates tracks -> 423 - ( { model | contextMenu = Just (Tracks.trackMenu tracks coordinates) } 424 - , Cmd.none 425 - ) 279 + HideContextMenu -> 280 + return { model | contextMenu = Nothing } 426 281 427 282 ----------------------------------------- 428 283 -- Import / Export 429 284 ----------------------------------------- 430 - Core.Export -> 431 - ( model 432 - , File.Download.string 433 - "diffuse.json" 434 - "application/json" 435 - ({ favourites = model.tracks.favourites 436 - , settings = Just (UserData.gatherSettings model) 437 - , sources = model.sources.collection 438 - , tracks = model.tracks.collection.untouched 439 - } 440 - |> Authentication.encodeHypaethral 441 - |> Json.Encode.encode 2 442 - ) 443 - ) 285 + Export -> 286 + { favourites = model.tracks.favourites 287 + , settings = Just (UserData.gatherSettings model) 288 + , sources = model.sources.collection 289 + , tracks = model.tracks.collection.untouched 290 + } 291 + |> Authentication.encodeHypaethral 292 + |> Json.Encode.encode 2 293 + |> File.Download.string "diffuse.json" "application/json" 294 + |> returnWithModel model 444 295 445 - Core.Import file -> 446 - ( { model | isLoading = True } 447 - , 250 296 + Import file -> 297 + 250 448 298 |> Process.sleep 449 299 |> Task.andThen (\_ -> File.toString file) 450 300 |> Task.perform ImportJson 451 - ) 301 + |> returnWithModel { model | isLoading = True } 452 302 453 - Core.ImportJson json -> 303 + ImportJson json -> 454 304 let 455 305 notification = 456 306 Notifications.success "Imported data successfully!" ··· 462 312 |> Result.withDefault Json.Encode.null 463 313 |> LoadHypaethralUserData 464 314 ) 465 - |> N5.andThen2 (update Core.SaveFavourites) 466 - |> N5.andThen2 (update Core.SaveSources) 467 - |> N5.andThen2 (update Core.SaveTracks) 468 - |> N5.andThen2 (update <| ShowNotification notification) 469 - |> R2.addCmd (do <| ChangeUrlUsingPage Page.Index) 470 - 471 - Core.InsertDemo -> 472 - model 473 - |> update (LoadHypaethralUserData UserData.demo) 474 - |> N5.andThen2 (update Core.SaveFavourites) 475 - |> N5.andThen2 (update Core.SaveSources) 476 - |> N5.andThen2 (update Core.SaveTracks) 315 + |> andThen (translateReply SaveFavourites) 316 + |> andThen (translateReply SaveSources) 317 + |> andThen (translateReply SaveTracks) 318 + |> andThen (update <| ShowNotification notification) 319 + |> andThen (update <| ChangeUrlUsingPage Page.Index) 477 320 478 - Core.RequestImport -> 479 - ( model 480 - , File.Select.file [ "application/json" ] Import 481 - ) 321 + RequestImport -> 322 + Import 323 + |> File.Select.file [ "application/json" ] 324 + |> returnWithModel model 482 325 483 326 ----------------------------------------- 484 327 -- Notifications 485 328 ----------------------------------------- 486 329 Core.DismissNotification args -> 487 - UI.Notifications.dismissNotification model args 330 + UI.Notifications.dismiss model args 488 331 489 332 RemoveNotification { id } -> 490 - ( { model 491 - | notifications = 492 - List.filter 493 - (Notifications.id >> (/=) id) 494 - model.notifications 495 - } 496 - , Cmd.none 497 - ) 333 + model.notifications 334 + |> List.filter (Notifications.id >> (/=) id) 335 + |> (\notifications -> { model | notifications = notifications }) 336 + |> return 498 337 499 338 ShowNotification notification -> 500 - UI.Notifications.showNotification model notification 339 + UI.Notifications.show notification model 501 340 502 341 ----------------------------------------- 503 342 -- URL 504 343 ----------------------------------------- 505 344 ChangeUrlUsingPage page -> 506 - ( model 507 - , Nav.pushUrl model.navKey (Page.toString page) 508 - ) 345 + page 346 + |> Page.toString 347 + |> Nav.pushUrl model.navKey 348 + |> returnWithModel model 509 349 510 - LinkClicked urlRequest -> 511 - case urlRequest of 512 - Browser.Internal url -> 513 - if url.path == "/about" then 514 - return model (Nav.load "/about") 350 + LinkClicked (Browser.Internal url) -> 351 + if url.path == "/about" then 352 + returnWithModel model (Nav.load "/about") 515 353 516 - else 517 - return model (Nav.pushUrl model.navKey <| Url.toString url) 354 + else 355 + returnWithModel model (Nav.pushUrl model.navKey <| Url.toString url) 518 356 519 - Browser.External href -> 520 - return model (Nav.load href) 357 + LinkClicked (Browser.External href) -> 358 + returnWithModel model (Nav.load href) 521 359 522 360 UrlChanged url -> 523 361 case Page.fromUrl url of 524 362 Just page -> 525 - ( { model 526 - | page = page 527 - , url = url 528 - } 529 - , Cmd.none 530 - ) 363 + return { model | page = page, url = url } 531 364 532 365 Nothing -> 533 - ( model 534 - , Nav.replaceUrl model.navKey "/" 535 - ) 366 + returnWithModel model (Nav.replaceUrl model.navKey "/") 367 + 368 + 369 + updateWithModel : Model -> Msg -> ( Model, Cmd Msg ) 370 + updateWithModel model msg = 371 + update msg model 536 372 537 373 538 374 539 375 -- 📣 ░░ CHILDREN & REPLIES 540 376 541 377 542 - translateReply : Reply -> Msg 543 - translateReply reply = 378 + translateReply : Reply -> Model -> ( Model, Cmd Msg ) 379 + translateReply reply model = 544 380 case reply of 545 - Reply.ActiveQueueItemChanged m -> 546 - Core.ActiveQueueItemChanged m 381 + ExternalAuth (Authentication.RemoteStorage _) input -> 382 + -- TODO: 383 + -- 1. Make request to RemoteStorage.webfingerAddress 384 + -- 2. If proper response -> Valid RemoteStorage 385 + -- If not -> Not a RemoteStorage -> Show notification 386 + -- 3. Extract oauth address from webfinger response 387 + let 388 + origin = 389 + Common.urlOrigin model.url 390 + in 391 + input 392 + |> Authentication.RemoteStorage.parseUserAddress 393 + |> Maybe.map (Authentication.RemoteStorage.oauthAddress { origin = origin }) 394 + |> Maybe.map Nav.load 395 + |> Maybe.withDefault Cmd.none 396 + |> returnWithModel model 547 397 548 - Reply.AddSourceToCollection source -> 549 - SourcesMsg (Sources.AddToCollection source) 398 + ExternalAuth _ _ -> 399 + return model 550 400 551 - Reply.DismissNotification opts -> 552 - Core.DismissNotification opts 401 + GoToPage page -> 402 + page 403 + |> ChangeUrlUsingPage 404 + |> updateWithModel model 553 405 554 - Reply.ExternalAuth a b -> 555 - Core.ExternalAuth a b 406 + Reply.ToggleLoadingScreen state -> 407 + update (Core.ToggleLoadingScreen state) model 556 408 557 - Reply.FillQueue -> 558 - Core.FillQueue 409 + ----------------------------------------- 410 + -- Context Menu 411 + ----------------------------------------- 412 + ShowMoreAuthenticationOptions coordinates -> 413 + return { model | contextMenu = Just (Authentication.moreOptionsMenu coordinates) } 559 414 560 - Reply.GoToPage page -> 561 - ChangeUrlUsingPage page 415 + ShowTracksContextMenu coordinates tracks -> 416 + return { model | contextMenu = Just (Tracks.trackMenu tracks coordinates) } 562 417 563 - Reply.InsertDemo -> 564 - Core.InsertDemo 418 + ----------------------------------------- 419 + -- Notifications 420 + ----------------------------------------- 421 + Reply.DismissNotification options -> 422 + UI.Notifications.dismiss model options 565 423 566 - Reply.PlayTrack identifiedTrack -> 567 - QueueMsg (Queue.InjectFirstAndPlay identifiedTrack) 424 + ShowErrorNotification string -> 425 + UI.Notifications.show (Notifications.stickyError string) model 426 + 427 + ShowSuccessNotification string -> 428 + UI.Notifications.show (Notifications.success string) model 429 + 430 + ShowWarningNotification string -> 431 + UI.Notifications.show (Notifications.stickyWarning string) model 432 + 433 + ----------------------------------------- 434 + -- Queue 435 + ----------------------------------------- 436 + ActiveQueueItemChanged maybeQueueItem -> 437 + let 438 + nowPlaying = 439 + Maybe.map .identifiedTrack maybeQueueItem 440 + 441 + portCmd = 442 + maybeQueueItem 443 + |> Maybe.map .identifiedTrack 444 + |> Maybe.map 445 + (UI.Queue.Common.makeEngineItem 446 + model.currentTime 447 + model.sources.collection 448 + ) 449 + |> Ports.activeQueueItemChanged 450 + in 451 + model 452 + |> update (TracksMsg <| Tracks.SetNowPlaying nowPlaying) 453 + |> addCommand portCmd 568 454 569 - Reply.ProcessSources -> 570 - Core.ProcessSources 455 + FillQueue -> 456 + model.tracks.collection.harvested 457 + |> Queue.Fill model.currentTime 458 + |> QueueMsg 459 + |> updateWithModel model 571 460 572 - Reply.RemoveTracksWithSourceId sourceId -> 573 - TracksMsg (Tracks.RemoveBySourceId sourceId) 461 + PlayTrack identifiedTrack -> 462 + identifiedTrack 463 + |> Queue.InjectFirstAndPlay 464 + |> QueueMsg 465 + |> updateWithModel model 574 466 575 - Reply.ResetQueue -> 576 - QueueMsg Queue.Reset 467 + ResetQueue -> 468 + update (QueueMsg Queue.Reset) model 577 469 578 - Reply.ShiftQueue -> 579 - QueueMsg Queue.Shift 470 + ShiftQueue -> 471 + update (QueueMsg Queue.Shift) model 580 472 581 - Reply.SaveEnclosedUserData -> 582 - Core.SaveEnclosedUserData 473 + ----------------------------------------- 474 + -- Sources & Tracks 475 + ----------------------------------------- 476 + AddSourceToCollection source -> 477 + source 478 + |> Sources.AddToCollection 479 + |> SourcesMsg 480 + |> updateWithModel model 583 481 584 - Reply.SaveFavourites -> 585 - Core.SaveFavourites 482 + ProcessSources -> 483 + let 484 + notification = 485 + Notifications.warning "Processing sources …" 586 486 587 - Reply.SaveSettings -> 588 - Core.SaveSettings 487 + notificationId = 488 + Notifications.id notification 589 489 590 - Reply.SaveSources -> 591 - Core.SaveSources 490 + sources = 491 + model.sources 592 492 593 - Reply.SaveTracks -> 594 - Core.SaveTracks 493 + newSources = 494 + { sources | processingNotificationId = Just notificationId } 495 + in 496 + [ ( "origin" 497 + , Json.Encode.string (Common.urlOrigin model.url) 498 + ) 499 + , ( "sources" 500 + , Json.Encode.list Sources.Encoding.encode model.sources.collection 501 + ) 502 + , ( "tracks" 503 + , Json.Encode.list Tracks.Encoding.encodeTrack model.tracks.collection.untouched 504 + ) 505 + ] 506 + |> Json.Encode.object 507 + |> Alien.broadcast Alien.ProcessSources 508 + |> Ports.toBrain 509 + |> returnWithModel { model | sources = newSources } 510 + |> andThen (UI.Notifications.show notification) 595 511 596 - Reply.ShowErrorNotification string -> 597 - ShowNotification (Notifications.stickyError string) 512 + RemoveTracksWithSourceId sourceId -> 513 + sourceId 514 + |> Tracks.RemoveBySourceId 515 + |> TracksMsg 516 + |> updateWithModel model 598 517 599 - Reply.ShowMoreAuthenticationOptions coordinates -> 600 - Core.ShowMoreAuthenticationOptions coordinates 518 + ----------------------------------------- 519 + -- User Data 520 + ----------------------------------------- 521 + InsertDemo -> 522 + model 523 + |> update (LoadHypaethralUserData UserData.demo) 524 + |> andThen (translateReply SaveFavourites) 525 + |> andThen (translateReply SaveSources) 526 + |> andThen (translateReply SaveTracks) 601 527 602 - Reply.ShowSuccessNotification string -> 603 - ShowNotification (Notifications.success string) 528 + SaveEnclosedUserData -> 529 + model 530 + |> UserData.exportEnclosed 531 + |> Alien.broadcast Alien.SaveEnclosedUserData 532 + |> Ports.toBrain 533 + |> returnWithModel model 604 534 605 - Reply.ShowWarningNotification string -> 606 - ShowNotification (Notifications.stickyWarning string) 535 + SaveFavourites -> 536 + model 537 + |> UserData.encodedFavourites 538 + |> Alien.broadcast Alien.SaveFavourites 539 + |> Ports.toBrain 540 + |> returnWithModel model 607 541 608 - Reply.ShowTracksContextMenu coordinates tracks -> 609 - Core.ShowTracksContextMenu coordinates tracks 542 + SaveSettings -> 543 + model 544 + |> UserData.gatherSettings 545 + |> Authentication.encodeSettings 546 + |> Alien.broadcast Alien.SaveSettings 547 + |> Ports.toBrain 548 + |> returnWithModel model 610 549 611 - Reply.ToggleLoadingScreen state -> 612 - Core.ToggleLoadingScreen state 550 + SaveSources -> 551 + let 552 + updateEnabledSourceIdsOnTracks = 553 + model.sources.collection 554 + |> Sources.enabledSourceIds 555 + |> Tracks.SetEnabledSourceIds 556 + |> TracksMsg 557 + |> update 613 558 559 + ( updatedModel, updatedCmd ) = 560 + updateEnabledSourceIdsOnTracks model 561 + in 562 + updatedModel 563 + |> UserData.encodedSources 564 + |> Alien.broadcast Alien.SaveSources 565 + |> Ports.toBrain 566 + |> returnWithModel updatedModel 567 + |> addCommand updatedCmd 614 568 615 - updateChild = 616 - N5.updateChild update translateReply 569 + SaveTracks -> 570 + model 571 + |> UserData.encodedTracks 572 + |> Alien.broadcast Alien.SaveTracks 573 + |> Ports.toBrain 574 + |> returnWithModel model 617 575 618 576 619 577
+37 -43
src/Applications/UI/Authentication.elm
··· 20 20 import Json.Encode 21 21 import Material.Icons.Av as Icons 22 22 import Material.Icons.Navigation as Icons 23 - import Replying exposing (R3D3, andThen3) 24 - import Return2 as R2 25 - import Return3 as R3 23 + import Return3 as Return exposing (..) 26 24 import Svg exposing (Svg) 27 25 import Tachyons.Classes as T 28 26 import UI.Kit ··· 128 126 | ConfirmInput 129 127 130 128 131 - update : Msg -> Model -> R3D3 Model Msg Reply 129 + update : Msg -> Model -> Return Model Msg Reply 132 130 update msg model = 133 131 case msg of 134 132 Bypass -> 135 - R3.withNothing model 133 + return model 136 134 137 135 Cancel -> 138 136 case model of 139 137 Authenticated method -> 140 - R3.withNothing (Authenticated method) 138 + return (Authenticated method) 141 139 142 140 InputScreen _ _ -> 143 - R3.withNothing Unauthenticated 141 + return Unauthenticated 144 142 145 143 NewEncryptionKeyScreen _ _ -> 146 - R3.withNothing Unauthenticated 144 + return Unauthenticated 147 145 148 146 UpdateEncryptionKeyScreen method _ -> 149 - R3.withNothing (Authenticated method) 147 + return (Authenticated method) 150 148 151 149 Unauthenticated -> 152 - R3.withNothing Unauthenticated 150 + return Unauthenticated 153 151 154 152 ShowMoreOptions mouseEvent -> 155 153 ( model ··· 164 162 ) 165 163 |> ShowMoreAuthenticationOptions 166 164 |> List.singleton 167 - |> Just 168 165 ) 169 166 170 167 SignIn method -> ··· 177 174 |> Alien.broadcast Alien.SignIn 178 175 |> Ports.toBrain 179 176 -- 180 - , Just [ ToggleLoadingScreen On ] 177 + , [ ToggleLoadingScreen On ] 181 178 ) 182 179 183 180 SignInWithPassphrase method passphrase -> 184 181 if String.length passphrase < minimumPassphraseLength then 185 - ( model 186 - , Cmd.none 187 - , Just [ ShowErrorNotification passphraseLengthErrorMessage ] 188 - ) 182 + addReply 183 + (ShowErrorNotification passphraseLengthErrorMessage) 184 + (return model) 189 185 190 186 else 191 187 ( Unauthenticated ··· 197 193 |> Alien.broadcast Alien.SignIn 198 194 |> Ports.toBrain 199 195 -- 200 - , Just [ ToggleLoadingScreen On ] 196 + , [ ToggleLoadingScreen On ] 201 197 ) 202 198 203 199 SignedIn method -> 204 - R3.withNothing (Authenticated method) 200 + return (Authenticated method) 205 201 206 202 ----------------------------------------- 207 203 -- Encryption ··· 209 205 KeepPassphraseInMemory passphrase -> 210 206 case model of 211 207 NewEncryptionKeyScreen method _ -> 212 - R3.withNothing (NewEncryptionKeyScreen method <| Just passphrase) 208 + return (NewEncryptionKeyScreen method <| Just passphrase) 213 209 214 210 UpdateEncryptionKeyScreen method _ -> 215 - R3.withNothing (UpdateEncryptionKeyScreen method <| Just passphrase) 211 + return (UpdateEncryptionKeyScreen method <| Just passphrase) 216 212 217 213 _ -> 218 - R3.withNothing model 214 + return model 219 215 220 216 ShowNewEncryptionKeyScreen method -> 221 - R3.withNothing (NewEncryptionKeyScreen method Nothing) 217 + return (NewEncryptionKeyScreen method Nothing) 222 218 223 219 ShowUpdateEncryptionKeyScreen method -> 224 - R3.withNothing (UpdateEncryptionKeyScreen method Nothing) 220 + return (UpdateEncryptionKeyScreen method Nothing) 225 221 226 222 UpdateEncryptionKey method passphrase -> 227 223 if String.length passphrase < minimumPassphraseLength then 228 - ( model 229 - , Cmd.none 230 - , Just [ ShowErrorNotification passphraseLengthErrorMessage ] 231 - ) 224 + addReply 225 + (ShowErrorNotification passphraseLengthErrorMessage) 226 + (return model) 232 227 233 228 else 234 - ( Authenticated method 235 - , passphrase 236 - |> Crypto.Hash.sha256 237 - |> Json.Encode.string 238 - |> Alien.broadcast Alien.UpdateEncryptionKey 239 - |> Ports.toBrain 240 - , Nothing 241 - ) 229 + returnCommandWithModel 230 + (Authenticated method) 231 + (passphrase 232 + |> Crypto.Hash.sha256 233 + |> Json.Encode.string 234 + |> Alien.broadcast Alien.UpdateEncryptionKey 235 + |> Ports.toBrain 236 + ) 242 237 243 238 ----------------------------------------- 244 239 -- More Input ··· 249 244 , question = opts.question 250 245 } 251 246 |> InputScreen method 252 - |> R3.withNothing 247 + |> return 253 248 254 249 Input string -> 255 250 case model of 256 251 InputScreen method opts -> 257 - R3.withNothing (InputScreen method { opts | input = string }) 252 + return (InputScreen method { opts | input = string }) 258 253 259 254 m -> 260 - R3.withNothing m 255 + return m 261 256 262 257 ConfirmInput -> 263 258 case model of 264 259 InputScreen method { input } -> 265 - ( model 266 - , Cmd.none 267 - , Just [ ExternalAuth method input ] 268 - ) 260 + addReply 261 + (ExternalAuth method input) 262 + (return model) 269 263 270 264 _ -> 271 - R3.withNothing model 265 + return model 272 266 273 267 274 268
+5 -13
src/Applications/UI/Backdrop.elm
··· 8 8 import Html.Styled.Events exposing (on) 9 9 import Html.Styled.Lazy as Lazy 10 10 import Json.Decode 11 - import Replying exposing (R3D3) 12 - import Return2 as R2 13 - import Return3 as R3 11 + import Return3 as Return exposing (..) 14 12 import Tachyons.Classes as T 15 13 import UI.Animations 16 14 import UI.Reply as Reply exposing (Reply) ··· 71 69 | Load String 72 70 73 71 74 - update : Msg -> Model -> R3D3 Model Msg Reply 72 + update : Msg -> Model -> Return Model Msg Reply 75 73 update msg model = 76 74 case msg of 77 75 Choose backdrop -> 78 - { model | chosen = Just backdrop } 79 - |> R2.withNoCmd 80 - |> R3.withReply [ Reply.SaveSettings ] 76 + return { model | chosen = Just backdrop } |> addReply Reply.SaveSettings 81 77 82 78 Default -> 83 - { model | chosen = Just default } 84 - |> R3.withNothing 79 + return { model | chosen = Just default } 85 80 86 81 Load backdrop -> 87 - [ backdrop ] 88 - |> List.append model.loaded 89 - |> (\list -> { model | loaded = list }) 90 - |> R3.withNothing 82 + return { model | loaded = model.loaded ++ [ backdrop ] } 91 83 92 84 93 85
-18
src/Applications/UI/Core.elm
··· 95 95 | SetAudioIsPlaying Bool 96 96 | Unstall 97 97 ----------------------------------------- 98 - -- Authentication 99 - ----------------------------------------- 100 - | ExternalAuth Authentication.Method String 101 - | ShowMoreAuthenticationOptions Coordinates 102 - ----------------------------------------- 103 98 -- Brain 104 99 ----------------------------------------- 105 - | ProcessSources 106 - | SaveEnclosedUserData 107 - | SaveFavourites 108 - | SaveSettings 109 - | SaveSources 110 - | SaveTracks 111 100 | SignOut 112 101 ----------------------------------------- 113 102 -- Children ··· 119 108 | SourcesMsg Sources.Msg 120 109 | TracksMsg Tracks.Msg 121 110 ----------------------------------------- 122 - -- Children, Pt. 2 123 - ----------------------------------------- 124 - | ActiveQueueItemChanged (Maybe Queue.Item) 125 - | FillQueue 126 - ----------------------------------------- 127 111 -- Context Menu 128 112 ----------------------------------------- 129 113 | HideContextMenu 130 - | ShowTracksContextMenu Coordinates (List IdentifiedTrack) 131 114 ----------------------------------------- 132 115 -- Import / Export 133 116 ----------------------------------------- 134 117 | Export 135 118 | Import File 136 119 | ImportJson String 137 - | InsertDemo 138 120 | RequestImport 139 121 ----------------------------------------- 140 122 -- Notifications
+14 -24
src/Applications/UI/Equalizer.elm
··· 11 11 import Html.Styled.Attributes exposing (css, style) 12 12 import Html.Styled.Events 13 13 import Material.Icons.Navigation as Icons 14 - import Replying exposing (R3D3) 14 + import Return3 as Return exposing (..) 15 15 import Svg.Styled exposing (Svg, polygon, svg) 16 16 import Svg.Styled.Attributes 17 17 import Tachyons.Classes as T ··· 70 70 | ResetKnob Knob 71 71 72 72 73 - update : Msg -> Model -> R3D3 Model Msg Reply 73 + update : Msg -> Model -> Return Model Msg Reply 74 74 update msg model = 75 75 case msg of 76 76 ----------------------------------------- 77 77 -- Activate 78 78 ----------------------------------------- 79 79 ActivateKnob theKnob { pointer } -> 80 - let 81 - ( x, y ) = 82 - pointer.clientPos 83 - in 84 - ( { model | activeKnob = Just theKnob, startCoordinates = { x = x, y = y } } 85 - , Cmd.none 86 - , Nothing 87 - ) 80 + { model 81 + | activeKnob = Just theKnob 82 + , startCoordinates = Coordinates.fromTuple pointer.clientPos 83 + } 84 + |> return 88 85 89 86 ----------------------------------------- 90 87 -- Adjust ··· 143 140 in 144 141 case value of 145 142 Just ( knobType, v ) -> 146 - ( newModel 147 - , adjustKnob knobType v 148 - , Nothing 149 - ) 143 + returnCommandWithModel newModel (adjustKnob knobType v) 150 144 151 145 Nothing -> 152 - ( newModel 153 - , Cmd.none 154 - , Nothing 155 - ) 146 + return newModel 156 147 157 148 ----------------------------------------- 158 149 -- Deactivate 159 150 ----------------------------------------- 160 151 DeactivateKnob _ -> 161 - ( { model | activeKnob = Nothing } 162 - , Cmd.none 163 - , Just [ UI.Reply.SaveEnclosedUserData ] 164 - ) 152 + Return.replyWithModel 153 + { model | activeKnob = Nothing } 154 + UI.Reply.SaveEnclosedUserData 165 155 166 156 ----------------------------------------- 167 157 -- Reset ··· 213 203 ] 214 204 215 205 216 - reset : Model -> Knob -> Float -> R3D3 Model Msg Reply 206 + reset : Model -> Knob -> Float -> Return Model Msg Reply 217 207 reset newModel knobType value = 218 208 ( newModel 219 209 , adjustKnob knobType value 220 - , Just [ UI.Reply.SaveEnclosedUserData ] 210 + , [ UI.Reply.SaveEnclosedUserData ] 221 211 ) 222 212 223 213
+10 -5
src/Applications/UI/Notifications.elm
··· 1 - module UI.Notifications exposing (dismissNotification, showNotification, view) 1 + module UI.Notifications exposing (dismiss, show, showWithModel, view) 2 2 3 3 import Chunky exposing (..) 4 4 import Color.Ext as Color ··· 21 21 -- 📣 22 22 23 23 24 - dismissNotification : Model -> { id : Int } -> ( Model, Cmd Msg ) 25 - dismissNotification model { id } = 24 + dismiss : Model -> { id : Int } -> ( Model, Cmd Msg ) 25 + dismiss model { id } = 26 26 ( { model 27 27 | notifications = 28 28 List.map ··· 41 41 ) 42 42 43 43 44 - showNotification : Model -> Notification Msg -> ( Model, Cmd Msg ) 45 - showNotification model notification = 44 + show : Notification Msg -> Model -> ( Model, Cmd Msg ) 45 + show notification model = 46 46 ( { model | notifications = notification :: model.notifications } 47 47 -- Hide notification after a certain amount of time, 48 48 -- unless it's a sticky notification. ··· 54 54 (\_ -> UI.Core.DismissNotification { id = Notifications.id notification }) 55 55 (Process.sleep 3000) 56 56 ) 57 + 58 + 59 + showWithModel : Model -> Notification Msg -> ( Model, Cmd Msg ) 60 + showWithModel model notification = 61 + show notification model 57 62 58 63 59 64
+55 -70
src/Applications/UI/Queue.elm
··· 1 1 module UI.Queue exposing (initialModel, update) 2 2 3 + import Conditional exposing (..) 3 4 import List.Extra as List 4 5 import Queue exposing (..) 5 - import Replying exposing (R3D3) 6 - import Return3 as R3 6 + import Return3 as Return exposing (..) 7 7 import Time 8 8 import Tracks exposing (IdentifiedTrack) 9 9 import UI.Ports as Ports ··· 34 34 -- 📣 35 35 36 36 37 - update : Msg -> Model -> R3D3 Model Msg Reply 37 + update : Msg -> Model -> Return Model Msg Reply 38 38 update msg model = 39 39 case msg of 40 40 ------------------------------------ 41 41 -- Combos 42 42 ------------------------------------ 43 43 InjectFirstAndPlay identifiedTrack -> 44 - let 45 - ( a, b, _ ) = 46 - update (InjectFirst { showNotification = False } [ identifiedTrack ]) model 47 - 48 - ( x, y, z ) = 49 - update Shift a 50 - in 51 - ( x, Cmd.batch [ b, y ], z ) 44 + [ identifiedTrack ] 45 + |> InjectFirst { showNotification = False } 46 + |> updateWithModel model 47 + |> andThen (update Shift) 52 48 53 49 ------------------------------------ 54 50 -- Future ··· 71 67 model.future 72 68 tracks 73 69 in 74 - ( { model | future = items ++ cleanedFuture } 75 - , Cmd.none 76 - -- Show notification 77 - -------------------- 78 - , (if showNotification then 79 - [ case tracks of 80 - [ t ] -> 81 - ShowSuccessNotification ("__" ++ t.tags.title ++ "__ will be played next") 70 + [ case tracks of 71 + [ t ] -> 72 + ("__" ++ t.tags.title ++ "__ will be played next") 73 + |> ShowSuccessNotification 82 74 83 - list -> 84 - list 85 - |> List.length 86 - |> String.fromInt 87 - |> (\s -> "__" ++ s ++ " tracks__ will be played next") 88 - |> ShowSuccessNotification 89 - ] 90 - 91 - else 92 - [] 93 - ) 94 - |> List.append [ FillQueue ] 95 - |> Just 96 - ) 75 + list -> 76 + list 77 + |> List.length 78 + |> String.fromInt 79 + |> (\s -> "__" ++ s ++ " tracks__ will be played next") 80 + |> ShowSuccessNotification 81 + ] 82 + |> (\list -> ifThenElse showNotification list []) 83 + |> returnRepliesWithModel { model | future = items ++ cleanedFuture } 84 + |> addReply FillQueue 97 85 98 86 -- # InjectLast 99 87 -- > Add an item after the last manual entry ··· 118 106 cleanedFuture 119 107 |> List.filter (.manualEntry >> (==) True) 120 108 |> List.length 121 - in 122 - ( { model 123 - | future = 109 + 110 + newFuture = 124 111 [] 125 112 ++ List.take manualItems cleanedFuture 126 113 ++ items 127 114 ++ List.drop manualItems cleanedFuture 128 - } 129 - , Cmd.none 130 - -- Show notification 131 - -------------------- 132 - , (if showNotification then 133 - [ case tracks of 134 - [ t ] -> 135 - ShowSuccessNotification ("__" ++ t.tags.title ++ "__ was added to the queue") 115 + in 116 + [ case tracks of 117 + [ t ] -> 118 + ("__" ++ t.tags.title ++ "__ was added to the queue") 119 + |> ShowSuccessNotification 136 120 137 - list -> 138 - list 139 - |> List.length 140 - |> String.fromInt 141 - |> (\s -> "__" ++ s ++ " tracks__ were added to the queue") 142 - |> ShowSuccessNotification 143 - ] 144 - 145 - else 146 - [] 147 - ) 148 - |> List.append [ FillQueue ] 149 - |> Just 150 - ) 121 + list -> 122 + list 123 + |> List.length 124 + |> String.fromInt 125 + |> (\s -> "__" ++ s ++ " tracks__ were added to the queue") 126 + |> ShowSuccessNotification 127 + ] 128 + |> (\list -> ifThenElse showNotification list []) 129 + |> returnRepliesWithModel { model | future = newFuture } 130 + |> addReply FillQueue 151 131 152 132 ----------------------------------------- 153 133 -- Position ··· 193 173 -- > Fill the queue with items. 194 174 -- 195 175 Fill timestamp tracks -> 196 - ( fillQueue timestamp tracks model, Cmd.none, Nothing ) 176 + return (fillQueue timestamp tracks model) 197 177 198 178 -- # Reset 199 179 -- > Renew the queue, meaning that the auto-generated items in the queue ··· 204 184 newFuture = 205 185 List.filter (.manualEntry >> (==) True) model.future 206 186 in 207 - ( { model | future = newFuture, ignored = [] } 208 - , Cmd.none 209 - , Just [ FillQueue ] 210 - ) 187 + returnRepliesWithModel 188 + { model | future = newFuture, ignored = [] } 189 + [ FillQueue ] 211 190 212 191 ------------------------------------ 213 192 -- Settings ··· 215 194 ToggleRepeat -> 216 195 ( { model | repeat = not model.repeat } 217 196 , Ports.setRepeat (not model.repeat) 218 - , Just [ SaveEnclosedUserData ] 197 + , [ SaveEnclosedUserData ] 219 198 ) 220 199 221 200 ToggleShuffle -> 222 201 { model | shuffle = not model.shuffle } 223 202 |> update Reset 224 - |> Replying.addReply SaveEnclosedUserData 203 + |> addReply SaveEnclosedUserData 204 + 205 + 206 + updateWithModel : Model -> Msg -> Return Model Msg Reply 207 + updateWithModel model msg = 208 + update msg model 225 209 226 210 227 211 228 212 -- 📣 ░░ COMMON 229 213 230 214 231 - changeActiveItem : Maybe Item -> Model -> R3D3 Model Msg Reply 215 + changeActiveItem : Maybe Item -> Model -> Return Model Msg Reply 232 216 changeActiveItem maybeItem model = 233 - ( { model | activeItem = maybeItem } 234 - , Cmd.none 235 - , Just [ ActiveQueueItemChanged maybeItem, FillQueue ] 236 - ) 217 + returnRepliesWithModel 218 + { model | activeItem = maybeItem } 219 + [ ActiveQueueItemChanged maybeItem 220 + , FillQueue 221 + ] 237 222 238 223 239 224 fillQueue : Time.Posix -> List IdentifiedTrack -> Model -> Model
+28 -13
src/Applications/UI/Reply.elm
··· 17 17 18 18 19 19 type Reply 20 - = ActiveQueueItemChanged (Maybe Queue.Item) 21 - | AddSourceToCollection Source 20 + = ExternalAuth Authentication.Method String 21 + | GoToPage Page 22 + | ToggleLoadingScreen Switch 23 + ----------------------------------------- 24 + -- Context Menu 25 + ----------------------------------------- 26 + | ShowMoreAuthenticationOptions Coordinates 27 + | ShowTracksContextMenu Coordinates (List IdentifiedTrack) 28 + ----------------------------------------- 29 + -- Notifications 30 + ----------------------------------------- 22 31 | DismissNotification { id : Int } 23 - | ExternalAuth Authentication.Method String 32 + | ShowErrorNotification String 33 + | ShowSuccessNotification String 34 + | ShowWarningNotification String 35 + ----------------------------------------- 36 + -- Queue 37 + ----------------------------------------- 38 + | ActiveQueueItemChanged (Maybe Queue.Item) 24 39 | FillQueue 25 - | GoToPage Page 26 - | InsertDemo 27 40 | PlayTrack IdentifiedTrack 28 - | ProcessSources 29 - | RemoveTracksWithSourceId String 30 41 | ResetQueue 31 42 | ShiftQueue 43 + ----------------------------------------- 44 + -- Sources & Tracks 45 + ----------------------------------------- 46 + | AddSourceToCollection Source 47 + | ProcessSources 48 + | RemoveTracksWithSourceId String 49 + ----------------------------------------- 50 + -- User Data 51 + ----------------------------------------- 52 + | InsertDemo 32 53 | SaveEnclosedUserData 33 54 | SaveFavourites 34 55 | SaveSettings 35 56 | SaveSources 36 57 | SaveTracks 37 - | ShowErrorNotification String 38 - | ShowMoreAuthenticationOptions Coordinates 39 - | ShowSuccessNotification String 40 - | ShowWarningNotification String 41 - | ShowTracksContextMenu Coordinates (List IdentifiedTrack) 42 - | ToggleLoadingScreen Switch
+19 -26
src/Applications/UI/Sources.elm
··· 9 9 import Material.Icons.Navigation as Icons 10 10 import Material.Icons.Notification as Icons 11 11 import Notifications exposing (Notification) 12 - import Replying exposing (R3D3, return) 13 - import Return2 14 - import Return3 12 + import Return3 as Return exposing (..) 15 13 import Sources exposing (..) 16 14 import Sources.Encoding 17 15 import Sources.Services as Services ··· 21 19 import UI.List 22 20 import UI.Navigation exposing (..) 23 21 import UI.Page 24 - import UI.Reply exposing (Reply) 22 + import UI.Reply exposing (Reply(..)) 25 23 import UI.Sources.Form as Form 26 24 import UI.Sources.Page as Sources exposing (..) 27 25 ··· 69 67 | UpdateSourceData Json.Value 70 68 71 69 72 - update : Msg -> Model -> R3D3 Model Msg Reply 70 + update : Msg -> Model -> Return Model Msg Reply 73 71 update msg model = 74 72 case msg of 75 73 Bypass -> 76 - Return3.withNothing model 74 + return model 77 75 78 76 FinishedProcessing -> 79 - ( { model | isProcessing = False } 80 - , Cmd.none 81 - , Maybe.map 82 - (\id -> [ UI.Reply.DismissNotification { id = id } ]) 83 - model.processingNotificationId 84 - ) 77 + model.processingNotificationId 78 + |> Maybe.map (\id -> [ DismissNotification { id = id } ]) 79 + |> Maybe.withDefault [] 80 + |> Return.repliesWithModel { model | isProcessing = False } 85 81 86 82 Process -> 87 83 if List.isEmpty model.collection then 88 - Return3.withNothing model 84 + return model 89 85 90 86 else 91 - ( { model | isProcessing = True } 92 - , Cmd.none 93 - , Just [ UI.Reply.ProcessSources ] 94 - ) 87 + returnReplyWithModel { model | isProcessing = True } ProcessSources 95 88 96 89 ----------------------------------------- 97 90 -- Children ··· 99 92 FormMsg sub -> 100 93 model.form 101 94 |> Form.update sub 102 - |> Return3.mapModel (\f -> { model | form = f }) 103 - |> Return3.mapCmd FormMsg 95 + |> mapModel (\f -> { model | form = f }) 96 + |> mapCmd FormMsg 104 97 105 98 ----------------------------------------- 106 99 -- Collection ··· 111 104 |> List.singleton 112 105 |> List.append model.collection 113 106 |> (\c -> { model | collection = c, isProcessing = True }) 114 - |> Return2.withNoCmd 115 - |> Return3.withReply 107 + |> return 108 + |> addReplies 116 109 [ UI.Reply.SaveSources 117 110 , UI.Reply.ProcessSources 118 111 ] ··· 121 114 model.collection 122 115 |> List.filter (.id >> (/=) sourceId) 123 116 |> (\c -> { model | collection = c }) 124 - |> Return2.withNoCmd 125 - |> Return3.withReply 117 + |> return 118 + |> addReplies 126 119 [ UI.Reply.SaveSources 127 120 , UI.Reply.RemoveTracksWithSourceId sourceId 128 121 ] ··· 144 137 ) 145 138 |> Maybe.map (\col -> { model | collection = col }) 146 139 |> Maybe.withDefault model 147 - |> Return2.withNoCmd 148 - |> Return3.withReply [ UI.Reply.SaveSources ] 140 + |> return 141 + |> addReply UI.Reply.SaveSources 149 142 150 143 151 144 ··· 176 169 UI.Navigation.local 177 170 [ ( Icon Icons.add 178 171 , Label "Add a new source" Shown 179 - , GoToPage (UI.Page.Sources New) 172 + , UI.Navigation.GoToPage (UI.Page.Sources New) 180 173 ) 181 174 182 175 -- Process
+6 -7
src/Applications/UI/Sources/Form.elm
··· 11 11 import List.Extra as List 12 12 import Material.Icons.Alert as Icons 13 13 import Material.Icons.Navigation as Icons 14 - import Replying exposing (R3D3) 14 + import Return3 as Return exposing (..) 15 15 import Sources exposing (..) 16 16 import Sources.Services as Services 17 17 import Sources.Services.Common ··· 71 71 | TakeStepBackwards 72 72 73 73 74 - update : Msg -> Model -> R3D3 Model Msg Reply 74 + update : Msg -> Model -> Return Model Msg Reply 75 75 update msg model = 76 76 ( ----------------------------------------- 77 77 -- Model ··· 124 124 ----------------------------------------- 125 125 , case msg of 126 126 AddSource -> 127 - Just 128 - [ UI.Reply.GoToPage (UI.Page.Sources UI.Sources.Page.Index) 129 - , UI.Reply.AddSourceToCollection model.context 130 - ] 127 + [ UI.Reply.GoToPage (UI.Page.Sources UI.Sources.Page.Index) 128 + , UI.Reply.AddSourceToCollection model.context 129 + ] 131 130 132 131 _ -> 133 - Nothing 132 + [] 134 133 ) 135 134 136 135
+35 -45
src/Applications/UI/Tracks.elm
··· 5 5 import Color 6 6 import Color.Ext as Color 7 7 import Common exposing (Switch(..)) 8 + import Coordinates 8 9 import Css 9 10 import Html.Styled as Html exposing (Html, text) 10 11 import Html.Styled.Attributes exposing (css, placeholder, tabindex, title, value) ··· 21 22 import Material.Icons.Editor as Icons 22 23 import Material.Icons.Image as Icons 23 24 import Maybe.Extra as Maybe 24 - import Replying as N5 exposing (R3D3) 25 - import Return2 as R2 26 - import Return3 as R3 25 + import Return3 as Return exposing (..) 27 26 import Tachyons.Classes as T 28 27 import Tracks exposing (..) 29 28 import Tracks.Collection as Collection exposing (..) ··· 64 63 -- 📣 65 64 66 65 67 - update : Msg -> Model -> R3D3 Model Msg Reply 66 + update : Msg -> Model -> Return Model Msg Reply 68 67 update msg model = 69 68 case msg of 70 69 Bypass -> 71 - R3.withNothing model 70 + return model 72 71 73 72 InfiniteListMsg infiniteList -> 74 - R3.withNothing { model | infiniteList = infiniteList } 73 + return { model | infiniteList = infiniteList } 75 74 76 75 Reply replies -> 77 - ( model 78 - , Cmd.none 79 - , Just replies 80 - ) 76 + returnRepliesWithModel model replies 81 77 82 78 ShowContextMenu track mouseEvent -> 83 - let 84 - ( x, y ) = 85 - mouseEvent.clientPos 86 - 87 - tracks = 88 - [ track ] 89 - in 90 - ( model 91 - , Cmd.none 92 - , Just [ ShowTracksContextMenu { x = x, y = y } tracks ] 93 - ) 79 + [ track ] 80 + |> ShowTracksContextMenu (Coordinates.fromTuple mouseEvent.clientPos) 81 + |> returnReplyWithModel model 94 82 95 83 ScrollToNowPlaying -> 96 84 let ··· 112 100 List -> 113 101 identifiedTrack 114 102 |> UI.Tracks.Scene.List.scrollToNowPlaying 115 - |> R2.withModel model 116 - |> R3.withNoReply 103 + |> Return.commandWithModel model 117 104 118 105 Nothing -> 119 - R3.withNothing model 106 + return model 120 107 121 108 SetEnabledSourceIds sourceIds -> 122 - R3.withNothing { model | enabledSourceIds = sourceIds } 109 + return { model | enabledSourceIds = sourceIds } 123 110 124 111 SetNowPlaying maybeIdentifiedTrack -> 125 112 let ··· 149 136 in 150 137 { model | sortBy = property, sortDirection = sortDir } 151 138 |> reviseCollection arrange 152 - |> N5.addReply SaveEnclosedUserData 139 + |> addReply SaveEnclosedUserData 153 140 154 141 ToggleHideDuplicates -> 155 142 { model | hideDuplicates = not model.hideDuplicates } 156 143 |> reviseCollection arrange 157 - |> N5.addReply SaveSettings 144 + |> addReply SaveSettings 158 145 159 146 ----------------------------------------- 160 147 -- Collection ··· 204 191 model.collection.harvested 205 192 |> List.getAt index 206 193 |> Maybe.map (toggleFavourite model) 207 - |> Maybe.withDefault (R3.withNothing model) 194 + |> Maybe.withDefault (return model) 208 195 209 196 -- > Filter collection by favourites only {toggle} 210 197 ToggleFavouritesOnly -> 211 198 { model | favouritesOnly = not model.favouritesOnly } 212 199 |> reviseCollection harvest 213 - |> N5.addReply SaveEnclosedUserData 200 + |> addReply SaveEnclosedUserData 214 201 215 202 ----------------------------------------- 216 203 -- Search ··· 218 205 ClearSearch -> 219 206 { model | searchResults = Nothing, searchTerm = Nothing } 220 207 |> reviseCollection harvest 221 - |> N5.addReply SaveEnclosedUserData 208 + |> addReply SaveEnclosedUserData 222 209 223 210 Search -> 224 211 case ( model.searchTerm, model.searchResults ) of 225 212 ( Just term, _ ) -> 226 - ( model 227 - , UI.Ports.giveBrain Alien.SearchTracks (Json.Encode.string <| String.trim term) 228 - , Nothing 229 - ) 213 + term 214 + |> String.trim 215 + |> Json.Encode.string 216 + |> UI.Ports.giveBrain Alien.SearchTracks 217 + |> Return.commandWithModel model 230 218 231 219 ( Nothing, Just _ ) -> 232 220 reviseCollection harvest { model | searchResults = Nothing } 233 221 234 222 ( Nothing, Nothing ) -> 235 - R3.withNothing model 223 + return model 236 224 237 225 SetSearchResults json -> 238 226 case model.searchTerm of ··· 242 230 |> Result.withDefault [] 243 231 |> (\results -> { model | searchResults = Just results }) 244 232 |> reviseCollection harvest 245 - |> N5.addReply (ToggleLoadingScreen Off) 233 + |> addReply (ToggleLoadingScreen Off) 246 234 247 235 Nothing -> 248 - R3.withNothing model 236 + return model 249 237 250 238 SetSearchTerm term -> 251 - R3.withReply 239 + addReplies 252 240 [ SaveEnclosedUserData ] 253 241 (case String.trim term of 254 242 "" -> 255 - R2.withNoCmd { model | searchTerm = Nothing } 243 + return { model | searchTerm = Nothing } 256 244 257 245 _ -> 258 - R2.withNoCmd { model | searchTerm = Just term } 246 + return { model | searchTerm = Just term } 259 247 ) 260 248 261 249 ··· 278 266 ) 279 267 280 268 281 - resolveParcel : Model -> Parcel -> R3D3 Model Msg Reply 269 + resolveParcel : Model -> Parcel -> Return Model Msg Reply 282 270 resolveParcel model ( _, newCollection ) = 283 271 let 284 272 modelWithNewCollection = ··· 291 279 List.map (Tuple.second >> .id) newCollection.harvested 292 280 in 293 281 ( modelWithNewCollection 282 + ---------- 294 283 -- Command 295 284 ---------- 296 285 , if oldHarvest /= newHarvest then ··· 300 289 301 290 else 302 291 Cmd.none 292 + -------- 303 293 -- Reply 304 294 -------- 305 - , (Just << Maybe.values) 295 + , Maybe.values 306 296 [ if model.collection.untouched /= newCollection.untouched then 307 297 Just SaveTracks 308 298 ··· 319 309 ) 320 310 321 311 322 - reviseCollection : (Parcel -> Parcel) -> Model -> R3D3 Model Msg Reply 312 + reviseCollection : (Parcel -> Parcel) -> Model -> Return Model Msg Reply 323 313 reviseCollection collector model = 324 314 model 325 315 |> makeParcel ··· 331 321 -- 📣 ░░ FAVOURITES 332 322 333 323 334 - toggleFavourite : Model -> IdentifiedTrack -> R3D3 Model Msg Reply 324 + toggleFavourite : Model -> IdentifiedTrack -> Return Model Msg Reply 335 325 toggleFavourite model ( i, t ) = 336 326 let 337 327 newFavourites = ··· 346 336 in 347 337 { model | favourites = newFavourites } 348 338 |> reviseCollection effect 349 - |> N5.addReply SaveFavourites 339 + |> addReply SaveFavourites 350 340 351 341 352 342
+15 -44
src/Applications/UI/UserData.elm
··· 8 8 import Json.Encode 9 9 import Maybe.Extra as Maybe 10 10 import Notifications 11 - import Replying as N5 exposing (R3D3) 12 - import Return3 as R3 11 + import Return3 exposing (..) 13 12 import Sources 14 13 import Sources.Encoding as Sources 15 14 import Tracks exposing (emptyCollection) ··· 52 51 } 53 52 54 53 55 - importHypaethral : Json.Value -> UI.Core.Model -> R3D3 UI.Core.Model UI.Core.Msg UI.Reply 54 + importHypaethral : Json.Value -> UI.Core.Model -> Return UI.Core.Model UI.Core.Msg UI.Reply 56 55 importHypaethral value model = 57 56 case decodeHypaethral value of 58 57 Ok data -> ··· 67 66 |> Just 68 67 |> (\c -> { backdrop | chosen = c }) 69 68 70 - ( sourcesModel, sourcesCmd, sourcesReply ) = 69 + ( sourcesModel, sourcesCmd, sourcesReplies ) = 71 70 importSources model.sources data 72 71 73 - ( tracksModel, tracksCmd, tracksReply ) = 72 + ( tracksModel, tracksCmd, tracksReplies ) = 74 73 importTracks model.tracks data 75 74 in 76 75 ( { model ··· 82 81 [ Cmd.map UI.Core.SourcesMsg sourcesCmd 83 82 , Cmd.map UI.Core.TracksMsg tracksCmd 84 83 ] 85 - , mergeReplies 86 - [ sourcesReply 87 - , tracksReply 88 - ] 84 + , sourcesReplies ++ tracksReplies 89 85 ) 90 86 91 87 Err err -> 92 88 err 93 89 |> Json.errorToString 94 90 |> Notifications.error 95 - |> UI.Notifications.showNotification model 96 - |> R3.withNoReply 91 + |> UI.Notifications.showWithModel model 92 + |> Return3.from2 97 93 98 94 99 95 100 96 -- ㊙️ 101 97 102 98 103 - importSources : Sources.Model -> HypaethralUserData -> R3D3 Sources.Model Sources.Msg UI.Reply 99 + importSources : Sources.Model -> HypaethralUserData -> Return Sources.Model Sources.Msg UI.Reply 104 100 importSources model data = 105 - ( { model 106 - | collection = data.sources 107 - } 108 - , Cmd.none 109 - , Nothing 110 - ) 101 + return { model | collection = data.sources } 111 102 112 103 113 - importTracks : Tracks.Model -> HypaethralUserData -> R3D3 Tracks.Model Tracks.Msg UI.Reply 104 + importTracks : Tracks.Model -> HypaethralUserData -> Return Tracks.Model Tracks.Msg UI.Reply 114 105 importTracks model data = 115 106 let 116 107 adjustedModel = ··· 127 118 identity 128 119 129 120 Nothing -> 130 - N5.addReply (UI.ToggleLoadingScreen Off) 121 + addReply (UI.ToggleLoadingScreen Off) 131 122 in 132 123 adjustedModel 133 124 |> Tracks.makeParcel 134 125 |> Tracks.identify 135 126 |> Tracks.resolveParcel adjustedModel 136 - |> N5.andThen3 (Tracks.update Tracks.Search) 127 + |> andThen (Tracks.update Tracks.Search) 137 128 |> addReplyIfNecessary 138 129 139 130 ··· 162 153 } 163 154 164 155 165 - importEnclosed : Json.Value -> UI.Core.Model -> R3D3 UI.Core.Model UI.Core.Msg UI.Reply 156 + importEnclosed : Json.Value -> UI.Core.Model -> Return UI.Core.Model UI.Core.Msg UI.Reply 166 157 importEnclosed value model = 167 158 let 168 159 { equalizer, queue, tracks } = ··· 202 193 [ Cmd.map UI.Core.EqualizerMsg (Equalizer.adjustAllKnobs newEqualizer) 203 194 , Ports.setRepeat data.repeat 204 195 ] 205 - , Nothing 196 + , [] 206 197 ) 207 198 208 199 Err err -> 209 - R3.withNothing model 210 - 211 - 212 - 213 - -- ㊙️ 214 - 215 - 216 - mergeReplies : List (Maybe (List UI.Reply)) -> Maybe (List UI.Reply) 217 - mergeReplies list = 218 - list 219 - |> List.foldl 220 - (\maybeReply replies -> 221 - case maybeReply of 222 - Just r -> 223 - replies ++ r 224 - 225 - Nothing -> 226 - replies 227 - ) 228 - [] 229 - |> Just 200 + return model 230 201 231 202 232 203
-128
src/Library/Replying.elm
··· 1 - module Replying exposing (R3D3, Updator, addReply, andThen2, andThen3, do, reducto, return, updateChild) 2 - 3 - import Return2 4 - import Return3 5 - import Task 6 - 7 - 8 - 9 - -- 🌳 10 - 11 - 12 - type alias R3D3 model msg reply = 13 - ( model, Cmd msg, Maybe (List reply) ) 14 - 15 - 16 - type alias Updator msg model = 17 - msg -> model -> ( model, Cmd msg ) 18 - 19 - 20 - 21 - -- 🔱 22 - 23 - 24 - {-| Add a reply. 25 - -} 26 - addReply : reply -> R3D3 model msg reply -> R3D3 model msg reply 27 - addReply reply ( model, cmd, maybeReplies ) = 28 - ( model 29 - , cmd 30 - , maybeReplies 31 - |> Maybe.map (\l -> l ++ [ reply ]) 32 - |> Maybe.withDefault [ reply ] 33 - |> Just 34 - ) 35 - 36 - 37 - {-| Add a list of replies in front of the existing replies. 38 - -} 39 - addRepliesInFront : List reply -> R3D3 model msg reply -> R3D3 model msg reply 40 - addRepliesInFront replies ( model, cmd, maybeReplies ) = 41 - ( model 42 - , cmd 43 - , maybeReplies 44 - |> Maybe.map (\l -> replies ++ l) 45 - |> Maybe.withDefault replies 46 - |> Just 47 - ) 48 - 49 - 50 - {-| Chain R2D2 `update` calls. 51 - -} 52 - andThen2 : (model -> ( model, Cmd msg )) -> ( model, Cmd msg ) -> ( model, Cmd msg ) 53 - andThen2 fn ( model, cmd ) = 54 - Return2.addCmd cmd (fn model) 55 - 56 - 57 - {-| Chain R3D3 `update` calls. 58 - -} 59 - andThen3 : (model -> R3D3 model msg reply) -> R3D3 model msg reply -> R3D3 model msg reply 60 - andThen3 fn ( model, cmd, maybeReplies ) = 61 - case maybeReplies of 62 - Just replies -> 63 - Return3.addCmd cmd (fn model) |> addRepliesInFront replies 64 - 65 - Nothing -> 66 - Return3.addCmd cmd (fn model) 67 - 68 - 69 - {-| Reduce a `R3D3` to a `R2D2`. 70 - -} 71 - reducto : Updator msg model -> (reply -> msg) -> R3D3 model msg reply -> ( model, Cmd msg ) 72 - reducto updator translator ( model, cmd, maybeReplies ) = 73 - maybeReplies 74 - |> Maybe.withDefault [] 75 - |> List.map translator 76 - |> List.foldl (andThenUpdate updator) ( model, cmd ) 77 - 78 - 79 - {-| Convenience function for returning the standard ( model, Cmd msg ) tuple. 80 - -} 81 - return : model -> Cmd msg -> ( model, Cmd msg ) 82 - return = 83 - Tuple.pair 84 - 85 - 86 - {-| Handle the state of a child. 87 - 88 - NOTE: Replies are performed from left to right. 89 - 90 - -} 91 - updateChild : 92 - (msg -> model -> ( model, Cmd msg )) 93 - -> (reply -> msg) 94 - -> 95 - { mapCmd : childMsg -> msg 96 - , mapModel : childModel -> model 97 - , update : childMsg -> childModel -> R3D3 childModel childMsg reply 98 - } 99 - -> { model : childModel, msg : childMsg } 100 - -> ( model, Cmd msg ) 101 - updateChild update translateReply context data = 102 - data.model 103 - |> context.update data.msg 104 - |> Return3.mapCmd context.mapCmd 105 - |> Return3.mapModel context.mapModel 106 - |> reducto update translateReply 107 - 108 - 109 - 110 - -- 🔱 ░░ TASKS 111 - 112 - 113 - do : msg -> Cmd msg 114 - do msg = 115 - Task.perform identity (Task.succeed msg) 116 - 117 - 118 - 119 - ----------------------------------------- 120 - -- ㊙️ 121 - ----------------------------------------- 122 - 123 - 124 - andThenUpdate : Updator msg model -> msg -> ( model, Cmd msg ) -> ( model, Cmd msg ) 125 - andThenUpdate updator msg ( model, cmd ) = 126 - model 127 - |> updator msg 128 - |> Tuple.mapSecond (\c -> Cmd.batch [ cmd, c ])
+59
src/Library/Return2.elm
··· 1 + module Return2 exposing (Return, addCommand, andThen, mapCommand, mapModel, return, returnWithModel, withModel) 2 + 3 + -- 🌳 4 + 5 + 6 + type alias Return model msg = 7 + ( model, Cmd msg ) 8 + 9 + 10 + 11 + -- 🔱 12 + 13 + 14 + andThen : (model -> Return model msg) -> Return model msg -> Return model msg 15 + andThen update ( model, cmd ) = 16 + let 17 + ( newModel, newCmd ) = 18 + update model 19 + in 20 + ( newModel 21 + , Cmd.batch [ cmd, newCmd ] 22 + ) 23 + 24 + 25 + return : model -> Return model msg 26 + return model = 27 + ( model, Cmd.none ) 28 + 29 + 30 + returnWithModel : model -> Cmd msg -> Return model msg 31 + returnWithModel = 32 + Tuple.pair 33 + 34 + 35 + 36 + -- 🔱 ░░░ ALIASES 37 + 38 + 39 + withModel = 40 + returnWithModel 41 + 42 + 43 + 44 + -- 🔱 ░░░ MODIFICATIONS 45 + 46 + 47 + addCommand : Cmd msg -> Return model msg -> Return model msg 48 + addCommand cmd ( model, earlierCmd ) = 49 + ( model 50 + , Cmd.batch [ earlierCmd, cmd ] 51 + ) 52 + 53 + 54 + mapCommand = 55 + Tuple.mapSecond 56 + 57 + 58 + mapModel = 59 + Tuple.mapFirst
+175
src/Library/Return3.elm
··· 1 + module Return3 exposing (Return, addCommand, addReplies, addReply, andThen, commandWithModel, from2, fromDebouncer, mapCmd, mapModel, mapReplies, repliesWithModel, replyWithModel, return, returnCommandWithModel, returnRepliesWithModel, returnReplyWithModel, three, wield, wieldNested) 2 + 3 + import Maybe.Extra as Maybe 4 + import Return2 5 + 6 + 7 + 8 + -- 🌳 9 + 10 + 11 + type alias Return model msg reply = 12 + ( model, Cmd msg, List reply ) 13 + 14 + 15 + 16 + -- 🔱 17 + 18 + 19 + andThen : (model -> Return model msg reply) -> Return model msg reply -> Return model msg reply 20 + andThen update ( model, cmd, replies ) = 21 + let 22 + ( newModel, newCmd, newReplies ) = 23 + update model 24 + in 25 + ( newModel 26 + , Cmd.batch [ cmd, newCmd ] 27 + , replies ++ newReplies 28 + ) 29 + 30 + 31 + from2 : Return2.Return model msg -> Return model msg reply 32 + from2 ( model, cmd ) = 33 + ( model, cmd, [] ) 34 + 35 + 36 + return : model -> Return model msg reply 37 + return model = 38 + ( model, Cmd.none, [] ) 39 + 40 + 41 + returnCommandWithModel : model -> Cmd msg -> Return model msg reply 42 + returnCommandWithModel model cmd = 43 + ( model, cmd, [] ) 44 + 45 + 46 + returnRepliesWithModel : model -> List reply -> Return model msg reply 47 + returnRepliesWithModel model replies = 48 + ( model, Cmd.none, replies ) 49 + 50 + 51 + returnReplyWithModel : model -> reply -> Return model msg reply 52 + returnReplyWithModel model reply = 53 + ( model, Cmd.none, [ reply ] ) 54 + 55 + 56 + three : model -> Cmd msg -> List reply -> Return model msg reply 57 + three model cmd replies = 58 + ( model, cmd, replies ) 59 + 60 + 61 + 62 + -- 🔱 ░░░ ALIASES 63 + 64 + 65 + commandWithModel = 66 + returnCommandWithModel 67 + 68 + 69 + repliesWithModel = 70 + returnRepliesWithModel 71 + 72 + 73 + replyWithModel = 74 + returnReplyWithModel 75 + 76 + 77 + 78 + -- 🔱 ░░░ MODIFICATIONS 79 + 80 + 81 + addCommand : Cmd msg -> Return model msg reply -> Return model msg reply 82 + addCommand cmd ( model, earlierCmd, replies ) = 83 + ( model 84 + , Cmd.batch [ earlierCmd, cmd ] 85 + , replies 86 + ) 87 + 88 + 89 + addReply : reply -> Return model msg reply -> Return model msg reply 90 + addReply reply = 91 + addReplies [ reply ] 92 + 93 + 94 + addReplies : List reply -> Return model msg reply -> Return model msg reply 95 + addReplies replies ( model, cmd, earlierReplies ) = 96 + ( model 97 + , cmd 98 + , earlierReplies ++ replies 99 + ) 100 + 101 + 102 + mapCmd : (msg -> newMsg) -> Return model msg reply -> Return model newMsg reply 103 + mapCmd fn ( model, cmd, replies ) = 104 + ( model, Cmd.map fn cmd, replies ) 105 + 106 + 107 + mapModel : (model -> newModel) -> Return model msg reply -> Return newModel msg reply 108 + mapModel fn ( model, cmd, replies ) = 109 + ( fn model, cmd, replies ) 110 + 111 + 112 + mapReplies : (reply -> newReply) -> Return model msg reply -> Return model msg newReply 113 + mapReplies fn ( model, cmd, replies ) = 114 + ( model, cmd, List.map fn replies ) 115 + 116 + 117 + 118 + -- 🔱 ░░░ WIELDING 119 + 120 + 121 + wield : 122 + (reply -> model -> Return2.Return model msg) 123 + -> Return model msg reply 124 + -> Return2.Return model msg 125 + wield replyTransformer ( model, cmd, replies ) = 126 + List.foldl 127 + (\reply ( accModel, accCmd ) -> 128 + Tuple.mapSecond 129 + (\c -> Cmd.batch [ accCmd, c ]) 130 + (replyTransformer reply accModel) 131 + ) 132 + ( model 133 + , cmd 134 + ) 135 + replies 136 + 137 + 138 + wieldNested : 139 + (reply -> model -> Return2.Return model msg) 140 + -> 141 + { mapCmd : subMsg -> msg 142 + , mapModel : subModel -> model 143 + , update : subMsg -> subModel -> ( subModel, Cmd subMsg, List reply ) 144 + } 145 + -> 146 + { model : subModel 147 + , msg : subMsg 148 + } 149 + -> Return2.Return model msg 150 + wieldNested replyTransformer a b = 151 + let 152 + cmdTransformer = 153 + a.mapCmd 154 + 155 + modelTransformer = 156 + a.mapModel 157 + 158 + ( subModel, subCmd, replies ) = 159 + a.update b.msg b.model 160 + in 161 + wield 162 + replyTransformer 163 + ( modelTransformer subModel 164 + , Cmd.map cmdTransformer subCmd 165 + , replies 166 + ) 167 + 168 + 169 + 170 + -- 🔱 ░░░ DEBOUNCER 171 + 172 + 173 + fromDebouncer : ( model, Cmd msg, Maybe reply ) -> Return model msg reply 174 + fromDebouncer ( a, b, c ) = 175 + ( a, b, Maybe.unwrap [] List.singleton c )
+12
src/Library/Task/Extra.elm
··· 1 + module Task.Extra exposing (do) 2 + 3 + import Task 4 + 5 + 6 + 7 + -- 🔱 8 + 9 + 10 + do : msg -> Cmd msg 11 + do msg = 12 + Task.perform identity (Task.succeed msg)