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.

Import / Export

+261 -72
+1 -1
elm.json
··· 15 15 "elm/browser": "1.0.1", 16 16 "elm/bytes": "1.0.7", 17 17 "elm/core": "1.0.2", 18 + "elm/file": "1.0.3", 18 19 "elm/html": "1.0.0", 19 20 "elm/http": "2.0.0", 20 21 "elm/json": "1.1.2", ··· 39 40 }, 40 41 "indirect": { 41 42 "Skinney/murmur3": "2.0.8", 42 - "elm/file": "1.0.1", 43 43 "elm/parser": "1.1.0", 44 44 "elm/random": "1.0.0", 45 45 "elm-explorations/test": "1.2.0",
+2 -9
src/Applications/Brain.elm
··· 232 232 233 233 saveHypaethralData : Model -> ( Model, Cmd Msg ) 234 234 saveHypaethralData model = 235 - let 236 - { favourites, sources, tracks } = 237 - model.hypaethralUserData 238 - in 239 - [ ( "favourites", Json.Encode.list Tracks.encodeFavourite favourites ) 240 - , ( "sources", Json.Encode.list Sources.encode sources ) 241 - , ( "tracks", Json.Encode.list Tracks.encodeTrack tracks ) 242 - ] 243 - |> Json.Encode.object 235 + model.hypaethralUserData 236 + |> Authentication.encodeHypaethralUserData 244 237 |> Authentication.SaveHypaethralData 245 238 |> AuthenticationMsg 246 239 |> (\msg -> update msg model)
+56 -9
src/Applications/UI.elm
··· 10 10 import Common 11 11 import Css exposing (url) 12 12 import Css.Global 13 + import File 14 + import File.Download 15 + import File.Select 13 16 import Html.Styled as Html exposing (Html, div, section, text, toUnstyled) 14 17 import Html.Styled.Attributes exposing (id, style) 15 18 import Html.Styled.Lazy as Lazy 16 - import Json.Encode as Encode 19 + import Json.Decode 20 + import Json.Encode 17 21 import Replying exposing (do, return) 18 22 import Return2 as R2 19 23 import Sources 20 24 import Sources.Encoding 21 25 import Tachyons.Classes as T 26 + import Task 22 27 import Time 23 28 import Tracks.Encoding 24 29 import UI.Authentication ··· 30 35 import UI.Ports as Ports 31 36 import UI.Reply as Reply exposing (Reply(..)) 32 37 import UI.Settings 38 + import UI.Settings.Page 33 39 import UI.Sources 40 + import UI.Sources.Page 34 41 import UI.Svg.Elements 35 42 import UI.Tracks 36 43 import UI.UserData ··· 128 135 Core.ProcessSources -> 129 136 ( model 130 137 , [ ( "origin" 131 - , Encode.string (Common.urlOrigin model.url) 138 + , Json.Encode.string (Common.urlOrigin model.url) 132 139 ) 133 140 , ( "sources" 134 - , Encode.list Sources.Encoding.encode model.sources.collection 141 + , Json.Encode.list Sources.Encoding.encode model.sources.collection 135 142 ) 136 143 , ( "tracks" 137 - , Encode.list Tracks.Encoding.encodeTrack model.tracks.collection.untouched 144 + , Json.Encode.list Tracks.Encoding.encodeTrack model.tracks.collection.untouched 138 145 ) 139 146 ] 140 - |> Encode.object 147 + |> Json.Encode.object 141 148 |> Alien.broadcast Alien.ProcessSources 142 149 |> Ports.toBrain 143 150 ) ··· 239 246 } 240 247 241 248 ----------------------------------------- 249 + -- Import / Export 250 + ----------------------------------------- 251 + Export -> 252 + ( model 253 + , File.Download.string 254 + "diffuse.json" 255 + "application/json" 256 + ({ favourites = model.tracks.favourites 257 + , sources = model.sources.collection 258 + , tracks = model.tracks.collection.untouched 259 + } 260 + |> Authentication.encodeHypaethralUserData 261 + |> Json.Encode.encode 2 262 + ) 263 + ) 264 + 265 + Import file -> 266 + ( { model | isLoading = True } 267 + , Task.perform ImportJson (File.toString file) 268 + ) 269 + 270 + ImportJson json -> 271 + model 272 + |> update 273 + (json 274 + |> Json.Decode.decodeString Json.Decode.value 275 + |> Result.withDefault Json.Encode.null 276 + |> LoadHypaethralUserData 277 + ) 278 + -- TODO: 279 + -- Show notication relating to import 280 + |> R2.addCmd (do <| ChangeUrlUsingPage Page.Index) 281 + 282 + RequestImport -> 283 + ( model 284 + , File.Select.file [ "application/json" ] Import 285 + ) 286 + 287 + ----------------------------------------- 242 288 -- URL 243 289 ----------------------------------------- 244 290 ChangeUrlUsingPage page -> ··· 415 461 [ Lazy.lazy 416 462 (UI.Navigation.global 417 463 [ ( Page.Index, "Tracks" ) 418 - , ( Page.Sources Sources.Index, "Sources" ) 419 - , ( Page.Settings, "Settings" ) 464 + , ( Page.Sources UI.Sources.Page.Index, "Sources" ) 465 + , ( Page.Settings UI.Settings.Page.Index, "Settings" ) 420 466 ] 421 467 ) 422 468 model.page ··· 436 482 empty 437 483 438 484 Page.NotFound -> 485 + -- TODO 439 486 UI.Kit.receptacle [ text "Page not found." ] 440 487 441 - Page.Settings -> 442 - UI.Settings.view model 488 + Page.Settings subPage -> 489 + UI.Settings.view subPage model 443 490 444 491 Page.Sources subPage -> 445 492 model.sources
+8
src/Applications/UI/Core.elm
··· 3 3 import Alien 4 4 import Browser 5 5 import Browser.Navigation as Nav 6 + import File exposing (File) 6 7 import Json.Encode as Json 7 8 import Time 8 9 import UI.Authentication ··· 68 69 | BackdropMsg UI.Backdrop.Msg 69 70 | SourcesMsg UI.Sources.Msg 70 71 | TracksMsg UI.Tracks.Msg 72 + ----------------------------------------- 73 + -- Import / Export 74 + ----------------------------------------- 75 + | Export 76 + | Import File 77 + | ImportJson String 78 + | RequestImport 71 79 ----------------------------------------- 72 80 -- URL 73 81 -----------------------------------------
+38 -7
src/Applications/UI/Kit.elm
··· 1 - module UI.Kit exposing (ButtonType(..), button, buttonFocus, canister, centeredContent, colorKit, colors, defaultFontFamilies, h1, h2, h3, headerFontFamilies, inputFocus, insulationWidth, intro, label, link, logoBackdrop, navFocus, receptacle, select, textArea, textField, textFocus, vessel) 1 + module UI.Kit exposing (ButtonType(..), button, buttonFocus, buttonWithColor, canister, centeredContent, colorKit, colors, defaultFontFamilies, h1, h2, h3, headerFontFamilies, inlineIcon, inputFocus, insulationWidth, intro, label, link, logoBackdrop, navFocus, receptacle, select, textArea, textField, textFocus, vessel) 2 2 3 3 import Chunky exposing (..) 4 4 import Color ··· 9 9 import Html.Styled.Attributes exposing (css, href, style) 10 10 import Html.Styled.Events exposing (onClick, onInput) 11 11 import Material.Icons.Hardware as Icons 12 + import Svg 12 13 import Tachyons.Classes as T 13 14 14 15 ··· 176 177 177 178 178 179 button : ButtonType -> msg -> Html msg -> Html msg 179 - button buttonType msg child = 180 + button = 181 + buttonWithColor colorKit.accent 182 + 183 + 184 + buttonWithColor : Color.Color -> ButtonType -> msg -> Html msg -> Html msg 185 + buttonWithColor buttonColor buttonType msg child = 180 186 slab 181 187 Html.button 182 - [ css buttonStyles 188 + [ css (buttonStyles buttonColor) 183 189 , onClick msg 184 190 ] 185 191 [ borderRadius ··· 288 294 [ Html.text text ] 289 295 290 296 297 + inlineIcon : (Color.Color -> Int -> Svg.Svg msg) -> Html msg 298 + inlineIcon icon = 299 + slab 300 + Html.span 301 + [ css inlineIconStyles ] 302 + [ T.mr1 ] 303 + [ Html.fromUnstyled (icon colors.text 14) ] 304 + 305 + 291 306 intro : Html msg -> Html msg 292 307 intro child = 293 308 slab ··· 431 446 ----------------------------------------- 432 447 433 448 434 - buttonStyles : List Css.Style 435 - buttonStyles = 436 - [ Css.borderColor (Color.toElmCssColor colorKit.accent) 437 - , Css.color (Color.toElmCssColor colorKit.accent) 449 + buttonStyles : Color.Color -> List Css.Style 450 + buttonStyles buttonColor = 451 + [ Css.borderColor (Color.toElmCssColor buttonColor) 452 + , Css.color (Color.toElmCssColor buttonColor) 438 453 , buttonFocus 439 454 ] 440 455 ··· 445 460 , Css.fontSize (px 11.25) 446 461 , Css.letterSpacing (px 0.25) 447 462 , Css.top (px -1) 463 + ] 464 + 465 + 466 + inlineIconStyles : List Css.Style 467 + inlineIconStyles = 468 + [ Css.fontSize (px 0) 469 + , Css.lineHeight (px 0) 470 + , Css.position Css.relative 471 + , Css.top (px -1) 472 + , Css.verticalAlign Css.sub 473 + 474 + -- 475 + , Css.Global.descendants 476 + [ Css.Global.selector "svg > g" 477 + [ Css.fill Css.currentColor ] 478 + ] 448 479 ] 449 480 450 481
+21 -4
src/Applications/UI/Page.elm
··· 1 1 module UI.Page exposing (Page(..), fromUrl, sameBase, toString) 2 2 3 - import Sources 3 + import UI.Settings.Page as Settings 4 + import UI.Sources.Page as Sources 4 5 import Url exposing (Url) 5 6 import Url.Parser exposing (..) 6 7 ··· 11 12 12 13 type Page 13 14 = Index 14 - | Settings 15 + | Settings Settings.Page 15 16 | Sources Sources.Page 16 17 -- 17 18 | NotFound ··· 37 38 NotFound -> 38 39 "/404" 39 40 40 - Settings -> 41 + ----------------------------------------- 42 + -- Settings 43 + ----------------------------------------- 44 + Settings Settings.ImportExport -> 45 + "/settings/import-export" 46 + 47 + Settings Settings.Index -> 41 48 "/settings" 42 49 43 50 ----------------------------------------- ··· 55 62 sameBase : Page -> Page -> Bool 56 63 sameBase a b = 57 64 case ( a, b ) of 65 + ( Settings _, Settings _ ) -> 66 + True 67 + 58 68 ( Sources _, Sources _ ) -> 59 69 True 60 70 ··· 71 81 oneOf 72 82 [ map Index top 73 83 , map NotFound (s "404") 74 - , map Settings (s "settings") 84 + 85 + ----------------------------------------- 86 + -- Settings 87 + ----------------------------------------- 88 + , map (Settings Settings.ImportExport) (s "settings" </> s "import-export") 89 + , map (Settings Settings.Index) (s "settings") 75 90 91 + ----------------------------------------- 76 92 -- Sources 93 + ----------------------------------------- 77 94 , map (Sources Sources.Index) (s "sources") 78 95 , map (Sources Sources.New) (s "sources" </> s "new") 79 96 ]
+12 -5
src/Applications/UI/Settings.elm
··· 9 9 import UI.Core 10 10 import UI.Kit 11 11 import UI.Navigation exposing (..) 12 - import UI.Page as Page 12 + import UI.Page 13 + import UI.Settings.ImportExport 14 + import UI.Settings.Page as Settings exposing (..) 13 15 14 16 15 17 16 18 -- 🗺 17 19 18 20 19 - view : UI.Core.Model -> Html UI.Core.Msg 20 - view = 21 - UI.Kit.receptacle << index 21 + view : Settings.Page -> UI.Core.Model -> Html UI.Core.Msg 22 + view page model = 23 + case page of 24 + ImportExport -> 25 + UI.Settings.ImportExport.view 26 + 27 + Index -> 28 + UI.Kit.receptacle (index model) 22 29 23 30 24 31 ··· 33 40 UI.Navigation.local 34 41 [ ( Icon Icons.import_export 35 42 , Label "Import / Export" Shown 36 - , PerformMsg UI.Core.Bypass 43 + , GoToPage (UI.Page.Settings ImportExport) 37 44 ) 38 45 , ( Icon Icons.exit_to_app 39 46 , Label "Sign out" Shown
+63
src/Applications/UI/Settings/ImportExport.elm
··· 1 + module UI.Settings.ImportExport exposing (view) 2 + 3 + import Chunky exposing (..) 4 + import Html.Styled as Html exposing (Html, text) 5 + import Material.Icons.Alert as Icons 6 + import Material.Icons.Navigation as Icons 7 + import Tachyons.Classes as T 8 + import UI.Core 9 + import UI.Kit exposing (ButtonType(..)) 10 + import UI.Navigation exposing (..) 11 + import UI.Page 12 + import UI.Settings.Page 13 + 14 + 15 + 16 + -- 🗺 17 + 18 + 19 + view : Html UI.Core.Msg 20 + view = 21 + UI.Kit.receptacle 22 + [ ----------------------------------------- 23 + -- Navigation 24 + ----------------------------------------- 25 + UI.Navigation.local 26 + [ ( Icon Icons.arrow_back 27 + , Label "Settings" Hidden 28 + , GoToPage (UI.Page.Settings UI.Settings.Page.Index) 29 + ) 30 + ] 31 + 32 + ----------------------------------------- 33 + -- Content 34 + ----------------------------------------- 35 + , UI.Kit.canister 36 + [ UI.Kit.h1 "Import / Export" 37 + 38 + -- Intro 39 + -------- 40 + , [ UI.Kit.inlineIcon Icons.warning 41 + , Html.strong [] [ text "All your data will be replaced when you import something." ] 42 + ] 43 + |> raw 44 + |> UI.Kit.intro 45 + 46 + -- Import 47 + --------- 48 + , chunk [ T.mb2, T.mt4 ] [ UI.Kit.label [] "Import" ] 49 + , UI.Kit.buttonWithColor 50 + UI.Kit.colorKit.base05 51 + WithText 52 + UI.Core.RequestImport 53 + (text "Choose file") 54 + 55 + -- Export 56 + --------- 57 + , chunk [ T.mb2, T.mt4 ] [ UI.Kit.label [] "Export" ] 58 + , UI.Kit.button 59 + WithText 60 + UI.Core.Export 61 + (text "Export data") 62 + ] 63 + ]
+8
src/Applications/UI/Settings/Page.elm
··· 1 + module UI.Settings.Page exposing (Page(..)) 2 + 3 + -- 🌳 4 + 5 + 6 + type Page 7 + = ImportExport 8 + | Index
+4 -3
src/Applications/UI/Sources.elm
··· 17 17 import UI.Kit exposing (ButtonType(..), select) 18 18 import UI.List 19 19 import UI.Navigation exposing (..) 20 - import UI.Page as Page 20 + import UI.Page 21 21 import UI.Reply exposing (Reply) 22 22 import UI.Sources.Form as Form 23 + import UI.Sources.Page as Sources exposing (..) 23 24 24 25 25 26 ··· 143 144 UI.Navigation.local 144 145 [ ( Icon Icons.add 145 146 , Label "Add a new source" Shown 146 - , GoToPage (Page.Sources New) 147 + , GoToPage (UI.Page.Sources New) 147 148 ) 148 149 149 150 -- Process ··· 175 176 , lineBreak 176 177 , text "It will not copy anything." 177 178 ] 178 - |> Html.span [] 179 + |> raw 179 180 |> UI.Kit.intro 180 181 181 182 -- List
+5 -20
src/Applications/UI/Sources/Form.elm
··· 18 18 import Tachyons.Classes as T 19 19 import UI.Kit exposing (ButtonType(..), select) 20 20 import UI.Navigation exposing (..) 21 - import UI.Page as Page 21 + import UI.Page 22 22 import UI.Ports 23 23 import UI.Reply exposing (Reply) 24 + import UI.Sources.Page 24 25 25 26 26 27 ··· 124 125 , case msg of 125 126 AddSource -> 126 127 Just 127 - [ UI.Reply.GoToPage (Page.Sources Sources.Index) 128 + [ UI.Reply.GoToPage (UI.Page.Sources UI.Sources.Page.Index) 128 129 , UI.Reply.AddSourceToCollection model.context 129 130 ] 130 131 ··· 178 179 UI.Navigation.local 179 180 [ ( Icon Icons.arrow_back 180 181 , Label "Back to list" Hidden 181 - , GoToPage (Page.Sources Sources.Index) 182 + , GoToPage (UI.Page.Sources UI.Sources.Page.Index) 182 183 ) 183 184 ] 184 185 ··· 296 297 ------- 297 298 , chunk 298 299 [ T.f6, T.flex, T.items_center, T.justify_center, T.lh_title, T.mt5, T.o_50 ] 299 - [ slab 300 - Html.span 301 - [ css warningIconStyles ] 302 - [ T.mr1 ] 303 - [ Html.fromUnstyled (Icons.warning UI.Kit.colors.text 14) ] 300 + [ UI.Kit.inlineIcon Icons.warning 304 301 , strong 305 302 [] 306 303 [ text "Make sure CORS is enabled" ] ··· 357 354 [ UI.Kit.centeredContent 358 355 [ UI.Kit.canister html ] 359 356 ] 360 - 361 - 362 - 363 - -- 🖼 364 - 365 - 366 - warningIconStyles : List Css.Style 367 - warningIconStyles = 368 - [ Css.fontSize (Css.px 0) 369 - , Css.lineHeight (Css.px 0) 370 - , Css.marginTop (Css.px -1) 371 - ]
+8
src/Applications/UI/Sources/Page.elm
··· 1 + module UI.Sources.Page exposing (Page(..)) 2 + 3 + -- 🌳 4 + 5 + 6 + type Page 7 + = Index 8 + | New
+2
src/Applications/UI/UserData.elm
··· 38 38 importHypaethral : Json.Value -> UI.Core.Model -> R3D3 UI.Core.Model UI.Core.Msg UI.Reply 39 39 importHypaethral value model = 40 40 let 41 + -- TODO: The app should notify the user if it's trying to import faulty data. 42 + -- (instead of doing nothing, like it is now) 41 43 data = 42 44 Result.withDefault emptyHypaethralUserData (decode value) 43 45
+14 -4
src/Library/Authentication.elm
··· 1 - module Authentication exposing (EnclosedUserData, HypaethralUserData, Method(..), decode, decodeMethod, decoder, emptyHypaethralUserData, encodeMethod, methodFromString, methodToString) 1 + module Authentication exposing (EnclosedUserData, HypaethralUserData, Method(..), decode, decodeMethod, decoder, emptyHypaethralUserData, encodeHypaethralUserData, encodeMethod, methodFromString, methodToString) 2 2 3 3 import Json.Decode as Json 4 + import Json.Decode.Ext as Json 4 5 import Json.Decode.Pipeline exposing (optional) 5 6 import Json.Encode 6 7 import Maybe.Extra as Maybe ··· 82 83 decoder : Json.Decoder HypaethralUserData 83 84 decoder = 84 85 Json.succeed HypaethralUserData 85 - |> optional "favourites" (Json.list Tracks.favouriteDecoder) [] 86 - |> optional "sources" (Json.list Sources.decoder) [] 87 - |> optional "tracks" (Json.list Tracks.trackDecoder) [] 86 + |> optional "favourites" (Json.listIgnore Tracks.favouriteDecoder) [] 87 + |> optional "sources" (Json.listIgnore Sources.decoder) [] 88 + |> optional "tracks" (Json.listIgnore Tracks.trackDecoder) [] 89 + 90 + 91 + encodeHypaethralUserData : HypaethralUserData -> Json.Value 92 + encodeHypaethralUserData { favourites, sources, tracks } = 93 + Json.Encode.object 94 + [ ( "favourites", Json.Encode.list Tracks.encodeFavourite favourites ) 95 + , ( "sources", Json.Encode.list Sources.encode sources ) 96 + , ( "tracks", Json.Encode.list Tracks.encodeTrack tracks ) 97 + ] 88 98 89 99 90 100 encodeMethod : Method -> Json.Value
+18
src/Library/Json/Decode/Ext.elm
··· 1 + module Json.Decode.Ext exposing (listIgnore) 2 + 3 + import Json.Decode exposing (Decoder) 4 + import Maybe.Extra as Maybe 5 + 6 + 7 + 8 + -- 🔱 9 + 10 + 11 + {-| A list decoder that always succeeds, throwing away the failures. 12 + -} 13 + listIgnore : Decoder a -> Decoder (List a) 14 + listIgnore decoder = 15 + decoder 16 + |> Json.Decode.maybe 17 + |> Json.Decode.list 18 + |> Json.Decode.map Maybe.values
+1 -10
src/Library/Sources.elm
··· 1 - module Sources exposing (Page(..), Property, Service(..), Source, SourceData, enabledSourceIds, setProperId) 1 + module Sources exposing (Property, Service(..), Source, SourceData, enabledSourceIds, setProperId) 2 2 3 3 import Conditional exposing (..) 4 4 import Dict exposing (Dict) ··· 40 40 41 41 type Service 42 42 = AmazonS3 43 - 44 - 45 - 46 - -- PAGE 47 - 48 - 49 - type Page 50 - = Index 51 - | New 52 43 53 44 54 45