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.

Add tracks header

+258 -120
+39 -38
src/Applications/UI.elm
··· 26 26 import Task 27 27 import Time 28 28 import Tracks.Encoding 29 - import UI.Authentication 30 - import UI.Backdrop 29 + import UI.Authentication as Authentication 30 + import UI.Backdrop as Backdrop 31 31 import UI.Core as Core exposing (Flags, Model, Msg(..), Switch(..)) 32 32 import UI.Kit 33 - import UI.Navigation 33 + import UI.Navigation as Navigation 34 34 import UI.Page as Page 35 35 import UI.Ports as Ports 36 36 import UI.Reply as Reply exposing (Reply(..)) 37 - import UI.Settings 37 + import UI.Settings as Settings 38 38 import UI.Settings.Page 39 - import UI.Sources 39 + import UI.Sources as Sources 40 40 import UI.Sources.Page 41 41 import UI.Svg.Elements 42 - import UI.Tracks 43 - import UI.UserData 42 + import UI.Tracks as Tracks 43 + import UI.Tracks.Core as Tracks 44 + import UI.UserData as UserData 44 45 import Url exposing (Url) 45 46 46 47 ··· 78 79 79 80 -- Children 80 81 ----------- 81 - , authentication = UI.Authentication.initialModel 82 - , backdrop = UI.Backdrop.initialModel 83 - , sources = UI.Sources.initialModel 84 - , tracks = UI.Tracks.initialModel 82 + , authentication = Authentication.initialModel 83 + , backdrop = Backdrop.initialModel 84 + , sources = Sources.initialModel 85 + , tracks = Tracks.initialModel 85 86 } 86 87 ----------------------------------------- 87 88 -- Initial command ··· 104 105 105 106 LoadEnclosedUserData json -> 106 107 model 107 - |> UI.UserData.importEnclosed json 108 + |> UserData.importEnclosed json 108 109 |> Replying.reducto update translateReply 109 110 110 111 LoadHypaethralUserData json -> 111 112 { model | isAuthenticated = True, isLoading = False } 112 - |> UI.UserData.importHypaethral json 113 + |> UserData.importHypaethral json 113 114 |> Replying.reducto update translateReply 114 115 115 116 SetCurrentTime time -> ··· 153 154 154 155 Core.SaveEnclosedUserData -> 155 156 model 156 - |> UI.UserData.exportEnclosed 157 + |> UserData.exportEnclosed 157 158 |> Alien.broadcast Alien.SaveEnclosedUserData 158 159 |> Ports.toBrain 159 160 |> R2.withModel model 160 161 161 162 Core.SaveFavourites -> 162 163 model 163 - |> UI.UserData.encodedFavourites 164 + |> UserData.encodedFavourites 164 165 |> Alien.broadcast Alien.SaveFavourites 165 166 |> Ports.toBrain 166 167 |> R2.withModel model ··· 170 171 updateEnabledSourceIdsOnTracks = 171 172 model.sources.collection 172 173 |> Sources.enabledSourceIds 173 - |> UI.Tracks.SetEnabledSourceIds 174 + |> Tracks.SetEnabledSourceIds 174 175 |> TracksMsg 175 176 |> update 176 177 ··· 178 179 updateEnabledSourceIdsOnTracks model 179 180 in 180 181 updatedModel 181 - |> UI.UserData.encodedSources 182 + |> UserData.encodedSources 182 183 |> Alien.broadcast Alien.SaveSources 183 184 |> Ports.toBrain 184 185 |> R2.withModel updatedModel ··· 186 187 187 188 Core.SaveTracks -> 188 189 model 189 - |> UI.UserData.encodedTracks 190 + |> UserData.encodedTracks 190 191 |> Alien.broadcast Alien.SaveTracks 191 192 |> Ports.toBrain 192 193 |> R2.withModel model ··· 200 201 in 201 202 { model 202 203 | isAuthenticated = False 203 - , sources = UI.Sources.initialModel 204 - , tracks = UI.Tracks.initialModel 204 + , sources = Sources.initialModel 205 + , tracks = Tracks.initialModel 205 206 } 206 - |> update (AuthenticationMsg UI.Authentication.DischargeMethod) 207 + |> update (AuthenticationMsg Authentication.DischargeMethod) 207 208 |> R2.addCmd alienSigningOut 208 209 209 210 ----------------------------------------- ··· 213 214 updateChild 214 215 { mapCmd = AuthenticationMsg 215 216 , mapModel = \child -> { model | authentication = child } 216 - , update = UI.Authentication.update 217 + , update = Authentication.update 217 218 } 218 219 { model = model.authentication 219 220 , msg = sub ··· 223 224 updateChild 224 225 { mapCmd = BackdropMsg 225 226 , mapModel = \child -> { model | backdrop = child } 226 - , update = UI.Backdrop.update 227 + , update = Backdrop.update 227 228 } 228 229 { model = model.backdrop 229 230 , msg = sub ··· 233 234 updateChild 234 235 { mapCmd = SourcesMsg 235 236 , mapModel = \child -> { model | sources = child } 236 - , update = UI.Sources.update 237 + , update = Sources.update 237 238 } 238 239 { model = model.sources 239 240 , msg = sub ··· 243 244 updateChild 244 245 { mapCmd = TracksMsg 245 246 , mapModel = \child -> { model | tracks = child } 246 - , update = UI.Tracks.update 247 + , update = Tracks.update 247 248 } 248 249 { model = model.tracks 249 250 , msg = sub ··· 328 329 translateReply reply = 329 330 case reply of 330 331 AddSourceToCollection source -> 331 - SourcesMsg (UI.Sources.AddToCollection source) 332 + SourcesMsg (Sources.AddToCollection source) 332 333 333 334 Chill -> 334 335 Bypass ··· 340 341 Core.ProcessSources 341 342 342 343 Reply.RemoveTracksWithSourceId sourceId -> 343 - TracksMsg (UI.Tracks.RemoveBySourceId sourceId) 344 + TracksMsg (Tracks.RemoveBySourceId sourceId) 344 345 345 346 Reply.SaveEnclosedUserData -> 346 347 Core.SaveEnclosedUserData ··· 375 376 translateAlienEvent event = 376 377 case Alien.tagFromString event.tag of 377 378 Just Alien.AddTracks -> 378 - TracksMsg (UI.Tracks.Add event.data) 379 + TracksMsg (Tracks.Add event.data) 379 380 380 381 Just Alien.AuthMethod -> 381 382 -- My brain told me which auth method we're using, 382 383 -- so we can tell the user in the UI. 383 - AuthenticationMsg (UI.Authentication.ActivateMethod event.data) 384 + AuthenticationMsg (Authentication.ActivateMethod event.data) 384 385 385 386 Just Alien.FinishedProcessingSources -> 386 - SourcesMsg UI.Sources.FinishedProcessing 387 + SourcesMsg Sources.FinishedProcessing 387 388 388 389 Just Alien.HideLoadingScreen -> 389 390 ToggleLoadingScreen Off ··· 395 396 LoadHypaethralUserData event.data 396 397 397 398 Just Alien.RemoveTracksByPath -> 398 - TracksMsg (UI.Tracks.RemoveByPaths event.data) 399 + TracksMsg (Tracks.RemoveByPaths event.data) 399 400 400 401 Just Alien.ReportGenericError -> 401 402 let ··· 414 415 Bypass 415 416 416 417 Just Alien.SearchTracks -> 417 - TracksMsg (UI.Tracks.SetSearchResults event.data) 418 + TracksMsg (Tracks.SetSearchResults event.data) 418 419 419 420 Just Alien.UpdateSourceData -> 420 421 -- TODO ··· 445 446 -- Backdrop 446 447 ----------------------------------------- 447 448 , model.backdrop 448 - |> Lazy.lazy UI.Backdrop.view 449 + |> Lazy.lazy Backdrop.view 449 450 |> Html.map BackdropMsg 450 451 451 452 ----------------------------------------- ··· 460 461 461 462 else 462 463 [ model.authentication 463 - |> UI.Authentication.view 464 + |> Authentication.view 464 465 |> Html.map AuthenticationMsg 465 466 ] 466 467 ) ··· 470 471 defaultScreen : Model -> List (Html Msg) 471 472 defaultScreen model = 472 473 [ Lazy.lazy 473 - (UI.Navigation.global 474 + (Navigation.global 474 475 [ ( Page.Index, "Tracks" ) 475 476 , ( Page.Sources UI.Sources.Page.Index, "Sources" ) 476 477 , ( Page.Settings UI.Settings.Page.Index, "Settings" ) ··· 483 484 ----------------------------------------- 484 485 , UI.Kit.vessel 485 486 [ model.tracks 486 - |> Lazy.lazy3 UI.Tracks.view model.page model.viewport.height 487 + |> Lazy.lazy3 Tracks.view model.page model.viewport.height 487 488 |> Html.map TracksMsg 488 489 489 490 -- Pages ··· 497 498 UI.Kit.receptacle [ text "Page not found." ] 498 499 499 500 Page.Settings subPage -> 500 - UI.Settings.view subPage model 501 + Settings.view subPage model 501 502 502 503 Page.Sources subPage -> 503 504 model.sources 504 - |> Lazy.lazy2 UI.Sources.view subPage 505 + |> Lazy.lazy2 Sources.view subPage 505 506 |> Html.map SourcesMsg 506 507 ] 507 508
+12 -12
src/Applications/UI/Core.elm
··· 6 6 import File exposing (File) 7 7 import Json.Encode as Json 8 8 import Time 9 - import UI.Authentication 10 - import UI.Backdrop 9 + import UI.Authentication as Authentication 10 + import UI.Backdrop as Backdrop 11 11 import UI.Page exposing (Page) 12 - import UI.Sources 13 - import UI.Tracks 12 + import UI.Sources as Sources 13 + import UI.Tracks.Core as Tracks 14 14 import Url exposing (Url) 15 15 16 16 ··· 37 37 ----------------------------------------- 38 38 -- Children 39 39 ----------------------------------------- 40 - , authentication : UI.Authentication.Model 41 - , backdrop : UI.Backdrop.Model 42 - , sources : UI.Sources.Model 43 - , tracks : UI.Tracks.Model 40 + , authentication : Authentication.Model 41 + , backdrop : Backdrop.Model 42 + , sources : Sources.Model 43 + , tracks : Tracks.Model 44 44 } 45 45 46 46 ··· 72 72 ----------------------------------------- 73 73 -- Children 74 74 ----------------------------------------- 75 - | AuthenticationMsg UI.Authentication.Msg 76 - | BackdropMsg UI.Backdrop.Msg 77 - | SourcesMsg UI.Sources.Msg 78 - | TracksMsg UI.Tracks.Msg 75 + | AuthenticationMsg Authentication.Msg 76 + | BackdropMsg Backdrop.Msg 77 + | SourcesMsg Sources.Msg 78 + | TracksMsg Tracks.Msg 79 79 ----------------------------------------- 80 80 -- Import / Export 81 81 -----------------------------------------
+12 -52
src/Applications/UI/Tracks.elm
··· 1 - module UI.Tracks exposing (Model, Msg(..), initialModel, makeParcel, resolveParcel, update, view) 1 + module UI.Tracks exposing (initialModel, makeParcel, resolveParcel, update, view) 2 2 3 3 import Alien 4 4 import Chunky exposing (..) ··· 29 29 import UI.Page exposing (Page) 30 30 import UI.Ports 31 31 import UI.Reply exposing (Reply(..)) 32 + import UI.Tracks.Core exposing (..) 32 33 import UI.Tracks.Scene.List 33 34 34 35 ··· 36 37 -- 🌳 37 38 38 39 39 - type alias Model = 40 - { collection : Collection 41 - , enabledSourceIds : List String 42 - , favourites : List Favourite 43 - , favouritesOnly : Bool 44 - , infiniteList : InfiniteList.Model 45 - , nowPlaying : Maybe IdentifiedTrack 46 - , scene : Scene 47 - , searchResults : Maybe (List String) 48 - , searchTerm : Maybe String 49 - , sortBy : SortBy 50 - , sortDirection : SortDirection 51 - } 52 - 53 - 54 - type Scene 55 - = List 56 - 57 - 58 40 initialModel : Model 59 41 initialModel = 60 42 { collection = emptyCollection ··· 75 57 -- 📣 76 58 77 59 78 - type Msg 79 - = Bypass 80 - | InfiniteListMsg InfiniteList.Model 81 - | SetEnabledSourceIds (List String) 82 - ----------------------------------------- 83 - -- Collection 84 - ----------------------------------------- 85 - | Add Json.Value 86 - | RemoveByPaths Json.Value 87 - | RemoveBySourceId String 88 - ----------------------------------------- 89 - -- Favourites 90 - ----------------------------------------- 91 - | ToggleFavouritesOnly 92 - ----------------------------------------- 93 - -- Search 94 - ----------------------------------------- 95 - | ClearSearch 96 - | Search 97 - | SetSearchResults Json.Value 98 - | SetSearchTerm String 99 - 100 - 101 60 update : Msg -> Model -> R3D3 Model Msg Reply 102 61 update msg model = 103 62 case msg of ··· 264 223 -- 265 224 , case model.scene of 266 225 List -> 267 - UI.Tracks.Scene.List.view 268 - { favouritesOnly = model.favouritesOnly 269 - , infiniteList = model.infiniteList 270 - , screenHeight = screenHeight 271 - , scrollMsg = InfiniteListMsg 272 - } 273 - model.collection.harvested 226 + UI.Tracks.Scene.List.view { height = screenHeight } model 274 227 ] 275 228 276 229 ··· 285 238 _ -> 286 239 -1 287 240 in 288 - chunk 289 - [ T.flex ] 241 + brick 242 + [ css navigationStyles ] 243 + [ T.flex, T.relative, T.z_4 ] 290 244 [ ----------------------------------------- 291 245 -- Part 1 292 246 ----------------------------------------- ··· 400 354 401 355 402 356 -- 🖼 357 + 358 + 359 + navigationStyles : List Css.Style 360 + navigationStyles = 361 + [ Css.boxShadow5 (Css.px 0) (Css.px 0) (Css.px 10) (Css.px 1) (Css.rgba 0 0 0 0.05) 362 + ] 403 363 404 364 405 365 searchStyles : List Css.Style
+59
src/Applications/UI/Tracks/Core.elm
··· 1 + module UI.Tracks.Core exposing (Model, Msg(..), Scene(..)) 2 + 3 + import InfiniteList 4 + import Json.Encode as Json 5 + import Tracks exposing (..) 6 + 7 + 8 + 9 + -- 🌳 10 + 11 + 12 + type alias Model = 13 + { collection : Collection 14 + , enabledSourceIds : List String 15 + , favourites : List Favourite 16 + , favouritesOnly : Bool 17 + , infiniteList : InfiniteList.Model 18 + , nowPlaying : Maybe IdentifiedTrack 19 + , scene : Scene 20 + , searchResults : Maybe (List String) 21 + , searchTerm : Maybe String 22 + , sortBy : SortBy 23 + , sortDirection : SortDirection 24 + } 25 + 26 + 27 + 28 + -- 📣 29 + 30 + 31 + type Msg 32 + = Bypass 33 + | InfiniteListMsg InfiniteList.Model 34 + | SetEnabledSourceIds (List String) 35 + ----------------------------------------- 36 + -- Collection 37 + ----------------------------------------- 38 + | Add Json.Value 39 + | RemoveByPaths Json.Value 40 + | RemoveBySourceId String 41 + ----------------------------------------- 42 + -- Favourites 43 + ----------------------------------------- 44 + | ToggleFavouritesOnly 45 + ----------------------------------------- 46 + -- Search 47 + ----------------------------------------- 48 + | ClearSearch 49 + | Search 50 + | SetSearchResults Json.Value 51 + | SetSearchTerm String 52 + 53 + 54 + 55 + -- OTHER 56 + 57 + 58 + type Scene 59 + = List
+135 -18
src/Applications/UI/Tracks/Scene/List.elm
··· 1 1 module UI.Tracks.Scene.List exposing (view) 2 2 3 3 import Chunky exposing (..) 4 + import Color 4 5 import Color.Ext as Color 5 6 import Conditional exposing (ifThenElse) 6 7 import Css ··· 8 9 import Html.Attributes as UnstyledHtmlAttributes 9 10 import Html.Styled as Html exposing (Html, text) 10 11 import Html.Styled.Attributes exposing (css, fromUnstyled) 12 + import Html.Styled.Lazy 11 13 import InfiniteList 14 + import Material.Icons.Navigation as Icons 12 15 import Tachyons.Classes as T 13 16 import Tracks exposing (..) 14 17 import UI.Kit 18 + import UI.Tracks.Core exposing (..) 15 19 16 20 17 21 18 22 -- 🗺 19 23 20 24 21 - type alias Necessities msg = 22 - { favouritesOnly : Bool 23 - , infiniteList : InfiniteList.Model 24 - , screenHeight : Float 25 - , scrollMsg : InfiniteList.Model -> msg 25 + type alias Necessities = 26 + { height : Float 26 27 } 27 28 28 29 29 - view : Necessities msg -> List IdentifiedTrack -> Html msg 30 - view necessities tracks = 30 + view : Necessities -> Model -> Html Msg 31 + view necessities model = 31 32 let 32 - { favouritesOnly, infiniteList, scrollMsg } = 33 - necessities 33 + { infiniteList } = 34 + model 34 35 in 35 36 brick 36 - [ fromUnstyled (InfiniteList.onScroll scrollMsg) ] 37 + [ fromUnstyled (InfiniteList.onScroll InfiniteListMsg) ] 37 38 [ T.flex_grow_1 38 39 , T.vh_25 39 40 , T.overflow_x_hidden 40 41 , T.overflow_y_scroll 41 42 ] 42 - [ Html.fromUnstyled 43 + [ Html.Styled.Lazy.lazy2 header model.sortBy model.sortDirection 44 + , Html.fromUnstyled 43 45 (InfiniteList.view 44 - (infiniteListConfig necessities) 46 + (infiniteListConfig necessities model) 45 47 infiniteList 46 - tracks 48 + model.collection.harvested 49 + ) 50 + ] 51 + 52 + 53 + 54 + -- HEADERS 55 + 56 + 57 + header : SortBy -> SortDirection -> Html Msg 58 + header sortBy sortDirection = 59 + let 60 + color = 61 + Color.rgb255 207 207 207 62 + 63 + sortIcon = 64 + (if sortDirection == Desc then 65 + Icons.expand_less 66 + 67 + else 68 + Icons.expand_more 47 69 ) 70 + color 71 + 15 72 + 73 + sortIconHtml = 74 + Html.fromUnstyled sortIcon 75 + 76 + maybeSortIcon s = 77 + ifThenElse (sortBy == s) (Just sortIconHtml) Nothing 78 + in 79 + brick 80 + [ css headerStyles ] 81 + [ T.bg_white, T.flex, T.fw6, T.relative, T.z_5 ] 82 + [ headerColumn "" 4.5 First Nothing 83 + , headerColumn "Title" 37.5 Between (maybeSortIcon Title) 84 + , headerColumn "Artist" 29.0 Between (maybeSortIcon Artist) 85 + , headerColumn "Album" 29.0 Last (maybeSortIcon Album) 48 86 ] 87 + 88 + 89 + headerStyles : List Css.Style 90 + headerStyles = 91 + [ Css.borderBottom3 (Css.px 1) Css.solid (Color.toElmCssColor UI.Kit.colors.subtleBorder) 92 + , Css.color (Color.toElmCssColor headerTextColor) 93 + , Css.fontSize (Css.px 11) 94 + ] 95 + 96 + 97 + headerTextColor : Color.Color 98 + headerTextColor = 99 + Color.rgb255 207 207 207 100 + 101 + 102 + 103 + -- HEADER COLUMN 104 + 105 + 106 + type Pos 107 + = First 108 + | Between 109 + | Last 110 + 111 + 112 + headerColumn : 113 + String 114 + -> Float 115 + -> Pos 116 + -> Maybe (Html msg) 117 + -> Html msg 118 + headerColumn text_ width pos maybeSortIcon = 119 + let 120 + paddingLeft = 121 + ifThenElse (pos == First) T.pl2 T.pl1 122 + 123 + paddingRight = 124 + ifThenElse (pos == Last) T.pr2 T.pr1 125 + in 126 + brick 127 + [ css 128 + [ Css.borderLeft3 129 + (Css.px <| ifThenElse (pos /= First) 1 0) 130 + Css.solid 131 + (Color.toElmCssColor UI.Kit.colors.subtleBorder) 132 + , Css.width (Css.pct width) 133 + ] 134 + ] 135 + [ T.lh_title 136 + , T.ph2 137 + , T.pv1 138 + , T.relative 139 + 140 + -- 141 + , ifThenElse (pos == First) "" T.pointer 142 + ] 143 + [ brick 144 + [ css [ Css.top (Css.px 1) ] ] 145 + [ T.relative ] 146 + [ text text_ ] 147 + , case maybeSortIcon of 148 + Just sortIcon -> 149 + brick 150 + [ css sortIconStyles ] 151 + [ T.absolute, T.mr1, T.right_0 ] 152 + [ sortIcon ] 153 + 154 + Nothing -> 155 + nothing 156 + ] 157 + 158 + 159 + sortIconStyles : List Css.Style 160 + sortIconStyles = 161 + [ Css.fontSize (Css.px 0) 162 + , Css.lineHeight (Css.px 0) 163 + , Css.top (Css.pct 50) 164 + , Css.transform (Css.translateY <| Css.pct -50) 165 + ] 49 166 50 167 51 168 ··· 143 260 -- INFINITE LIST 144 261 145 262 146 - infiniteListConfig : Necessities msg -> InfiniteList.Config IdentifiedTrack msg 147 - infiniteListConfig necessities = 263 + infiniteListConfig : Necessities -> Model -> InfiniteList.Config IdentifiedTrack Msg 264 + infiniteListConfig necessities model = 148 265 InfiniteList.withCustomContainer 149 266 infiniteListContainer 150 267 (InfiniteList.config 151 - { itemView = itemView necessities 268 + { itemView = itemView model 152 269 , itemHeight = InfiniteList.withConstantHeight (round rowHeight) 153 - , containerHeight = round necessities.screenHeight 270 + , containerHeight = round necessities.height 154 271 } 155 272 ) 156 273 ··· 184 301 ] 185 302 186 303 187 - itemView : Necessities msg -> Int -> Int -> IdentifiedTrack -> UnstyledHtml.Html msg 304 + itemView : Model -> Int -> Int -> IdentifiedTrack -> UnstyledHtml.Html Msg 188 305 itemView { favouritesOnly } _ idx ( identifiers, track ) = 189 306 Html.toUnstyled <| 190 307 slab
+1
src/Applications/UI/UserData.elm
··· 16 16 import UI.Reply as UI 17 17 import UI.Sources as Sources 18 18 import UI.Tracks as Tracks 19 + import UI.Tracks.Core as Tracks 19 20 20 21 21 22