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

Configure Feed

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

Split up the saving of hypaethral user data

+315 -180
+1
elm.json
··· 8 8 "dependencies": { 9 9 "direct": { 10 10 "Chadtech/return": "1.0.2", 11 + "NoRedInk/elm-json-decode-pipeline": "1.0.0", 11 12 "avh4/elm-color": "1.0.0", 12 13 "danfishgold/base64-bytes": "1.0.1", 13 14 "danmarcab/material-icons": "1.0.0",
+123 -21
src/Applications/Brain.elm
··· 1 1 module Brain exposing (main) 2 2 3 3 import Alien 4 + import Authentication exposing (HypaethralUserData) 4 5 import Brain.Authentication as Authentication 5 6 import Brain.Core exposing (..) 6 7 import Brain.Ports 7 8 import Brain.Reply as Reply exposing (Reply(..)) 8 9 import Brain.Sources.Processing as Processing 9 10 import Brain.Sources.Processing.Common as Processing 10 - import Json.Decode 11 + import Json.Decode as Json 12 + import Json.Decode.Pipeline exposing (optional) 13 + import Json.Encode 11 14 import Replying exposing (return) 15 + import Sources.Encoding as Sources 12 16 import Sources.Processing.Encoding as Processing 17 + import Tracks.Encoding as Tracks 13 18 14 19 15 20 ··· 35 40 -- Initial model 36 41 ----------------------------------------- 37 42 { authentication = Authentication.initialModel 43 + , hypaethralUserData = Authentication.emptyHypaethralUserData 38 44 , processing = Processing.initialModel 39 45 } 40 46 ----------------------------------------- ··· 67 73 ----------------------------------------- 68 74 -- Children 69 75 ----------------------------------------- 76 + AuthenticationMsg Authentication.PerformSignOut -> 77 + -- When signing out, remove all traces of the user's data. 78 + updateAuthentication 79 + { model | hypaethralUserData = Authentication.emptyHypaethralUserData } 80 + Authentication.PerformSignOut 81 + 70 82 AuthenticationMsg sub -> 71 - updateChild 72 - { mapCmd = AuthenticationMsg 73 - , mapModel = \child -> { model | authentication = child } 74 - , update = Authentication.update 75 - } 76 - { model = model.authentication 77 - , msg = sub 78 - } 83 + updateAuthentication model sub 79 84 80 85 ProcessingMsg sub -> 81 - updateChild 82 - { mapCmd = ProcessingMsg 83 - , mapModel = \child -> { model | processing = child } 84 - , update = Processing.update 85 - } 86 - { model = model.processing 87 - , msg = sub 88 - } 86 + updateProcessing model sub 87 + 88 + ----------------------------------------- 89 + -- User data 90 + ----------------------------------------- 91 + LoadHypaethralUserData value -> 92 + let 93 + decodedData = 94 + value 95 + |> Authentication.decode 96 + |> Result.withDefault model.hypaethralUserData 97 + in 98 + ( { model | hypaethralUserData = decodedData } 99 + , Brain.Ports.toUI (Alien.broadcast Alien.LoadHypaethralUserData value) 100 + ) 101 + 102 + SaveFavourites value -> 103 + value 104 + |> Json.decodeValue (Json.list Tracks.favouriteDecoder) 105 + |> Result.withDefault model.hypaethralUserData.favourites 106 + |> hypaethralLenses.setFavourites model 107 + |> saveHypaethralData 108 + 109 + SaveSources value -> 110 + value 111 + |> Json.decodeValue (Json.list Sources.decoder) 112 + |> Result.withDefault model.hypaethralUserData.sources 113 + |> hypaethralLenses.setSources model 114 + |> saveHypaethralData 115 + 116 + SaveTracks value -> 117 + value 118 + |> Json.decodeValue (Json.list Tracks.trackDecoder) 119 + |> Result.withDefault model.hypaethralUserData.tracks 120 + |> hypaethralLenses.setTracks model 121 + |> saveHypaethralData 89 122 90 123 91 124 ··· 101 134 ----------------------------------------- 102 135 -- To UI 103 136 ----------------------------------------- 137 + GiveUI Alien.LoadHypaethralUserData data -> 138 + LoadHypaethralUserData data 139 + 104 140 GiveUI tag data -> 105 141 NotifyUI (Alien.broadcast tag data) 106 142 ··· 112 148 Replying.updateChild update translateReply 113 149 114 150 151 + updateAuthentication : Model -> Authentication.Msg -> ( Model, Cmd Msg ) 152 + updateAuthentication model sub = 153 + updateChild 154 + { mapCmd = AuthenticationMsg 155 + , mapModel = \child -> { model | authentication = child } 156 + , update = Authentication.update 157 + } 158 + { model = model.authentication 159 + , msg = sub 160 + } 161 + 162 + 163 + updateProcessing : Model -> Processing.Msg -> ( Model, Cmd Msg ) 164 + updateProcessing model sub = 165 + updateChild 166 + { mapCmd = ProcessingMsg 167 + , mapModel = \child -> { model | processing = child } 168 + , update = Processing.update 169 + } 170 + { model = model.processing 171 + , msg = sub 172 + } 173 + 174 + 175 + 176 + -- 📣 ░░ USER DATA 177 + 178 + 179 + hypaethralLenses = 180 + { setFavourites = makeHypaethralLens (\h f -> { h | favourites = f }) 181 + , setSources = makeHypaethralLens (\h s -> { h | sources = s }) 182 + , setTracks = makeHypaethralLens (\h t -> { h | tracks = t }) 183 + } 184 + 185 + 186 + makeHypaethralLens : (HypaethralUserData -> a -> HypaethralUserData) -> Model -> a -> Model 187 + makeHypaethralLens setter model value = 188 + let 189 + h = 190 + model.hypaethralUserData 191 + in 192 + { model | hypaethralUserData = setter h value } 193 + 194 + 195 + saveHypaethralData : Model -> ( Model, Cmd Msg ) 196 + saveHypaethralData model = 197 + let 198 + { favourites, sources, tracks } = 199 + model.hypaethralUserData 200 + in 201 + [ ( "favourites", Json.Encode.list Tracks.encodeFavourite favourites ) 202 + , ( "sources", Json.Encode.list Sources.encode sources ) 203 + , ( "tracks", Json.Encode.list Tracks.encodeTrack tracks ) 204 + ] 205 + |> Json.Encode.object 206 + |> Authentication.SaveHypaethralData 207 + |> AuthenticationMsg 208 + |> (\msg -> update msg model) 209 + 210 + 115 211 116 212 -- 📰 117 213 ··· 141 237 Just Alien.ProcessSources -> 142 238 -- Only proceed to the processing if we got all the necessary data, 143 239 -- otherwise report an error in the UI. 144 - case Json.Decode.decodeValue Processing.argumentsDecoder event.data of 240 + case Json.decodeValue Processing.argumentsDecoder event.data of 145 241 Ok arguments -> 146 242 arguments 147 243 |> Processing.Process ··· 149 245 150 246 Err error -> 151 247 error 152 - |> Json.Decode.errorToString 248 + |> Json.errorToString 153 249 |> Alien.report Alien.ReportGenericError 154 250 |> NotifyUI 155 251 156 252 Just Alien.SaveEnclosedUserData -> 157 253 AuthenticationMsg (Authentication.SaveEnclosedData event.data) 158 254 159 - Just Alien.SaveHypaethralUserData -> 160 - AuthenticationMsg (Authentication.SaveHypaethralData event.data) 255 + Just Alien.SaveFavourites -> 256 + SaveFavourites event.data 257 + 258 + Just Alien.SaveSources -> 259 + SaveSources event.data 260 + 261 + Just Alien.SaveTracks -> 262 + SaveTracks event.data 161 263 162 264 Just Alien.SignIn -> 163 265 AuthenticationMsg (Authentication.PerformSignIn event.data)
+1 -1
src/Applications/Brain/Authentication.elm
··· 16 16 Steps: 17 17 18 18 1. Get active method (if none, we're signed out) 19 - 2. Get unrestricted data 19 + 2. Get hypaethral data 20 20 21 21 -} 22 22
+10
src/Applications/Brain/Core.elm
··· 1 1 module Brain.Core exposing (Flags, Model, Msg(..)) 2 2 3 3 import Alien 4 + import Authentication 4 5 import Brain.Authentication as Authentication 5 6 import Brain.Sources.Processing.Common as Processing 7 + import Json.Decode as Json 6 8 7 9 8 10 ··· 19 21 20 22 type alias Model = 21 23 { authentication : Authentication.Model 24 + , hypaethralUserData : Authentication.HypaethralUserData 22 25 , processing : Processing.Model 23 26 } 24 27 ··· 35 38 ----------------------------------------- 36 39 | AuthenticationMsg Authentication.Msg 37 40 | ProcessingMsg Processing.Msg 41 + ----------------------------------------- 42 + -- User data 43 + ----------------------------------------- 44 + | LoadHypaethralUserData Json.Value 45 + | SaveFavourites Json.Value 46 + | SaveSources Json.Value 47 + | SaveTracks Json.Value
+60 -41
src/Applications/UI.elm
··· 123 123 ) 124 124 125 125 ----------------------------------------- 126 - -- Children 127 - ----------------------------------------- 128 - BackdropMsg sub -> 129 - updateChild 130 - { mapCmd = BackdropMsg 131 - , mapModel = \child -> { model | backdrop = child } 132 - , update = UI.Backdrop.update 133 - } 134 - { model = model.backdrop 135 - , msg = sub 136 - } 137 - 138 - SourcesMsg sub -> 139 - updateChild 140 - { mapCmd = SourcesMsg 141 - , mapModel = \child -> { model | sources = child } 142 - , update = UI.Sources.update 143 - } 144 - { model = model.sources 145 - , msg = sub 146 - } 147 - 148 - TracksMsg sub -> 149 - updateChild 150 - { mapCmd = TracksMsg 151 - , mapModel = \child -> { model | tracks = child } 152 - , update = UI.Tracks.update 153 - } 154 - { model = model.tracks 155 - , msg = sub 156 - } 157 - 158 - ----------------------------------------- 159 126 -- Brain 160 127 ----------------------------------------- 161 128 NotifyBrain alienEvent -> ··· 185 152 , Cmd.none 186 153 ) 187 154 188 - Core.SaveHypaethralUserData -> 189 - ( model 190 - , model 191 - |> UI.UserData.exportHypaethral 192 - |> Alien.broadcast Alien.SaveHypaethralUserData 155 + Core.SaveFavourites -> 156 + model 157 + |> UI.UserData.encodedFavourites 158 + |> Alien.broadcast Alien.SaveFavourites 159 + |> Ports.toBrain 160 + |> Return2.withModel model 161 + 162 + Core.SaveSources -> 163 + model 164 + |> UI.UserData.encodedSources 165 + |> Alien.broadcast Alien.SaveSources 166 + |> Ports.toBrain 167 + |> Return2.withModel model 168 + 169 + Core.SaveTracks -> 170 + model 171 + |> UI.UserData.encodedTracks 172 + |> Alien.broadcast Alien.SaveTracks 193 173 |> Ports.toBrain 194 - ) 174 + |> Return2.withModel model 195 175 196 176 SignIn method -> 197 177 ( model ··· 211 191 ) 212 192 213 193 ----------------------------------------- 194 + -- Children 195 + ----------------------------------------- 196 + BackdropMsg sub -> 197 + updateChild 198 + { mapCmd = BackdropMsg 199 + , mapModel = \child -> { model | backdrop = child } 200 + , update = UI.Backdrop.update 201 + } 202 + { model = model.backdrop 203 + , msg = sub 204 + } 205 + 206 + SourcesMsg sub -> 207 + updateChild 208 + { mapCmd = SourcesMsg 209 + , mapModel = \child -> { model | sources = child } 210 + , update = UI.Sources.update 211 + } 212 + { model = model.sources 213 + , msg = sub 214 + } 215 + 216 + TracksMsg sub -> 217 + updateChild 218 + { mapCmd = TracksMsg 219 + , mapModel = \child -> { model | tracks = child } 220 + , update = UI.Tracks.update 221 + } 222 + { model = model.tracks 223 + , msg = sub 224 + } 225 + 226 + ----------------------------------------- 214 227 -- URL 215 228 ----------------------------------------- 216 229 ChangeUrlUsingPage page -> ··· 261 274 Reply.SaveEnclosedUserData -> 262 275 Core.SaveEnclosedUserData 263 276 264 - Reply.SaveHypaethralUserData -> 265 - Core.SaveHypaethralUserData 277 + Reply.SaveFavourites -> 278 + Core.SaveFavourites 279 + 280 + Reply.SaveSources -> 281 + Core.SaveSources 282 + 283 + Reply.SaveTracks -> 284 + Core.SaveTracks 266 285 267 286 268 287 updateChild =
+9 -7
src/Applications/UI/Core.elm
··· 52 52 | SetCurrentTime Time.Posix 53 53 | ToggleLoadingScreen Switch 54 54 ----------------------------------------- 55 - -- Children 56 - ----------------------------------------- 57 - | BackdropMsg UI.Backdrop.Msg 58 - | SourcesMsg UI.Sources.Msg 59 - | TracksMsg UI.Tracks.Msg 60 - ----------------------------------------- 61 55 -- Brain 62 56 ----------------------------------------- 63 57 | NotifyBrain Alien.Event 64 58 | ProcessSources 65 59 | SaveEnclosedUserData 66 - | SaveHypaethralUserData 60 + | SaveFavourites 61 + | SaveSources 62 + | SaveTracks 67 63 | SignIn Authentication.Method 68 64 | SignOut 65 + ----------------------------------------- 66 + -- Children 67 + ----------------------------------------- 68 + | BackdropMsg UI.Backdrop.Msg 69 + | SourcesMsg UI.Sources.Msg 70 + | TracksMsg UI.Tracks.Msg 69 71 ----------------------------------------- 70 72 -- URL 71 73 -----------------------------------------
+5 -1
src/Applications/UI/Reply.elm
··· 1 1 module UI.Reply exposing (Reply(..)) 2 2 3 + import Alien 4 + import Json.Decode as Json 3 5 import Sources exposing (Source) 4 6 import UI.Page exposing (Page) 5 7 ··· 14 16 | GoToPage Page 15 17 | ProcessSources 16 18 | SaveEnclosedUserData 17 - | SaveHypaethralUserData 19 + | SaveFavourites 20 + | SaveSources 21 + | SaveTracks
+15 -15
src/Applications/UI/Sources.elm
··· 52 52 | FinishedProcessing 53 53 | Process 54 54 ----------------------------------------- 55 + -- Children 56 + ----------------------------------------- 57 + | FormMsg Form.Msg 58 + ----------------------------------------- 55 59 -- Collection 56 60 ----------------------------------------- 57 61 | AddToCollection Source 58 62 | RemoveFromCollection String 59 - ----------------------------------------- 60 - -- Children 61 - ----------------------------------------- 62 - | FormMsg Form.Msg 63 63 64 64 65 65 update : Msg -> Model -> R3D3 Model Msg Reply ··· 81 81 ) 82 82 83 83 ----------------------------------------- 84 + -- Children 85 + ----------------------------------------- 86 + FormMsg sub -> 87 + model.form 88 + |> Form.update sub 89 + |> Return3.mapModel (\f -> { model | form = f }) 90 + |> Return3.mapCmd FormMsg 91 + 92 + ----------------------------------------- 84 93 -- Collection 85 94 ----------------------------------------- 86 95 AddToCollection source -> ··· 90 99 |> List.append model.collection 91 100 |> (\c -> { model | collection = c }) 92 101 |> Return2.withNoCmd 93 - |> Return3.withReply [ UI.Reply.SaveHypaethralUserData ] 102 + |> Return3.withReply [ UI.Reply.SaveSources ] 94 103 95 104 RemoveFromCollection sourceId -> 96 105 model.collection 97 106 |> List.filter (.id >> (/=) sourceId) 98 107 |> (\c -> { model | collection = c }) 99 108 |> Return2.withNoCmd 100 - |> Return3.withReply [ UI.Reply.SaveHypaethralUserData ] 101 - 102 - ----------------------------------------- 103 - -- Children 104 - ----------------------------------------- 105 - FormMsg sub -> 106 - model.form 107 - |> Form.update sub 108 - |> Return3.mapModel (\f -> { model | form = f }) 109 - |> Return3.mapCmd FormMsg 109 + |> Return3.withReply [ UI.Reply.SaveSources ] 110 110 111 111 112 112
+1 -1
src/Applications/UI/Tracks.elm
··· 148 148 if model.collection.untouched /= newCollection.untouched then 149 149 ( modelWithNewCollection 150 150 , Cmd.none 151 - , Just [ SaveHypaethralUserData ] 151 + , Just [ SaveTracks ] 152 152 ) 153 153 154 154 else
+38 -84
src/Applications/UI/UserData.elm
··· 1 - module UI.UserData exposing (exportHypaethral, importHypaethral) 2 - 3 - {-| Import user data into or export user data from the UI.Core.Model 4 - -} 1 + module UI.UserData exposing (encodedFavourites, encodedSources, encodedTracks, importHypaethral) 5 2 6 3 import Authentication exposing (..) 7 - import Json.Decode as Decode 8 - import Json.Encode as Encode 4 + import Json.Decode as Json 5 + import Json.Decode.Pipeline exposing (..) 6 + import Json.Encode 9 7 import Replying exposing (R3D3) 10 8 import Sources.Encoding as Sources 11 9 import Tracks exposing (emptyCollection) ··· 18 16 19 17 20 18 21 - ----------------------------------------- 22 - -- 📭 23 - ----------------------------------------- 19 + -- 🔱 20 + 21 + 22 + encodedFavourites : UI.Core.Model -> Json.Value 23 + encodedFavourites { tracks } = 24 + Json.Encode.list Tracks.encodeFavourite tracks.favourites 25 + 26 + 27 + encodedSources : UI.Core.Model -> Json.Value 28 + encodedSources { sources } = 29 + Json.Encode.list Sources.encode sources.collection 30 + 31 + 32 + encodedTracks : UI.Core.Model -> Json.Value 33 + encodedTracks { tracks } = 34 + Json.Encode.list Tracks.encodeTrack tracks.collection.untouched 24 35 25 36 26 - importHypaethral : Decode.Value -> UI.Core.Model -> R3D3 UI.Core.Model UI.Core.Msg UI.Reply 37 + importHypaethral : Json.Value -> UI.Core.Model -> R3D3 UI.Core.Model UI.Core.Msg UI.Reply 27 38 importHypaethral value model = 28 39 let 29 40 data = ··· 50 61 ) 51 62 52 63 53 - mergeReplies : List (Maybe (List UI.Reply)) -> Maybe (List UI.Reply) 54 - mergeReplies list = 55 - list 56 - |> List.foldl 57 - (\maybeReply replies -> 58 - case maybeReply of 59 - Just r -> 60 - replies ++ r 61 - 62 - Nothing -> 63 - replies 64 - ) 65 - [] 66 - |> Just 67 - 68 64 69 - 70 - -- ░░ IMPORTING HYPAETHRAL 65 + -- ㊙️ 71 66 72 67 73 68 importSources : Sources.Model -> HypaethralUserData -> R3D3 Sources.Model Sources.Msg UI.Reply 74 69 importSources model data = 75 70 ( { model 76 - | collection = Maybe.withDefault [] data.sources 71 + | collection = data.sources 77 72 } 78 73 , Cmd.none 79 74 , Nothing ··· 83 78 importTracks : Tracks.Model -> HypaethralUserData -> R3D3 Tracks.Model Tracks.Msg UI.Reply 84 79 importTracks model data = 85 80 let 86 - tracks = 87 - Maybe.withDefault [] data.tracks 88 - 89 81 adjustedModel = 90 82 { model 91 - | collection = { emptyCollection | untouched = tracks } 92 - , favourites = Maybe.withDefault [] data.favourites 83 + | collection = { emptyCollection | untouched = data.tracks } 84 + , favourites = data.favourites 93 85 } 94 86 in 95 87 adjustedModel ··· 98 90 |> Tracks.resolveParcel adjustedModel 99 91 100 92 101 - 102 - -- ░░ DECODING 103 - 93 + mergeReplies : List (Maybe (List UI.Reply)) -> Maybe (List UI.Reply) 94 + mergeReplies list = 95 + list 96 + |> List.foldl 97 + (\maybeReply replies -> 98 + case maybeReply of 99 + Just r -> 100 + replies ++ r 104 101 105 - decode : Decode.Value -> Result Decode.Error HypaethralUserData 106 - decode = 107 - Decode.decodeValue decoder 108 - 109 - 110 - decoder : Decode.Decoder HypaethralUserData 111 - decoder = 112 - Decode.map3 113 - HypaethralUserData 114 - (Decode.maybe <| Decode.field "favourites" <| Decode.list Tracks.favouriteDecoder) 115 - (Decode.maybe <| Decode.field "sources" <| Decode.list Sources.decoder) 116 - (Decode.maybe <| Decode.field "tracks" <| Decode.list Tracks.trackDecoder) 117 - 118 - 119 - 120 - -- ░░ FALLBACKS 121 - 122 - 123 - emptyHypaethralUserData : HypaethralUserData 124 - emptyHypaethralUserData = 125 - { favourites = Nothing 126 - , sources = Nothing 127 - , tracks = Nothing 128 - } 129 - 130 - 131 - 132 - ----------------------------------------- 133 - -- 📮 134 - ----------------------------------------- 135 - 136 - 137 - exportHypaethral : UI.Core.Model -> Encode.Value 138 - exportHypaethral = 139 - encode 140 - 141 - 142 - 143 - -- ░░ ENCODING 144 - 145 - 146 - encode : UI.Core.Model -> Encode.Value 147 - encode model = 148 - Encode.object 149 - [ ( "favourites", Encode.list Tracks.encodeFavourite model.tracks.favourites ) 150 - , ( "sources", Encode.list Sources.encode model.sources.collection ) 151 - , ( "tracks", Encode.list Tracks.encodeTrack model.tracks.collection.untouched ) 152 - ] 102 + Nothing -> 103 + replies 104 + ) 105 + [] 106 + |> Just
+19 -5
src/Library/Alien.elm
··· 25 25 -- from UI 26 26 | ProcessSources 27 27 | SaveEnclosedUserData 28 - | SaveHypaethralUserData 28 + | SaveFavourites 29 + | SaveSources 30 + | SaveTracks 29 31 | SignIn 30 32 | SignOut 31 33 -- to UI ··· 89 91 SaveEnclosedUserData -> 90 92 "SAVE_ENCLOSED_USER_DATA" 91 93 92 - SaveHypaethralUserData -> 93 - "SAVE_HYPAETHRAL_USER_DATA" 94 + SaveFavourites -> 95 + "SAVE_FAVOURITES" 96 + 97 + SaveSources -> 98 + "SAVE_SOURCES" 99 + 100 + SaveTracks -> 101 + "SAVE_TRACKS" 94 102 95 103 SignIn -> 96 104 "SIGN_IN" ··· 150 158 "SAVE_ENCLOSED_USER_DATA" -> 151 159 Just SaveEnclosedUserData 152 160 153 - "SAVE_HYPAETHRAL_USER_DATA" -> 154 - Just SaveHypaethralUserData 161 + "SAVE_FAVOURITES" -> 162 + Just SaveFavourites 163 + 164 + "SAVE_SOURCES" -> 165 + Just SaveSources 166 + 167 + "SAVE_TRACKS" -> 168 + Just SaveTracks 155 169 156 170 "SIGN_IN" -> 157 171 Just SignIn
+33 -4
src/Library/Authentication.elm
··· 1 - module Authentication exposing (EnclosedUserData, HypaethralUserData, Method(..), methodFromString, methodToString) 1 + module Authentication exposing (EnclosedUserData, HypaethralUserData, Method(..), decode, decoder, emptyHypaethralUserData, methodFromString, methodToString) 2 2 3 + import Json.Decode as Json 4 + import Json.Decode.Pipeline exposing (optional) 3 5 import Sources 6 + import Sources.Encoding as Sources 4 7 import Tracks 8 + import Tracks.Encoding as Tracks 5 9 6 10 7 11 ··· 17 21 18 22 19 23 type alias HypaethralUserData = 20 - { favourites : Maybe (List Tracks.Favourite) 21 - , sources : Maybe (List Sources.Source) 22 - , tracks : Maybe (List Tracks.Track) 24 + { favourites : List Tracks.Favourite 25 + , sources : List Sources.Source 26 + , tracks : List Tracks.Track 23 27 } 24 28 25 29 ··· 27 31 -- 🔱 28 32 29 33 34 + emptyHypaethralUserData : HypaethralUserData 35 + emptyHypaethralUserData = 36 + { favourites = [] 37 + , sources = [] 38 + , tracks = [] 39 + } 40 + 41 + 30 42 methodToString : Method -> String 31 43 methodToString method = 32 44 case method of ··· 42 54 43 55 _ -> 44 56 Nothing 57 + 58 + 59 + 60 + -- 🔱 ░░ DECODING 61 + 62 + 63 + decode : Json.Value -> Result Json.Error HypaethralUserData 64 + decode = 65 + Json.decodeValue decoder 66 + 67 + 68 + decoder : Json.Decoder HypaethralUserData 69 + decoder = 70 + Json.succeed HypaethralUserData 71 + |> optional "favourites" (Json.list Tracks.favouriteDecoder) [] 72 + |> optional "sources" (Json.list Sources.decoder) [] 73 + |> optional "tracks" (Json.list Tracks.trackDecoder) []