Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

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

Merge pull request #41 from tsirysndr/feat/play-tracks-at-specific-position

webui: play track located at specific position from a list

authored by

Tsiry Sandratraina and committed by
GitHub
27cbb7f0 37083d9e

+1478 -289
+66 -4
crates/graphql/src/schema/playback.rs
··· 158 158 ctx: &Context<'_>, 159 159 album_id: String, 160 160 shuffle: Option<bool>, 161 + position: Option<i32>, 161 162 ) -> Result<i32, Error> { 162 163 let pool = ctx.data::<Pool<Sqlite>>()?; 163 164 let tracks = repo::album_tracks::find_by_album(pool.clone(), &album_id).await?; ··· 174 175 client.put(&url).send().await?; 175 176 } 176 177 177 - let url = format!("{}/playlists/start", rockbox_url()); 178 + let url = match position { 179 + Some(p) => format!("{}/playlists/start?start_index={}", rockbox_url(), p), 180 + None => format!("{}/playlists/start", rockbox_url()), 181 + }; 182 + 178 183 client.put(&url).send().await?; 179 184 180 185 Ok(0) ··· 185 190 ctx: &Context<'_>, 186 191 artist_id: String, 187 192 shuffle: Option<bool>, 193 + position: Option<i32>, 188 194 ) -> Result<i32, Error> { 189 195 let pool = ctx.data::<Pool<Sqlite>>()?; 190 196 let client = ctx.data::<reqwest::Client>().unwrap(); ··· 201 207 client.put(&url).send().await?; 202 208 } 203 209 204 - let url = format!("{}/playlists/start", rockbox_url()); 210 + let url = match position { 211 + Some(p) => format!("{}/playlists/start?start_index={}", rockbox_url(), p), 212 + None => format!("{}/playlists/start", rockbox_url()), 213 + }; 214 + 205 215 client.put(&url).send().await?; 206 216 207 217 Ok(0) ··· 212 222 _ctx: &Context<'_>, 213 223 _playlist_id: String, 214 224 _shuffle: Option<bool>, 225 + _position: Option<i32>, 215 226 ) -> Result<i32, Error> { 216 227 todo!() 217 228 } ··· 222 233 path: String, 223 234 recurse: Option<bool>, 224 235 shuffle: Option<bool>, 236 + position: Option<i32>, 225 237 ) -> Result<i32, Error> { 226 238 let client = ctx.data::<reqwest::Client>().unwrap(); 227 239 let mut tracks: Vec<String> = vec![]; 228 240 241 + let recurse = match position { 242 + Some(_) => Some(false), 243 + None => recurse, 244 + }; 245 + 229 246 if !std::path::Path::new(&path).is_dir() { 230 247 return Err(Error::new("Invalid path")); 231 248 } ··· 263 280 client.put(&url).send().await?; 264 281 } 265 282 266 - let url = format!("{}/playlists/start", rockbox_url()); 283 + let url = match position { 284 + Some(p) => format!("{}/playlists/start?start_index={}", rockbox_url(), p), 285 + None => format!("{}/playlists/start", rockbox_url()), 286 + }; 287 + 267 288 client.put(&url).send().await?; 268 289 269 290 Ok(0) ··· 293 314 &self, 294 315 ctx: &Context<'_>, 295 316 shuffle: Option<bool>, 317 + position: Option<i32>, 296 318 ) -> Result<i32, Error> { 297 319 let pool = ctx.data::<Pool<Sqlite>>()?; 298 320 let tracks = repo::favourites::all_tracks(pool.clone()) ··· 314 336 client.put(&url).send().await?; 315 337 } 316 338 317 - let url = format!("{}/playlists/start", rockbox_url()); 339 + let url = match position { 340 + Some(p) => format!("{}/playlists/start?start_index={}", rockbox_url(), p), 341 + None => format!("{}/playlists/start", rockbox_url()), 342 + }; 343 + 344 + client.put(&url).send().await?; 345 + 346 + Ok(0) 347 + } 348 + 349 + async fn play_all_tracks( 350 + &self, 351 + ctx: &Context<'_>, 352 + shuffle: Option<bool>, 353 + position: Option<i32>, 354 + ) -> Result<i32, Error> { 355 + let pool = ctx.data::<Pool<Sqlite>>()?; 356 + let tracks = repo::track::all(pool.clone()) 357 + .await? 358 + .into_iter() 359 + .map(|t| t.path) 360 + .collect::<Vec<String>>(); 361 + 362 + let client = ctx.data::<reqwest::Client>().unwrap(); 363 + let body = serde_json::json!({ 364 + "tracks": tracks, 365 + }); 366 + 367 + let url = format!("{}/playlists", rockbox_url()); 368 + client.post(&url).json(&body).send().await?; 369 + 370 + if let Some(true) = shuffle { 371 + let url = format!("{}/playlists/shuffle", rockbox_url()); 372 + client.put(&url).send().await?; 373 + } 374 + 375 + let url = match position { 376 + Some(p) => format!("{}/playlists/start?start_index={}", rockbox_url(), p), 377 + None => format!("{}/playlists/start", rockbox_url()), 378 + }; 379 + 318 380 client.put(&url).send().await?; 319 381 320 382 Ok(0)
+1 -1
crates/library/src/repo/album_tracks.rs
··· 46 46 SELECT * FROM album_tracks 47 47 LEFT JOIN track ON album_tracks.track_id = track.id 48 48 WHERE album_tracks.album_id = $1 49 - ORDER BY track_number ASC 49 + ORDER BY disc_number, track_number ASC 50 50 "#, 51 51 ) 52 52 .bind(album_id)
+12
crates/rpc/proto/rockbox/v1alpha1/playback.proto
··· 106 106 message PlayAlbumRequest { 107 107 string album_id = 1; 108 108 optional bool shuffle = 2; 109 + optional int32 position = 3; 109 110 } 110 111 111 112 message PlayAlbumResponse {} ··· 113 114 message PlayArtistTracksRequest { 114 115 string artist_id = 1; 115 116 optional bool shuffle = 2; 117 + optional int32 position = 3; 116 118 } 117 119 118 120 message PlayArtistTracksResponse {} ··· 128 130 string path = 1; 129 131 optional bool shuffle = 2; 130 132 optional bool recurse = 3; 133 + optional bool position = 4; 131 134 } 132 135 133 136 message PlayDirectoryResponse {} ··· 140 143 141 144 message PlayLikedTracksRequest { 142 145 optional bool shuffle = 1; 146 + optional int32 position = 2; 143 147 } 144 148 145 149 message PlayLikedTracksResponse {} 146 150 151 + message PlayAllTracksRequest { 152 + optional bool shuffle = 1; 153 + optional int32 position = 2; 154 + } 155 + 156 + message PlayAllTracksResponse {} 157 + 147 158 service PlaybackService { 148 159 rpc Play(PlayRequest) returns (PlayResponse) {} 149 160 rpc Pause(PauseRequest) returns (PauseResponse) {} ··· 163 174 rpc PlayDirectory(PlayDirectoryRequest) returns (PlayDirectoryResponse) {} 164 175 rpc PlayTrack(PlayTrackRequest) returns (PlayTrackResponse) {} 165 176 rpc PlayLikedTracks(PlayLikedTracksRequest) returns (PlayLikedTracksResponse) {} 177 + rpc PlayAllTracks(PlayAllTracksRequest) returns (PlayAllTracksResponse) {} 166 178 }
+97
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 2004 2004 pub album_id: ::prost::alloc::string::String, 2005 2005 #[prost(bool, optional, tag = "2")] 2006 2006 pub shuffle: ::core::option::Option<bool>, 2007 + #[prost(int32, optional, tag = "3")] 2008 + pub position: ::core::option::Option<i32>, 2007 2009 } 2008 2010 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2009 2011 pub struct PlayAlbumResponse {} ··· 2013 2015 pub artist_id: ::prost::alloc::string::String, 2014 2016 #[prost(bool, optional, tag = "2")] 2015 2017 pub shuffle: ::core::option::Option<bool>, 2018 + #[prost(int32, optional, tag = "3")] 2019 + pub position: ::core::option::Option<i32>, 2016 2020 } 2017 2021 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2018 2022 pub struct PlayArtistTracksResponse {} ··· 2033 2037 pub shuffle: ::core::option::Option<bool>, 2034 2038 #[prost(bool, optional, tag = "3")] 2035 2039 pub recurse: ::core::option::Option<bool>, 2040 + #[prost(bool, optional, tag = "4")] 2041 + pub position: ::core::option::Option<bool>, 2036 2042 } 2037 2043 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2038 2044 pub struct PlayDirectoryResponse {} ··· 2047 2053 pub struct PlayLikedTracksRequest { 2048 2054 #[prost(bool, optional, tag = "1")] 2049 2055 pub shuffle: ::core::option::Option<bool>, 2056 + #[prost(int32, optional, tag = "2")] 2057 + pub position: ::core::option::Option<i32>, 2050 2058 } 2051 2059 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2052 2060 pub struct PlayLikedTracksResponse {} 2061 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2062 + pub struct PlayAllTracksRequest { 2063 + #[prost(bool, optional, tag = "1")] 2064 + pub shuffle: ::core::option::Option<bool>, 2065 + #[prost(int32, optional, tag = "2")] 2066 + pub position: ::core::option::Option<i32>, 2067 + } 2068 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2069 + pub struct PlayAllTracksResponse {} 2053 2070 /// Generated client implementations. 2054 2071 pub mod playback_service_client { 2055 2072 #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] ··· 2607 2624 ); 2608 2625 self.inner.unary(req, path, codec).await 2609 2626 } 2627 + pub async fn play_all_tracks( 2628 + &mut self, 2629 + request: impl tonic::IntoRequest<super::PlayAllTracksRequest>, 2630 + ) -> std::result::Result< 2631 + tonic::Response<super::PlayAllTracksResponse>, 2632 + tonic::Status, 2633 + > { 2634 + self.inner 2635 + .ready() 2636 + .await 2637 + .map_err(|e| { 2638 + tonic::Status::new( 2639 + tonic::Code::Unknown, 2640 + format!("Service was not ready: {}", e.into()), 2641 + ) 2642 + })?; 2643 + let codec = tonic::codec::ProstCodec::default(); 2644 + let path = http::uri::PathAndQuery::from_static( 2645 + "/rockbox.v1alpha1.PlaybackService/PlayAllTracks", 2646 + ); 2647 + let mut req = request.into_request(); 2648 + req.extensions_mut() 2649 + .insert( 2650 + GrpcMethod::new("rockbox.v1alpha1.PlaybackService", "PlayAllTracks"), 2651 + ); 2652 + self.inner.unary(req, path, codec).await 2653 + } 2610 2654 } 2611 2655 } 2612 2656 /// Generated server implementations. ··· 2725 2769 request: tonic::Request<super::PlayLikedTracksRequest>, 2726 2770 ) -> std::result::Result< 2727 2771 tonic::Response<super::PlayLikedTracksResponse>, 2772 + tonic::Status, 2773 + >; 2774 + async fn play_all_tracks( 2775 + &self, 2776 + request: tonic::Request<super::PlayAllTracksRequest>, 2777 + ) -> std::result::Result< 2778 + tonic::Response<super::PlayAllTracksResponse>, 2728 2779 tonic::Status, 2729 2780 >; 2730 2781 } ··· 3605 3656 let inner = self.inner.clone(); 3606 3657 let fut = async move { 3607 3658 let method = PlayLikedTracksSvc(inner); 3659 + let codec = tonic::codec::ProstCodec::default(); 3660 + let mut grpc = tonic::server::Grpc::new(codec) 3661 + .apply_compression_config( 3662 + accept_compression_encodings, 3663 + send_compression_encodings, 3664 + ) 3665 + .apply_max_message_size_config( 3666 + max_decoding_message_size, 3667 + max_encoding_message_size, 3668 + ); 3669 + let res = grpc.unary(method, req).await; 3670 + Ok(res) 3671 + }; 3672 + Box::pin(fut) 3673 + } 3674 + "/rockbox.v1alpha1.PlaybackService/PlayAllTracks" => { 3675 + #[allow(non_camel_case_types)] 3676 + struct PlayAllTracksSvc<T: PlaybackService>(pub Arc<T>); 3677 + impl< 3678 + T: PlaybackService, 3679 + > tonic::server::UnaryService<super::PlayAllTracksRequest> 3680 + for PlayAllTracksSvc<T> { 3681 + type Response = super::PlayAllTracksResponse; 3682 + type Future = BoxFuture< 3683 + tonic::Response<Self::Response>, 3684 + tonic::Status, 3685 + >; 3686 + fn call( 3687 + &mut self, 3688 + request: tonic::Request<super::PlayAllTracksRequest>, 3689 + ) -> Self::Future { 3690 + let inner = Arc::clone(&self.0); 3691 + let fut = async move { 3692 + <T as PlaybackService>::play_all_tracks(&inner, request) 3693 + .await 3694 + }; 3695 + Box::pin(fut) 3696 + } 3697 + } 3698 + let accept_compression_encodings = self.accept_compression_encodings; 3699 + let send_compression_encodings = self.send_compression_encodings; 3700 + let max_decoding_message_size = self.max_decoding_message_size; 3701 + let max_encoding_message_size = self.max_encoding_message_size; 3702 + let inner = self.inner.clone(); 3703 + let fut = async move { 3704 + let method = PlayAllTracksSvc(inner); 3608 3705 let codec = tonic::codec::ProstCodec::default(); 3609 3706 let mut grpc = tonic::server::Grpc::new(codec) 3610 3707 .apply_compression_config(
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+74 -4
crates/rpc/src/playback.rs
··· 205 205 let request = request.into_inner(); 206 206 let album_id = request.album_id; 207 207 let shuffle = request.shuffle; 208 + let position = request.position; 208 209 let tracks = repo::album_tracks::find_by_album(self.pool.clone(), &album_id) 209 210 .await 210 211 .map_err(|e| tonic::Status::internal(e.to_string()))?; ··· 229 230 .map_err(|e| tonic::Status::internal(e.to_string()))?; 230 231 } 231 232 232 - let url = format!("{}/playlists/start", rockbox_url()); 233 + let url = match position { 234 + Some(position) => format!("{}/playlists/start?start_index={}", rockbox_url(), position), 235 + None => format!("{}/playlists/start", rockbox_url()), 236 + }; 237 + 233 238 self.client 234 239 .put(&url) 235 240 .send() ··· 246 251 let request = request.into_inner(); 247 252 let artist_id = request.artist_id; 248 253 let shuffle = request.shuffle; 254 + let position = request.position; 249 255 let tracks = repo::artist_tracks::find_by_artist(self.pool.clone(), &artist_id) 250 256 .await 251 257 .map_err(|e| tonic::Status::internal(e.to_string()))?; ··· 270 276 .map_err(|e| tonic::Status::internal(e.to_string()))?; 271 277 } 272 278 273 - let url = format!("{}/playlists/start", rockbox_url()); 279 + let url = match position { 280 + Some(position) => format!("{}/playlists/start?start_index={}", rockbox_url(), position), 281 + None => format!("{}/playlists/start", rockbox_url()), 282 + }; 283 + 274 284 self.client 275 285 .put(&url) 276 286 .send() ··· 294 304 let path = request.path; 295 305 let recurse = request.recurse; 296 306 let shuffle = request.shuffle; 307 + let position = request.position; 297 308 let mut tracks: Vec<String> = vec![]; 309 + 310 + let recurse = match position { 311 + Some(_) => Some(false), 312 + None => recurse, 313 + }; 298 314 299 315 if !std::path::Path::new(&path).is_dir() { 300 316 return Err(tonic::Status::invalid_argument("Path is not a directory")); ··· 351 367 .map_err(|e| tonic::Status::internal(e.to_string()))?; 352 368 } 353 369 354 - let url = format!("{}/playlists/start", rockbox_url()); 370 + let url = match position { 371 + Some(position) => format!("{}/playlists/start?start_index={}", rockbox_url(), position), 372 + None => format!("{}/playlists/start", rockbox_url()), 373 + }; 374 + 355 375 self.client 356 376 .put(&url) 357 377 .send() ··· 400 420 ) -> Result<tonic::Response<PlayLikedTracksResponse>, tonic::Status> { 401 421 let request = request.into_inner(); 402 422 let shuffle = request.shuffle; 423 + let position = request.position; 403 424 let tracks = repo::favourites::all_tracks(self.pool.clone()) 404 425 .await 405 426 .map_err(|e| tonic::Status::internal(e.to_string()))?; ··· 424 445 .map_err(|e| tonic::Status::internal(e.to_string()))?; 425 446 } 426 447 427 - let url = format!("{}/playlists/start", rockbox_url()); 448 + let url = match position { 449 + Some(position) => format!("{}/playlists/start?start_index={}", rockbox_url(), position), 450 + None => format!("{}/playlists/start", rockbox_url()), 451 + }; 452 + 428 453 self.client 429 454 .put(&url) 430 455 .send() ··· 432 457 .map_err(|e| tonic::Status::internal(e.to_string()))?; 433 458 434 459 Ok(tonic::Response::new(PlayLikedTracksResponse::default())) 460 + } 461 + 462 + async fn play_all_tracks( 463 + &self, 464 + request: tonic::Request<PlayAllTracksRequest>, 465 + ) -> Result<tonic::Response<PlayAllTracksResponse>, tonic::Status> { 466 + let request = request.into_inner(); 467 + let shuffle = request.shuffle; 468 + let position = request.position; 469 + let tracks = repo::track::all(self.pool.clone()) 470 + .await 471 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 472 + let body = serde_json::json!({ 473 + "tracks": tracks.into_iter().map(|t| t.path).collect::<Vec<String>>(), 474 + }); 475 + 476 + let url = format!("{}/playlists", rockbox_url()); 477 + self.client 478 + .post(&url) 479 + .json(&body) 480 + .send() 481 + .await 482 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 483 + 484 + if let Some(true) = shuffle { 485 + let url = format!("{}/playlists/shuffle", rockbox_url()); 486 + self.client 487 + .put(&url) 488 + .send() 489 + .await 490 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 491 + } 492 + 493 + let url = match position { 494 + Some(position) => format!("{}/playlists/start?start_index={}", rockbox_url(), position), 495 + None => format!("{}/playlists/start", rockbox_url()), 496 + }; 497 + 498 + self.client 499 + .put(&url) 500 + .send() 501 + .await 502 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 503 + 504 + Ok(tonic::Response::new(PlayAllTracksResponse::default())) 435 505 } 436 506 }
+101
webui/rockbox/graphql.schema.json
··· 1195 1195 "deprecationReason": null 1196 1196 }, 1197 1197 { 1198 + "name": "position", 1199 + "description": null, 1200 + "type": { 1201 + "kind": "SCALAR", 1202 + "name": "Int", 1203 + "ofType": null 1204 + }, 1205 + "defaultValue": null, 1206 + "isDeprecated": false, 1207 + "deprecationReason": null 1208 + }, 1209 + { 1210 + "name": "shuffle", 1211 + "description": null, 1212 + "type": { 1213 + "kind": "SCALAR", 1214 + "name": "Boolean", 1215 + "ofType": null 1216 + }, 1217 + "defaultValue": null, 1218 + "isDeprecated": false, 1219 + "deprecationReason": null 1220 + } 1221 + ], 1222 + "type": { 1223 + "kind": "NON_NULL", 1224 + "name": null, 1225 + "ofType": { 1226 + "kind": "SCALAR", 1227 + "name": "Int", 1228 + "ofType": null 1229 + } 1230 + }, 1231 + "isDeprecated": false, 1232 + "deprecationReason": null 1233 + }, 1234 + { 1235 + "name": "playAllTracks", 1236 + "description": null, 1237 + "args": [ 1238 + { 1239 + "name": "position", 1240 + "description": null, 1241 + "type": { 1242 + "kind": "SCALAR", 1243 + "name": "Int", 1244 + "ofType": null 1245 + }, 1246 + "defaultValue": null, 1247 + "isDeprecated": false, 1248 + "deprecationReason": null 1249 + }, 1250 + { 1198 1251 "name": "shuffle", 1199 1252 "description": null, 1200 1253 "type": { ··· 1234 1287 "name": "String", 1235 1288 "ofType": null 1236 1289 } 1290 + }, 1291 + "defaultValue": null, 1292 + "isDeprecated": false, 1293 + "deprecationReason": null 1294 + }, 1295 + { 1296 + "name": "position", 1297 + "description": null, 1298 + "type": { 1299 + "kind": "SCALAR", 1300 + "name": "Int", 1301 + "ofType": null 1237 1302 }, 1238 1303 "defaultValue": null, 1239 1304 "isDeprecated": false, ··· 1285 1350 "deprecationReason": null 1286 1351 }, 1287 1352 { 1353 + "name": "position", 1354 + "description": null, 1355 + "type": { 1356 + "kind": "SCALAR", 1357 + "name": "Int", 1358 + "ofType": null 1359 + }, 1360 + "defaultValue": null, 1361 + "isDeprecated": false, 1362 + "deprecationReason": null 1363 + }, 1364 + { 1288 1365 "name": "recurse", 1289 1366 "description": null, 1290 1367 "type": { ··· 1325 1402 "name": "playLikedTracks", 1326 1403 "description": null, 1327 1404 "args": [ 1405 + { 1406 + "name": "position", 1407 + "description": null, 1408 + "type": { 1409 + "kind": "SCALAR", 1410 + "name": "Int", 1411 + "ofType": null 1412 + }, 1413 + "defaultValue": null, 1414 + "isDeprecated": false, 1415 + "deprecationReason": null 1416 + }, 1328 1417 { 1329 1418 "name": "shuffle", 1330 1419 "description": null, ··· 1365 1454 "name": "String", 1366 1455 "ofType": null 1367 1456 } 1457 + }, 1458 + "defaultValue": null, 1459 + "isDeprecated": false, 1460 + "deprecationReason": null 1461 + }, 1462 + { 1463 + "name": "position", 1464 + "description": null, 1465 + "type": { 1466 + "kind": "SCALAR", 1467 + "name": "Int", 1468 + "ofType": null 1368 1469 }, 1369 1470 "defaultValue": null, 1370 1471 "isDeprecated": false,
+1
webui/rockbox/src/Components/AlbumDetails/AlbumDetails.stories.tsx
··· 36 36 tracks: [], 37 37 }, 38 38 volumes: [], 39 + onPlayTrack: fn(), 39 40 }, 40 41 };
+1
webui/rockbox/src/Components/AlbumDetails/AlbumDetails.test.tsx
··· 36 36 tracks: [], 37 37 }} 38 38 volumes={[]} 39 + onPlayTrack={vi.fn()} 39 40 /> 40 41 </RecoilRoot> 41 42 </Providers>
+18 -1
webui/rockbox/src/Components/AlbumDetails/AlbumDetails.tsx
··· 29 29 import { Album } from "../../Hooks/GraphQL"; 30 30 import AlbumArt from "../../Assets/albumart.svg"; 31 31 import ContextMenu from "../ContextMenu"; 32 + import _ from "lodash"; 32 33 33 34 const columnHelper = createColumnHelper<Track>(); 34 35 ··· 40 41 album?: Album | null; 41 42 volumes: Track[][]; 42 43 enableBlur?: boolean; 44 + onPlayTrack: (position: number, disc: number) => void; 43 45 }; 44 46 45 47 const AlbumDetails: FC<AlbumDetailsProps> = (props) => { ··· 47 49 columnHelper.accessor("trackNumber", { 48 50 header: "#", 49 51 size: 20, 50 - cell: (info) => info.getValue(), 52 + cell: (info) => ( 53 + <div style={{ position: "relative" }}> 54 + <div className="tracknumber">{info.getValue()}</div> 55 + <div 56 + className="floating-play" 57 + onClick={() => 58 + props.onPlayTrack( 59 + info.row.index, 60 + _.get(info, "row.original.discnum", 1) 61 + ) 62 + } 63 + > 64 + <Play color="#000" small /> 65 + </div> 66 + </div> 67 + ), 51 68 }), 52 69 columnHelper.accessor("title", { 53 70 header: "Title",
+20
webui/rockbox/src/Components/AlbumDetails/AlbumDetailsWithData.tsx
··· 91 91 }); 92 92 } 93 93 94 + function onPlayTrack(position: number, disc: number) { 95 + let realPosition = 0; 96 + if (disc > 1) { 97 + // get the real position, since we have multiple discs, 98 + // we need to calculate the real position, 99 + // disc are ordered by volume 100 + for (let i = 0; i < disc - 1; i++) { 101 + realPosition += volumes[i].length; 102 + } 103 + realPosition += position; 104 + } 105 + playAlbum({ 106 + variables: { 107 + albumId: id!, 108 + position: disc > 1 ? realPosition : position, 109 + }, 110 + }); 111 + } 112 + 94 113 return ( 95 114 <AlbumDetails 96 115 onGoBack={() => navigate(-1)} ··· 102 121 album={album as any} 103 122 volumes={volumes} 104 123 enableBlur={enableBlur} 124 + onPlayTrack={onPlayTrack} 105 125 /> 106 126 ); 107 127 };
+326 -14
webui/rockbox/src/Components/AlbumDetails/__snapshots__/AlbumDetails.test.tsx.snap
··· 505 505 </div> 506 506 </div> 507 507 <table 508 - style="width: 100%; margin-top: 31px;" 508 + style="width: 100%; margin-top: 20px;" 509 509 > 510 510 <thead> 511 511 <tr ··· 543 543 <td 544 544 style="width: 20px; overflow: hidden;" 545 545 > 546 - 1 546 + <div 547 + style="position: relative;" 548 + > 549 + <div 550 + class="tracknumber" 551 + > 552 + 1 553 + </div> 554 + <div 555 + class="floating-play" 556 + > 557 + <svg 558 + fill="none" 559 + height="20" 560 + width="20" 561 + xmlns="http://www.w3.org/2000/svg" 562 + > 563 + <path 564 + class="ionicon" 565 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 566 + style="fill: #000;" 567 + /> 568 + </svg> 569 + </div> 570 + </div> 547 571 </td> 548 572 <td 549 573 style="width: 150px; overflow: hidden;" ··· 700 724 <td 701 725 style="width: 20px; overflow: hidden;" 702 726 > 703 - 2 727 + <div 728 + style="position: relative;" 729 + > 730 + <div 731 + class="tracknumber" 732 + > 733 + 2 734 + </div> 735 + <div 736 + class="floating-play" 737 + > 738 + <svg 739 + fill="none" 740 + height="20" 741 + width="20" 742 + xmlns="http://www.w3.org/2000/svg" 743 + > 744 + <path 745 + class="ionicon" 746 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 747 + style="fill: #000;" 748 + /> 749 + </svg> 750 + </div> 751 + </div> 704 752 </td> 705 753 <td 706 754 style="width: 150px; overflow: hidden;" ··· 857 905 <td 858 906 style="width: 20px; overflow: hidden;" 859 907 > 860 - 3 908 + <div 909 + style="position: relative;" 910 + > 911 + <div 912 + class="tracknumber" 913 + > 914 + 3 915 + </div> 916 + <div 917 + class="floating-play" 918 + > 919 + <svg 920 + fill="none" 921 + height="20" 922 + width="20" 923 + xmlns="http://www.w3.org/2000/svg" 924 + > 925 + <path 926 + class="ionicon" 927 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 928 + style="fill: #000;" 929 + /> 930 + </svg> 931 + </div> 932 + </div> 861 933 </td> 862 934 <td 863 935 style="width: 150px; overflow: hidden;" ··· 1014 1086 <td 1015 1087 style="width: 20px; overflow: hidden;" 1016 1088 > 1017 - 4 1089 + <div 1090 + style="position: relative;" 1091 + > 1092 + <div 1093 + class="tracknumber" 1094 + > 1095 + 4 1096 + </div> 1097 + <div 1098 + class="floating-play" 1099 + > 1100 + <svg 1101 + fill="none" 1102 + height="20" 1103 + width="20" 1104 + xmlns="http://www.w3.org/2000/svg" 1105 + > 1106 + <path 1107 + class="ionicon" 1108 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 1109 + style="fill: #000;" 1110 + /> 1111 + </svg> 1112 + </div> 1113 + </div> 1018 1114 </td> 1019 1115 <td 1020 1116 style="width: 150px; overflow: hidden;" ··· 1171 1267 <td 1172 1268 style="width: 20px; overflow: hidden;" 1173 1269 > 1174 - 5 1270 + <div 1271 + style="position: relative;" 1272 + > 1273 + <div 1274 + class="tracknumber" 1275 + > 1276 + 5 1277 + </div> 1278 + <div 1279 + class="floating-play" 1280 + > 1281 + <svg 1282 + fill="none" 1283 + height="20" 1284 + width="20" 1285 + xmlns="http://www.w3.org/2000/svg" 1286 + > 1287 + <path 1288 + class="ionicon" 1289 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 1290 + style="fill: #000;" 1291 + /> 1292 + </svg> 1293 + </div> 1294 + </div> 1175 1295 </td> 1176 1296 <td 1177 1297 style="width: 150px; overflow: hidden;" ··· 1328 1448 <td 1329 1449 style="width: 20px; overflow: hidden;" 1330 1450 > 1331 - 6 1451 + <div 1452 + style="position: relative;" 1453 + > 1454 + <div 1455 + class="tracknumber" 1456 + > 1457 + 6 1458 + </div> 1459 + <div 1460 + class="floating-play" 1461 + > 1462 + <svg 1463 + fill="none" 1464 + height="20" 1465 + width="20" 1466 + xmlns="http://www.w3.org/2000/svg" 1467 + > 1468 + <path 1469 + class="ionicon" 1470 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 1471 + style="fill: #000;" 1472 + /> 1473 + </svg> 1474 + </div> 1475 + </div> 1332 1476 </td> 1333 1477 <td 1334 1478 style="width: 150px; overflow: hidden;" ··· 1485 1629 <td 1486 1630 style="width: 20px; overflow: hidden;" 1487 1631 > 1488 - 7 1632 + <div 1633 + style="position: relative;" 1634 + > 1635 + <div 1636 + class="tracknumber" 1637 + > 1638 + 7 1639 + </div> 1640 + <div 1641 + class="floating-play" 1642 + > 1643 + <svg 1644 + fill="none" 1645 + height="20" 1646 + width="20" 1647 + xmlns="http://www.w3.org/2000/svg" 1648 + > 1649 + <path 1650 + class="ionicon" 1651 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 1652 + style="fill: #000;" 1653 + /> 1654 + </svg> 1655 + </div> 1656 + </div> 1489 1657 </td> 1490 1658 <td 1491 1659 style="width: 150px; overflow: hidden;" ··· 1642 1810 <td 1643 1811 style="width: 20px; overflow: hidden;" 1644 1812 > 1645 - 8 1813 + <div 1814 + style="position: relative;" 1815 + > 1816 + <div 1817 + class="tracknumber" 1818 + > 1819 + 8 1820 + </div> 1821 + <div 1822 + class="floating-play" 1823 + > 1824 + <svg 1825 + fill="none" 1826 + height="20" 1827 + width="20" 1828 + xmlns="http://www.w3.org/2000/svg" 1829 + > 1830 + <path 1831 + class="ionicon" 1832 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 1833 + style="fill: #000;" 1834 + /> 1835 + </svg> 1836 + </div> 1837 + </div> 1646 1838 </td> 1647 1839 <td 1648 1840 style="width: 150px; overflow: hidden;" ··· 1799 1991 <td 1800 1992 style="width: 20px; overflow: hidden;" 1801 1993 > 1802 - 9 1994 + <div 1995 + style="position: relative;" 1996 + > 1997 + <div 1998 + class="tracknumber" 1999 + > 2000 + 9 2001 + </div> 2002 + <div 2003 + class="floating-play" 2004 + > 2005 + <svg 2006 + fill="none" 2007 + height="20" 2008 + width="20" 2009 + xmlns="http://www.w3.org/2000/svg" 2010 + > 2011 + <path 2012 + class="ionicon" 2013 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 2014 + style="fill: #000;" 2015 + /> 2016 + </svg> 2017 + </div> 2018 + </div> 1803 2019 </td> 1804 2020 <td 1805 2021 style="width: 150px; overflow: hidden;" ··· 1956 2172 <td 1957 2173 style="width: 20px; overflow: hidden;" 1958 2174 > 1959 - 10 2175 + <div 2176 + style="position: relative;" 2177 + > 2178 + <div 2179 + class="tracknumber" 2180 + > 2181 + 10 2182 + </div> 2183 + <div 2184 + class="floating-play" 2185 + > 2186 + <svg 2187 + fill="none" 2188 + height="20" 2189 + width="20" 2190 + xmlns="http://www.w3.org/2000/svg" 2191 + > 2192 + <path 2193 + class="ionicon" 2194 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 2195 + style="fill: #000;" 2196 + /> 2197 + </svg> 2198 + </div> 2199 + </div> 1960 2200 </td> 1961 2201 <td 1962 2202 style="width: 150px; overflow: hidden;" ··· 2113 2353 <td 2114 2354 style="width: 20px; overflow: hidden;" 2115 2355 > 2116 - 11 2356 + <div 2357 + style="position: relative;" 2358 + > 2359 + <div 2360 + class="tracknumber" 2361 + > 2362 + 11 2363 + </div> 2364 + <div 2365 + class="floating-play" 2366 + > 2367 + <svg 2368 + fill="none" 2369 + height="20" 2370 + width="20" 2371 + xmlns="http://www.w3.org/2000/svg" 2372 + > 2373 + <path 2374 + class="ionicon" 2375 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 2376 + style="fill: #000;" 2377 + /> 2378 + </svg> 2379 + </div> 2380 + </div> 2117 2381 </td> 2118 2382 <td 2119 2383 style="width: 150px; overflow: hidden;" ··· 2270 2534 <td 2271 2535 style="width: 20px; overflow: hidden;" 2272 2536 > 2273 - 12 2537 + <div 2538 + style="position: relative;" 2539 + > 2540 + <div 2541 + class="tracknumber" 2542 + > 2543 + 12 2544 + </div> 2545 + <div 2546 + class="floating-play" 2547 + > 2548 + <svg 2549 + fill="none" 2550 + height="20" 2551 + width="20" 2552 + xmlns="http://www.w3.org/2000/svg" 2553 + > 2554 + <path 2555 + class="ionicon" 2556 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 2557 + style="fill: #000;" 2558 + /> 2559 + </svg> 2560 + </div> 2561 + </div> 2274 2562 </td> 2275 2563 <td 2276 2564 style="width: 150px; overflow: hidden;" ··· 2427 2715 <td 2428 2716 style="width: 20px; overflow: hidden;" 2429 2717 > 2430 - 13 2718 + <div 2719 + style="position: relative;" 2720 + > 2721 + <div 2722 + class="tracknumber" 2723 + > 2724 + 13 2725 + </div> 2726 + <div 2727 + class="floating-play" 2728 + > 2729 + <svg 2730 + fill="none" 2731 + height="20" 2732 + width="20" 2733 + xmlns="http://www.w3.org/2000/svg" 2734 + > 2735 + <path 2736 + class="ionicon" 2737 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 2738 + style="fill: #000;" 2739 + /> 2740 + </svg> 2741 + </div> 2742 + </div> 2431 2743 </td> 2432 2744 <td 2433 2745 style="width: 150px; overflow: hidden;"
+1
webui/rockbox/src/Components/ArtistDetails/ArtistDetails.stories.tsx
··· 30 30 onLikeTrack: fn(), 31 31 onUnlikeTrack: fn(), 32 32 onGoBack: fn(), 33 + onPlayTrack: fn(), 33 34 }, 34 35 };
+1
webui/rockbox/src/Components/ArtistDetails/ArtistDetails.test.tsx
··· 25 25 onLikeTrack={vi.fn()} 26 26 onUnlikeTrack={vi.fn()} 27 27 onGoBack={vi.fn()} 28 + onPlayTrack={vi.fn()} 28 29 /> 29 30 </Providers> 30 31 </MockedProvider>
+132 -98
webui/rockbox/src/Components/ArtistDetails/ArtistDetails.tsx
··· 3 3 import Sidebar from "../Sidebar/Sidebar"; 4 4 import ControlBar from "../ControlBar"; 5 5 import { 6 - SmallAlbumCover, 7 6 BackButton, 8 7 ButtonGroup, 9 8 Container, ··· 14 13 Separator, 15 14 Title, 16 15 Link, 16 + SmallAlbumCover, 17 + AlbumCoverAlt, 17 18 } from "./styles"; 18 19 import ArrowBack from "../Icons/ArrowBack"; 19 20 import Shuffle from "../Icons/Shuffle"; ··· 22 23 import { createColumnHelper } from "@tanstack/react-table"; 23 24 import { Track } from "../../Types/track"; 24 25 import Table from "../Table"; 25 - import AlbumArt from "../../Assets/albumart.svg"; 26 26 import { Cell, Grid } from "baseui/layout-grid"; 27 27 import "./styles.css"; 28 28 import ContextMenu from "../ContextMenu"; 29 29 import Album from "../Album"; 30 + import TrackIcon from "../Icons/Track"; 30 31 31 32 const columnHelper = createColumnHelper<Track>(); 32 - const columns = [ 33 - columnHelper.accessor("albumArt", { 34 - header: "Title", 35 - size: 48, 36 - cell: (info) => ( 37 - <SmallAlbumCover src={info.getValue() || AlbumArt} alt="album art" /> 38 - ), 39 - }), 40 - columnHelper.accessor("title", { 41 - header: "", 42 - cell: (info) => ( 43 - <div 44 - style={{ 45 - minWidth: 150, 46 - width: "calc(100% - 20px)", 47 - maxWidth: "300px", 48 - fontSize: 14, 49 - textOverflow: "ellipsis", 50 - overflow: "hidden", 51 - whiteSpace: "nowrap", 52 - cursor: "pointer", 53 - color: "#000", 54 - }} 55 - > 56 - {info.getValue()} 57 - </div> 58 - ), 59 - }), 60 - columnHelper.accessor("artist", { 61 - header: "Artist", 62 - cell: (info) => ( 63 - <div 64 - style={{ 65 - minWidth: 150, 66 - width: "calc(100% - 20px)", 67 - maxWidth: "300px", 68 - fontSize: 14, 69 - textOverflow: "ellipsis", 70 - overflow: "hidden", 71 - whiteSpace: "nowrap", 72 - cursor: "pointer", 73 - color: "#000", 74 - }} 75 - > 76 - <Link to={`/artists/${info.row.original.artistId}`}> 77 - {info.getValue()} 78 - </Link> 79 - </div> 80 - ), 81 - }), 82 - columnHelper.accessor("album", { 83 - header: "Album", 84 - cell: (info) => ( 85 - <div 86 - style={{ 87 - width: "calc(100% - 20px)", 88 - maxWidth: "calc(100vw - 800px)", 89 - fontSize: 14, 90 - overflow: "hidden", 91 - textOverflow: "ellipsis", 92 - whiteSpace: "nowrap", 93 - cursor: "pointer", 94 - color: "#000", 95 - }} 96 - > 97 - <Link to={`/albums/${info.row.original.albumId}`}> 98 - {info.getValue()} 99 - </Link> 100 - </div> 101 - ), 102 - }), 103 - columnHelper.accessor("time", { 104 - header: "Time", 105 - size: 50, 106 - cell: (info) => info.getValue(), 107 - }), 108 - columnHelper.accessor("id", { 109 - header: "", 110 - size: 100, 111 - cell: (info) => ( 112 - <ButtonGroup style={{ justifyContent: "flex-end", alignItems: "center" }}> 113 - <ContextMenu 114 - track={{ 115 - id: info.row.original.id, 116 - title: info.row.original.title, 117 - artist: info.row.original.artist, 118 - time: info.row.original.time, 119 - cover: info.row.original.albumArt, 120 - path: info.row.original.path, 121 - }} 122 - /> 123 - </ButtonGroup> 124 - ), 125 - }), 126 - ]; 127 33 128 34 export type ArtistDetailsProps = { 129 35 name: string; ··· 137 43 onLikeTrack: (track: any) => void; 138 44 onUnlikeTrack: (track: any) => void; 139 45 onGoBack: () => void; 46 + onPlayTrack: (position: number) => void; 140 47 }; 141 48 142 49 const ArtistDetails: FC<ArtistDetailsProps> = (props) => { 50 + const columns = [ 51 + columnHelper.accessor("albumArt", { 52 + header: "Title", 53 + size: 48, 54 + cell: (info) => ( 55 + <> 56 + {info.getValue() && ( 57 + <div className="album-cover-container"> 58 + <SmallAlbumCover 59 + src={info.getValue()!} 60 + alt="album art" 61 + effect="blur" 62 + /> 63 + <div 64 + onClick={() => props.onPlayTrack(info.row.index)} 65 + className="floating-play" 66 + > 67 + <Play small color={info.getValue() ? "#fff" : "#000"} /> 68 + </div> 69 + </div> 70 + )} 71 + {!info.getValue() && ( 72 + <div className="album-cover-container"> 73 + <AlbumCoverAlt> 74 + <TrackIcon width={28} height={28} color="#a4a3a3" /> 75 + </AlbumCoverAlt> 76 + <div 77 + onClick={() => props.onPlayTrack(info.row.index)} 78 + className="floating-play" 79 + > 80 + <Play small color={info.getValue() ? "#fff" : "#000"} /> 81 + </div> 82 + </div> 83 + )} 84 + </> 85 + ), 86 + }), 87 + columnHelper.accessor("title", { 88 + header: "", 89 + cell: (info) => ( 90 + <div 91 + style={{ 92 + minWidth: 150, 93 + width: "calc(100% - 20px)", 94 + maxWidth: "300px", 95 + fontSize: 14, 96 + textOverflow: "ellipsis", 97 + overflow: "hidden", 98 + whiteSpace: "nowrap", 99 + cursor: "pointer", 100 + color: "#000", 101 + }} 102 + > 103 + {info.getValue()} 104 + </div> 105 + ), 106 + }), 107 + columnHelper.accessor("artist", { 108 + header: "Artist", 109 + cell: (info) => ( 110 + <div 111 + style={{ 112 + minWidth: 150, 113 + width: "calc(100% - 20px)", 114 + maxWidth: "300px", 115 + fontSize: 14, 116 + textOverflow: "ellipsis", 117 + overflow: "hidden", 118 + whiteSpace: "nowrap", 119 + cursor: "pointer", 120 + color: "#000", 121 + }} 122 + > 123 + <Link to={`/artists/${info.row.original.artistId}`}> 124 + {info.getValue()} 125 + </Link> 126 + </div> 127 + ), 128 + }), 129 + columnHelper.accessor("album", { 130 + header: "Album", 131 + cell: (info) => ( 132 + <div 133 + style={{ 134 + width: "calc(100% - 20px)", 135 + maxWidth: "calc(100vw - 800px)", 136 + fontSize: 14, 137 + overflow: "hidden", 138 + textOverflow: "ellipsis", 139 + whiteSpace: "nowrap", 140 + cursor: "pointer", 141 + color: "#000", 142 + }} 143 + > 144 + <Link to={`/albums/${info.row.original.albumId}`}> 145 + {info.getValue()} 146 + </Link> 147 + </div> 148 + ), 149 + }), 150 + columnHelper.accessor("time", { 151 + header: "Time", 152 + size: 50, 153 + cell: (info) => info.getValue(), 154 + }), 155 + columnHelper.accessor("id", { 156 + header: "", 157 + size: 100, 158 + cell: (info) => ( 159 + <ButtonGroup 160 + style={{ justifyContent: "flex-end", alignItems: "center" }} 161 + > 162 + <ContextMenu 163 + track={{ 164 + id: info.row.original.id, 165 + title: info.row.original.title, 166 + artist: info.row.original.artist, 167 + time: info.row.original.time, 168 + cover: info.row.original.albumArt, 169 + path: info.row.original.path, 170 + }} 171 + /> 172 + </ButtonGroup> 173 + ), 174 + }), 175 + ]; 176 + 143 177 return ( 144 178 <Container> 145 179 <Sidebar active="artists" /> ··· 169 203 </ButtonGroup> 170 204 <Title>Tracks</Title> 171 205 <Table columns={columns as any} tracks={props.tracks} /> 172 - <Title style={{ marginBottom: 20, marginTop: 50 }}>Albums</Title> 206 + <Title style={{ marginTop: 50 }}>Albums</Title> 173 207 <div style={{ marginBottom: 100 }}> 174 208 <Grid 175 209 gridColumns={[2, 4, 5]}
+10
webui/rockbox/src/Components/ArtistDetails/ArtistDetailsWithData.tsx
··· 38 38 }); 39 39 }; 40 40 41 + const onPlayTrack = (position: number) => { 42 + playArtistTracks({ 43 + variables: { 44 + artistId: id!, 45 + position, 46 + }, 47 + }); 48 + }; 49 + 41 50 return ( 42 51 <ArtistDetails 43 52 name={data?.artist?.name || ""} ··· 52 61 onLikeTrack={() => {}} 53 62 onUnlikeTrack={() => {}} 54 63 onGoBack={() => navigate(-1)} 64 + onPlayTrack={onPlayTrack} 55 65 /> 56 66 ); 57 67 };
+92 -17
webui/rockbox/src/Components/ArtistDetails/__snapshots__/ArtistDetails.test.tsx.snap
··· 474 474 Tracks 475 475 </div> 476 476 <table 477 - style="width: 100%; margin-top: 31px;" 477 + style="width: 100%; margin-top: 20px;" 478 478 > 479 479 <thead> 480 480 <tr ··· 515 515 <td 516 516 style="width: 48px; overflow: hidden;" 517 517 > 518 - <img 519 - alt="album art" 520 - class="css-16tu1q9" 521 - src="https://resources.tidal.com/images/09b59e6e/717e/43e3/b2e2/d2a153c24775/320x320.jpg" 522 - /> 518 + <div 519 + class="album-cover-container" 520 + > 521 + <span 522 + class=" lazy-load-image-background blur" 523 + style="color: transparent; display: inline-block;" 524 + > 525 + <img 526 + alt="album art" 527 + class="css-16tu1q9" 528 + src="https://resources.tidal.com/images/09b59e6e/717e/43e3/b2e2/d2a153c24775/320x320.jpg" 529 + /> 530 + </span> 531 + <div 532 + class="floating-play" 533 + > 534 + <svg 535 + fill="none" 536 + height="20" 537 + width="20" 538 + xmlns="http://www.w3.org/2000/svg" 539 + > 540 + <path 541 + class="ionicon" 542 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 543 + style="fill: #fff;" 544 + /> 545 + </svg> 546 + </div> 547 + </div> 523 548 </td> 524 549 <td 525 550 style="width: 150px; overflow: hidden;" ··· 690 715 <td 691 716 style="width: 48px; overflow: hidden;" 692 717 > 693 - <img 694 - alt="album art" 695 - class="css-16tu1q9" 696 - src="https://resources.tidal.com/images/09b59e6e/717e/43e3/b2e2/d2a153c24775/320x320.jpg" 697 - /> 718 + <div 719 + class="album-cover-container" 720 + > 721 + <span 722 + class=" lazy-load-image-background blur" 723 + style="color: transparent; display: inline-block;" 724 + > 725 + <img 726 + alt="album art" 727 + class="css-16tu1q9" 728 + src="https://resources.tidal.com/images/09b59e6e/717e/43e3/b2e2/d2a153c24775/320x320.jpg" 729 + /> 730 + </span> 731 + <div 732 + class="floating-play" 733 + > 734 + <svg 735 + fill="none" 736 + height="20" 737 + width="20" 738 + xmlns="http://www.w3.org/2000/svg" 739 + > 740 + <path 741 + class="ionicon" 742 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 743 + style="fill: #fff;" 744 + /> 745 + </svg> 746 + </div> 747 + </div> 698 748 </td> 699 749 <td 700 750 style="width: 150px; overflow: hidden;" ··· 865 915 <td 866 916 style="width: 48px; overflow: hidden;" 867 917 > 868 - <img 869 - alt="album art" 870 - class="css-16tu1q9" 871 - src="https://resources.tidal.com/images/f853861c/0c5f/4e73/b608/eeb00618fe6f/320x320.jpg" 872 - /> 918 + <div 919 + class="album-cover-container" 920 + > 921 + <span 922 + class=" lazy-load-image-background blur" 923 + style="color: transparent; display: inline-block;" 924 + > 925 + <img 926 + alt="album art" 927 + class="css-16tu1q9" 928 + src="https://resources.tidal.com/images/f853861c/0c5f/4e73/b608/eeb00618fe6f/320x320.jpg" 929 + /> 930 + </span> 931 + <div 932 + class="floating-play" 933 + > 934 + <svg 935 + fill="none" 936 + height="20" 937 + width="20" 938 + xmlns="http://www.w3.org/2000/svg" 939 + > 940 + <path 941 + class="ionicon" 942 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 943 + style="fill: #fff;" 944 + /> 945 + </svg> 946 + </div> 947 + </div> 873 948 </td> 874 949 <td 875 950 style="width: 150px; overflow: hidden;" ··· 1038 1113 </table> 1039 1114 <div 1040 1115 class="css-1jqyuzd" 1041 - style="margin-bottom: 20px; margin-top: 50px;" 1116 + style="margin-top: 50px;" 1042 1117 > 1043 1118 Albums 1044 1119 </div>
+12
webui/rockbox/src/Components/ArtistDetails/styles.tsx
··· 177 177 border-radius: 3px; 178 178 cursor: pointer; 179 179 `; 180 + 181 + export const AlbumCoverAlt = styled.div<{ current?: boolean }>` 182 + height: 48px; 183 + width: 48px; 184 + border-radius: 4px; 185 + cursor: pointer; 186 + background-color: ${(props) => props.theme.colors.cover}; 187 + display: flex; 188 + justify-content: center; 189 + align-items: center; 190 + ${({ current }) => `opacity: ${current ? 0 : 1};`} 191 + `;
+2 -2
webui/rockbox/src/Components/ControlBar/CurrentTrack/CurrentTrack.tsx
··· 85 85 > 86 86 <Time>{formatTime(nowPlaying.progress)}</Time> 87 87 <ArtistAlbum> 88 - {_.get(nowPlaying, "artist.length", 0) > 65 89 - ? `${nowPlaying.artist?.substring(0, 54)}...` 88 + {_.get(nowPlaying, "artist.length", 0) > 40 89 + ? `${nowPlaying.artist?.substring(0, 40)}...` 90 90 : nowPlaying.artist} 91 91 <Separator>-</Separator> 92 92 <Album to={`/albums/${nowPlaying.albumId}`}>
+2 -1
webui/rockbox/src/Components/ControlBar/PlayQueue/PlayQueue.tsx
··· 132 132 </div> 133 133 )} 134 134 {!tracks[virtualItem.index].cover && ( 135 - <div className="album-cover-container queue"> 135 + <div className="album-cover-container"> 136 136 <AlbumCoverAlt> 137 137 <TrackIcon width={28} height={28} color="#a4a3a3" /> 138 138 </AlbumCoverAlt> 139 139 <div 140 140 onClick={() => _onPlayTrackAt(virtualItem.index)} 141 141 className="floating-play queue" 142 + style={{ left: 19, top: 10 }} 142 143 > 143 144 <Play 144 145 size={16}
+1 -1
webui/rockbox/src/Components/ControlBar/PlayQueue/styles.css
··· 10 10 .album-cover-container.queue .floating-play { 11 11 display: none; 12 12 position: absolute; 13 - left: 10px; 13 + left: 8px; 14 14 top: 19px; 15 15 } 16 16
+2
webui/rockbox/src/Components/Files/Files.stories.tsx
··· 22 22 files, 23 23 canGoBack: true, 24 24 onGoBack: fn(), 25 + onPlayDirectory: fn(), 26 + onPlayTrack: fn(), 25 27 }, 26 28 };
+7 -1
webui/rockbox/src/Components/Files/Files.test.tsx
··· 13 13 <MemoryRouter initialEntries={["/"]}> 14 14 <MockedProvider mocks={mocks}> 15 15 <Providers> 16 - <Files files={files} canGoBack={true} onGoBack={vi.fn()} /> 16 + <Files 17 + files={files} 18 + canGoBack={true} 19 + onGoBack={vi.fn()} 20 + onPlayDirectory={vi.fn()} 21 + onPlayTrack={vi.fn()} 22 + /> 17 23 </Providers> 18 24 </MockedProvider> 19 25 </MemoryRouter>
+40 -3
webui/rockbox/src/Components/Files/Files.tsx
··· 20 20 import { Spinner } from "baseui/spinner"; 21 21 import MainView from "../MainView"; 22 22 import ContextMenu from "./ContextMenu"; 23 + import Play from "../Icons/Play"; 23 24 24 25 const columnHelper = createColumnHelper<File>(); 25 26 ··· 28 29 canGoBack: boolean; 29 30 onGoBack: () => void; 30 31 refetching?: boolean; 32 + onPlayTrack: (path: string, index: number) => void; 33 + onPlayDirectory: (path: string) => void; 31 34 }; 32 35 33 36 const Files: FC<FilesProps> = (props) => { ··· 44 47 marginLeft: 10, 45 48 }} 46 49 > 47 - {info.row.original.isDirectory && <Folder2 size={20} />} 48 - {!info.row.original.isDirectory && <MusicNoteBeamed size={20} />} 50 + {info.row.original.isDirectory && ( 51 + <div> 52 + <div 53 + className="play" 54 + onClick={() => props.onPlayDirectory(info.row.original.path)} 55 + > 56 + <Play small /> 57 + </div> 58 + <div className="folder"> 59 + <Folder2 size={20} /> 60 + </div> 61 + </div> 62 + )} 63 + {!info.row.original.isDirectory && ( 64 + <div> 65 + <div 66 + className="play" 67 + onClick={() => { 68 + const parent = info.row.original.path.split("/").slice(0, -1); 69 + props.onPlayTrack(parent.join("/") || "/", info.row.index); 70 + }} 71 + > 72 + <Play small /> 73 + </div> 74 + <div className="folder"> 75 + <MusicNoteBeamed size={20} /> 76 + </div> 77 + </div> 78 + )} 49 79 </div> 50 80 ), 51 81 }), ··· 59 89 </Directory> 60 90 )} 61 91 {!info.row.original.isDirectory && ( 62 - <AudioFile>{info.getValue()}</AudioFile> 92 + <AudioFile 93 + onClick={() => { 94 + const parent = info.row.original.path.split("/").slice(0, -1); 95 + props.onPlayTrack(parent.join("/") || "/", info.row.index); 96 + }} 97 + > 98 + {info.getValue()} 99 + </AudioFile> 63 100 )} 64 101 </> 65 102 ),
+24 -1
webui/rockbox/src/Components/Files/FilesWithData.tsx
··· 1 1 import { FC, useEffect, useState } from "react"; 2 2 import Files from "./Files"; 3 - import { useGetEntriesQuery } from "../../Hooks/GraphQL"; 3 + import { 4 + useGetEntriesQuery, 5 + usePlayDirectoryMutation, 6 + } from "../../Hooks/GraphQL"; 4 7 import { useNavigate, useSearchParams } from "react-router-dom"; 5 8 6 9 const FilesWithData: FC = () => { ··· 10 13 const [params] = useSearchParams(); 11 14 const path = params.get("q"); 12 15 const canGoBack = !!path; 16 + const [playDirectory] = usePlayDirectoryMutation(); 13 17 14 18 const files = 15 19 data?.treeGetEntries.map((x) => ({ ··· 20 24 21 25 const onGoBack = () => navigate(-1); 22 26 27 + const onPlayDirectory = (path: string) => { 28 + playDirectory({ 29 + variables: { 30 + path, 31 + }, 32 + }); 33 + }; 34 + 35 + const onPlayTrack = (path: string, position: number) => { 36 + playDirectory({ 37 + variables: { 38 + path, 39 + position, 40 + }, 41 + }); 42 + }; 43 + 23 44 useEffect(() => { 24 45 setRefetching(true); 25 46 refetch({ ··· 36 57 canGoBack={canGoBack} 37 58 onGoBack={onGoBack} 38 59 refetching={refetching} 60 + onPlayDirectory={onPlayDirectory} 61 + onPlayTrack={onPlayTrack} 39 62 /> 40 63 ); 41 64 };
+260 -106
webui/rockbox/src/Components/Files/__snapshots__/Files.test.tsx.snap
··· 407 407 Files 408 408 </div> 409 409 <table 410 - style="width: 100%; margin-top: 31px;" 410 + style="width: 100%; margin-top: 20px;" 411 411 > 412 412 <thead> 413 413 <tr ··· 434 434 <div 435 435 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 436 436 > 437 - <svg 438 - aria-hidden="true" 439 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 440 - fill="currentColor" 441 - focusable="false" 442 - height="20" 443 - viewBox="0 0 16 16" 444 - width="20" 445 - xmlns="http://www.w3.org/2000/svg" 446 - > 447 - <path 448 - d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 449 - /> 450 - </svg> 437 + <div> 438 + <div 439 + class="play" 440 + > 441 + <svg 442 + fill="none" 443 + height="20" 444 + width="20" 445 + xmlns="http://www.w3.org/2000/svg" 446 + > 447 + <path 448 + class="ionicon" 449 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 450 + style="fill: #000;" 451 + /> 452 + </svg> 453 + </div> 454 + <div 455 + class="folder" 456 + > 457 + <svg 458 + aria-hidden="true" 459 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 460 + fill="currentColor" 461 + focusable="false" 462 + height="20" 463 + viewBox="0 0 16 16" 464 + width="20" 465 + xmlns="http://www.w3.org/2000/svg" 466 + > 467 + <path 468 + d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 469 + /> 470 + </svg> 471 + </div> 472 + </div> 451 473 </div> 452 474 </td> 453 475 <td ··· 519 541 <div 520 542 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 521 543 > 522 - <svg 523 - aria-hidden="true" 524 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 525 - fill="currentColor" 526 - focusable="false" 527 - height="20" 528 - viewBox="0 0 16 16" 529 - width="20" 530 - xmlns="http://www.w3.org/2000/svg" 531 - > 532 - <path 533 - d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 534 - /> 535 - </svg> 544 + <div> 545 + <div 546 + class="play" 547 + > 548 + <svg 549 + fill="none" 550 + height="20" 551 + width="20" 552 + xmlns="http://www.w3.org/2000/svg" 553 + > 554 + <path 555 + class="ionicon" 556 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 557 + style="fill: #000;" 558 + /> 559 + </svg> 560 + </div> 561 + <div 562 + class="folder" 563 + > 564 + <svg 565 + aria-hidden="true" 566 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 567 + fill="currentColor" 568 + focusable="false" 569 + height="20" 570 + viewBox="0 0 16 16" 571 + width="20" 572 + xmlns="http://www.w3.org/2000/svg" 573 + > 574 + <path 575 + d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 576 + /> 577 + </svg> 578 + </div> 579 + </div> 536 580 </div> 537 581 </td> 538 582 <td ··· 604 648 <div 605 649 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 606 650 > 607 - <svg 608 - aria-hidden="true" 609 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 610 - fill="currentColor" 611 - focusable="false" 612 - height="20" 613 - viewBox="0 0 16 16" 614 - width="20" 615 - xmlns="http://www.w3.org/2000/svg" 616 - > 617 - <path 618 - d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 619 - /> 620 - </svg> 651 + <div> 652 + <div 653 + class="play" 654 + > 655 + <svg 656 + fill="none" 657 + height="20" 658 + width="20" 659 + xmlns="http://www.w3.org/2000/svg" 660 + > 661 + <path 662 + class="ionicon" 663 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 664 + style="fill: #000;" 665 + /> 666 + </svg> 667 + </div> 668 + <div 669 + class="folder" 670 + > 671 + <svg 672 + aria-hidden="true" 673 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 674 + fill="currentColor" 675 + focusable="false" 676 + height="20" 677 + viewBox="0 0 16 16" 678 + width="20" 679 + xmlns="http://www.w3.org/2000/svg" 680 + > 681 + <path 682 + d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 683 + /> 684 + </svg> 685 + </div> 686 + </div> 621 687 </div> 622 688 </td> 623 689 <td ··· 689 755 <div 690 756 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 691 757 > 692 - <svg 693 - aria-hidden="true" 694 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 695 - fill="currentColor" 696 - focusable="false" 697 - height="20" 698 - viewBox="0 0 16 16" 699 - width="20" 700 - xmlns="http://www.w3.org/2000/svg" 701 - > 702 - <path 703 - d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 704 - /> 705 - </svg> 758 + <div> 759 + <div 760 + class="play" 761 + > 762 + <svg 763 + fill="none" 764 + height="20" 765 + width="20" 766 + xmlns="http://www.w3.org/2000/svg" 767 + > 768 + <path 769 + class="ionicon" 770 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 771 + style="fill: #000;" 772 + /> 773 + </svg> 774 + </div> 775 + <div 776 + class="folder" 777 + > 778 + <svg 779 + aria-hidden="true" 780 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 781 + fill="currentColor" 782 + focusable="false" 783 + height="20" 784 + viewBox="0 0 16 16" 785 + width="20" 786 + xmlns="http://www.w3.org/2000/svg" 787 + > 788 + <path 789 + d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 790 + /> 791 + </svg> 792 + </div> 793 + </div> 706 794 </div> 707 795 </td> 708 796 <td ··· 774 862 <div 775 863 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 776 864 > 777 - <svg 778 - aria-hidden="true" 779 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 780 - fill="currentColor" 781 - focusable="false" 782 - height="20" 783 - viewBox="0 0 16 16" 784 - width="20" 785 - xmlns="http://www.w3.org/2000/svg" 786 - > 787 - <path 788 - d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 789 - /> 790 - </svg> 865 + <div> 866 + <div 867 + class="play" 868 + > 869 + <svg 870 + fill="none" 871 + height="20" 872 + width="20" 873 + xmlns="http://www.w3.org/2000/svg" 874 + > 875 + <path 876 + class="ionicon" 877 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 878 + style="fill: #000;" 879 + /> 880 + </svg> 881 + </div> 882 + <div 883 + class="folder" 884 + > 885 + <svg 886 + aria-hidden="true" 887 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 888 + fill="currentColor" 889 + focusable="false" 890 + height="20" 891 + viewBox="0 0 16 16" 892 + width="20" 893 + xmlns="http://www.w3.org/2000/svg" 894 + > 895 + <path 896 + d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 897 + /> 898 + </svg> 899 + </div> 900 + </div> 791 901 </div> 792 902 </td> 793 903 <td ··· 859 969 <div 860 970 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 861 971 > 862 - <svg 863 - aria-hidden="true" 864 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 865 - fill="currentColor" 866 - focusable="false" 867 - height="20" 868 - viewBox="0 0 16 16" 869 - width="20" 870 - xmlns="http://www.w3.org/2000/svg" 871 - > 872 - <path 873 - d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 874 - /> 875 - </svg> 972 + <div> 973 + <div 974 + class="play" 975 + > 976 + <svg 977 + fill="none" 978 + height="20" 979 + width="20" 980 + xmlns="http://www.w3.org/2000/svg" 981 + > 982 + <path 983 + class="ionicon" 984 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 985 + style="fill: #000;" 986 + /> 987 + </svg> 988 + </div> 989 + <div 990 + class="folder" 991 + > 992 + <svg 993 + aria-hidden="true" 994 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 995 + fill="currentColor" 996 + focusable="false" 997 + height="20" 998 + viewBox="0 0 16 16" 999 + width="20" 1000 + xmlns="http://www.w3.org/2000/svg" 1001 + > 1002 + <path 1003 + d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9zM2.5 3a.5.5 0 0 0-.5.5V6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5zM14 7H2v5.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V7z" 1004 + /> 1005 + </svg> 1006 + </div> 1007 + </div> 876 1008 </div> 877 1009 </td> 878 1010 <td ··· 944 1076 <div 945 1077 style="display: flex; align-items: center; justify-content: center; margin-left: 10px;" 946 1078 > 947 - <svg 948 - aria-hidden="true" 949 - class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 950 - fill="currentColor" 951 - focusable="false" 952 - height="20" 953 - viewBox="0 0 16 16" 954 - width="20" 955 - xmlns="http://www.w3.org/2000/svg" 956 - > 957 - <path 958 - d="M6 13c0 1.105-1.12 2-2.5 2S1 14.105 1 13c0-1.104 1.12-2 2.5-2s2.5.896 2.5 2zm9-2c0 1.105-1.12 2-2.5 2s-2.5-.895-2.5-2 1.12-2 2.5-2 2.5.895 2.5 2z" 959 - /> 960 - <path 961 - d="M14 11V2h1v9h-1zM6 3v10H5V3h1z" 962 - fill-rule="evenodd" 963 - /> 964 - <path 965 - d="M5 2.905a1 1 0 0 1 .9-.995l8-.8a1 1 0 0 1 1.1.995V3L5 4V2.905z" 966 - /> 967 - </svg> 1079 + <div> 1080 + <div 1081 + class="play" 1082 + > 1083 + <svg 1084 + fill="none" 1085 + height="20" 1086 + width="20" 1087 + xmlns="http://www.w3.org/2000/svg" 1088 + > 1089 + <path 1090 + class="ionicon" 1091 + d="M7.386 16c-.23 0-.456-.057-.656-.165-.45-.24-.73-.706-.73-1.213V4.378a1.387 1.387 0 0 1 2.071-1.197l9.296 5.241c.394.232.633.64.633 1.077 0 .438-.239.845-.633 1.078L8.07 15.819a1.39 1.39 0 0 1-.684.181Z" 1092 + style="fill: #000;" 1093 + /> 1094 + </svg> 1095 + </div> 1096 + <div 1097 + class="folder" 1098 + > 1099 + <svg 1100 + aria-hidden="true" 1101 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 1102 + fill="currentColor" 1103 + focusable="false" 1104 + height="20" 1105 + viewBox="0 0 16 16" 1106 + width="20" 1107 + xmlns="http://www.w3.org/2000/svg" 1108 + > 1109 + <path 1110 + d="M6 13c0 1.105-1.12 2-2.5 2S1 14.105 1 13c0-1.104 1.12-2 2.5-2s2.5.896 2.5 2zm9-2c0 1.105-1.12 2-2.5 2s-2.5-.895-2.5-2 1.12-2 2.5-2 2.5.895 2.5 2z" 1111 + /> 1112 + <path 1113 + d="M14 11V2h1v9h-1zM6 3v10H5V3h1z" 1114 + fill-rule="evenodd" 1115 + /> 1116 + <path 1117 + d="M5 2.905a1 1 0 0 1 .9-.995l8-.8a1 1 0 0 1 1.1.995V3L5 4V2.905z" 1118 + /> 1119 + </svg> 1120 + </div> 1121 + </div> 968 1122 </div> 969 1123 </td> 970 1124 <td
+3 -3
webui/rockbox/src/Components/Likes/Likes.tsx
··· 31 31 32 32 export type TracksProps = { 33 33 tracks: Track[]; 34 - onPlayTrack: (id: string) => void; 34 + onPlayTrack: (index: number) => void; 35 35 onPlayAll: () => void; 36 36 onShuffleAll: () => void; 37 37 }; ··· 71 71 effect="blur" 72 72 /> 73 73 <div 74 - onClick={() => props.onPlayTrack(info.row.original.id)} 74 + onClick={() => props.onPlayTrack(info.row.index)} 75 75 className="floating-play" 76 76 > 77 77 <Play small color={info.getValue() ? "#fff" : "#000"} /> ··· 84 84 <TrackIcon width={28} height={28} color="#a4a3a3" /> 85 85 </AlbumCoverAlt> 86 86 <div 87 - onClick={() => props.onPlayTrack(info.row.original.id)} 87 + onClick={() => props.onPlayTrack(info.row.index)} 88 88 className="floating-play" 89 89 > 90 90 <Play small color={info.getValue() ? "#fff" : "#000"} />
+6 -2
webui/rockbox/src/Components/Likes/LikesWithData.tsx
··· 49 49 // eslint-disable-next-line react-hooks/exhaustive-deps 50 50 }, [data, loading]); 51 51 52 - const onPlayTrack = (trackId: string) => { 53 - console.log(">>", trackId); 52 + const onPlayTrack = (position: number) => { 53 + playLikedTracks({ 54 + variables: { 55 + position, 56 + }, 57 + }); 54 58 }; 55 59 56 60 const onPlayAll = () => {
+2 -2
webui/rockbox/src/Components/Likes/styles.css
··· 16 16 .album-cover-container .floating-play { 17 17 display: none; 18 18 position: absolute; 19 - left: 14px; 20 - top: 14px; 19 + left: 17px; 20 + top: 13px; 21 21 } 22 22 23 23 .album-cover-container:hover .floating-play {
+1 -1
webui/rockbox/src/Components/Table/Table.tsx
··· 28 28 }); 29 29 30 30 return ( 31 - <table style={{ width: "100%", marginTop: 31 }}> 31 + <table style={{ width: "100%", marginTop: 20 }}> 32 32 <thead> 33 33 {table.getHeaderGroups().map((headerGroup) => ( 34 34 <tr
+3 -3
webui/rockbox/src/Components/Tracks/Tracks.tsx
··· 26 26 27 27 export type TracksProps = { 28 28 tracks: Track[]; 29 - onPlayTrack: (id: string) => void; 29 + onPlayTrack: (index: number) => void; 30 30 }; 31 31 32 32 const Tracks: FC<TracksProps> = (props) => { ··· 63 63 effect="blur" 64 64 /> 65 65 <div 66 - onClick={() => props.onPlayTrack(info.row.original.id)} 66 + onClick={() => props.onPlayTrack(info.row.index)} 67 67 className="floating-play" 68 68 > 69 69 <Play size={16} color={info.getValue() ? "#fff" : "#000"} /> ··· 76 76 <TrackIcon width={28} height={28} color="#a4a3a3" /> 77 77 </AlbumCoverAlt> 78 78 <div 79 - onClick={() => props.onPlayTrack(info.row.original.id)} 79 + onClick={() => props.onPlayTrack(info.row.index)} 80 80 className="floating-play" 81 81 > 82 82 <Play size={16} color={info.getValue() ? "#fff" : "#000"} />
+8 -3
webui/rockbox/src/Components/Tracks/TracksWithData.tsx
··· 1 1 import { FC, useEffect, useState } from "react"; 2 2 import Tracks from "./Tracks"; 3 - import { useTracksQuery } from "../../Hooks/GraphQL"; 3 + import { usePlayAllTracksMutation, useTracksQuery } from "../../Hooks/GraphQL"; 4 4 import { useTimeFormat } from "../../Hooks/useFormat"; 5 5 import { Track } from "../../Types/track"; 6 6 ··· 8 8 const { data, loading } = useTracksQuery(); 9 9 const [tracks, setTracks] = useState<Track[]>([]); 10 10 const { formatTime } = useTimeFormat(); 11 + const [playAllTracks] = usePlayAllTracksMutation(); 11 12 12 13 useEffect(() => { 13 14 if (!data || loading) { ··· 33 34 // eslint-disable-next-line react-hooks/exhaustive-deps 34 35 }, [data, loading]); 35 36 36 - const onPlayTrack = (trackId: string) => { 37 - console.log(">>", trackId); 37 + const onPlayTrack = (position: number) => { 38 + playAllTracks({ 39 + variables: { 40 + position, 41 + }, 42 + }); 38 43 }; 39 44 40 45 return <Tracks tracks={tracks} onPlayTrack={onPlayTrack} />;
+1 -1
webui/rockbox/src/Components/Tracks/__snapshots__/Tracks.test.tsx.snap
··· 435 435 style="height: 48px; color: rgba(0, 0, 0, 0.54);" 436 436 > 437 437 <th 438 - style="text-align: left; width: 20px;" 438 + style="width: 20px;" 439 439 > 440 440 # 441 441 </th>
+1 -1
webui/rockbox/src/Components/Tracks/styles.css
··· 15 15 .album-cover-container.songs .floating-play { 16 16 display: none; 17 17 position: absolute; 18 - left: 17px; 18 + left: 22px; 19 19 top: 10px; 20 20 } 21 21
+7 -1
webui/rockbox/src/Components/VirtualizedTable/VirtualizedTable.tsx
··· 73 73 {headerGroup.headers.map((header) => ( 74 74 <th 75 75 key={header.id} 76 - style={{ textAlign: "left", width: header.getSize() }} 76 + style={{ 77 + textAlign: 78 + header.column.columnDef.header !== "#" 79 + ? "left" 80 + : undefined, 81 + width: header.getSize(), 82 + }} 77 83 > 78 84 {header.isPlaceholder 79 85 ? null
+32 -8
webui/rockbox/src/GraphQL/Playback/Mutation.ts
··· 31 31 `; 32 32 33 33 export const PLAY_ALBUM = gql` 34 - mutation PlayAlbum($albumId: String!, $shuffle: Boolean) { 35 - playAlbum(albumId: $albumId, shuffle: $shuffle) 34 + mutation PlayAlbum($albumId: String!, $shuffle: Boolean, $position: Int) { 35 + playAlbum(albumId: $albumId, shuffle: $shuffle, position: $position) 36 36 } 37 37 `; 38 38 39 39 export const PLAY_ARTIST_TRACKS = gql` 40 - mutation PlayArtistTracks($artistId: String!, $shuffle: Boolean) { 41 - playArtistTracks(artistId: $artistId, shuffle: $shuffle) 40 + mutation PlayArtistTracks( 41 + $artistId: String! 42 + $shuffle: Boolean 43 + $position: Int 44 + ) { 45 + playArtistTracks( 46 + artistId: $artistId 47 + shuffle: $shuffle 48 + position: $position 49 + ) 42 50 } 43 51 `; 44 52 45 53 export const PLAY_DIRECTORY = gql` 46 - mutation PlayDirectory($path: String!, $recurse: Boolean, $shuffle: Boolean) { 47 - playDirectory(path: $path, recurse: $recurse, shuffle: $shuffle) 54 + mutation PlayDirectory( 55 + $path: String! 56 + $recurse: Boolean 57 + $shuffle: Boolean 58 + $position: Int 59 + ) { 60 + playDirectory( 61 + path: $path 62 + recurse: $recurse 63 + shuffle: $shuffle 64 + position: $position 65 + ) 48 66 } 49 67 `; 50 68 ··· 55 73 `; 56 74 57 75 export const PLAY_LIKED_TRACKS = gql` 58 - mutation PlayLikedTracks($shuffle: Boolean) { 59 - playLikedTracks(shuffle: $shuffle) 76 + mutation PlayLikedTracks($shuffle: Boolean, $position: Int) { 77 + playLikedTracks(shuffle: $shuffle, position: $position) 78 + } 79 + `; 80 + 81 + export const PLAY_ALL_TRACKS = gql` 82 + mutation PlayAllTracks($shuffle: Boolean, $position: Int) { 83 + playAllTracks(shuffle: $shuffle, position: $position) 60 84 } 61 85 `;
+73 -8
webui/rockbox/src/Hooks/GraphQL.tsx
··· 91 91 pcmbufSetLowLatency: Scalars['String']['output']; 92 92 play: Scalars['Int']['output']; 93 93 playAlbum: Scalars['Int']['output']; 94 + playAllTracks: Scalars['Int']['output']; 94 95 playArtistTracks: Scalars['Int']['output']; 95 96 playDirectory: Scalars['Int']['output']; 96 97 playLikedTracks: Scalars['Int']['output']; ··· 174 175 175 176 export type MutationPlayAlbumArgs = { 176 177 albumId: Scalars['String']['input']; 178 + position?: InputMaybe<Scalars['Int']['input']>; 179 + shuffle?: InputMaybe<Scalars['Boolean']['input']>; 180 + }; 181 + 182 + 183 + export type MutationPlayAllTracksArgs = { 184 + position?: InputMaybe<Scalars['Int']['input']>; 177 185 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 178 186 }; 179 187 180 188 181 189 export type MutationPlayArtistTracksArgs = { 182 190 artistId: Scalars['String']['input']; 191 + position?: InputMaybe<Scalars['Int']['input']>; 183 192 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 184 193 }; 185 194 186 195 187 196 export type MutationPlayDirectoryArgs = { 188 197 path: Scalars['String']['input']; 198 + position?: InputMaybe<Scalars['Int']['input']>; 189 199 recurse?: InputMaybe<Scalars['Boolean']['input']>; 190 200 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 191 201 }; 192 202 193 203 194 204 export type MutationPlayLikedTracksArgs = { 205 + position?: InputMaybe<Scalars['Int']['input']>; 195 206 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 196 207 }; 197 208 198 209 199 210 export type MutationPlayPlaylistArgs = { 200 211 playlistId: Scalars['String']['input']; 212 + position?: InputMaybe<Scalars['Int']['input']>; 201 213 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 202 214 }; 203 215 ··· 660 672 export type PlayAlbumMutationVariables = Exact<{ 661 673 albumId: Scalars['String']['input']; 662 674 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 675 + position?: InputMaybe<Scalars['Int']['input']>; 663 676 }>; 664 677 665 678 ··· 668 681 export type PlayArtistTracksMutationVariables = Exact<{ 669 682 artistId: Scalars['String']['input']; 670 683 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 684 + position?: InputMaybe<Scalars['Int']['input']>; 671 685 }>; 672 686 673 687 ··· 677 691 path: Scalars['String']['input']; 678 692 recurse?: InputMaybe<Scalars['Boolean']['input']>; 679 693 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 694 + position?: InputMaybe<Scalars['Int']['input']>; 680 695 }>; 681 696 682 697 ··· 691 706 692 707 export type PlayLikedTracksMutationVariables = Exact<{ 693 708 shuffle?: InputMaybe<Scalars['Boolean']['input']>; 709 + position?: InputMaybe<Scalars['Int']['input']>; 694 710 }>; 695 711 696 712 697 713 export type PlayLikedTracksMutation = { __typename?: 'Mutation', playLikedTracks: number }; 714 + 715 + export type PlayAllTracksMutationVariables = Exact<{ 716 + shuffle?: InputMaybe<Scalars['Boolean']['input']>; 717 + position?: InputMaybe<Scalars['Int']['input']>; 718 + }>; 719 + 720 + 721 + export type PlayAllTracksMutation = { __typename?: 'Mutation', playAllTracks: number }; 698 722 699 723 export type GetCurrentTrackQueryVariables = Exact<{ [key: string]: never; }>; 700 724 ··· 1502 1526 export type NextMutationResult = Apollo.MutationResult<NextMutation>; 1503 1527 export type NextMutationOptions = Apollo.BaseMutationOptions<NextMutation, NextMutationVariables>; 1504 1528 export const PlayAlbumDocument = gql` 1505 - mutation PlayAlbum($albumId: String!, $shuffle: Boolean) { 1506 - playAlbum(albumId: $albumId, shuffle: $shuffle) 1529 + mutation PlayAlbum($albumId: String!, $shuffle: Boolean, $position: Int) { 1530 + playAlbum(albumId: $albumId, shuffle: $shuffle, position: $position) 1507 1531 } 1508 1532 `; 1509 1533 export type PlayAlbumMutationFn = Apollo.MutationFunction<PlayAlbumMutation, PlayAlbumMutationVariables>; ··· 1523 1547 * variables: { 1524 1548 * albumId: // value for 'albumId' 1525 1549 * shuffle: // value for 'shuffle' 1550 + * position: // value for 'position' 1526 1551 * }, 1527 1552 * }); 1528 1553 */ ··· 1534 1559 export type PlayAlbumMutationResult = Apollo.MutationResult<PlayAlbumMutation>; 1535 1560 export type PlayAlbumMutationOptions = Apollo.BaseMutationOptions<PlayAlbumMutation, PlayAlbumMutationVariables>; 1536 1561 export const PlayArtistTracksDocument = gql` 1537 - mutation PlayArtistTracks($artistId: String!, $shuffle: Boolean) { 1538 - playArtistTracks(artistId: $artistId, shuffle: $shuffle) 1562 + mutation PlayArtistTracks($artistId: String!, $shuffle: Boolean, $position: Int) { 1563 + playArtistTracks(artistId: $artistId, shuffle: $shuffle, position: $position) 1539 1564 } 1540 1565 `; 1541 1566 export type PlayArtistTracksMutationFn = Apollo.MutationFunction<PlayArtistTracksMutation, PlayArtistTracksMutationVariables>; ··· 1555 1580 * variables: { 1556 1581 * artistId: // value for 'artistId' 1557 1582 * shuffle: // value for 'shuffle' 1583 + * position: // value for 'position' 1558 1584 * }, 1559 1585 * }); 1560 1586 */ ··· 1566 1592 export type PlayArtistTracksMutationResult = Apollo.MutationResult<PlayArtistTracksMutation>; 1567 1593 export type PlayArtistTracksMutationOptions = Apollo.BaseMutationOptions<PlayArtistTracksMutation, PlayArtistTracksMutationVariables>; 1568 1594 export const PlayDirectoryDocument = gql` 1569 - mutation PlayDirectory($path: String!, $recurse: Boolean, $shuffle: Boolean) { 1570 - playDirectory(path: $path, recurse: $recurse, shuffle: $shuffle) 1595 + mutation PlayDirectory($path: String!, $recurse: Boolean, $shuffle: Boolean, $position: Int) { 1596 + playDirectory( 1597 + path: $path 1598 + recurse: $recurse 1599 + shuffle: $shuffle 1600 + position: $position 1601 + ) 1571 1602 } 1572 1603 `; 1573 1604 export type PlayDirectoryMutationFn = Apollo.MutationFunction<PlayDirectoryMutation, PlayDirectoryMutationVariables>; ··· 1588 1619 * path: // value for 'path' 1589 1620 * recurse: // value for 'recurse' 1590 1621 * shuffle: // value for 'shuffle' 1622 + * position: // value for 'position' 1591 1623 * }, 1592 1624 * }); 1593 1625 */ ··· 1630 1662 export type PlayTrackMutationResult = Apollo.MutationResult<PlayTrackMutation>; 1631 1663 export type PlayTrackMutationOptions = Apollo.BaseMutationOptions<PlayTrackMutation, PlayTrackMutationVariables>; 1632 1664 export const PlayLikedTracksDocument = gql` 1633 - mutation PlayLikedTracks($shuffle: Boolean) { 1634 - playLikedTracks(shuffle: $shuffle) 1665 + mutation PlayLikedTracks($shuffle: Boolean, $position: Int) { 1666 + playLikedTracks(shuffle: $shuffle, position: $position) 1635 1667 } 1636 1668 `; 1637 1669 export type PlayLikedTracksMutationFn = Apollo.MutationFunction<PlayLikedTracksMutation, PlayLikedTracksMutationVariables>; ··· 1650 1682 * const [playLikedTracksMutation, { data, loading, error }] = usePlayLikedTracksMutation({ 1651 1683 * variables: { 1652 1684 * shuffle: // value for 'shuffle' 1685 + * position: // value for 'position' 1653 1686 * }, 1654 1687 * }); 1655 1688 */ ··· 1660 1693 export type PlayLikedTracksMutationHookResult = ReturnType<typeof usePlayLikedTracksMutation>; 1661 1694 export type PlayLikedTracksMutationResult = Apollo.MutationResult<PlayLikedTracksMutation>; 1662 1695 export type PlayLikedTracksMutationOptions = Apollo.BaseMutationOptions<PlayLikedTracksMutation, PlayLikedTracksMutationVariables>; 1696 + export const PlayAllTracksDocument = gql` 1697 + mutation PlayAllTracks($shuffle: Boolean, $position: Int) { 1698 + playAllTracks(shuffle: $shuffle, position: $position) 1699 + } 1700 + `; 1701 + export type PlayAllTracksMutationFn = Apollo.MutationFunction<PlayAllTracksMutation, PlayAllTracksMutationVariables>; 1702 + 1703 + /** 1704 + * __usePlayAllTracksMutation__ 1705 + * 1706 + * To run a mutation, you first call `usePlayAllTracksMutation` within a React component and pass it any options that fit your needs. 1707 + * When your component renders, `usePlayAllTracksMutation` returns a tuple that includes: 1708 + * - A mutate function that you can call at any time to execute the mutation 1709 + * - An object with fields that represent the current status of the mutation's execution 1710 + * 1711 + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; 1712 + * 1713 + * @example 1714 + * const [playAllTracksMutation, { data, loading, error }] = usePlayAllTracksMutation({ 1715 + * variables: { 1716 + * shuffle: // value for 'shuffle' 1717 + * position: // value for 'position' 1718 + * }, 1719 + * }); 1720 + */ 1721 + export function usePlayAllTracksMutation(baseOptions?: Apollo.MutationHookOptions<PlayAllTracksMutation, PlayAllTracksMutationVariables>) { 1722 + const options = {...defaultOptions, ...baseOptions} 1723 + return Apollo.useMutation<PlayAllTracksMutation, PlayAllTracksMutationVariables>(PlayAllTracksDocument, options); 1724 + } 1725 + export type PlayAllTracksMutationHookResult = ReturnType<typeof usePlayAllTracksMutation>; 1726 + export type PlayAllTracksMutationResult = Apollo.MutationResult<PlayAllTracksMutation>; 1727 + export type PlayAllTracksMutationOptions = Apollo.BaseMutationOptions<PlayAllTracksMutation, PlayAllTracksMutationVariables>; 1663 1728 export const GetCurrentTrackDocument = gql` 1664 1729 query GetCurrentTrack { 1665 1730 currentTrack {
+37 -1
webui/rockbox/src/index.css
··· 33 33 -webkit-font-smoothing: antialiased; 34 34 -moz-osx-font-smoothing: grayscale; 35 35 overflow-y: hidden; 36 - } 36 + } 37 + 38 + tr td div .floating-play { 39 + cursor: pointer; 40 + display: none; 41 + } 42 + 43 + 44 + tr:hover td div .floating-play { 45 + cursor: pointer; 46 + margin-left: -5px; 47 + display: block; 48 + } 49 + 50 + 51 + tr:hover td div .tracknumber { 52 + display: none; 53 + } 54 + 55 + 56 + tr td div .play { 57 + cursor: pointer; 58 + display: none; 59 + } 60 + 61 + 62 + tr:hover td div .play { 63 + cursor: pointer; 64 + margin-left: -5px; 65 + margin-top: 5px; 66 + display: block; 67 + } 68 + 69 + 70 + tr:hover td div .folder { 71 + display: none; 72 + }
+1 -1
webui/rockbox/tsconfig.app.tsbuildinfo
··· 1 - {"root":["./src/App.tsx","./src/Theme.ts","./src/emotion.d.ts","./src/main.tsx","./src/mocks.ts","./src/vite-env.d.ts","./src/Components/AlbumDetails/AlbumDetails.stories.tsx","./src/Components/AlbumDetails/AlbumDetails.test.tsx","./src/Components/AlbumDetails/AlbumDetails.tsx","./src/Components/AlbumDetails/AlbumDetailsWithData.tsx","./src/Components/AlbumDetails/index.tsx","./src/Components/AlbumDetails/mocks.tsx","./src/Components/AlbumDetails/styles.tsx","./src/Components/Albums/Albums.stories.tsx","./src/Components/Albums/Albums.test.tsx","./src/Components/Albums/Albums.tsx","./src/Components/Albums/AlbumsWithData.tsx","./src/Components/Albums/index.tsx","./src/Components/Albums/mocks.tsx","./src/Components/Albums/styles.tsx","./src/Components/ArtistDetails/ArtistDetails.stories.tsx","./src/Components/ArtistDetails/ArtistDetails.test.tsx","./src/Components/ArtistDetails/ArtistDetails.tsx","./src/Components/ArtistDetails/ArtistDetailsWithData.tsx","./src/Components/ArtistDetails/index.tsx","./src/Components/ArtistDetails/mocks.tsx","./src/Components/ArtistDetails/styles.tsx","./src/Components/Artists/Artists.stories.tsx","./src/Components/Artists/Artists.test.tsx","./src/Components/Artists/Artists.tsx","./src/Components/Artists/ArtistsWithData.tsx","./src/Components/Artists/index.tsx","./src/Components/Artists/mocks.tsx","./src/Components/Artists/styles.tsx","./src/Components/Button/Button.test.tsx","./src/Components/Button/Button.tsx","./src/Components/Button/index.tsx","./src/Components/ControlBar/ControlBar.stories.tsx","./src/Components/ControlBar/ControlBar.test.tsx","./src/Components/ControlBar/ControlBar.tsx","./src/Components/ControlBar/ControlBarState.tsx","./src/Components/ControlBar/ControlBarWithData.tsx","./src/Components/ControlBar/index.tsx","./src/Components/ControlBar/styles.tsx","./src/Components/ControlBar/CurrentTrack/CurrentTrack.tsx","./src/Components/ControlBar/CurrentTrack/index.tsx","./src/Components/ControlBar/CurrentTrack/styles.tsx","./src/Components/ControlBar/PlayQueue/PlayQueue.stories.tsx","./src/Components/ControlBar/PlayQueue/PlayQueue.test.tsx","./src/Components/ControlBar/PlayQueue/PlayQueue.tsx","./src/Components/ControlBar/PlayQueue/PlayQueueWithData.tsx","./src/Components/ControlBar/PlayQueue/index.tsx","./src/Components/ControlBar/PlayQueue/mocks.tsx","./src/Components/ControlBar/PlayQueue/styles.tsx","./src/Components/ControlBar/RightMenu/RightMenu.tsx","./src/Components/ControlBar/RightMenu/index.tsx","./src/Components/ControlBar/RightMenu/styles.tsx","./src/Components/Extensions/Extensions.stories.tsx","./src/Components/Extensions/Extensions.tsx","./src/Components/Extensions/index.tsx","./src/Components/Files/Files.stories.tsx","./src/Components/Files/Files.test.tsx","./src/Components/Files/Files.tsx","./src/Components/Files/FilesWithData.tsx","./src/Components/Files/index.tsx","./src/Components/Files/mocks.tsx","./src/Components/Files/styles.tsx","./src/Components/Filter/Filter.test.tsx","./src/Components/Filter/Filter.tsx","./src/Components/Filter/index.tsx","./src/Components/Folder/Folder.stories.tsx","./src/Components/Folder/Folder.tsx","./src/Components/Folder/index.tsx","./src/Components/Icons/Add.tsx","./src/Components/Icons/AlbumCover.tsx","./src/Components/Icons/ArrowBack.tsx","./src/Components/Icons/Artist.tsx","./src/Components/Icons/Heart.tsx","./src/Components/Icons/HeartOutline.tsx","./src/Components/Icons/Next.tsx","./src/Components/Icons/Pause.tsx","./src/Components/Icons/Play.tsx","./src/Components/Icons/Previous.tsx","./src/Components/Icons/Repeat.tsx","./src/Components/Icons/Search.tsx","./src/Components/Icons/Shuffle.tsx","./src/Components/Icons/Speaker.tsx","./src/Components/Icons/Track.tsx","./src/Components/MainView/MainView.tsx","./src/Components/MainView/MainViewWithData.tsx","./src/Components/MainView/index.tsx","./src/Components/MainView/styles.tsx","./src/Components/PlaylistDetails/PlaylistDetails.stories.tsx","./src/Components/PlaylistDetails/PlaylistDetails.tsx","./src/Components/PlaylistDetails/index.tsx","./src/Components/Playlists/Playlists.stories.tsx","./src/Components/Playlists/Playlists.tsx","./src/Components/Playlists/index.tsx","./src/Components/Settings/SettingsState.ts","./src/Components/Sidebar/Sidebar.test.tsx","./src/Components/Sidebar/Sidebar.tsx","./src/Components/Sidebar/SidebarWithData.tsx","./src/Components/Sidebar/Stidebar.stories.tsx","./src/Components/Sidebar/index.tsx","./src/Components/Sidebar/styles.tsx","./src/Components/Table/Table.tsx","./src/Components/Table/index.tsx","./src/Components/Tracks/Tracks.stories.tsx","./src/Components/Tracks/Tracks.test.tsx","./src/Components/Tracks/Tracks.tsx","./src/Components/Tracks/TracksWithData.tsx","./src/Components/Tracks/index.tsx","./src/Components/Tracks/mocks.tsx","./src/Components/Tracks/styles.tsx","./src/Components/VirtualizedTable/VirtualizedTable.tsx","./src/Components/VirtualizedTable/index.tsx","./src/Containers/AlbumDetails/AlbumDetailsPage.tsx","./src/Containers/AlbumDetails/index.tsx","./src/Containers/Albums/AlbumsPage.tsx","./src/Containers/Albums/index.tsx","./src/Containers/ArtistDetails/ArtistDetailsPage.tsx","./src/Containers/ArtistDetails/index.tsx","./src/Containers/Artists/ArtistsPage.tsx","./src/Containers/Artists/index.tsx","./src/Containers/Extensions/ExtensionsPage.tsx","./src/Containers/Extensions/index.tsx","./src/Containers/Files/FilesPage.tsx","./src/Containers/Files/index.tsx","./src/Containers/Playlists/PlaylistsPage.tsx","./src/Containers/Playlists/index.tsx","./src/Containers/Tracks/TracksPage.tsx","./src/Containers/Tracks/index.tsx","./src/GraphQL/Browse/Query.ts","./src/GraphQL/Library/Query.ts","./src/GraphQL/Playback/Mutation.ts","./src/GraphQL/Playback/Query.ts","./src/GraphQL/Playback/Subscription.ts","./src/GraphQL/Playlist/Mutation.ts","./src/GraphQL/Playlist/Query.ts","./src/GraphQL/Playlist/Subscription.ts","./src/GraphQL/System/Query.ts","./src/Hooks/GraphQL.tsx","./src/Hooks/useFormat.tsx","./src/Hooks/usePlayQueue.tsx","./src/Hooks/useResumePlaylist.tsx","./src/Providers/GraphQLProvider.tsx","./src/Providers/ThemeProvider.tsx","./src/Providers/index.tsx","./src/Types/file.ts","./src/Types/track.ts","./src/stories/Button.stories.ts","./src/stories/Button.tsx","./src/stories/Header.stories.ts","./src/stories/Header.tsx","./src/stories/Page.stories.ts","./src/stories/Page.tsx"],"version":"5.6.2"} 1 + {"root":["./src/App.tsx","./src/Theme.ts","./src/emotion.d.ts","./src/main.tsx","./src/mocks.ts","./src/vite-env.d.ts","./src/Components/Album/Album.stories.tsx","./src/Components/Album/Album.test.tsx","./src/Components/Album/Album.tsx","./src/Components/Album/AlbumWithData.tsx","./src/Components/Album/index.tsx","./src/Components/Album/styles.tsx","./src/Components/Album/ContextMenu/ChildMenu.tsx","./src/Components/Album/ContextMenu/ContextMenu.stories.tsx","./src/Components/Album/ContextMenu/ContextMenu.test.tsx","./src/Components/Album/ContextMenu/ContextMenu.tsx","./src/Components/Album/ContextMenu/ContextMenuWithData.tsx","./src/Components/Album/ContextMenu/index.tsx","./src/Components/Album/ContextMenu/styles.tsx","./src/Components/AlbumDetails/AlbumDetails.stories.tsx","./src/Components/AlbumDetails/AlbumDetails.test.tsx","./src/Components/AlbumDetails/AlbumDetails.tsx","./src/Components/AlbumDetails/AlbumDetailsWithData.tsx","./src/Components/AlbumDetails/index.tsx","./src/Components/AlbumDetails/mocks.tsx","./src/Components/AlbumDetails/styles.tsx","./src/Components/Albums/Albums.stories.tsx","./src/Components/Albums/Albums.test.tsx","./src/Components/Albums/Albums.tsx","./src/Components/Albums/AlbumsWithData.tsx","./src/Components/Albums/index.tsx","./src/Components/Albums/mocks.tsx","./src/Components/Albums/styles.tsx","./src/Components/ArtistDetails/ArtistDetails.stories.tsx","./src/Components/ArtistDetails/ArtistDetails.test.tsx","./src/Components/ArtistDetails/ArtistDetails.tsx","./src/Components/ArtistDetails/ArtistDetailsWithData.tsx","./src/Components/ArtistDetails/index.tsx","./src/Components/ArtistDetails/mocks.tsx","./src/Components/ArtistDetails/styles.tsx","./src/Components/Artists/Artists.stories.tsx","./src/Components/Artists/Artists.test.tsx","./src/Components/Artists/Artists.tsx","./src/Components/Artists/ArtistsWithData.tsx","./src/Components/Artists/index.tsx","./src/Components/Artists/mocks.tsx","./src/Components/Artists/styles.tsx","./src/Components/Button/Button.test.tsx","./src/Components/Button/Button.tsx","./src/Components/Button/index.tsx","./src/Components/ContextMenu/ChildMenu.tsx","./src/Components/ContextMenu/ContextMenu.stories.tsx","./src/Components/ContextMenu/ContextMenu.test.tsx","./src/Components/ContextMenu/ContextMenu.tsx","./src/Components/ContextMenu/ContextMenuWithData.tsx","./src/Components/ContextMenu/index.tsx","./src/Components/ContextMenu/styles.tsx","./src/Components/ControlBar/ControlBar.stories.tsx","./src/Components/ControlBar/ControlBar.test.tsx","./src/Components/ControlBar/ControlBar.tsx","./src/Components/ControlBar/ControlBarState.tsx","./src/Components/ControlBar/ControlBarWithData.tsx","./src/Components/ControlBar/index.tsx","./src/Components/ControlBar/styles.tsx","./src/Components/ControlBar/CurrentTrack/CurrentTrack.tsx","./src/Components/ControlBar/CurrentTrack/index.tsx","./src/Components/ControlBar/CurrentTrack/styles.ts","./src/Components/ControlBar/PlayQueue/PlayQueue.stories.tsx","./src/Components/ControlBar/PlayQueue/PlayQueue.test.tsx","./src/Components/ControlBar/PlayQueue/PlayQueue.tsx","./src/Components/ControlBar/PlayQueue/PlayQueueWithData.tsx","./src/Components/ControlBar/PlayQueue/index.tsx","./src/Components/ControlBar/PlayQueue/mocks.tsx","./src/Components/ControlBar/PlayQueue/styles.tsx","./src/Components/ControlBar/RightMenu/RightMenu.tsx","./src/Components/ControlBar/RightMenu/index.tsx","./src/Components/ControlBar/RightMenu/styles.tsx","./src/Components/ControlBar/RightMenu/Volume/Volume.stories.tsx","./src/Components/ControlBar/RightMenu/Volume/Volume.test.tsx","./src/Components/ControlBar/RightMenu/Volume/Volume.tsx","./src/Components/ControlBar/RightMenu/Volume/VolumeWithData.tsx","./src/Components/ControlBar/RightMenu/Volume/index.tsx","./src/Components/ControlBar/RightMenu/Volume/styles.tsx","./src/Components/Extensions/Extensions.stories.tsx","./src/Components/Extensions/Extensions.tsx","./src/Components/Extensions/index.tsx","./src/Components/Files/Files.stories.tsx","./src/Components/Files/Files.test.tsx","./src/Components/Files/Files.tsx","./src/Components/Files/FilesWithData.tsx","./src/Components/Files/index.tsx","./src/Components/Files/mocks.tsx","./src/Components/Files/styles.tsx","./src/Components/Files/ContextMenu/ChildMenu.tsx","./src/Components/Files/ContextMenu/ContextMenu.stories.tsx","./src/Components/Files/ContextMenu/ContextMenu.test.tsx","./src/Components/Files/ContextMenu/ContextMenu.tsx","./src/Components/Files/ContextMenu/ContextMenuWithData.tsx","./src/Components/Files/ContextMenu/index.tsx","./src/Components/Files/ContextMenu/styles.tsx","./src/Components/Filter/Filter.test.tsx","./src/Components/Filter/Filter.tsx","./src/Components/Filter/index.tsx","./src/Components/Folder/Folder.stories.tsx","./src/Components/Folder/Folder.tsx","./src/Components/Folder/index.tsx","./src/Components/Icons/Add.tsx","./src/Components/Icons/AlbumCover.tsx","./src/Components/Icons/ArrowBack.tsx","./src/Components/Icons/Artist.tsx","./src/Components/Icons/Heart.tsx","./src/Components/Icons/HeartOutline.tsx","./src/Components/Icons/Next.tsx","./src/Components/Icons/Pause.tsx","./src/Components/Icons/Play.tsx","./src/Components/Icons/Previous.tsx","./src/Components/Icons/Repeat.tsx","./src/Components/Icons/Search.tsx","./src/Components/Icons/Shuffle.tsx","./src/Components/Icons/Speaker.tsx","./src/Components/Icons/Track.tsx","./src/Components/Likes/Likes.tsx","./src/Components/Likes/LikesState.ts","./src/Components/Likes/LikesWithData.tsx","./src/Components/Likes/index.tsx","./src/Components/Likes/styles.tsx","./src/Components/MainView/MainView.tsx","./src/Components/MainView/MainViewWithData.tsx","./src/Components/MainView/index.tsx","./src/Components/MainView/styles.tsx","./src/Components/PlaylistDetails/PlaylistDetails.stories.tsx","./src/Components/PlaylistDetails/PlaylistDetails.tsx","./src/Components/PlaylistDetails/index.tsx","./src/Components/Playlists/Playlists.stories.tsx","./src/Components/Playlists/Playlists.tsx","./src/Components/Playlists/index.tsx","./src/Components/Settings/SettingsState.ts","./src/Components/Sidebar/Sidebar.test.tsx","./src/Components/Sidebar/Sidebar.tsx","./src/Components/Sidebar/SidebarWithData.tsx","./src/Components/Sidebar/Stidebar.stories.tsx","./src/Components/Sidebar/index.tsx","./src/Components/Sidebar/styles.tsx","./src/Components/Table/Table.tsx","./src/Components/Table/index.tsx","./src/Components/Tracks/Tracks.stories.tsx","./src/Components/Tracks/Tracks.test.tsx","./src/Components/Tracks/Tracks.tsx","./src/Components/Tracks/TracksWithData.tsx","./src/Components/Tracks/index.tsx","./src/Components/Tracks/mocks.tsx","./src/Components/Tracks/styles.tsx","./src/Components/VirtualizedTable/VirtualizedTable.tsx","./src/Components/VirtualizedTable/index.tsx","./src/Containers/AlbumDetails/AlbumDetailsPage.tsx","./src/Containers/AlbumDetails/index.tsx","./src/Containers/Albums/AlbumsPage.tsx","./src/Containers/Albums/index.tsx","./src/Containers/ArtistDetails/ArtistDetailsPage.tsx","./src/Containers/ArtistDetails/index.tsx","./src/Containers/Artists/ArtistsPage.tsx","./src/Containers/Artists/index.tsx","./src/Containers/Extensions/ExtensionsPage.tsx","./src/Containers/Extensions/index.tsx","./src/Containers/Files/FilesPage.tsx","./src/Containers/Files/index.tsx","./src/Containers/Likes/LikesPage.tsx","./src/Containers/Likes/index.tsx","./src/Containers/Playlists/PlaylistsPage.tsx","./src/Containers/Playlists/index.tsx","./src/Containers/Tracks/TracksPage.tsx","./src/Containers/Tracks/index.tsx","./src/GraphQL/Browse/Query.ts","./src/GraphQL/Library/Mutation.ts","./src/GraphQL/Library/Query.ts","./src/GraphQL/Playback/Mutation.ts","./src/GraphQL/Playback/Query.ts","./src/GraphQL/Playback/Subscription.ts","./src/GraphQL/Playlist/Mutation.ts","./src/GraphQL/Playlist/Query.ts","./src/GraphQL/Playlist/Subscription.ts","./src/GraphQL/Settings/Query.tsx","./src/GraphQL/Sound/Mutation.tsx","./src/GraphQL/System/Query.ts","./src/Hooks/GraphQL.tsx","./src/Hooks/useFormat.tsx","./src/Hooks/usePlayQueue.tsx","./src/Hooks/useResumePlaylist.tsx","./src/Providers/GraphQLProvider.tsx","./src/Providers/ThemeProvider.tsx","./src/Providers/index.tsx","./src/Types/file.ts","./src/Types/playlist.ts","./src/Types/track.ts","./src/stories/Button.stories.ts","./src/stories/Button.tsx","./src/stories/Header.stories.ts","./src/stories/Header.tsx","./src/stories/Page.stories.ts","./src/stories/Page.tsx"],"version":"5.6.2"}