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.

Closes #305

+126 -55
+1
CHANGELOG.md
··· 6 6 - Add support for non-standard `audio/x-flac` mimetype. 7 7 - Fixes a playback issue with Google Drive on Safari/iOS. 8 8 - Improves Google Drive support, access tokens are now refreshed when needed (before it only refreshed on source processing) 9 + - Reduces the track pool when selecting a cover. In other words, when you're using the album art mode and select an image, only those tracks will be in the "automatic" queue (automatic queue, as in, not manually added queue items). This behaviour can be disabled on the settings page if you prefer the old behaviour. 9 10 - Shift + Click actions for the top-right nav items on the tracks page. 10 11 - Several playlist improvements: add to queue, add options to autogenerated playlists, convert autogenerated playlist into regular playlist, etc. 11 12
+2 -5
Justfile
··· 175 175 # 176 176 177 177 @dev: build 178 - just watch-wo-build & just server 178 + just watch & just server 179 179 180 180 181 181 @doc-tests: ··· 229 229 @test: doc-tests 230 230 231 231 232 - @watch: build watch-wo-build 233 - 234 - 235 - @watch-wo-build: 232 + @watch: 236 233 echo "> Watching" 237 234 just watch-css & just watch-elm & just watch-js & just watch-system 238 235
+3 -3
flake.lock
··· 75 75 "nixpkgs": "nixpkgs_2" 76 76 }, 77 77 "locked": { 78 - "lastModified": 1662173844, 79 - "narHash": "sha256-+ZgW98Y8fZkgFSylE+Mzalumw+kw3SVivZznbJqQaj8=", 78 + "lastModified": 1662778811, 79 + "narHash": "sha256-lpkekAbs8FNoqpbxuMpkdNMiRnWxDyvAyR+pWxW2PTs=", 80 80 "owner": "oxalica", 81 81 "repo": "rust-overlay", 82 - "rev": "8ac6d40380dc4ec86f1ff591d5c14c8ae1d77a18", 82 + "rev": "70d8df25fbe9cc5c74900b11d06ef120200f3948", 83 83 "type": "github" 84 84 }, 85 85 "original": {
+1
src/Applications/UI.elm
··· 196 196 , cachedTracksOnly = False 197 197 , cachingTracksInProgress = [] 198 198 , covers = { arranged = [], harvested = [] } 199 + , coverSelectionReducesPool = True 199 200 , favourites = [] 200 201 , favouritesOnly = False 201 202 , grouping = Nothing
+1
src/Applications/UI/Authentication/State.elm
··· 520 520 521 521 -- Tracks 522 522 --------- 523 + , coverSelectionReducesPool = True 523 524 , favourites = [] 524 525 , hideDuplicates = False 525 526 , searchResults = Nothing
+24 -1
src/Applications/UI/Queue/State.elm
··· 111 111 fill model = 112 112 let 113 113 ( availableTracks, timestamp ) = 114 - ( model.tracks.harvested 114 + ( case ( model.selectedCover, model.coverSelectionReducesPool ) of 115 + ( Just cover, True ) -> 116 + Tuple.first <| List.foldl coverTracksHarvester ( [], cover.trackIds ) model.tracks.harvested 117 + 118 + _ -> 119 + model.tracks.harvested 120 + -- 115 121 , model.currentTime 116 122 ) 117 123 ··· 419 425 420 426 421 427 -- ⚗️ 428 + 429 + 430 + coverTracksHarvester : 431 + IdentifiedTrack 432 + -> ( List IdentifiedTrack, List String ) 433 + -> ( List IdentifiedTrack, List String ) 434 + coverTracksHarvester ( i, t ) ( acc, coverTrackIds ) = 435 + case List.findIndex ((==) t.id) coverTrackIds of 436 + Just idx -> 437 + ( acc ++ [ ( i, t ) ] 438 + , List.removeAt idx coverTrackIds 439 + ) 440 + 441 + Nothing -> 442 + ( acc 443 + , coverTrackIds 444 + ) 422 445 423 446 424 447 moveItem : { from : Int, to : Int, shuffle : Bool } -> List Item -> List Item
+9
src/Applications/UI/Settings.elm
··· 35 35 { authenticationMethod : Maybe User.Layer.Method 36 36 , buildTimestamp : Int 37 37 , chosenBackgroundImage : Maybe String 38 + , coverSelectionReducesPool : Bool 38 39 , currentTimeZone : Time.Zone 39 40 , extractedBackdropColor : Maybe Color 40 41 , hideDuplicateTracks : Bool ··· 345 346 , UI.Kit.checkbox 346 347 { checked = deps.rememberProgress 347 348 , toggleMsg = ToggleRememberProgress 349 + } 350 + ] 351 + , chunk 352 + [ "w-full", "md:w-1/2" ] 353 + [ label "Cover selection reduces track pool" 354 + , UI.Kit.checkbox 355 + { checked = deps.coverSelectionReducesPool 356 + , toggleMsg = TracksMsg Tracks.ToggleCoverSelectionReducesPool 348 357 } 349 358 ] 350 359 ]
-32
src/Applications/UI/Tracks/Covers.elm
··· 246 246 -- ⚗️ 247 247 248 248 249 - coverGroup : SortBy -> IdentifiedTrack -> String 250 - coverGroup sort ( identifiers, { tags } as track ) = 251 - (case sort of 252 - Artist -> 253 - tags.artist 254 - 255 - Album -> 256 - -- There is the possibility of albums with the same name, 257 - -- such as "Greatests Hits". 258 - -- To make sure we treat those as different albums, 259 - -- we prefix the album by its parent directory. 260 - identifiers.parentDirectory ++ tags.album 261 - 262 - PlaylistIndex -> 263 - "" 264 - 265 - Title -> 266 - tags.title 267 - ) 268 - |> String.trim 269 - |> String.toLower 270 - 271 - 272 - coverKey : Bool -> Track -> String 273 - coverKey isVariousArtists { tags } = 274 - if isVariousArtists then 275 - tags.album 276 - 277 - else 278 - tags.artist ++ " --- " ++ tags.album 279 - 280 - 281 249 makeCover sortBy_ gathering collection = 282 250 let 283 251 closedGathering =
+36 -9
src/Applications/UI/Tracks/State.elm
··· 67 67 ToggleCachedOnly -> 68 68 toggleCachedOnly 69 69 70 + ToggleCoverSelectionReducesPool -> 71 + toggleCoverSelectionReducesPool 72 + 70 73 ToggleFavouritesOnly -> 71 74 toggleFavouritesOnly 72 75 ··· 212 215 Cmd.none 213 216 ) 214 217 |> return { model | scene = scene, selectedCover = Nothing } 218 + |> andThen 219 + (if model.coverSelectionReducesPool then 220 + Queue.reset 221 + 222 + else 223 + Return.singleton 224 + ) 215 225 |> andThen Common.forceTracksRerender 216 226 |> andThen User.saveEnclosedUserData 217 227 ··· 241 251 242 252 deselectCover : Manager 243 253 deselectCover model = 244 - Return.singleton { model | selectedCover = Nothing } 254 + (if model.coverSelectionReducesPool then 255 + Queue.reset 256 + 257 + else 258 + Return.singleton 259 + ) 260 + { model | selectedCover = Nothing } 245 261 246 262 247 263 download : String -> List Track -> Manager ··· 575 591 576 592 selectCover : Cover -> Manager 577 593 selectCover cover model = 578 - return 579 - { model | selectedCover = Just cover } 580 - (Ports.loadAlbumCovers { list = False, coverView = True }) 594 + { model | selectedCover = Just cover } 595 + |> (if model.coverSelectionReducesPool then 596 + Queue.reset 597 + 598 + else 599 + Return.singleton 600 + ) 601 + |> Return.command (Ports.loadAlbumCovers { list = False, coverView = True }) 581 602 582 603 583 604 setSearchResults : Json.Value -> Manager ··· 844 865 |> andThen Common.forceTracksRerender 845 866 846 867 868 + toggleCoverSelectionReducesPool : Manager 869 + toggleCoverSelectionReducesPool model = 870 + { model | coverSelectionReducesPool = not model.coverSelectionReducesPool } 871 + |> Queue.reset 872 + |> andThen User.saveSettings 873 + 874 + 847 875 toggleFavourite : Int -> Manager 848 876 toggleFavourite index model = 849 877 case List.getAt index model.tracks.harvested of ··· 958 986 model.tracks.harvested 959 987 newCollection.harvested 960 988 961 - searchChanged = 989 + scrollContextChanged = 962 990 newScrollContext /= model.tracks.scrollContext 963 991 964 992 modelWithNewCollection = 965 - (if model.scene == List && searchChanged then 993 + (if model.scene == List && scrollContextChanged then 966 994 \m -> { m | infiniteList = InfiniteList.updateScroll scrollEvent m.infiniteList } 967 995 968 996 else ··· 995 1023 ----------------------------------------- 996 1024 -- Command 997 1025 ----------------------------------------- 998 - , if searchChanged then 1026 + , if scrollContextChanged then 999 1027 case model.scene of 1000 1028 Covers -> 1001 1029 UI.Tracks.Scene.Covers.scrollToTop ··· 1009 1037 1010 1038 1011 1039 whenHarvestChanges = 1012 - andThen Queue.reset >> andThen harvestCovers 1040 + andThen harvestCovers >> andThen Queue.reset 1013 1041 1014 1042 1015 1043 whenArrangementChanges = ··· 1036 1064 importHypaethral data selectedPlaylist model = 1037 1065 { model 1038 1066 | favourites = data.favourites 1039 - , hideDuplicates = Maybe.unwrap False .hideDuplicates data.settings 1040 1067 , selectedPlaylist = selectedPlaylist 1041 1068 , tracks = { emptyCollection | untouched = data.tracks } 1042 1069 }
+1
src/Applications/UI/Tracks/Types.elm
··· 44 44 | RemoveFavourites (List IdentifiedTrack) 45 45 | SortBy SortBy 46 46 | ToggleFavourite Int 47 + | ToggleCoverSelectionReducesPool 47 48 ----------------------------------------- 48 49 -- Groups 49 50 -----------------------------------------
+1
src/Applications/UI/Types.elm
··· 160 160 , cachedTracksOnly : Bool 161 161 , cachingTracksInProgress : List String 162 162 , covers : { arranged : List Tracks.Cover, harvested : List Tracks.Cover } 163 + , coverSelectionReducesPool : Bool 163 164 , favourites : List Favourite 164 165 , favouritesOnly : Bool 165 166 , grouping : Maybe Grouping
+2 -1
src/Applications/UI/User/State/Export.elm
··· 33 33 34 34 35 35 gatherSettings : Model -> Settings 36 - gatherSettings { chosenBackdrop, hideDuplicates, lastFm, processAutomatically, rememberProgress } = 36 + gatherSettings { chosenBackdrop, coverSelectionReducesPool, hideDuplicates, lastFm, processAutomatically, rememberProgress } = 37 37 { backgroundImage = chosenBackdrop 38 + , coverSelectionReducesPool = coverSelectionReducesPool 38 39 , hideDuplicates = hideDuplicates 39 40 , lastFm = lastFm.sessionKey 40 41 , processAutomatically = processAutomatically
+2
src/Applications/UI/User/State/Import.elm
··· 176 176 selectedPlaylist 177 177 { model 178 178 | chosenBackdrop = chosenBackdrop 179 + , coverSelectionReducesPool = Maybe.unwrap True .coverSelectionReducesPool data.settings 180 + , hideDuplicates = Maybe.unwrap False .hideDuplicates data.settings 179 181 , lastFm = { lastFmModel | sessionKey = Maybe.andThen .lastFm data.settings } 180 182 , playlists = newPlaylistsCollection 181 183 , playlistToActivate = Nothing
+1
src/Applications/UI/View.elm
··· 178 178 { authenticationMethod = Authentication.extractMethod model.authentication 179 179 , buildTimestamp = model.buildTimestamp 180 180 , chosenBackgroundImage = model.chosenBackdrop 181 + , coverSelectionReducesPool = model.coverSelectionReducesPool 181 182 , currentTimeZone = model.currentTimeZone 182 183 , extractedBackdropColor = model.extractedBackdropColor 183 184 , hideDuplicateTracks = model.hideDuplicates
+5
src/Library/Settings.elm
··· 12 12 13 13 type alias Settings = 14 14 { backgroundImage : Maybe String 15 + , coverSelectionReducesPool : Bool 15 16 , hideDuplicates : Bool 16 17 , lastFm : Maybe String 17 18 , processAutomatically : Bool ··· 28 29 Json.Encode.object 29 30 [ ( "backgroundImage" 30 31 , Maybe.unwrap Json.Encode.null Json.Encode.string settings.backgroundImage 32 + ) 33 + , ( "coverSelectionReducesPool" 34 + , Json.Encode.bool settings.coverSelectionReducesPool 31 35 ) 32 36 , ( "hideDuplicates" 33 37 , Json.Encode.bool settings.hideDuplicates ··· 52 56 decoder = 53 57 Json.succeed Settings 54 58 |> optional "backgroundImage" (Json.maybe Json.string) Nothing 59 + |> optional "coverSelectionReducesPool" Json.bool True 55 60 |> optional "hideDuplicates" Json.bool False 56 61 |> optional "lastFm" (Json.maybe Json.string) Nothing 57 62 |> optional "processAutomatically" Json.bool True
+32
src/Library/Tracks.elm
··· 224 224 -- MORE STUFF 225 225 226 226 227 + coverGroup : SortBy -> IdentifiedTrack -> String 228 + coverGroup sort ( identifiers, { tags } as track ) = 229 + (case sort of 230 + Artist -> 231 + tags.artist 232 + 233 + Album -> 234 + -- There is the possibility of albums with the same name, 235 + -- such as "Greatests Hits". 236 + -- To make sure we treat those as different albums, 237 + -- we prefix the album by its parent directory. 238 + identifiers.parentDirectory ++ tags.album 239 + 240 + PlaylistIndex -> 241 + "" 242 + 243 + Title -> 244 + tags.title 245 + ) 246 + |> String.trim 247 + |> String.toLower 248 + 249 + 250 + coverKey : Bool -> Track -> String 251 + coverKey isVariousArtists { tags } = 252 + if isVariousArtists then 253 + tags.album 254 + 255 + else 256 + tags.artist ++ " --- " ++ tags.album 257 + 258 + 227 259 isNowPlaying : IdentifiedTrack -> IdentifiedTrack -> Bool 228 260 isNowPlaying ( a, b ) ( x, y ) = 229 261 a.indexInPlaylist == x.indexInPlaylist && b.id == y.id
+4
src/Library/Tracks/Collection/Internal/Harvest.elm
··· 120 120 |> (\c -> ( deps, c )) 121 121 122 122 123 + 124 + -- 🛠 125 + 126 + 123 127 harvester : 124 128 IdentifiedTrack 125 129 -> ( List IdentifiedTrack, List String )
+1 -4
stack.yaml
··· 1 - resolver: lts-19.16 1 + resolver: nightly-2022-09-10 2 2 recommend-stack-upgrade: false 3 3 allow-newer: true 4 - 5 - extra-deps: 6 - - protolude-0.3.2