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.

Initial work for tracks

+538 -13
+2 -1
elm.json
··· 29 29 "jorgengranseth/elm-string-format": "1.0.1", 30 30 "justgage/tachyons-elm": "4.1.1", 31 31 "noahzgordon/elm-color-extra": "1.0.1", 32 + "pilatch/flip": "1.0.0", 32 33 "rtfeldman/elm-css": "16.0.0", 33 34 "rtfeldman/elm-hex": "1.0.0", 34 35 "ryannhg/date-format": "2.3.0", ··· 48 49 "direct": {}, 49 50 "indirect": {} 50 51 } 51 - } 52 + }
+1 -1
src/Applications/Brain.elm
··· 89 89 90 90 91 91 92 - -- 📣 ░░░ CHILDREN & REPLIES 92 + -- 📣 ░░ CHILDREN & REPLIES 93 93 94 94 95 95 translateReply : Reply -> Msg
+18 -4
src/Applications/UI.elm
··· 31 31 import UI.Settings 32 32 import UI.Sources 33 33 import UI.Svg.Elements 34 + import UI.Tracks 34 35 import UI.UserData 35 36 import Url exposing (Url) 36 37 ··· 69 70 -- Children 70 71 , backdrop = UI.Backdrop.initialModel 71 72 , sources = UI.Sources.initialModel 73 + , tracks = UI.Tracks.initialModel 72 74 } 73 75 ----------------------------------------- 74 76 -- Initial command ··· 133 135 , msg = sub 134 136 } 135 137 138 + TracksMsg sub -> 139 + updateChild 140 + { mapCmd = TracksMsg 141 + , mapModel = \child -> { model | tracks = child } 142 + , update = UI.Tracks.update 143 + } 144 + { model = model.tracks 145 + , msg = sub 146 + } 147 + 136 148 ----------------------------------------- 137 149 -- Brain 138 150 ----------------------------------------- ··· 211 223 212 224 213 225 214 - -- 📣 ░░░ CHILDREN & REPLIES 226 + -- 📣 ░░ CHILDREN & REPLIES 215 227 216 228 217 229 translateReply : Reply -> Msg ··· 352 364 ----------------------------------------- 353 365 , case model.page of 354 366 Page.Index -> 355 - empty 367 + model.tracks 368 + |> Lazy.lazy UI.Tracks.view 369 + |> Html.map TracksMsg 356 370 357 371 Page.NotFound -> 358 372 text "Page not found." ··· 375 389 376 390 377 391 378 - -- 🗺 ░░░ BITS 392 + -- 🗺 ░░ BITS 379 393 380 394 381 395 content : List (Html msg) -> Html msg ··· 398 412 399 413 400 414 401 - -- 🖼 ░░░ GLOBAL 415 + -- 🖼 ░░ GLOBAL 402 416 403 417 404 418 globalCss : List Css.Global.Snippet
+3
src/Applications/UI/Core.elm
··· 8 8 import UI.Backdrop 9 9 import UI.Page exposing (Page) 10 10 import UI.Sources 11 + import UI.Tracks 11 12 import Url exposing (Url) 12 13 13 14 ··· 35 36 ----------------------------------------- 36 37 , backdrop : UI.Backdrop.Model 37 38 , sources : UI.Sources.Model 39 + , tracks : UI.Tracks.Model 38 40 } 39 41 40 42 ··· 52 54 ----------------------------------------- 53 55 | BackdropMsg UI.Backdrop.Msg 54 56 | SourcesMsg UI.Sources.Msg 57 + | TracksMsg UI.Tracks.Msg 55 58 ----------------------------------------- 56 59 -- Brain 57 60 -----------------------------------------
+48
src/Applications/UI/Tracks.elm
··· 1 + module UI.Tracks exposing (Model, Msg(..), initialModel, update, view) 2 + 3 + import Chunky exposing (..) 4 + import Html.Styled as Html exposing (Html, text) 5 + import Replying exposing (R3D3) 6 + import Return3 7 + import Tracks exposing (..) 8 + import UI.Kit 9 + import UI.Reply exposing (Reply) 10 + 11 + 12 + 13 + -- 🌳 14 + 15 + 16 + type alias Model = 17 + { collection : Collection 18 + } 19 + 20 + 21 + initialModel : Model 22 + initialModel = 23 + { collection = emptyCollection 24 + } 25 + 26 + 27 + 28 + -- 📣 29 + 30 + 31 + type Msg 32 + = Bypass 33 + 34 + 35 + update : Msg -> Model -> R3D3 Model Msg Reply 36 + update msg model = 37 + case msg of 38 + Bypass -> 39 + Return3.withNothing model 40 + 41 + 42 + 43 + -- 🗺 44 + 45 + 46 + view : Model -> Html Msg 47 + view model = 48 + UI.Kit.vessel []
+4 -4
src/Applications/UI/UserData.elm
··· 36 36 37 37 38 38 39 - -- 📭 ░░░ IMPORTING HYPAETHRAL 39 + -- 📭 ░░ IMPORTING HYPAETHRAL 40 40 41 41 42 42 importSources : UI.Sources.Model -> HypaethralBundle -> UI.Sources.Model ··· 45 45 46 46 47 47 48 - -- 📭 ░░░ DECODING 48 + -- 📭 ░░ DECODING 49 49 50 50 51 51 decode : Decode.Value -> Result Decode.Error HypaethralUserData ··· 61 61 62 62 63 63 64 - -- 📭 ░░░ FALLBACKS 64 + -- 📭 ░░ FALLBACKS 65 65 66 66 67 67 emptyHypaethralUserData : HypaethralUserData ··· 81 81 82 82 83 83 84 - -- 📮 ░░░ ENCODING 84 + -- 📮 ░░ ENCODING 85 85 86 86 87 87 encode : UI.Core.Model -> Encode.Value
+1 -1
src/Library/Replying.elm
··· 47 47 48 48 49 49 50 - -- 🔱 ░░░ TASKS 50 + -- 🔱 ░░ TASKS 51 51 52 52 53 53 do : msg -> Cmd msg
+32 -1
src/Library/Tracks.elm
··· 1 - module Tracks exposing (Collection, Favourite, IdentifiedTrack, Identifiers, Tags, Track, emptyCollection, emptyIdentifiedTrack, emptyTags, emptyTrack, makeTrack, missingId) 1 + module Tracks exposing (Collection, Favourite, IdentifiedTrack, Identifiers, Parcel, SortBy(..), SortDirection(..), Tags, Track, emptyCollection, emptyIdentifiedTrack, emptyTags, emptyTrack, makeTrack, missingId) 2 2 3 3 import Base64 4 4 import Bytes.Encode ··· 79 79 -- Filtered by search results, favourites, etc. 80 80 , harvested : List IdentifiedTrack 81 81 } 82 + 83 + 84 + type alias Parcel = 85 + ( -- Things I need to make a proper collection: 86 + { enabledSourceIds : List String 87 + , favourites : List Favourite 88 + , favouritesOnly : Bool 89 + , nowPlaying : Maybe IdentifiedTrack 90 + , searchResults : Maybe (List String) 91 + , sortBy : SortBy 92 + , sortDirection : SortDirection 93 + } 94 + -- The collection itself: 95 + , Collection 96 + ) 97 + 98 + 99 + 100 + -- SORTING 101 + 102 + 103 + type SortBy 104 + = Artist 105 + | Album 106 + | PlaylistIndex 107 + | Title 108 + 109 + 110 + type SortDirection 111 + = Asc 112 + | Desc 82 113 83 114 84 115
+58
src/Library/Tracks/Collection/Internal.elm
··· 1 + module Tracks.Collection.Internal exposing 2 + ( arrange 3 + , build 4 + , buildf 5 + , harvest 6 + , identify 7 + , initialize 8 + ) 9 + 10 + import Flip exposing (flip) 11 + import Tracks exposing (Parcel, Track) 12 + import Tracks.Collection.Internal.Arrange as Internal 13 + import Tracks.Collection.Internal.Harvest as Internal 14 + import Tracks.Collection.Internal.Identify as Internal 15 + 16 + 17 + 18 + -- 🔱 19 + 20 + 21 + build : List Track -> Parcel -> Parcel 22 + build tracks = 23 + initialize tracks >> identify >> arrange >> harvest 24 + 25 + 26 + buildf : Parcel -> List Track -> Parcel 27 + buildf = 28 + flip build 29 + 30 + 31 + 32 + -- INITIALIZE 33 + 34 + 35 + initialize : List Track -> Parcel -> Parcel 36 + initialize tracks ( dependencies, collection ) = 37 + ( dependencies 38 + , { collection | untouched = tracks } 39 + ) 40 + 41 + 42 + 43 + -- RE-EXPORT 44 + 45 + 46 + identify : Parcel -> Parcel 47 + identify = 48 + Internal.identify 49 + 50 + 51 + arrange : Parcel -> Parcel 52 + arrange = 53 + Internal.arrange 54 + 55 + 56 + harvest : Parcel -> Parcel 57 + harvest = 58 + Internal.harvest
+16
src/Library/Tracks/Collection/Internal/Arrange.elm
··· 1 + module Tracks.Collection.Internal.Arrange exposing (arrange) 2 + 3 + import Tracks exposing (..) 4 + import Tracks.Sorting as Sorting 5 + 6 + 7 + 8 + -- 🍯 9 + 10 + 11 + arrange : Parcel -> Parcel 12 + arrange ( deps, collection ) = 13 + collection.identified 14 + |> Sorting.sort deps.sortBy deps.sortDirection 15 + |> (\x -> { collection | arranged = x }) 16 + |> (\x -> ( deps, x ))
+71
src/Library/Tracks/Collection/Internal/Harvest.elm
··· 1 + module Tracks.Collection.Internal.Harvest exposing (harvest) 2 + 3 + import List.Extra as List 4 + import Maybe.Extra as Maybe 5 + import Tracks exposing (..) 6 + 7 + 8 + 9 + -- 🍯 10 + 11 + 12 + harvest : Parcel -> Parcel 13 + harvest ( deps, collection ) = 14 + let 15 + harvested = 16 + case deps.searchResults of 17 + Just [] -> 18 + [] 19 + 20 + Just trackIds -> 21 + collection.arranged 22 + |> List.foldl harvester ( [], trackIds ) 23 + |> Tuple.first 24 + 25 + Nothing -> 26 + collection.arranged 27 + 28 + filters = 29 + [ -- Favourites / Missing 30 + ----------------------- 31 + if deps.favouritesOnly then 32 + Tuple.first >> .isFavourite >> (==) True 33 + 34 + else 35 + Tuple.first >> .isMissing >> (==) False 36 + ] 37 + 38 + theFilter x = 39 + List.foldl 40 + (\filter bool -> 41 + if bool == True then 42 + filter x 43 + 44 + else 45 + bool 46 + ) 47 + True 48 + filters 49 + in 50 + harvested 51 + |> List.filter theFilter 52 + |> List.indexedMap (\idx tup -> Tuple.mapFirst (\i -> { i | indexInList = idx }) tup) 53 + |> (\h -> { collection | harvested = h }) 54 + |> (\c -> ( deps, c )) 55 + 56 + 57 + harvester : 58 + IdentifiedTrack 59 + -> ( List IdentifiedTrack, List String ) 60 + -> ( List IdentifiedTrack, List String ) 61 + harvester ( i, t ) ( acc, trackIds ) = 62 + case List.findIndex ((==) t.id) trackIds of 63 + Just idx -> 64 + ( acc ++ [ ( i, t ) ] 65 + , List.removeAt idx trackIds 66 + ) 67 + 68 + Nothing -> 69 + ( acc 70 + , trackIds 71 + )
+140
src/Library/Tracks/Collection/Internal/Identify.elm
··· 1 + module Tracks.Collection.Internal.Identify exposing (identify) 2 + 3 + import List.Extra as List 4 + import Tracks exposing (..) 5 + import Tracks.Favourites as Favourites 6 + 7 + 8 + 9 + -- 🔱 10 + 11 + 12 + identify : Parcel -> Parcel 13 + identify ( deps, collection ) = 14 + let 15 + ( identifiedUnsorted, missingFavourites ) = 16 + List.foldl 17 + (identifyTrack 18 + deps.enabledSourceIds 19 + deps.favourites 20 + deps.nowPlaying 21 + ) 22 + ( [], deps.favourites ) 23 + collection.untouched 24 + in 25 + identifiedUnsorted 26 + |> List.append (List.map makeMissingFavouriteTrack missingFavourites) 27 + |> (\x -> { collection | identified = x }) 28 + |> (\x -> ( deps, x )) 29 + 30 + 31 + 32 + -- IDENTIFY 33 + 34 + 35 + identifyTrack : 36 + List String 37 + -> List Favourite 38 + -> Maybe IdentifiedTrack 39 + -> Track 40 + -> ( List IdentifiedTrack, List Favourite ) 41 + -> ( List IdentifiedTrack, List Favourite ) 42 + identifyTrack enabledSourceIds favourites nowPlaying track = 43 + case List.member track.sourceId enabledSourceIds of 44 + True -> 45 + partTwo favourites nowPlaying track 46 + 47 + False -> 48 + identity 49 + 50 + 51 + partTwo : 52 + List Favourite 53 + -> Maybe IdentifiedTrack 54 + -> Track 55 + -> ( List IdentifiedTrack, List Favourite ) 56 + -> ( List IdentifiedTrack, List Favourite ) 57 + partTwo favourites nowPlaying track ( acc, remainingFavourites ) = 58 + let 59 + isNP = 60 + nowPlaying 61 + |> Maybe.map (Tuple.second >> .id >> (==) track.id) 62 + |> Maybe.withDefault False 63 + 64 + isFavourite_ = 65 + isFavourite track 66 + 67 + isFav = 68 + List.any isFavourite_ favourites 69 + 70 + identifiedTrack = 71 + ( { indexInList = 0 72 + , indexInPlaylist = Nothing 73 + , isFavourite = isFav 74 + , isMissing = False 75 + , isNowPlaying = isNP 76 + , isSelected = False 77 + } 78 + , track 79 + ) 80 + in 81 + case isFav of 82 + -- 83 + -- A favourite 84 + -- 85 + True -> 86 + ( identifiedTrack :: acc 87 + , remainingFavourites 88 + |> List.findIndex isFavourite_ 89 + |> Maybe.map (\idx -> List.removeAt idx remainingFavourites) 90 + |> Maybe.withDefault remainingFavourites 91 + ) 92 + 93 + -- 94 + -- Not a favourite 95 + -- 96 + False -> 97 + ( identifiedTrack :: acc 98 + , remainingFavourites 99 + ) 100 + 101 + 102 + 103 + -- FAVOURITES 104 + 105 + 106 + isFavourite : Track -> (Favourite -> Bool) 107 + isFavourite track = 108 + Favourites.match 109 + { artist = track.tags.artist 110 + , title = track.tags.title 111 + } 112 + 113 + 114 + makeMissingFavouriteTrack : Favourite -> IdentifiedTrack 115 + makeMissingFavouriteTrack fav = 116 + let 117 + tags = 118 + { disc = 1 119 + , nr = 0 120 + , artist = fav.artist 121 + , title = fav.title 122 + , album = missingId 123 + , genre = Nothing 124 + , picture = Nothing 125 + , year = Nothing 126 + } 127 + in 128 + ( { indexInList = 0 129 + , indexInPlaylist = Nothing 130 + , isFavourite = True 131 + , isMissing = True 132 + , isNowPlaying = False 133 + , isSelected = False 134 + } 135 + , { tags = tags 136 + , id = missingId 137 + , path = missingId 138 + , sourceId = missingId 139 + } 140 + )
+23
src/Library/Tracks/Favourites.elm
··· 1 + module Tracks.Favourites exposing (match) 2 + 3 + import Tracks exposing (Favourite, Track) 4 + 5 + 6 + 7 + -- 🔱 8 + 9 + 10 + match : Favourite -> Favourite -> Bool 11 + match a b = 12 + let 13 + ( aa, at ) = 14 + ( String.toLower a.artist 15 + , String.toLower a.title 16 + ) 17 + 18 + ( ba, bt ) = 19 + ( String.toLower b.artist 20 + , String.toLower b.title 21 + ) 22 + in 23 + aa == ba && at == bt
+120
src/Library/Tracks/Sorting.elm
··· 1 + module Tracks.Sorting exposing (sort) 2 + 3 + import Tracks exposing (..) 4 + 5 + 6 + 7 + -- 🔱 8 + 9 + 10 + sort : SortBy -> SortDirection -> List IdentifiedTrack -> List IdentifiedTrack 11 + sort property direction list = 12 + let 13 + sortFn = 14 + case property of 15 + Album -> 16 + sortByAlbum 17 + 18 + Artist -> 19 + sortByArtist 20 + 21 + PlaylistIndex -> 22 + sortByPlaylistIndex 23 + 24 + Title -> 25 + sortByTitle 26 + 27 + dirFn = 28 + if direction == Desc then 29 + List.reverse 30 + 31 + else 32 + identity 33 + in 34 + list 35 + |> List.sortWith sortFn 36 + |> dirFn 37 + 38 + 39 + 40 + -- BY 41 + 42 + 43 + sortByAlbum : IdentifiedTrack -> IdentifiedTrack -> Order 44 + sortByAlbum ( _, a ) ( _, b ) = 45 + EQ 46 + |> andThenCompare album a b 47 + |> andThenCompare disc a b 48 + |> andThenCompare nr a b 49 + |> andThenCompare artist a b 50 + |> andThenCompare title a b 51 + 52 + 53 + sortByArtist : IdentifiedTrack -> IdentifiedTrack -> Order 54 + sortByArtist ( _, a ) ( _, b ) = 55 + EQ 56 + |> andThenCompare artist a b 57 + |> andThenCompare album a b 58 + |> andThenCompare disc a b 59 + |> andThenCompare nr a b 60 + |> andThenCompare title a b 61 + 62 + 63 + sortByTitle : IdentifiedTrack -> IdentifiedTrack -> Order 64 + sortByTitle ( _, a ) ( _, b ) = 65 + EQ 66 + |> andThenCompare title a b 67 + |> andThenCompare artist a b 68 + |> andThenCompare album a b 69 + 70 + 71 + sortByPlaylistIndex : IdentifiedTrack -> IdentifiedTrack -> Order 72 + sortByPlaylistIndex ( a, _ ) ( b, _ ) = 73 + andThenCompare (.indexInPlaylist >> Maybe.withDefault 0) a b EQ 74 + 75 + 76 + 77 + -- TAGS 78 + 79 + 80 + album : Track -> String 81 + album = 82 + .tags >> .album >> low 83 + 84 + 85 + artist : Track -> String 86 + artist = 87 + .tags >> .artist >> low 88 + 89 + 90 + title : Track -> String 91 + title = 92 + .tags >> .title >> low 93 + 94 + 95 + disc : Track -> Int 96 + disc = 97 + .tags >> .disc 98 + 99 + 100 + nr : Track -> Int 101 + nr = 102 + .tags >> .nr 103 + 104 + 105 + 106 + -- COMMON 107 + 108 + 109 + andThenCompare : (ctx -> comparable) -> ctx -> ctx -> Order -> Order 110 + andThenCompare fn a b order = 111 + if order == EQ then 112 + compare (fn a) (fn b) 113 + 114 + else 115 + order 116 + 117 + 118 + low : String -> String 119 + low = 120 + String.toLower
+1 -1
src/README.md
··· 6 6 - Applications/UI 7 7 - Library 8 8 9 - `UI` is the Elm application that'll be executed on the main thread (ie. the UI thread) and `Brain` is the Elm application that'll live inside a web worker. `UI` will be the main application and `Brain` does the heavy lifting. The code shared between these two applications lives in `Library`. 9 + `UI` is the Elm application that'll be executed on the main thread (ie. the UI thread) and `Brain` is the Elm application that'll live inside a web worker. `UI` will be the main application and `Brain` does the heavy lifting. The code shared between these two applications lives in `Library`. The library also contains the more "generic", code that's not necessarily tied to one or the other. 10 10 11 11 12 12