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 #44 from tsirysndr/feat/settings

webui: add settings page and handle settings changes

authored by

Tsiry Sandratraina and committed by
GitHub
ac499fc5 e3c25be0

+4201 -239
+39 -2
Cargo.lock
··· 475 475 476 476 [[package]] 477 477 name = "anyhow" 478 - version = "1.0.90" 478 + version = "1.0.91" 479 479 source = "registry+https://github.com/rust-lang/crates.io-index" 480 - checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" 480 + checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" 481 481 482 482 [[package]] 483 483 name = "arc-swap" ··· 5806 5806 "owo-colors 4.1.0", 5807 5807 "rockbox-library", 5808 5808 "rockbox-search", 5809 + "rockbox-settings", 5809 5810 "tokio", 5810 5811 ] 5811 5812 ··· 5933 5934 "rockbox-library", 5934 5935 "rockbox-rpc", 5935 5936 "rockbox-search", 5937 + "rockbox-settings", 5936 5938 "rockbox-sys", 5937 5939 "rockbox-types", 5938 5940 "serde", ··· 5940 5942 "sqlx", 5941 5943 "threadpool", 5942 5944 "tokio", 5945 + ] 5946 + 5947 + [[package]] 5948 + name = "rockbox-settings" 5949 + version = "0.1.0" 5950 + dependencies = [ 5951 + "anyhow", 5952 + "rockbox-sys", 5953 + "toml", 5943 5954 ] 5944 5955 5945 5956 [[package]] ··· 6394 6405 "itoa", 6395 6406 "memchr", 6396 6407 "ryu", 6408 + "serde", 6409 + ] 6410 + 6411 + [[package]] 6412 + name = "serde_spanned" 6413 + version = "0.6.8" 6414 + source = "registry+https://github.com/rust-lang/crates.io-index" 6415 + checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 6416 + dependencies = [ 6397 6417 "serde", 6398 6418 ] 6399 6419 ··· 7749 7769 ] 7750 7770 7751 7771 [[package]] 7772 + name = "toml" 7773 + version = "0.8.19" 7774 + source = "registry+https://github.com/rust-lang/crates.io-index" 7775 + checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 7776 + dependencies = [ 7777 + "serde", 7778 + "serde_spanned", 7779 + "toml_datetime", 7780 + "toml_edit", 7781 + ] 7782 + 7783 + [[package]] 7752 7784 name = "toml_datetime" 7753 7785 version = "0.6.8" 7754 7786 source = "registry+https://github.com/rust-lang/crates.io-index" 7755 7787 checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 7788 + dependencies = [ 7789 + "serde", 7790 + ] 7756 7791 7757 7792 [[package]] 7758 7793 name = "toml_edit" ··· 7761 7796 checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 7762 7797 dependencies = [ 7763 7798 "indexmap 2.5.0", 7799 + "serde", 7800 + "serde_spanned", 7764 7801 "toml_datetime", 7765 7802 "winnow", 7766 7803 ]
+49
apps/settings.c
··· 830 830 #endif 831 831 } 832 832 833 + void audio_settings_apply() 834 + { 835 + 836 + sound_settings_apply(); 837 + 838 + #ifdef HAVE_DISK_STORAGE 839 + audio_set_buffer_margin(global_settings.buffer_margin); 840 + #endif 841 + 842 + #ifdef HAVE_PLAY_FREQ 843 + /* before crossfade */ 844 + // audio_set_playback_frequency(global_settings.play_frequency); 845 + #endif 846 + #ifdef HAVE_CROSSFADE 847 + // audio_set_crossfade(global_settings.crossfade); 848 + #endif 849 + // replaygain_update(); 850 + dsp_set_crossfeed_type(global_settings.crossfeed); 851 + dsp_set_crossfeed_direct_gain(global_settings.crossfeed_direct_gain); 852 + dsp_set_crossfeed_cross_params(global_settings.crossfeed_cross_gain, 853 + global_settings.crossfeed_hf_attenuation, 854 + global_settings.crossfeed_hf_cutoff); 855 + 856 + /* Configure software equalizer, hardware eq is handled in audio_init() */ 857 + dsp_eq_enable(global_settings.eq_enabled); 858 + dsp_set_eq_precut(global_settings.eq_precut); 859 + for(int i = 0; i < EQ_NUM_BANDS; i++) { 860 + dsp_set_eq_coefs(i, &global_settings.eq_band_settings[i]); 861 + } 862 + 863 + dsp_dither_enable(global_settings.dithering_enabled); 864 + dsp_surround_set_balance(global_settings.surround_balance); 865 + dsp_surround_set_cutoff(global_settings.surround_fx1, global_settings.surround_fx2); 866 + dsp_surround_mix(global_settings.surround_mix); 867 + dsp_surround_enable(global_settings.surround_enabled); 868 + dsp_afr_enable(global_settings.afr_enabled); 869 + dsp_pbe_precut(global_settings.pbe_precut); 870 + dsp_pbe_enable(global_settings.pbe); 871 + #ifdef HAVE_PITCHCONTROL 872 + dsp_timestretch_enable(global_settings.timestretch_enabled); 873 + #endif 874 + dsp_set_compressor(&global_settings.compressor_settings); 875 + 876 + #ifdef HAVE_SPDIF_POWER 877 + spdif_power_enable(global_settings.spdif_enable); 878 + #endif 879 + 880 + } 881 + 833 882 void settings_apply(bool read_disk) 834 883 { 835 884 logf("%s", __func__);
+1
crates/cli/Cargo.toml
··· 12 12 owo-colors = "4.1.0" 13 13 rockbox-library = {path = "../library"} 14 14 rockbox-search = {path = "../search"} 15 + rockbox-settings = {path = "../settings"} 15 16 tokio = {version = "1.36.0", features = ["full"]}
+1 -1
crates/cli/src/lib.rs
··· 74 74 Ok(_) => false, 75 75 Err(_) => false, 76 76 }; 77 - let path = env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home)); 77 + let path = rockbox_settings::get_music_dir().unwrap_or(format!("{}/Music", home)); 78 78 let rt = tokio::runtime::Runtime::new().unwrap(); 79 79 rt.block_on(async { 80 80 let pool = create_connection_pool().await?;
+2 -1
crates/graphql/src/schema/mod.rs
··· 3 3 use library::{LibraryMutation, LibraryQuery}; 4 4 use playback::{PlaybackMutation, PlaybackQuery, PlaybackSubscription}; 5 5 use playlist::{PlaylistMutation, PlaylistQuery, PlaylistSubscription}; 6 - use settings::SettingsQuery; 6 + use settings::{SettingsMutation, SettingsQuery}; 7 7 use sound::{SoundMutation, SoundQuery}; 8 8 use system::SystemQuery; 9 9 ··· 34 34 PlaylistMutation, 35 35 SoundMutation, 36 36 LibraryMutation, 37 + SettingsMutation, 37 38 ); 38 39 39 40 #[derive(MergedSubscription, Default)]
+9 -2
crates/graphql/src/schema/objects/eq_band_setting.rs
··· 2 2 use rockbox_sys as rb; 3 3 use serde::{Deserialize, Serialize}; 4 4 5 + #[derive(Default, Clone, Serialize, Deserialize, InputObject)] 6 + pub struct EqBandSettingInput { 7 + pub cutoff: i32, 8 + pub q: i32, 9 + pub gain: i32, 10 + } 11 + 5 12 #[derive(Default, Clone, Serialize, Deserialize)] 6 13 pub struct EqBandSetting { 7 - pub cutoff: i32, // Hz 14 + pub cutoff: i32, 8 15 pub q: i32, 9 - pub gain: i32, // +/- dB 16 + pub gain: i32, 10 17 } 11 18 12 19 #[Object]
+1
crates/graphql/src/schema/objects/mod.rs
··· 4 4 pub mod compressor_settings; 5 5 pub mod entry; 6 6 pub mod eq_band_setting; 7 + pub mod new_global_settings; 7 8 pub mod playlist; 8 9 pub mod replaygain_settings; 9 10 pub mod search;
+35
crates/graphql/src/schema/objects/new_global_settings.rs
··· 1 + use async_graphql::*; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + use super::{eq_band_setting::EqBandSettingInput, replaygain_settings::ReplaygainSettingsInput}; 5 + 6 + #[derive(Default, Serialize, Deserialize, InputObject)] 7 + pub struct NewGlobalSettings { 8 + pub music_dir: Option<String>, 9 + pub playlist_shuffle: Option<bool>, 10 + pub repeat_mode: Option<i32>, 11 + pub bass: Option<i32>, 12 + pub treble: Option<i32>, 13 + pub bass_cutoff: Option<i32>, 14 + pub treble_cutoff: Option<i32>, 15 + pub crossfade: Option<i32>, 16 + pub fade_on_stop: Option<bool>, 17 + pub fade_in_delay: Option<i32>, 18 + pub fade_in_duration: Option<i32>, 19 + pub fade_out_delay: Option<i32>, 20 + pub fade_out_duration: Option<i32>, 21 + pub fade_out_mixmode: Option<i32>, 22 + pub balance: Option<i32>, 23 + pub stereo_width: Option<i32>, 24 + pub stereosw_mode: Option<i32>, 25 + pub surround_enabled: Option<bool>, 26 + pub surround_balance: Option<i32>, 27 + pub surround_fx1: Option<i32>, 28 + pub surround_fx2: Option<i32>, 29 + pub party_mode: Option<bool>, 30 + pub channel_config: Option<i32>, 31 + pub player_name: Option<String>, 32 + pub eq_enabled: Option<bool>, 33 + pub eq_band_settings: Option<Vec<EqBandSettingInput>>, 34 + pub replaygain_settings: Option<ReplaygainSettingsInput>, 35 + }
+8
crates/graphql/src/schema/objects/replaygain_settings.rs
··· 2 2 use rockbox_sys as rb; 3 3 use serde::{Deserialize, Serialize}; 4 4 5 + #[derive(Default, Clone, Serialize, Deserialize, InputObject)] 6 + pub struct ReplaygainSettingsInput { 7 + pub noclip: bool, // scale to prevent clips 8 + pub r#type: i32, // 0=track gain, 1=album gain, 2=track gain if shuffle is on, album gain otherwise, 4=off 9 + pub preamp: i32, // scale replaygained tracks by this 10 + } 11 + 12 + 5 13 #[derive(Default, Clone, Serialize, Deserialize)] 6 14 pub struct ReplaygainSettings { 7 15 pub noclip: bool, // scale to prevent clips
+9 -3
crates/graphql/src/schema/objects/user_settings.rs
··· 4 4 5 5 use super::{ 6 6 compressor_settings::CompressorSettings, eq_band_setting::EqBandSetting, 7 - replaygain_settings::ReplaygainSettings, settings_list::SettingsList, 7 + replaygain_settings::ReplaygainSettings, 8 8 }; 9 9 10 10 #[derive(Default, Clone, Serialize, Deserialize)] 11 11 pub struct UserSettings { 12 + pub music_dir: String, 12 13 pub volume: i32, 13 14 pub balance: i32, 14 15 pub bass: i32, ··· 248 249 pub surround_enabled: i32, 249 250 pub surround_balance: i32, 250 251 pub surround_fx1: i32, 251 - pub surround_fx2: bool, 252 + pub surround_fx2: i32, 252 253 pub surround_method2: bool, 253 254 pub surround_mix: i32, 254 255 ··· 263 264 264 265 #[Object] 265 266 impl UserSettings { 267 + async fn music_dir(&self) -> &str { 268 + &self.music_dir 269 + } 270 + 266 271 async fn volume(&self) -> i32 { 267 272 self.volume 268 273 } ··· 1019 1024 self.surround_fx1 1020 1025 } 1021 1026 1022 - async fn surround_fx2(&self) -> bool { 1027 + async fn surround_fx2(&self) -> i32 { 1023 1028 self.surround_fx2 1024 1029 } 1025 1030 ··· 1055 1060 impl From<rb::types::user_settings::UserSettings> for UserSettings { 1056 1061 fn from(settings: rb::types::user_settings::UserSettings) -> Self { 1057 1062 Self { 1063 + music_dir: settings.music_dir, 1058 1064 volume: settings.volume, 1059 1065 balance: settings.balance, 1060 1066 bass: settings.bass,
+16
crates/graphql/src/schema/settings.rs
··· 3 3 use crate::{rockbox_url, schema::objects::user_settings::UserSettings}; 4 4 use rockbox_sys as rb; 5 5 6 + use super::objects::new_global_settings::NewGlobalSettings; 7 + 6 8 #[derive(Default)] 7 9 pub struct SettingsQuery; 8 10 ··· 23 25 24 26 #[derive(Default)] 25 27 pub struct SettingsMutation; 28 + 29 + #[Object] 30 + impl SettingsMutation { 31 + async fn save_settings( 32 + &self, 33 + ctx: &Context<'_>, 34 + settings: NewGlobalSettings, 35 + ) -> Result<bool, Error> { 36 + let client = ctx.data::<reqwest::Client>().unwrap(); 37 + let url = format!("{}/settings", rockbox_url()); 38 + let response = client.put(url).json(&settings).send().await?; 39 + Ok(response.status().is_success()) 40 + } 41 + }
+1
crates/graphql/src/server.rs
··· 131 131 .route("/albums", web::get().to(index_spa)) 132 132 .route("/files", web::get().to(index_spa)) 133 133 .route("/likes", web::get().to(index_spa)) 134 + .route("/settings", web::get().to(index_spa)) 134 135 .route("/artists/{_:.*}", web::get().to(index_spa)) 135 136 .route("/albums/{_:.*}", web::get().to(index_spa)) 136 137 .route("/playlists/{_:.*}", web::get().to(index_spa))
+35 -1
crates/rpc/proto/rockbox/v1alpha1/settings.proto
··· 212 212 int32 surround_enabled = 173; 213 213 int32 surround_balance = 174; 214 214 int32 surround_fx1 = 175; 215 - bool surround_fx2 = 176; 215 + int32 surround_fx2 = 176; 216 216 bool surround_method2 = 177; 217 217 int32 surround_mix = 178; 218 218 int32 pbe = 179; ··· 220 220 int32 afr_enabled = 181; 221 221 int32 governor = 182; 222 222 int32 stereosw_mode = 183; 223 + string music_dir = 184; 223 224 } 224 225 226 + message SaveSettingsRequest { 227 + optional string music_dir = 1; 228 + optional bool playlist_shuffle = 2; 229 + optional int32 repeat_mode = 3; 230 + optional int32 bass = 4; 231 + optional int32 treble = 5; 232 + optional int32 bass_cutoff = 6; 233 + optional int32 treble_cutoff = 7; 234 + optional int32 crossfade = 8; 235 + optional bool fade_on_stop = 9; 236 + optional int32 fade_in_delay = 10; 237 + optional int32 fade_in_duration = 11; 238 + optional int32 fade_out_delay = 12; 239 + optional int32 fade_out_duration = 13; 240 + optional int32 fade_out_mixmode = 14; 241 + optional int32 balance = 15; 242 + optional int32 stereo_width = 16; 243 + optional int32 stereosw_mode = 17; 244 + optional int32 surround_enabled = 18; 245 + optional int32 surround_balance = 19; 246 + optional int32 surround_fx1 = 20; 247 + optional int32 surround_fx2 = 21; 248 + optional bool party_mode = 22; 249 + optional int32 channel_config = 23; 250 + optional string player_name = 24; 251 + optional bool eq_enabled = 25; 252 + repeated EqBandSetting eq_band_settings = 26; 253 + optional ReplaygainSettings replaygain_settings = 27; 254 + } 255 + 256 + message SaveSettingsResponse {} 257 + 225 258 service SettingsService { 226 259 rpc GetSettingsList(GetSettingsListRequest) returns (GetSettingsListResponse); 227 260 rpc GetGlobalSettings(GetGlobalSettingsRequest) returns (GetGlobalSettingsResponse); 261 + rpc SaveSettings(SaveSettingsRequest) returns (SaveSettingsResponse); 228 262 }
+142 -2
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 6194 6194 pub surround_balance: i32, 6195 6195 #[prost(int32, tag = "175")] 6196 6196 pub surround_fx1: i32, 6197 - #[prost(bool, tag = "176")] 6198 - pub surround_fx2: bool, 6197 + #[prost(int32, tag = "176")] 6198 + pub surround_fx2: i32, 6199 6199 #[prost(bool, tag = "177")] 6200 6200 pub surround_method2: bool, 6201 6201 #[prost(int32, tag = "178")] ··· 6210 6210 pub governor: i32, 6211 6211 #[prost(int32, tag = "183")] 6212 6212 pub stereosw_mode: i32, 6213 + #[prost(string, tag = "184")] 6214 + pub music_dir: ::prost::alloc::string::String, 6213 6215 } 6216 + #[derive(Clone, PartialEq, ::prost::Message)] 6217 + pub struct SaveSettingsRequest { 6218 + #[prost(string, optional, tag = "1")] 6219 + pub music_dir: ::core::option::Option<::prost::alloc::string::String>, 6220 + #[prost(bool, optional, tag = "2")] 6221 + pub playlist_shuffle: ::core::option::Option<bool>, 6222 + #[prost(int32, optional, tag = "3")] 6223 + pub repeat_mode: ::core::option::Option<i32>, 6224 + #[prost(int32, optional, tag = "4")] 6225 + pub bass: ::core::option::Option<i32>, 6226 + #[prost(int32, optional, tag = "5")] 6227 + pub treble: ::core::option::Option<i32>, 6228 + #[prost(int32, optional, tag = "6")] 6229 + pub bass_cutoff: ::core::option::Option<i32>, 6230 + #[prost(int32, optional, tag = "7")] 6231 + pub treble_cutoff: ::core::option::Option<i32>, 6232 + #[prost(int32, optional, tag = "8")] 6233 + pub crossfade: ::core::option::Option<i32>, 6234 + #[prost(bool, optional, tag = "9")] 6235 + pub fade_on_stop: ::core::option::Option<bool>, 6236 + #[prost(int32, optional, tag = "10")] 6237 + pub fade_in_delay: ::core::option::Option<i32>, 6238 + #[prost(int32, optional, tag = "11")] 6239 + pub fade_in_duration: ::core::option::Option<i32>, 6240 + #[prost(int32, optional, tag = "12")] 6241 + pub fade_out_delay: ::core::option::Option<i32>, 6242 + #[prost(int32, optional, tag = "13")] 6243 + pub fade_out_duration: ::core::option::Option<i32>, 6244 + #[prost(int32, optional, tag = "14")] 6245 + pub fade_out_mixmode: ::core::option::Option<i32>, 6246 + #[prost(int32, optional, tag = "15")] 6247 + pub balance: ::core::option::Option<i32>, 6248 + #[prost(int32, optional, tag = "16")] 6249 + pub stereo_width: ::core::option::Option<i32>, 6250 + #[prost(int32, optional, tag = "17")] 6251 + pub stereosw_mode: ::core::option::Option<i32>, 6252 + #[prost(int32, optional, tag = "18")] 6253 + pub surround_enabled: ::core::option::Option<i32>, 6254 + #[prost(int32, optional, tag = "19")] 6255 + pub surround_balance: ::core::option::Option<i32>, 6256 + #[prost(int32, optional, tag = "20")] 6257 + pub surround_fx1: ::core::option::Option<i32>, 6258 + #[prost(int32, optional, tag = "21")] 6259 + pub surround_fx2: ::core::option::Option<i32>, 6260 + #[prost(bool, optional, tag = "22")] 6261 + pub party_mode: ::core::option::Option<bool>, 6262 + #[prost(int32, optional, tag = "23")] 6263 + pub channel_config: ::core::option::Option<i32>, 6264 + #[prost(string, optional, tag = "24")] 6265 + pub player_name: ::core::option::Option<::prost::alloc::string::String>, 6266 + #[prost(bool, optional, tag = "25")] 6267 + pub eq_enabled: ::core::option::Option<bool>, 6268 + #[prost(message, repeated, tag = "26")] 6269 + pub eq_band_settings: ::prost::alloc::vec::Vec<EqBandSetting>, 6270 + #[prost(message, optional, tag = "27")] 6271 + pub replaygain_settings: ::core::option::Option<ReplaygainSettings>, 6272 + } 6273 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6274 + pub struct SaveSettingsResponse {} 6214 6275 /// Generated client implementations. 6215 6276 pub mod settings_service_client { 6216 6277 #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] ··· 6356 6417 ); 6357 6418 self.inner.unary(req, path, codec).await 6358 6419 } 6420 + pub async fn save_settings( 6421 + &mut self, 6422 + request: impl tonic::IntoRequest<super::SaveSettingsRequest>, 6423 + ) -> std::result::Result< 6424 + tonic::Response<super::SaveSettingsResponse>, 6425 + tonic::Status, 6426 + > { 6427 + self.inner 6428 + .ready() 6429 + .await 6430 + .map_err(|e| { 6431 + tonic::Status::new( 6432 + tonic::Code::Unknown, 6433 + format!("Service was not ready: {}", e.into()), 6434 + ) 6435 + })?; 6436 + let codec = tonic::codec::ProstCodec::default(); 6437 + let path = http::uri::PathAndQuery::from_static( 6438 + "/rockbox.v1alpha1.SettingsService/SaveSettings", 6439 + ); 6440 + let mut req = request.into_request(); 6441 + req.extensions_mut() 6442 + .insert( 6443 + GrpcMethod::new("rockbox.v1alpha1.SettingsService", "SaveSettings"), 6444 + ); 6445 + self.inner.unary(req, path, codec).await 6446 + } 6359 6447 } 6360 6448 } 6361 6449 /// Generated server implementations. ··· 6377 6465 request: tonic::Request<super::GetGlobalSettingsRequest>, 6378 6466 ) -> std::result::Result< 6379 6467 tonic::Response<super::GetGlobalSettingsResponse>, 6468 + tonic::Status, 6469 + >; 6470 + async fn save_settings( 6471 + &self, 6472 + request: tonic::Request<super::SaveSettingsRequest>, 6473 + ) -> std::result::Result< 6474 + tonic::Response<super::SaveSettingsResponse>, 6380 6475 tonic::Status, 6381 6476 >; 6382 6477 } ··· 6533 6628 let inner = self.inner.clone(); 6534 6629 let fut = async move { 6535 6630 let method = GetGlobalSettingsSvc(inner); 6631 + let codec = tonic::codec::ProstCodec::default(); 6632 + let mut grpc = tonic::server::Grpc::new(codec) 6633 + .apply_compression_config( 6634 + accept_compression_encodings, 6635 + send_compression_encodings, 6636 + ) 6637 + .apply_max_message_size_config( 6638 + max_decoding_message_size, 6639 + max_encoding_message_size, 6640 + ); 6641 + let res = grpc.unary(method, req).await; 6642 + Ok(res) 6643 + }; 6644 + Box::pin(fut) 6645 + } 6646 + "/rockbox.v1alpha1.SettingsService/SaveSettings" => { 6647 + #[allow(non_camel_case_types)] 6648 + struct SaveSettingsSvc<T: SettingsService>(pub Arc<T>); 6649 + impl< 6650 + T: SettingsService, 6651 + > tonic::server::UnaryService<super::SaveSettingsRequest> 6652 + for SaveSettingsSvc<T> { 6653 + type Response = super::SaveSettingsResponse; 6654 + type Future = BoxFuture< 6655 + tonic::Response<Self::Response>, 6656 + tonic::Status, 6657 + >; 6658 + fn call( 6659 + &mut self, 6660 + request: tonic::Request<super::SaveSettingsRequest>, 6661 + ) -> Self::Future { 6662 + let inner = Arc::clone(&self.0); 6663 + let fut = async move { 6664 + <T as SettingsService>::save_settings(&inner, request).await 6665 + }; 6666 + Box::pin(fut) 6667 + } 6668 + } 6669 + let accept_compression_encodings = self.accept_compression_encodings; 6670 + let send_compression_encodings = self.send_compression_encodings; 6671 + let max_decoding_message_size = self.max_decoding_message_size; 6672 + let max_encoding_message_size = self.max_encoding_message_size; 6673 + let inner = self.inner.clone(); 6674 + let fut = async move { 6675 + let method = SaveSettingsSvc(inner); 6536 6676 let codec = tonic::codec::ProstCodec::default(); 6537 6677 let mut grpc = tonic::server::Grpc::new(codec) 6538 6678 .apply_compression_config(
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+56 -2
crates/rpc/src/lib.rs
··· 24 24 use rockbox_sys::types::{ 25 25 mp3_entry::Mp3Entry, 26 26 system_status::SystemStatus, 27 - user_settings::{CompressorSettings, EqBandSetting, ReplaygainSettings, UserSettings}, 27 + user_settings::{ 28 + CompressorSettings, EqBandSetting, NewGlobalSettings, ReplaygainSettings, 29 + UserSettings, 30 + }, 28 31 }; 29 32 use tantivy::schema::Schema; 30 33 use tantivy::schema::SchemaBuilder; ··· 32 35 use tantivy::TantivyDocument; 33 36 use v1alpha1::{ 34 37 Album, Artist, CurrentTrackResponse, Entry, GetGlobalSettingsResponse, 35 - GetGlobalStatusResponse, NextTrackResponse, SearchResponse, Track, 38 + GetGlobalStatusResponse, NextTrackResponse, SaveSettingsRequest, SearchResponse, Track, 36 39 }; 37 40 38 41 #[path = "rockbox.v1alpha1.rs"] ··· 437 440 let afr_enabled = settings.afr_enabled; 438 441 let governor = settings.governor; 439 442 let stereosw_mode = settings.stereosw_mode; 443 + let music_dir = settings.music_dir; 440 444 441 445 GetGlobalSettingsResponse { 446 + music_dir, 442 447 volume, 443 448 balance, 444 449 bass, ··· 1096 1101 genre_id, 1097 1102 created_at, 1098 1103 updated_at, 1104 + } 1105 + } 1106 + } 1107 + 1108 + impl Into<NewGlobalSettings> for SaveSettingsRequest { 1109 + fn into(self) -> NewGlobalSettings { 1110 + NewGlobalSettings { 1111 + music_dir: self.music_dir, 1112 + playlist_shuffle: self.playlist_shuffle, 1113 + repeat_mode: self.repeat_mode, 1114 + bass: self.bass, 1115 + treble: self.treble, 1116 + bass_cutoff: self.bass_cutoff, 1117 + treble_cutoff: self.treble_cutoff, 1118 + crossfade: self.crossfade, 1119 + fade_on_stop: self.fade_on_stop, 1120 + fade_in_delay: self.fade_in_delay, 1121 + fade_in_duration: self.fade_in_duration, 1122 + fade_out_delay: self.fade_out_delay, 1123 + fade_out_duration: self.fade_out_duration, 1124 + fade_out_mixmode: self.fade_out_mixmode, 1125 + balance: self.balance, 1126 + stereo_width: self.stereo_width, 1127 + stereosw_mode: self.stereosw_mode, 1128 + surround_enabled: self.surround_enabled, 1129 + surround_balance: self.surround_balance, 1130 + surround_fx1: self.surround_fx1, 1131 + surround_fx2: self.surround_fx2, 1132 + party_mode: self.party_mode, 1133 + channel_config: self.channel_config, 1134 + player_name: self.player_name, 1135 + eq_enabled: self.eq_enabled, 1136 + eq_band_settings: Some( 1137 + self.eq_band_settings 1138 + .into_iter() 1139 + .map(|band| EqBandSetting { 1140 + cutoff: band.cutoff, 1141 + q: band.q, 1142 + gain: band.gain, 1143 + }) 1144 + .collect(), 1145 + ), 1146 + replaygain_settings: self.replaygain_settings.map(|settings| { 1147 + ReplaygainSettings { 1148 + noclip: settings.noclip, 1149 + r#type: settings.r#type, 1150 + preamp: settings.preamp, 1151 + } 1152 + }), 1099 1153 } 1100 1154 } 1101 1155 }
-1
crates/rpc/src/library.rs
··· 1 1 use rockbox_library::{entity::favourites::Favourites, repo}; 2 2 use rockbox_search::search_entities; 3 - use rockbox_types::SearchResults; 4 3 use sqlx::Sqlite; 5 4 6 5 use crate::{
+19 -1
crates/rpc/src/settings.rs
··· 1 - use rockbox_sys::types::user_settings::UserSettings; 1 + use rockbox_sys::types::user_settings::{NewGlobalSettings, UserSettings}; 2 2 use tonic::{Request, Response, Status}; 3 3 4 4 use crate::{ 5 5 api::rockbox::v1alpha1::{ 6 6 settings_service_server::SettingsService, GetGlobalSettingsRequest, 7 7 GetGlobalSettingsResponse, GetSettingsListRequest, GetSettingsListResponse, 8 + SaveSettingsRequest, SaveSettingsResponse, 8 9 }, 9 10 rockbox_url, 10 11 }; ··· 53 54 .map_err(|e| Status::internal(e.to_string()))?; 54 55 //let settings = response.json::<UserSettings>().await?; 55 56 todo!() 57 + } 58 + 59 + async fn save_settings( 60 + &self, 61 + request: Request<SaveSettingsRequest>, 62 + ) -> Result<Response<SaveSettingsResponse>, Status> { 63 + let settings = request.into_inner(); 64 + let settings: NewGlobalSettings = settings.into(); 65 + 66 + let url = format!("{}/settings", rockbox_url()); 67 + self.client 68 + .put(url) 69 + .json(&settings) 70 + .send() 71 + .await 72 + .map_err(|e| Status::internal(e.to_string()))?; 73 + Ok(Response::new(SaveSettingsResponse::default())) 56 74 } 57 75 }
+1
crates/server/Cargo.toml
··· 17 17 rockbox-library = {path = "../library"} 18 18 rockbox-rpc = {path = "../rpc"} 19 19 rockbox-search = {path = "../search"} 20 + rockbox-settings = {path = "../settings"} 20 21 rockbox-sys = {path = "../sys"} 21 22 rockbox-types = {path = "../types"} 22 23 serde = {version = "1.0.210", features = ["derive"]}
+1 -1
crates/server/src/handlers/browse.rs
··· 32 32 } 33 33 34 34 let mut fs_cache = ctx.fs_cache.lock().await; 35 - if let Some(entries) = fs_cache.get(path.into()) { 35 + if let Some(entries) = fs_cache.get(&path.to_string()) { 36 36 update_cache(ctx, path, show_hidden); 37 37 res.json(entries); 38 38 return Ok(());
+1
crates/server/src/handlers/mod.rs
··· 63 63 async_handler!(system, get_status); 64 64 async_handler!(system, scan_library); 65 65 async_handler!(settings, get_global_settings); 66 + async_handler!(settings, update_global_settings); 66 67 async_handler!(docs, get_openapi); 67 68 async_handler!(docs, index); 68 69 async_handler!(search, search);
+1 -1
crates/server/src/handlers/playlists.rs
··· 19 19 return Ok(()); 20 20 } 21 21 let body = req.body.as_ref().unwrap(); 22 - let new_playslist: NewPlaylist = serde_json::from_str(&body).unwrap(); 22 + let new_playslist: NewPlaylist = serde_json::from_str(body).unwrap(); 23 23 24 24 if new_playslist.tracks.is_empty() { 25 25 return Ok(());
+14
crates/server/src/handlers/settings.rs
··· 1 1 use crate::http::{Context, Request, Response}; 2 2 use anyhow::Error; 3 3 use rockbox_sys as rb; 4 + use rockbox_sys::types::user_settings::NewGlobalSettings; 4 5 5 6 pub async fn get_global_settings( 6 7 _ctx: &Context, ··· 11 12 res.json(&settings); 12 13 Ok(()) 13 14 } 15 + 16 + pub async fn update_global_settings( 17 + _ctx: &Context, 18 + req: &Request, 19 + res: &mut Response, 20 + ) -> Result<(), Error> { 21 + let body = req.body.as_ref().unwrap(); 22 + let settings: NewGlobalSettings = serde_json::from_str(body)?; 23 + rockbox_settings::load_settings(Some(settings))?; 24 + rockbox_settings::write_settings()?; 25 + res.set_status(204); 26 + Ok(()) 27 + }
+1 -1
crates/server/src/handlers/system.rs
··· 27 27 28 28 pub async fn scan_library(ctx: &Context, _req: &Request, res: &mut Response) -> Result<(), Error> { 29 29 let home = env::var("HOME")?; 30 - let path = env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home)); 30 + let path = rockbox_settings::get_music_dir().unwrap_or(format!("{}/Music", home)); 31 31 scan_audio_files(ctx.pool.clone(), path.into()).await?; 32 32 let tracks = repo::track::all(ctx.pool.clone()).await?; 33 33 let albums = repo::album::all(ctx.pool.clone()).await?;
+12 -1
crates/server/src/lib.rs
··· 33 33 34 34 #[no_mangle] 35 35 pub extern "C" fn start_server() { 36 + match rockbox_settings::load_settings(None) { 37 + Ok(_) => {} 38 + Err(e) => { 39 + println!("Warning loading settings: {}", e); 40 + } 41 + } 42 + 36 43 let mut app = RockboxHttpServer::new(); 37 44 38 45 app.get("/albums", get_albums); ··· 77 84 app.get("/version", get_rockbox_version); 78 85 app.get("/status", get_status); 79 86 app.get("/settings", get_global_settings); 87 + app.put("/settings", update_global_settings); 80 88 app.put("/scan-library", scan_library); 81 89 app.get("/search", search); 82 90 ··· 194 202 195 203 #[no_mangle] 196 204 pub extern "C" fn start_broker() { 197 - let rt = tokio::runtime::Runtime::new().unwrap(); 205 + let rt = tokio::runtime::Builder::new_current_thread() 206 + .enable_all() 207 + .build() 208 + .unwrap(); 198 209 let pool = rt 199 210 .block_on(rockbox_library::create_connection_pool()) 200 211 .unwrap();
+9
crates/settings/Cargo.toml
··· 1 + [package] 2 + edition = "2021" 3 + name = "rockbox-settings" 4 + version = "0.1.0" 5 + 6 + [dependencies] 7 + anyhow = "1.0.91" 8 + rockbox-sys = {path = "../sys"} 9 + toml = "0.8.19"
+63
crates/settings/src/lib.rs
··· 1 + use anyhow::Error; 2 + use rockbox_sys::{self as rb, types::user_settings::NewGlobalSettings}; 3 + 4 + pub fn load_settings(settings: Option<NewGlobalSettings>) -> Result<(), Error> { 5 + let settings: NewGlobalSettings = match settings { 6 + Some(settings) => settings, 7 + None => { 8 + let home = std::env::var("HOME")?; 9 + let path = format!("{}/.config/rockbox.org/settings.toml", home); 10 + let content = std::fs::read_to_string(&path)?; 11 + toml::from_str(&content)? 12 + } 13 + }; 14 + 15 + if let Some(music_dir) = settings.clone().music_dir { 16 + if let Ok(_) = std::fs::metadata(&music_dir) { 17 + std::env::set_var( 18 + "ROCKBOX_LIBRARY", 19 + music_dir.replace("$HOME", &std::env::var("HOME")?), 20 + ); 21 + } 22 + } 23 + 24 + rb::settings::save_settings(settings.clone()); 25 + 26 + rb::settings::apply_audio_settings(); 27 + 28 + let enabled = unsafe { rb::global_settings.eq_enabled }; 29 + rb::sound::pcmbuf_set_low_latency(true); 30 + rb::sound::dsp::eq_enable(enabled); 31 + rb::sound::pcmbuf_set_low_latency(false); 32 + 33 + Ok(()) 34 + } 35 + 36 + pub fn write_settings() -> Result<(), Error> { 37 + let settings = rb::settings::get_global_settings(); 38 + let mut settings: NewGlobalSettings = settings.into(); 39 + let home = std::env::var("HOME")?; 40 + 41 + settings.music_dir = 42 + Some(std::env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home))); 43 + 44 + let content = toml::to_string(&settings)?; 45 + 46 + let path = format!("{}/.config/rockbox.org/settings.toml", home); 47 + std::fs::write(&path, content)?; 48 + Ok(()) 49 + } 50 + 51 + pub fn get_music_dir() -> Result<String, Error> { 52 + let home = std::env::var("HOME")?; 53 + let path = format!("{}/.config/rockbox.org/settings.toml", home); 54 + 55 + if let Err(_) = std::fs::metadata(&path) { 56 + return Ok(std::env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home))); 57 + } 58 + 59 + let content = std::fs::read_to_string(&path)?; 60 + let settings: NewGlobalSettings = toml::from_str(&content)?; 61 + let music_dir = std::env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home)); 62 + Ok(settings.music_dir.unwrap_or(music_dir)) 63 + }
+1 -1
crates/sys/Cargo.toml
··· 5 5 6 6 [dependencies] 7 7 anyhow = "1.0.89" 8 - serde = { version = "1.0.210", features = ["derive"] } 8 + serde = {version = "1.0.210", features = ["derive"]}
+87 -60
crates/sys/src/lib.rs
··· 84 84 }; 85 85 } 86 86 87 + #[macro_export] 88 + macro_rules! set_value_setting { 89 + ($setting:expr, $global_setting:expr) => { 90 + if let Some(value) = $setting { 91 + $global_setting = value; 92 + } 93 + }; 94 + } 95 + 96 + #[macro_export] 97 + macro_rules! set_str_setting { 98 + ($setting:expr, $global_setting:expr, $len:expr) => { 99 + if let Some(player_name) = $setting { 100 + let mut array = [0u8; $len]; // Initialize a fixed-size array with zeros 101 + let bytes = player_name.as_bytes(); // Convert the String to bytes 102 + 103 + let copy_len = bytes.len().min($len); 104 + array[..copy_len].copy_from_slice(&bytes[..copy_len]); 105 + $global_setting = array; 106 + } 107 + }; 108 + } 109 + 87 110 #[repr(C)] 88 111 #[derive(Debug, Copy, Clone)] 89 112 pub struct Mp3Entry { ··· 749 772 pub crossfeed_hf_cutoff: c_uint, 750 773 751 774 // EQ 752 - pub eq_enabled: c_uchar, 775 + pub eq_enabled: bool, 753 776 pub eq_precut: c_uint, 754 777 pub eq_band_settings: [EqBandSetting; EQ_NUM_BANDS], 755 778 ··· 757 780 pub beep: c_int, 758 781 pub keyclick: c_int, 759 782 pub keyclick_repeats: c_int, 760 - pub dithering_enabled: c_uchar, 761 - pub timestretch_enabled: c_uchar, 783 + pub dithering_enabled: bool, 784 + pub timestretch_enabled: bool, 762 785 763 786 // Misc options 764 787 pub list_accel_start_delay: c_int, ··· 769 792 770 793 pub pause_rewind: c_int, 771 794 pub unplug_mode: c_int, 772 - pub unplug_autoresume: c_uchar, 795 + pub unplug_autoresume: bool, 773 796 774 797 pub qs_items: [*const SettingsList; QUICKSCREEN_ITEM_COUNT], 775 798 ··· 780 803 pub dirfilter: c_int, 781 804 pub show_filename_ext: c_int, 782 805 pub default_codepage: c_int, 783 - pub hold_lr_for_scroll_in_list: c_uchar, 784 - pub play_selected: c_uchar, 806 + pub hold_lr_for_scroll_in_list: bool, 807 + pub play_selected: bool, 785 808 pub single_mode: c_int, 786 - pub party_mode: c_uchar, 787 - pub cuesheet: c_uchar, 788 - pub car_adapter_mode: c_uchar, 809 + pub party_mode: bool, 810 + pub cuesheet: bool, 811 + pub car_adapter_mode: bool, 789 812 pub car_adapter_mode_delay: c_int, 790 813 pub start_in_screen: c_int, 791 814 pub ff_rewind_min_step: c_int, ··· 794 817 pub peak_meter_release: c_int, 795 818 pub peak_meter_hold: c_int, 796 819 pub peak_meter_clip_hold: c_int, 797 - pub peak_meter_dbfs: c_uchar, 820 + pub peak_meter_dbfs: bool, 798 821 pub peak_meter_min: c_int, 799 822 pub peak_meter_max: c_int, 800 823 ··· 807 830 pub max_files_in_playlist: c_int, 808 831 pub volume_type: c_int, 809 832 pub battery_display: c_int, 810 - pub show_icons: c_uchar, 833 + pub show_icons: bool, 811 834 pub statusbar: c_int, 812 835 813 836 pub scrollbar: c_int, ··· 817 840 pub list_separator_height: c_int, 818 841 pub list_separator_color: c_int, 819 842 820 - pub browse_current: c_uchar, 821 - pub scroll_paginated: c_uchar, 822 - pub list_wraparound: c_uchar, 843 + pub browse_current: bool, 844 + pub scroll_paginated: bool, 845 + pub list_wraparound: bool, 823 846 pub list_order: c_int, 824 847 pub scroll_speed: c_int, 825 848 pub bidir_limit: c_int, ··· 828 851 829 852 pub autoloadbookmark: c_int, 830 853 pub autocreatebookmark: c_int, 831 - pub autoupdatebookmark: c_uchar, 854 + pub autoupdatebookmark: bool, 832 855 pub usemrb: c_int, 833 856 834 - pub dircache: c_uchar, 857 + pub dircache: bool, 835 858 pub tagcache_ram: c_int, 836 - pub tagcache_autoupdate: c_uchar, 837 - pub autoresume_enable: c_uchar, 859 + pub tagcache_autoupdate: bool, 860 + pub autoresume_enable: bool, 838 861 pub autoresume_automatic: c_int, 839 862 pub autoresume_paths: [c_uchar; MAX_PATHNAME + 1], 840 - pub runtimedb: c_uchar, 863 + pub runtimedb: bool, 841 864 pub tagcache_scan_paths: [c_uchar; MAX_PATHNAME + 1], 842 865 pub tagcache_db_path: [c_uchar; MAX_PATHNAME + 1], 843 866 pub backdrop_file: [c_uchar; MAX_PATHNAME + 1], ··· 853 876 854 877 pub repeat_mode: c_int, 855 878 pub next_folder: c_int, 856 - pub constrain_next_folder: c_uchar, 879 + pub constrain_next_folder: bool, 857 880 pub recursive_dir_insert: c_int, 858 - pub fade_on_stop: c_uchar, 859 - pub playlist_shuffle: c_uchar, 860 - pub warnon_erase_dynplaylist: c_uchar, 861 - pub keep_current_track_on_replace_playlist: c_uchar, 862 - pub show_shuffled_adding_options: c_uchar, 881 + pub fade_on_stop: bool, 882 + pub playlist_shuffle: bool, 883 + pub warnon_erase_dynplaylist: bool, 884 + pub keep_current_track_on_replace_playlist: bool, 885 + pub show_shuffled_adding_options: bool, 863 886 pub show_queue_options: c_int, 864 887 pub album_art: c_int, 865 - pub rewind_across_tracks: c_uchar, 888 + pub rewind_across_tracks: bool, 866 889 867 - pub playlist_viewer_icons: c_uchar, 868 - pub playlist_viewer_indices: c_uchar, 890 + pub playlist_viewer_icons: bool, 891 + pub playlist_viewer_indices: bool, 869 892 pub playlist_viewer_track_display: c_int, 870 893 871 - pub talk_menu: c_uchar, 894 + pub talk_menu: bool, 872 895 pub talk_dir: c_int, 873 - pub talk_dir_clip: c_uchar, 896 + pub talk_dir_clip: bool, 874 897 pub talk_file: c_int, 875 - pub talk_file_clip: c_uchar, 876 - pub talk_filetype: c_uchar, 877 - pub talk_battery_level: c_uchar, 898 + pub talk_file_clip: bool, 899 + pub talk_filetype: bool, 900 + pub talk_battery_level: bool, 878 901 pub talk_mixer_amp: c_int, 879 902 880 - pub sort_case: c_uchar, 903 + pub sort_case: bool, 881 904 pub sort_dir: c_int, 882 905 pub sort_file: c_int, 883 906 pub interpret_numbers: c_int, ··· 885 908 pub poweroff: c_int, 886 909 pub battery_capacity: c_int, 887 910 pub battery_type: c_int, 888 - pub spdif_enable: c_uchar, 911 + pub spdif_enable: bool, 889 912 pub usb_charging: c_int, 890 913 891 914 pub contrast: c_int, 892 - pub invert: c_uchar, 893 - pub flip_display: c_uchar, 915 + pub invert: bool, 916 + pub flip_display: bool, 894 917 pub cursor_style: c_int, 895 918 pub screen_scroll_step: c_int, 896 919 pub show_path_in_browser: c_int, 897 - pub offset_out_of_view: c_uchar, 898 - pub disable_mainmenu_scrolling: c_uchar, 920 + pub offset_out_of_view: bool, 921 + pub disable_mainmenu_scrolling: bool, 899 922 pub icon_file: [c_uchar; MAX_FILENAME + 1], 900 923 pub viewers_icon_file: [c_uchar; MAX_FILENAME + 1], 901 924 pub font_file: [c_uchar; MAX_FILENAME + 1], 902 925 pub glyphs_to_cache: c_int, 903 926 pub kbd_file: [c_uchar; MAX_FILENAME + 1], 904 927 pub backlight_timeout: c_int, 905 - pub caption_backlight: c_uchar, 906 - pub bl_filter_first_keypress: c_uchar, 928 + pub caption_backlight: bool, 929 + pub bl_filter_first_keypress: bool, 907 930 pub backlight_timeout_plugged: c_int, 908 - pub bt_selective_softlock_actions: c_uchar, 931 + pub bt_selective_softlock_actions: bool, 909 932 pub bt_selective_softlock_actions_mask: c_int, 910 - pub bl_selective_actions: c_uchar, 933 + pub bl_selective_actions: bool, 911 934 pub bl_selective_actions_mask: c_int, 912 935 pub backlight_on_button_hold: c_int, 913 936 pub lcd_sleep_after_backlight_off: c_int, 914 937 pub brightness: c_int, 915 938 916 939 pub speaker_mode: c_int, 917 - pub prevent_skip: c_uchar, 940 + pub prevent_skip: bool, 918 941 919 942 pub touch_mode: c_int, 920 943 pub ts_calibration_data: TouchscreenParameter, 921 944 922 - pub pitch_mode_semitone: c_uchar, 923 - pub pitch_mode_timestretch: c_uchar, 945 + pub pitch_mode_semitone: bool, 946 + pub pitch_mode_timestretch: bool, 924 947 925 - pub usb_hid: c_uchar, 948 + pub usb_hid: bool, 926 949 pub usb_keypad_mode: c_int, 927 950 928 - pub usb_skip_first_drive: c_uchar, 951 + pub usb_skip_first_drive: bool, 929 952 930 953 pub ui_vp_config: [c_uchar; 64], 931 954 pub player_name: [c_uchar; 64], ··· 933 956 pub compressor_settings: CompressorSettings, 934 957 935 958 pub sleeptimer_duration: c_int, 936 - pub sleeptimer_on_startup: c_uchar, 937 - pub keypress_restarts_sleeptimer: c_uchar, 959 + pub sleeptimer_on_startup: bool, 960 + pub keypress_restarts_sleeptimer: bool, 938 961 939 - pub show_shutdown_message: c_uchar, 962 + pub show_shutdown_message: bool, 940 963 941 964 pub hotkey_wps: c_int, 942 965 pub hotkey_tree: c_int, ··· 949 972 950 973 pub power_mode: c_int, 951 974 952 - pub keyclick_hardware: c_uchar, 975 + pub keyclick_hardware: bool, 953 976 954 977 pub start_directory: [c_uchar; MAX_PATHNAME + 1], 955 - pub root_menu_customized: c_uchar, 956 - pub shortcuts_replaces_qs: c_uchar, 978 + pub root_menu_customized: bool, 979 + pub shortcuts_replaces_qs: bool, 957 980 958 981 pub play_frequency: c_int, 959 982 pub volume_limit: c_int, ··· 964 987 pub surround_enabled: c_int, 965 988 pub surround_balance: c_int, 966 989 pub surround_fx1: c_int, 967 - pub surround_fx2: c_uchar, 968 - pub surround_method2: c_uchar, 990 + pub surround_fx2: c_int, 991 + pub surround_method2: bool, 969 992 pub surround_mix: c_int, 970 993 971 994 pub pbe: c_int, ··· 981 1004 #[repr(C)] 982 1005 #[derive(Debug, Copy, Clone)] 983 1006 pub struct ReplaygainSettings { 984 - pub noclip: c_uchar, // scale to prevent clips 1007 + pub noclip: bool, // scale to prevent clips 985 1008 pub r#type: c_int, // 0=track gain, 1=album gain, 2=track gain if shuffle is on, album gain otherwise, 4=off 986 1009 pub preamp: c_int, // scale replaygained tracks by this 987 1010 } ··· 989 1012 #[repr(C)] 990 1013 #[derive(Debug, Copy, Clone)] 991 1014 pub struct EqBandSetting { 992 - pub cutoff: c_int, // Hz 1015 + pub cutoff: c_int, 993 1016 pub q: c_int, 994 - pub gain: c_int, // +/- dB 1017 + pub gain: c_int, 995 1018 } 996 1019 997 1020 #[repr(C)] ··· 1051 1074 } 1052 1075 1053 1076 extern "C" { 1054 - pub static global_settings: UserSettings; 1077 + pub static mut global_settings: UserSettings; 1055 1078 pub static global_status: SystemStatus; 1056 1079 pub static language_strings: *mut *mut c_char; 1057 1080 pub static core_bitmaps: CbmpBitmapInfoEntry; ··· 1160 1183 fn pcmbuf_set_low_latency(state: c_uchar); 1161 1184 fn system_sound_play(sound: SystemSound); 1162 1185 fn keyclick_click(rawbutton: c_uchar, action: c_int); 1186 + fn audio_set_crossfade(crossfade: c_int); 1163 1187 1164 1188 // Browsing 1165 1189 fn rockbox_browse_at(path: *const c_char) -> c_int; ··· 1290 1314 fn get_settings_list(count: *mut c_int) -> SettingsList; 1291 1315 fn find_setting(variable: *const c_void) -> SettingsList; 1292 1316 fn settings_save() -> c_int; 1317 + fn settings_apply(read_disk: c_uchar); 1318 + fn audio_settings_apply(); 1293 1319 fn option_screen( 1294 1320 setting: *mut SettingsList, 1295 1321 parent: [Viewport; NB_SCREENS], ··· 1333 1359 get_talk_id: Option<extern "C" fn(c_int, c_int) -> c_int>, 1334 1360 ); 1335 1361 fn set_bool(string: *const c_char, variable: *const c_uchar) -> c_uchar; 1362 + fn rb_get_crossfade_mode() -> i32; 1336 1363 1337 1364 // Misc 1338 1365 fn codec_load_file();
+112 -2
crates/sys/src/settings.rs
··· 1 1 use std::ffi::{c_char, c_int, c_uchar, c_void, CString}; 2 2 3 - use crate::{types::user_settings::UserSettings, OptItems, SettingsList, Viewport, NB_SCREENS}; 3 + use crate::{ 4 + set_str_setting, set_value_setting, 5 + types::user_settings::{NewGlobalSettings, UserSettings}, 6 + EqBandSetting, OptItems, SettingsList, Viewport, EQ_NUM_BANDS, NB_SCREENS, 7 + }; 4 8 5 9 pub fn get_global_settings() -> UserSettings { 6 - unsafe { crate::global_settings }.into() 10 + unsafe { 11 + crate::rb_get_crossfade_mode(); 12 + crate::global_settings 13 + } 14 + .into() 7 15 } 8 16 9 17 pub fn get_settings_list(mut count: i32) -> SettingsList { ··· 122 130 let ret = unsafe { crate::set_bool(string.as_ptr(), variable) }; 123 131 ret != 0 124 132 } 133 + 134 + pub fn get_crossfade_mode() -> i32 { 135 + unsafe { crate::rb_get_crossfade_mode() } 136 + } 137 + 138 + pub fn save_settings(settings: NewGlobalSettings) { 139 + unsafe { 140 + set_value_setting!( 141 + settings.playlist_shuffle, 142 + crate::global_settings.playlist_shuffle 143 + ); 144 + set_value_setting!(settings.repeat_mode, crate::global_settings.repeat_mode); 145 + set_value_setting!(settings.bass, crate::global_settings.bass); 146 + set_value_setting!(settings.treble, crate::global_settings.treble); 147 + set_value_setting!(settings.bass_cutoff, crate::global_settings.bass_cutoff); 148 + set_value_setting!(settings.treble_cutoff, crate::global_settings.treble_cutoff); 149 + 150 + set_value_setting!(settings.crossfade, crate::global_settings.crossfade); 151 + crate::sound::audio_set_crossfade(crate::global_settings.crossfade); 152 + 153 + set_value_setting!(settings.fade_on_stop, crate::global_settings.fade_on_stop); 154 + 155 + set_value_setting!( 156 + settings.fade_in_delay, 157 + crate::global_settings.crossfade_fade_in_delay 158 + ); 159 + set_value_setting!( 160 + settings.fade_in_duration, 161 + crate::global_settings.crossfade_fade_in_duration 162 + ); 163 + 164 + set_value_setting!( 165 + settings.fade_out_delay, 166 + crate::global_settings.crossfade_fade_out_delay 167 + ); 168 + crate::sound::audio_set_crossfade(crate::global_settings.crossfade); 169 + 170 + set_value_setting!( 171 + settings.fade_out_duration, 172 + crate::global_settings.crossfade_fade_out_duration 173 + ); 174 + 175 + crate::sound::audio_set_crossfade(crate::global_settings.crossfade); 176 + 177 + set_value_setting!( 178 + settings.fade_out_mixmode, 179 + crate::global_settings.crossfade_fade_out_mixmode 180 + ); 181 + set_value_setting!(settings.balance, crate::global_settings.balance); 182 + set_value_setting!(settings.stereo_width, crate::global_settings.stereo_width); 183 + set_value_setting!(settings.stereosw_mode, crate::global_settings.stereosw_mode); 184 + 185 + set_value_setting!( 186 + settings.surround_enabled, 187 + crate::global_settings.surround_enabled 188 + ); 189 + set_value_setting!( 190 + settings.surround_balance, 191 + crate::global_settings.surround_balance 192 + ); 193 + set_value_setting!(settings.surround_fx1, crate::global_settings.surround_fx1); 194 + set_value_setting!(settings.surround_fx2, crate::global_settings.surround_fx2); 195 + set_value_setting!(settings.party_mode, crate::global_settings.party_mode); 196 + set_value_setting!( 197 + settings.channel_config, 198 + crate::global_settings.channel_config 199 + ); 200 + set_str_setting!(settings.player_name, crate::global_settings.player_name, 64); 201 + set_value_setting!(settings.eq_enabled, crate::global_settings.eq_enabled); 202 + 203 + if let Some(eq_band_settings) = settings.eq_band_settings { 204 + let mut array = [EqBandSetting { 205 + cutoff: 0, 206 + gain: 0, 207 + q: 0, 208 + }; EQ_NUM_BANDS]; 209 + 210 + for (i, eq_band_setting) in eq_band_settings.into_iter().enumerate() { 211 + array[i] = eq_band_setting.into(); 212 + } 213 + 214 + crate::global_settings.eq_band_settings = array; 215 + } 216 + 217 + if let Some(replaygain_settings) = settings.replaygain_settings { 218 + crate::global_settings.replaygain_settings = replaygain_settings.into(); 219 + } 220 + } 221 + } 222 + 223 + pub fn apply_settings(read_disk: bool) { 224 + unsafe { 225 + let read_disk = if read_disk { 1 } else { 0 }; 226 + crate::settings_apply(read_disk); 227 + } 228 + } 229 + 230 + pub fn apply_audio_settings() { 231 + unsafe { 232 + crate::audio_settings_apply(); 233 + } 234 + }
+4
crates/sys/src/sound/mod.rs
··· 74 74 let rawbutton = if rawbutton { 1 } else { 0 }; 75 75 unsafe { crate::keyclick_click(rawbutton, action) } 76 76 } 77 + 78 + pub fn audio_set_crossfade(crossfade: i32) { 79 + unsafe { crate::audio_set_crossfade(crossfade) } 80 + }
+152 -63
crates/sys/src/types/user_settings.rs
··· 3 3 use crate::cast_ptr; 4 4 use serde::{Deserialize, Serialize}; 5 5 6 - #[derive(Serialize, Deserialize)] 6 + #[derive(Default, Debug, Clone, Serialize, Deserialize)] 7 7 pub struct ReplaygainSettings { 8 8 pub noclip: bool, // scale to prevent clips 9 9 pub r#type: i32, // 0=track gain, 1=album gain, 2=track gain if shuffle is on, album gain otherwise, 4=off ··· 12 12 13 13 impl From<crate::ReplaygainSettings> for ReplaygainSettings { 14 14 fn from(settings: crate::ReplaygainSettings) -> Self { 15 - let noclip = if settings.noclip == 1 { true } else { false }; 15 + let noclip = settings.noclip; 16 16 Self { 17 17 noclip, 18 18 r#type: settings.r#type, ··· 21 21 } 22 22 } 23 23 24 - #[derive(Serialize, Deserialize)] 24 + impl Into<crate::ReplaygainSettings> for ReplaygainSettings { 25 + fn into(self) -> crate::ReplaygainSettings { 26 + crate::ReplaygainSettings { 27 + noclip: self.noclip, 28 + r#type: self.r#type, 29 + preamp: self.preamp, 30 + } 31 + } 32 + } 33 + 34 + #[derive(Default, Debug, Clone, Serialize, Deserialize)] 25 35 pub struct EqBandSetting { 26 - pub cutoff: i32, // Hz 36 + pub cutoff: i32, 27 37 pub q: i32, 28 - pub gain: i32, // +/- dB 38 + pub gain: i32, 29 39 } 30 40 31 41 impl From<crate::EqBandSetting> for EqBandSetting { ··· 34 44 cutoff: setting.cutoff, 35 45 q: setting.q, 36 46 gain: setting.gain, 47 + } 48 + } 49 + } 50 + 51 + impl Into<crate::EqBandSetting> for EqBandSetting { 52 + fn into(self) -> crate::EqBandSetting { 53 + crate::EqBandSetting { 54 + cutoff: self.cutoff, 55 + q: self.q, 56 + gain: self.gain, 37 57 } 38 58 } 39 59 } ··· 88 108 89 109 #[derive(Serialize, Deserialize)] 90 110 pub struct UserSettings { 111 + pub music_dir: String, 112 + 91 113 // Audio settings 92 114 pub volume: i32, 93 115 pub balance: i32, ··· 329 351 pub surround_enabled: i32, 330 352 pub surround_balance: i32, 331 353 pub surround_fx1: i32, 332 - pub surround_fx2: bool, 354 + pub surround_fx2: i32, 333 355 pub surround_method2: bool, 334 356 pub surround_mix: i32, 335 357 ··· 344 366 345 367 impl From<crate::UserSettings> for UserSettings { 346 368 fn from(settings: crate::UserSettings) -> Self { 369 + let home = std::env::var("HOME").unwrap(); 347 370 Self { 371 + music_dir: std::env::var("ROCKBOX_LIBRARY") 372 + .unwrap_or_else(|_| format!("{}/Music", home)), 348 373 volume: settings.volume, 349 374 balance: settings.balance, 350 375 bass: settings.bass, ··· 365 390 crossfeed_cross_gain: settings.crossfeed_cross_gain as u32, 366 391 crossfeed_hf_attenuation: settings.crossfeed_hf_attenuation as u32, 367 392 crossfeed_hf_cutoff: settings.crossfeed_hf_cutoff as u32, 368 - eq_enabled: settings.eq_enabled != 0, 393 + eq_enabled: settings.eq_enabled, 369 394 eq_precut: settings.eq_precut as u32, 370 395 eq_band_settings: settings 371 396 .eq_band_settings ··· 375 400 beep: settings.beep, 376 401 keyclick: settings.keyclick, 377 402 keyclick_repeats: settings.keyclick_repeats, 378 - dithering_enabled: settings.dithering_enabled != 0, 379 - timestretch_enabled: settings.timestretch_enabled != 0, 403 + dithering_enabled: settings.dithering_enabled, 404 + timestretch_enabled: settings.timestretch_enabled, 380 405 list_accel_start_delay: settings.list_accel_start_delay, 381 406 list_accel_wait: settings.list_accel_wait, 382 407 touchpad_sensitivity: settings.touchpad_sensitivity, 383 408 touchpad_deadzone: settings.touchpad_deadzone, 384 409 pause_rewind: settings.pause_rewind, 385 410 unplug_mode: settings.unplug_mode, 386 - unplug_autoresume: settings.unplug_autoresume != 0, 411 + unplug_autoresume: settings.unplug_autoresume, 387 412 timeformat: settings.timeformat, 388 413 disk_spindown: settings.disk_spindown, 389 414 buffer_margin: settings.buffer_margin, 390 415 dirfilter: settings.dirfilter, 391 416 show_filename_ext: settings.show_filename_ext, 392 417 default_codepage: settings.default_codepage, 393 - hold_lr_for_scroll_in_list: settings.hold_lr_for_scroll_in_list != 0, 394 - play_selected: settings.play_selected != 0, 418 + hold_lr_for_scroll_in_list: settings.hold_lr_for_scroll_in_list, 419 + play_selected: settings.play_selected, 395 420 single_mode: settings.single_mode, 396 - party_mode: settings.party_mode != 0, 397 - cuesheet: settings.cuesheet != 0, 398 - car_adapter_mode: settings.car_adapter_mode != 0, 421 + party_mode: settings.party_mode, 422 + cuesheet: settings.cuesheet, 423 + car_adapter_mode: settings.car_adapter_mode, 399 424 car_adapter_mode_delay: settings.car_adapter_mode_delay, 400 425 start_in_screen: settings.start_in_screen, 401 426 ff_rewind_min_step: settings.ff_rewind_min_step, ··· 403 428 peak_meter_release: settings.peak_meter_release, 404 429 peak_meter_hold: settings.peak_meter_hold, 405 430 peak_meter_clip_hold: settings.peak_meter_clip_hold, 406 - peak_meter_dbfs: settings.peak_meter_dbfs != 0, 431 + peak_meter_dbfs: settings.peak_meter_dbfs, 407 432 peak_meter_min: settings.peak_meter_min, 408 433 peak_meter_max: settings.peak_meter_max, 409 434 wps_file: unsafe { ··· 431 456 max_files_in_playlist: settings.max_files_in_playlist, 432 457 volume_type: settings.volume_type, 433 458 battery_display: settings.battery_display, 434 - show_icons: settings.show_icons != 0, 459 + show_icons: settings.show_icons, 435 460 statusbar: settings.statusbar, 436 461 scrollbar: settings.scrollbar, 437 462 scrollbar_width: settings.scrollbar_width, 438 463 list_line_padding: settings.list_line_padding, 439 464 list_separator_height: settings.list_separator_height, 440 465 list_separator_color: settings.list_separator_color, 441 - browse_current: settings.browse_current != 0, 442 - scroll_paginated: settings.scroll_paginated != 0, 443 - list_wraparound: settings.list_wraparound != 0, 466 + browse_current: settings.browse_current, 467 + scroll_paginated: settings.scroll_paginated, 468 + list_wraparound: settings.list_wraparound, 444 469 list_order: settings.list_order, 445 470 scroll_speed: settings.scroll_speed, 446 471 bidir_limit: settings.bidir_limit, ··· 448 473 scroll_step: settings.scroll_step, 449 474 autoloadbookmark: settings.autoloadbookmark, 450 475 autocreatebookmark: settings.autocreatebookmark, 451 - autoupdatebookmark: settings.autoupdatebookmark != 0, 476 + autoupdatebookmark: settings.autoupdatebookmark, 452 477 usemrb: settings.usemrb, 453 - dircache: settings.dircache != 0, 478 + dircache: settings.dircache, 454 479 tagcache_ram: settings.tagcache_ram, 455 - tagcache_autoupdate: settings.tagcache_autoupdate != 0, 456 - autoresume_enable: settings.autoresume_enable != 0, 480 + tagcache_autoupdate: settings.tagcache_autoupdate, 481 + autoresume_enable: settings.autoresume_enable, 457 482 autoresume_automatic: settings.autoresume_automatic, 458 483 autoresume_paths: unsafe { 459 484 CStr::from_ptr(cast_ptr!(settings.autoresume_paths.as_ptr())) 460 485 .to_string_lossy() 461 486 .into_owned() 462 487 }, 463 - runtimedb: settings.runtimedb != 0, 488 + runtimedb: settings.runtimedb, 464 489 tagcache_scan_paths: unsafe { 465 490 CStr::from_ptr(cast_ptr!(settings.tagcache_scan_paths.as_ptr())) 466 491 .to_string_lossy() ··· 489 514 browser_default: settings.browser_default, 490 515 repeat_mode: settings.repeat_mode, 491 516 next_folder: settings.next_folder, 492 - constrain_next_folder: settings.constrain_next_folder != 0, 517 + constrain_next_folder: settings.constrain_next_folder, 493 518 recursive_dir_insert: settings.recursive_dir_insert, 494 - fade_on_stop: settings.fade_on_stop != 0, 495 - playlist_shuffle: settings.playlist_shuffle != 0, 496 - warnon_erase_dynplaylist: settings.warnon_erase_dynplaylist != 0, 497 - keep_current_track_on_replace_playlist: settings.keep_current_track_on_replace_playlist 498 - != 0, 499 - show_shuffled_adding_options: settings.show_shuffled_adding_options != 0, 519 + fade_on_stop: settings.fade_on_stop, 520 + playlist_shuffle: settings.playlist_shuffle, 521 + warnon_erase_dynplaylist: settings.warnon_erase_dynplaylist, 522 + keep_current_track_on_replace_playlist: settings.keep_current_track_on_replace_playlist, 523 + show_shuffled_adding_options: settings.show_shuffled_adding_options, 500 524 show_queue_options: settings.show_queue_options, 501 525 album_art: settings.album_art, 502 - rewind_across_tracks: settings.rewind_across_tracks != 0, 503 - playlist_viewer_icons: settings.playlist_viewer_icons != 0, 504 - playlist_viewer_indices: settings.playlist_viewer_indices != 0, 526 + rewind_across_tracks: settings.rewind_across_tracks, 527 + playlist_viewer_icons: settings.playlist_viewer_icons, 528 + playlist_viewer_indices: settings.playlist_viewer_indices, 505 529 playlist_viewer_track_display: settings.playlist_viewer_track_display, 506 - talk_menu: settings.talk_menu != 0, 530 + talk_menu: settings.talk_menu, 507 531 talk_dir: settings.talk_dir, 508 - talk_dir_clip: settings.talk_dir_clip != 0, 532 + talk_dir_clip: settings.talk_dir_clip, 509 533 talk_file: settings.talk_file, 510 - talk_file_clip: settings.talk_file_clip != 0, 511 - talk_filetype: settings.talk_filetype != 0, 512 - talk_battery_level: settings.talk_battery_level != 0, 534 + talk_file_clip: settings.talk_file_clip, 535 + talk_filetype: settings.talk_filetype, 536 + talk_battery_level: settings.talk_battery_level, 513 537 talk_mixer_amp: settings.talk_mixer_amp, 514 - sort_case: settings.sort_case != 0, 538 + sort_case: settings.sort_case, 515 539 sort_dir: settings.sort_dir, 516 540 sort_file: settings.sort_file, 517 541 interpret_numbers: settings.interpret_numbers, 518 542 poweroff: settings.poweroff, 519 543 battery_capacity: settings.battery_capacity, 520 544 battery_type: settings.battery_type, 521 - spdif_enable: settings.spdif_enable != 0, 545 + spdif_enable: settings.spdif_enable, 522 546 usb_charging: settings.usb_charging, 523 547 contrast: settings.contrast, 524 - invert: settings.invert != 0, 525 - flip_display: settings.flip_display != 0, 548 + invert: settings.invert, 549 + flip_display: settings.flip_display, 526 550 cursor_style: settings.cursor_style, 527 551 screen_scroll_step: settings.screen_scroll_step, 528 552 show_path_in_browser: settings.show_path_in_browser, 529 - offset_out_of_view: settings.offset_out_of_view != 0, 530 - disable_mainmenu_scrolling: settings.disable_mainmenu_scrolling != 0, 553 + offset_out_of_view: settings.offset_out_of_view, 554 + disable_mainmenu_scrolling: settings.disable_mainmenu_scrolling, 531 555 icon_file: unsafe { 532 556 CStr::from_ptr(cast_ptr!(settings.icon_file.as_ptr())) 533 557 .to_string_lossy() ··· 550 574 .into_owned() 551 575 }, 552 576 backlight_timeout: settings.backlight_timeout, 553 - caption_backlight: settings.caption_backlight != 0, 554 - bl_filter_first_keypress: settings.bl_filter_first_keypress != 0, 577 + caption_backlight: settings.caption_backlight, 578 + bl_filter_first_keypress: settings.bl_filter_first_keypress, 555 579 backlight_timeout_plugged: settings.backlight_timeout_plugged, 556 - bt_selective_softlock_actions: settings.bt_selective_softlock_actions != 0, 580 + bt_selective_softlock_actions: settings.bt_selective_softlock_actions, 557 581 bt_selective_softlock_actions_mask: settings.bt_selective_softlock_actions_mask, 558 - bl_selective_actions: settings.bl_selective_actions != 0, 582 + bl_selective_actions: settings.bl_selective_actions, 559 583 bl_selective_actions_mask: settings.bl_selective_actions_mask, 560 584 backlight_on_button_hold: settings.backlight_on_button_hold, 561 585 lcd_sleep_after_backlight_off: settings.lcd_sleep_after_backlight_off, 562 586 brightness: settings.brightness, 563 587 speaker_mode: settings.speaker_mode, 564 - prevent_skip: settings.prevent_skip != 0, 588 + prevent_skip: settings.prevent_skip, 565 589 touch_mode: settings.touch_mode, 566 590 ts_calibration_data: TouchscreenParameter::from(settings.ts_calibration_data), 567 - pitch_mode_semitone: settings.pitch_mode_semitone != 0, 568 - pitch_mode_timestretch: settings.pitch_mode_timestretch != 0, 569 - usb_hid: settings.usb_hid != 0, 591 + pitch_mode_semitone: settings.pitch_mode_semitone, 592 + pitch_mode_timestretch: settings.pitch_mode_timestretch, 593 + usb_hid: settings.usb_hid, 570 594 usb_keypad_mode: settings.usb_keypad_mode, 571 - usb_skip_first_drive: settings.usb_skip_first_drive != 0, 595 + usb_skip_first_drive: settings.usb_skip_first_drive, 572 596 player_name: unsafe { 573 597 CStr::from_ptr(cast_ptr!(settings.player_name.as_ptr())) 574 598 .to_string_lossy() ··· 576 600 }, 577 601 compressor_settings: CompressorSettings::from(settings.compressor_settings), 578 602 sleeptimer_duration: settings.sleeptimer_duration, 579 - sleeptimer_on_startup: settings.sleeptimer_on_startup != 0, 580 - keypress_restarts_sleeptimer: settings.keypress_restarts_sleeptimer != 0, 581 - show_shutdown_message: settings.show_shutdown_message != 0, 603 + sleeptimer_on_startup: settings.sleeptimer_on_startup, 604 + keypress_restarts_sleeptimer: settings.keypress_restarts_sleeptimer, 605 + show_shutdown_message: settings.show_shutdown_message, 582 606 hotkey_wps: settings.hotkey_wps, 583 607 hotkey_tree: settings.hotkey_tree, 584 608 resume_rewind: settings.resume_rewind, 585 609 depth_3d: settings.depth_3d, 586 610 roll_off: settings.roll_off, 587 611 power_mode: settings.power_mode, 588 - keyclick_hardware: settings.keyclick_hardware != 0, 612 + keyclick_hardware: settings.keyclick_hardware, 589 613 start_directory: unsafe { 590 614 CStr::from_ptr(cast_ptr!(settings.start_directory.as_ptr())) 591 615 .to_string_lossy() 592 616 .into_owned() 593 617 }, 594 - root_menu_customized: settings.root_menu_customized != 0, 595 - shortcuts_replaces_qs: settings.shortcuts_replaces_qs != 0, 618 + root_menu_customized: settings.root_menu_customized, 619 + shortcuts_replaces_qs: settings.shortcuts_replaces_qs, 596 620 play_frequency: settings.play_frequency, 597 621 volume_limit: settings.volume_limit, 598 622 volume_adjust_mode: settings.volume_adjust_mode, ··· 600 624 surround_enabled: settings.surround_enabled, 601 625 surround_balance: settings.surround_balance, 602 626 surround_fx1: settings.surround_fx1, 603 - surround_fx2: settings.surround_fx2 != 0, 604 - surround_method2: settings.surround_method2 != 0, 627 + surround_fx2: settings.surround_fx2, 628 + surround_method2: settings.surround_method2, 605 629 surround_mix: settings.surround_mix, 606 630 pbe: settings.pbe, 607 631 pbe_precut: settings.pbe_precut, ··· 611 635 } 612 636 } 613 637 } 638 + 639 + #[derive(Default, Debug, Clone, Serialize, Deserialize)] 640 + pub struct NewGlobalSettings { 641 + pub music_dir: Option<String>, 642 + pub playlist_shuffle: Option<bool>, 643 + pub repeat_mode: Option<i32>, 644 + pub bass: Option<i32>, 645 + pub treble: Option<i32>, 646 + pub bass_cutoff: Option<i32>, 647 + pub treble_cutoff: Option<i32>, 648 + pub crossfade: Option<i32>, 649 + pub fade_on_stop: Option<bool>, 650 + pub fade_in_delay: Option<i32>, 651 + pub fade_in_duration: Option<i32>, 652 + pub fade_out_delay: Option<i32>, 653 + pub fade_out_duration: Option<i32>, 654 + pub fade_out_mixmode: Option<i32>, 655 + pub balance: Option<i32>, 656 + pub stereo_width: Option<i32>, 657 + pub stereosw_mode: Option<i32>, 658 + pub surround_enabled: Option<i32>, 659 + pub surround_balance: Option<i32>, 660 + pub surround_fx1: Option<i32>, 661 + pub surround_fx2: Option<i32>, 662 + pub party_mode: Option<bool>, 663 + pub channel_config: Option<i32>, 664 + pub player_name: Option<String>, 665 + pub eq_enabled: Option<bool>, 666 + pub eq_band_settings: Option<Vec<EqBandSetting>>, 667 + pub replaygain_settings: Option<ReplaygainSettings>, 668 + } 669 + 670 + impl From<UserSettings> for NewGlobalSettings { 671 + fn from(settings: UserSettings) -> Self { 672 + Self { 673 + music_dir: None, 674 + playlist_shuffle: Some(settings.playlist_shuffle), 675 + repeat_mode: Some(settings.repeat_mode), 676 + bass: Some(settings.bass), 677 + treble: Some(settings.treble), 678 + bass_cutoff: Some(settings.bass_cutoff), 679 + treble_cutoff: Some(settings.treble_cutoff), 680 + crossfade: Some(settings.crossfade), 681 + fade_on_stop: Some(settings.fade_on_stop), 682 + fade_in_delay: Some(settings.crossfade_fade_in_delay), 683 + fade_in_duration: Some(settings.crossfade_fade_in_duration), 684 + fade_out_delay: Some(settings.crossfade_fade_out_delay), 685 + fade_out_duration: Some(settings.crossfade_fade_out_duration), 686 + fade_out_mixmode: Some(settings.crossfade_fade_out_mixmode), 687 + balance: Some(settings.balance), 688 + stereo_width: Some(settings.stereo_width), 689 + stereosw_mode: Some(settings.stereosw_mode), 690 + surround_enabled: Some(settings.surround_enabled), 691 + surround_balance: Some(settings.surround_balance), 692 + surround_fx1: Some(settings.surround_fx1), 693 + surround_fx2: Some(settings.surround_fx2), 694 + party_mode: Some(settings.party_mode), 695 + channel_config: Some(settings.channel_config), 696 + player_name: Some(settings.player_name), 697 + eq_enabled: Some(settings.eq_enabled), 698 + eq_band_settings: Some(settings.eq_band_settings), 699 + replaygain_settings: Some(settings.replaygain_settings), 700 + } 701 + } 702 + }
+15
crates/types/src/lib.rs
··· 43 43 pub liked_albums: Vec<LikedAlbum>, 44 44 pub files: Vec<File>, 45 45 } 46 + 47 + #[derive(Default, Serialize, Deserialize)] 48 + pub struct EqBandSetting { 49 + pub cutoff: i32, 50 + pub q: i32, 51 + pub gain: i32, 52 + } 53 + 54 + #[derive(Default, Serialize, Deserialize)] 55 + pub struct ReplaygainSettings { 56 + pub enabled: bool, 57 + pub preamp: i32, 58 + pub peak: i32, 59 + pub clip: i32, 60 + }
+5
src/main.zig
··· 2 2 const playlist = @import("rockbox/playlist.zig"); 3 3 const metadata = @import("rockbox/metadata.zig"); 4 4 const tree = @import("rockbox/tree.zig"); 5 + const settings = @import("rockbox/settings.zig"); 5 6 6 7 extern fn main_c() c_int; 7 8 extern fn parse_args(argc: usize, argv: [*]const [*]const u8) c_int; ··· 102 103 export fn rb_max_playlist_size() c_int { 103 104 return playlist.max_playlist_size(); 104 105 } 106 + 107 + export fn rb_get_crossfade_mode() c_int { 108 + return settings.get_crossfade_mode(); 109 + }
+361
src/rockbox/settings.zig
··· 1 + pub const std = @import("std"); 2 + 3 + pub const EQ_NUM_BANDS = 10; 4 + pub const QUICKSCREEN_ITEM_COUNT = 4; 5 + pub const MAX_FILENAME = 32; 6 + pub const MAX_PATHNAME = 256; 7 + 8 + pub const ReplaygainSettings = extern struct { 9 + noclip: bool, 10 + type: c_int, 11 + preamp: c_int, 12 + }; 13 + 14 + pub const EqBandSetting = extern struct { 15 + cutoff: c_int, 16 + gain: c_int, 17 + q: c_int, 18 + }; 19 + 20 + pub const StorageType = extern union { int_val: c_int }; 21 + 22 + pub const SoundSetting = extern struct { setting: c_int }; 23 + 24 + pub const BoolSetting = extern struct { lang_yes: c_int, lang_no: c_int }; 25 + 26 + pub const FilenameSetting = extern struct { 27 + prefix: [*]const u8, 28 + suffix: [*]const u8, 29 + max_len: c_int, 30 + }; 31 + 32 + pub const IntSetting = extern struct { 33 + unit: c_int, 34 + step: c_int, 35 + min: c_int, 36 + max: c_int, 37 + }; 38 + 39 + pub const ChoiceSettingData = extern struct { 40 + desc: [*]const [*]const u8, 41 + talks: [*]const c_int, 42 + }; 43 + 44 + pub const ChoiceSetting = extern struct { 45 + count: c_int, 46 + data: ChoiceSettingData, 47 + }; 48 + 49 + pub const TableSetting = extern struct { 50 + unit: c_int, 51 + count: c_int, 52 + values: [*]const c_int, 53 + }; 54 + 55 + pub const CustomSetting = extern struct { 56 + unit: c_int, 57 + count: c_int, 58 + values: [*]const c_int, 59 + }; 60 + 61 + pub const SettingsTypeUnion = extern union { 62 + sound_setting: SoundSetting, 63 + bool_setting: BoolSetting, 64 + filename_setting: FilenameSetting, 65 + int_setting: IntSetting, 66 + choice_setting: ChoiceSetting, 67 + table_setting: TableSetting, 68 + custom_setting: CustomSetting, 69 + }; 70 + 71 + pub const SettingsList = extern struct { 72 + flags: c_uint, 73 + lang_id: c_int, 74 + default_val: StorageType, 75 + cfg_name: [*]const u8, 76 + cfg_vals: [*]const u8, 77 + settings_type: SettingsTypeUnion, 78 + }; 79 + 80 + pub const TouchscreenParameter = extern struct { 81 + A: c_int, 82 + B: c_int, 83 + C: c_int, 84 + D: c_int, 85 + E: c_int, 86 + F: c_int, 87 + divider: c_int, 88 + }; 89 + 90 + pub const CompressorSettings = extern struct { 91 + threshold: c_int, 92 + makeup_gain: c_int, 93 + ratio: c_int, 94 + knee: c_int, 95 + release_time: c_int, 96 + attack_time: c_int, 97 + }; 98 + 99 + pub const UserSettings = extern struct { 100 + // Audio settings 101 + volume: c_int, 102 + balance: c_int, 103 + bass: c_int, 104 + treble: c_int, 105 + channel_config: c_int, 106 + stereo_width: c_int, 107 + 108 + bass_cutoff: c_int, 109 + treble_cutoff: c_int, 110 + 111 + crossfade: c_int, 112 + crossfade_fade_in_delay: c_int, 113 + crossfade_fade_out_delay: c_int, 114 + crossfade_fade_in_duration: c_int, 115 + crossfade_fade_out_duration: c_int, 116 + crossfade_fade_out_mixmode: c_int, 117 + 118 + // Replaygain 119 + replaygain_settings: ReplaygainSettings, 120 + 121 + // Crossfeed 122 + crossfeed: c_int, 123 + crossfeed_direct_gain: c_uint, 124 + crossfeed_cross_gain: c_uint, 125 + crossfeed_hf_attenuation: c_uint, 126 + crossfeed_hf_cutoff: c_uint, 127 + 128 + // EQ 129 + eq_enabled: u8, 130 + eq_precut: c_uint, 131 + eq_band_settings: [EQ_NUM_BANDS]EqBandSetting, 132 + 133 + // Misc. swcodec 134 + beep: c_int, 135 + keyclick: c_int, 136 + keyclick_repeats: c_int, 137 + dithering_enabled: u8, 138 + timestretch_enabled: u8, 139 + 140 + // Misc options 141 + list_accel_start_delay: c_int, 142 + list_accel_wait: c_int, 143 + 144 + touchpad_sensitivity: c_int, 145 + touchpad_deadzone: c_int, 146 + 147 + pause_rewind: c_int, 148 + unplug_mode: c_int, 149 + unplug_autoresume: u8, 150 + 151 + qs_items: [QUICKSCREEN_ITEM_COUNT]SettingsList, 152 + 153 + timeformat: c_int, 154 + disk_spindown: c_int, 155 + buffer_margin: c_int, 156 + 157 + dirfilter: c_int, 158 + show_filename_ext: c_int, 159 + default_codepage: c_int, 160 + hold_lr_for_scroll_in_list: u8, 161 + play_selected: u8, 162 + single_mode: c_int, 163 + party_mode: u8, 164 + cuesheet: u8, 165 + car_adapter_mode: u8, 166 + car_adapter_mode_delay: c_int, 167 + start_in_screen: c_int, 168 + ff_rewind_min_step: c_int, 169 + ff_rewind_accel: c_int, 170 + 171 + peak_meter_release: c_int, 172 + peak_meter_hold: c_int, 173 + peak_meter_clip_hold: c_int, 174 + peak_meter_dbfs: u8, 175 + peak_meter_min: c_int, 176 + peak_meter_max: c_int, 177 + 178 + wps_file: [MAX_FILENAME + 1]u8, 179 + sbs_file: [MAX_FILENAME + 1]u8, 180 + lang_file: [MAX_FILENAME + 1]u8, 181 + playlist_catalog_dir: [MAX_PATHNAME + 1]u8, 182 + skip_length: c_int, 183 + max_files_in_dir: c_int, 184 + max_files_in_playlist: c_int, 185 + volume_type: c_int, 186 + battery_display: c_int, 187 + show_icons: u8, 188 + statusbar: c_int, 189 + 190 + scrollbar: c_int, 191 + scrollbar_width: c_int, 192 + 193 + list_line_padding: c_int, 194 + list_separator_height: c_int, 195 + list_separator_color: c_int, 196 + 197 + browse_current: u8, 198 + scroll_paginated: u8, 199 + list_wraparound: u8, 200 + list_order: c_int, 201 + scroll_speed: c_int, 202 + bidir_limit: c_int, 203 + scroll_delay: c_int, 204 + scroll_step: c_int, 205 + 206 + autoloadbookmark: c_int, 207 + autocreatebookmark: c_int, 208 + autoupdatebookmark: u8, 209 + usemrb: c_int, 210 + 211 + dircache: u8, 212 + tagcache_ram: c_int, 213 + tagcache_autoupdate: u8, 214 + autoresume_enable: u8, 215 + autoresume_automatic: c_int, 216 + autoresume_paths: [MAX_PATHNAME + 1]u8, 217 + runtimedb: u8, 218 + tagcache_scan_paths: [MAX_PATHNAME + 1]u8, 219 + tagcache_db_path: [MAX_PATHNAME + 1]u8, 220 + backdrop_file: [MAX_PATHNAME + 1]u8, 221 + 222 + bg_color: c_int, 223 + fg_color: c_int, 224 + lss_color: c_int, 225 + lse_color: c_int, 226 + lst_color: c_int, 227 + colors_file: [MAX_FILENAME + 1]u8, 228 + 229 + browser_default: c_int, 230 + 231 + repeat_mode: c_int, 232 + next_folder: c_int, 233 + constrain_next_folder: u8, 234 + recursive_dir_insert: c_int, 235 + fade_on_stop: u8, 236 + playlist_shuffle: u8, 237 + warnon_erase_dynplaylist: u8, 238 + keep_current_track_on_replace_playlist: u8, 239 + show_shuffled_adding_options: u8, 240 + show_queue_options: c_int, 241 + album_art: c_int, 242 + rewind_across_tracks: u8, 243 + 244 + playlist_viewer_icons: u8, 245 + playlist_viewer_indices: u8, 246 + playlist_viewer_track_display: c_int, 247 + 248 + talk_menu: u8, 249 + talk_dir: c_int, 250 + talk_dir_clip: u8, 251 + talk_file: c_int, 252 + talk_file_clip: u8, 253 + talk_filetype: u8, 254 + talk_battery_level: u8, 255 + talk_mixer_amp: c_int, 256 + 257 + sort_case: u8, 258 + sort_dir: c_int, 259 + sort_file: c_int, 260 + interpret_numbers: c_int, 261 + 262 + poweroff: c_int, 263 + battery_capacity: c_int, 264 + battery_type: c_int, 265 + spdif_enable: u8, 266 + usb_charging: c_int, 267 + 268 + contrast: c_int, 269 + invert: u8, 270 + flip_display: u8, 271 + cursor_style: c_int, 272 + screen_scroll_step: c_int, 273 + show_path_in_browser: c_int, 274 + offset_out_of_view: u8, 275 + disable_mainmenu_scrolling: u8, 276 + icon_file: [MAX_FILENAME + 1]u8, 277 + viewers_icon_file: [MAX_FILENAME + 1]u8, 278 + font_file: [MAX_FILENAME + 1]u8, 279 + glyphs_to_cache: c_int, 280 + kbd_file: [MAX_FILENAME + 1]u8, 281 + backlight_timeout: c_int, 282 + caption_backlight: u8, 283 + bl_filter_first_keypress: u8, 284 + backlight_timeout_plugged: c_int, 285 + bt_selective_softlock_actions: u8, 286 + bt_selective_softlock_actions_mask: c_int, 287 + bl_selective_actions: u8, 288 + bl_selective_actions_mask: c_int, 289 + backlight_on_button_hold: c_int, 290 + lcd_sleep_after_backlight_off: c_int, 291 + brightness: c_int, 292 + 293 + speaker_mode: c_int, 294 + prevent_skip: u8, 295 + 296 + touch_mode: c_int, 297 + ts_calibration_data: TouchscreenParameter, 298 + 299 + pitch_mode_semitone: u8, 300 + pitch_mode_timestretch: u8, 301 + 302 + usb_hid: u8, 303 + usb_keypad_mode: c_int, 304 + 305 + usb_skip_first_drive: u8, 306 + 307 + ui_vp_config: [64]u8, 308 + player_name: [64]u8, 309 + 310 + compressor_settings: CompressorSettings, 311 + 312 + sleeptimer_duration: c_int, 313 + sleeptimer_on_startup: u8, 314 + keypress_restarts_sleeptimer: u8, 315 + 316 + show_shutdown_message: u8, 317 + 318 + hotkey_wps: c_int, 319 + hotkey_tree: c_int, 320 + 321 + resume_rewind: c_int, 322 + 323 + depth_3d: c_int, 324 + 325 + roll_off: c_int, 326 + 327 + power_mode: c_int, 328 + 329 + keyclick_hardware: u8, 330 + 331 + start_directory: [MAX_PATHNAME + 1]u8, 332 + root_menu_customized: u8, 333 + shortcuts_replaces_qs: u8, 334 + 335 + play_frequency: c_int, 336 + volume_limit: c_int, 337 + 338 + volume_adjust_mode: c_int, 339 + volume_adjust_norm_steps: c_int, 340 + 341 + surround_enabled: c_int, 342 + surround_balance: c_int, 343 + surround_fx1: c_int, 344 + surround_fx2: u8, 345 + surround_method2: u8, 346 + surround_mix: c_int, 347 + 348 + pbe: c_int, 349 + pbe_precut: c_int, 350 + 351 + afr_enabled: c_int, 352 + 353 + governor: c_int, 354 + stereosw_mode: c_int, 355 + }; 356 + 357 + extern var global_settings: UserSettings; 358 + 359 + pub fn get_crossfade_mode() c_int { 360 + return global_settings.crossfade; 361 + }
+127 -1
webui/rockbox/deno.lock
··· 12 12 "npm:@graphql-codegen/typescript-operations@4.3.0": "4.3.0_graphql@15.7.2", 13 13 "npm:@graphql-codegen/typescript-react-apollo@4.3.2": "4.3.2_graphql@15.7.2", 14 14 "npm:@graphql-codegen/typescript@4.1.0": "4.1.0_graphql@15.7.2", 15 + "npm:@mui/material@^6.1.4": "6.1.5_@emotion+react@11.13.3__react@18.3.1_@emotion+styled@11.13.0__@emotion+react@11.13.3___react@18.3.1__react@18.3.1_@types+react@18.3.11_react@18.3.1_react-dom@18.3.1__react@18.3.1", 15 16 "npm:@storybook/addon-essentials@^8.3.4": "8.3.5_storybook@8.3.5", 16 17 "npm:@storybook/addon-interactions@^8.3.4": "8.3.5_storybook@8.3.5", 17 18 "npm:@storybook/addon-links@^8.3.4": "8.3.5_react@18.3.1_storybook@8.3.5", ··· 43 44 "npm:@types/react-dom@^18.3.0": "18.3.0", 44 45 "npm:@types/react-lazy-load-image-component@^1.6.4": "1.6.4", 45 46 "npm:@types/react@^18.3.11": "18.3.11", 47 + "npm:@types/wicg-file-system-access@^2023.10.5": "2023.10.5", 46 48 "npm:@vitejs/plugin-react@^4.3.1": "4.3.2_vite@5.4.8__@types+node@22.7.5_@babel+core@7.25.8_@types+node@22.7.5", 47 49 "npm:@vitest/ui@^2.1.2": "2.1.2_vitest@2.1.2__@types+node@22.7.5__@vitest+ui@2.1.2__jsdom@25.0.1__@vitest+spy@2.1.2__vite@5.4.8___@types+node@22.7.5_@types+node@22.7.5_jsdom@25.0.1", 48 50 "npm:baseui@12.2.0": "12.2.0_react@18.3.1_react-dom@18.3.1__react@18.3.1_styletron-react@6.1.1__react@18.3.1_date-fns@2.30.0_@types+react@18.3.11", 51 + "npm:bignumber.js@^9.1.2": "9.1.2", 49 52 "npm:cross-env@^7.0.3": "7.0.3", 53 + "npm:dayjs@^1.11.13": "1.11.13", 50 54 "npm:eslint-plugin-react-hooks@^5.1.0-rc.0": "5.1.0-rc-fb9a90fa48-20240614_eslint@9.12.0", 51 55 "npm:eslint-plugin-react-refresh@~0.4.9": "0.4.12_eslint@9.12.0", 52 56 "npm:eslint-plugin-storybook@0.9": "0.9.0_eslint@9.12.0_typescript@5.6.3", ··· 1452 1456 "react@18.3.1" 1453 1457 ] 1454 1458 }, 1459 + "@mui/core-downloads-tracker@6.1.5": { 1460 + "integrity": "sha512-3J96098GrC95XsLw/TpGNMxhUOnoG9NZ/17Pfk1CrJj+4rcuolsF2RdF3XAFTu/3a/A+5ouxlSIykzYz6Ee87g==" 1461 + }, 1462 + "@mui/material@6.1.5_@emotion+react@11.13.3__react@18.3.1_@emotion+styled@11.13.0__@emotion+react@11.13.3___react@18.3.1__react@18.3.1_@types+react@18.3.11_react@18.3.1_react-dom@18.3.1__react@18.3.1": { 1463 + "integrity": "sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==", 1464 + "dependencies": [ 1465 + "@babel/runtime", 1466 + "@emotion/react", 1467 + "@emotion/styled", 1468 + "@mui/core-downloads-tracker", 1469 + "@mui/system", 1470 + "@mui/types", 1471 + "@mui/utils", 1472 + "@popperjs/core", 1473 + "@types/react", 1474 + "@types/react-transition-group", 1475 + "clsx@2.1.1", 1476 + "csstype", 1477 + "prop-types", 1478 + "react@18.3.1", 1479 + "react-dom@18.3.1_react@18.3.1", 1480 + "react-is@18.3.1", 1481 + "react-transition-group" 1482 + ] 1483 + }, 1484 + "@mui/private-theming@6.1.5_@types+react@18.3.11_react@18.3.1": { 1485 + "integrity": "sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==", 1486 + "dependencies": [ 1487 + "@babel/runtime", 1488 + "@mui/utils", 1489 + "@types/react", 1490 + "prop-types", 1491 + "react@18.3.1" 1492 + ] 1493 + }, 1494 + "@mui/styled-engine@6.1.5_@emotion+react@11.13.3__react@18.3.1_@emotion+styled@11.13.0__@emotion+react@11.13.3___react@18.3.1__react@18.3.1_react@18.3.1": { 1495 + "integrity": "sha512-tiyWzMkHeWlOoE6AqomWvYvdml8Nv5k5T+LDwOiwHEawx8P9Lyja6ZwWPU6xljwPXYYPT2KBp1XvMly7dsK46A==", 1496 + "dependencies": [ 1497 + "@babel/runtime", 1498 + "@emotion/cache", 1499 + "@emotion/react", 1500 + "@emotion/serialize", 1501 + "@emotion/sheet", 1502 + "@emotion/styled", 1503 + "csstype", 1504 + "prop-types", 1505 + "react@18.3.1" 1506 + ] 1507 + }, 1508 + "@mui/system@6.1.5_@emotion+react@11.13.3__react@18.3.1_@emotion+styled@11.13.0__@emotion+react@11.13.3___react@18.3.1__react@18.3.1_@types+react@18.3.11_react@18.3.1": { 1509 + "integrity": "sha512-vPM9ocQ8qquRDByTG3XF/wfYTL7IWL/20EiiKqByLDps8wOmbrDG9rVznSE3ZbcjFCFfMRMhtxvN92bwe/63SA==", 1510 + "dependencies": [ 1511 + "@babel/runtime", 1512 + "@emotion/react", 1513 + "@emotion/styled", 1514 + "@mui/private-theming", 1515 + "@mui/styled-engine", 1516 + "@mui/types", 1517 + "@mui/utils", 1518 + "@types/react", 1519 + "clsx@2.1.1", 1520 + "csstype", 1521 + "prop-types", 1522 + "react@18.3.1" 1523 + ] 1524 + }, 1525 + "@mui/types@7.2.18_@types+react@18.3.11": { 1526 + "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", 1527 + "dependencies": [ 1528 + "@types/react" 1529 + ] 1530 + }, 1531 + "@mui/utils@6.1.5_@types+react@18.3.11_react@18.3.1": { 1532 + "integrity": "sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==", 1533 + "dependencies": [ 1534 + "@babel/runtime", 1535 + "@mui/types", 1536 + "@types/prop-types", 1537 + "@types/react", 1538 + "clsx@2.1.1", 1539 + "prop-types", 1540 + "react@18.3.1", 1541 + "react-is@18.3.1" 1542 + ] 1543 + }, 1455 1544 "@nodelib/fs.scandir@2.1.5": { 1456 1545 "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 1457 1546 "dependencies": [ ··· 1471 1560 }, 1472 1561 "@polka/url@1.0.0-next.28": { 1473 1562 "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" 1563 + }, 1564 + "@popperjs/core@2.11.8": { 1565 + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" 1474 1566 }, 1475 1567 "@remix-run/router@1.19.2": { 1476 1568 "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==" ··· 2219 2311 "csstype" 2220 2312 ] 2221 2313 }, 2314 + "@types/react-transition-group@4.4.11": { 2315 + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", 2316 + "dependencies": [ 2317 + "@types/react" 2318 + ] 2319 + }, 2222 2320 "@types/react-virtualized-auto-sizer@1.0.4": { 2223 2321 "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==", 2224 2322 "dependencies": [ ··· 2270 2368 }, 2271 2369 "@types/uuid@9.0.8": { 2272 2370 "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" 2371 + }, 2372 + "@types/wicg-file-system-access@2023.10.5": { 2373 + "integrity": "sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==" 2273 2374 }, 2274 2375 "@types/ws@8.5.12": { 2275 2376 "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", ··· 2794 2895 "open" 2795 2896 ] 2796 2897 }, 2898 + "bignumber.js@9.1.2": { 2899 + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" 2900 + }, 2797 2901 "bl@4.1.0": { 2798 2902 "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 2799 2903 "dependencies": [ ··· 3052 3156 }, 3053 3157 "clsx@1.2.1": { 3054 3158 "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" 3159 + }, 3160 + "clsx@2.1.1": { 3161 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" 3055 3162 }, 3056 3163 "color-convert@1.9.3": { 3057 3164 "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", ··· 3426 3533 "dependencies": [ 3427 3534 "@babel/runtime" 3428 3535 ] 3536 + }, 3537 + "dayjs@1.11.13": { 3538 + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" 3429 3539 }, 3430 3540 "debounce@1.2.1": { 3431 3541 "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" ··· 5325 5435 "react-is@18.1.0": { 5326 5436 "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==" 5327 5437 }, 5438 + "react-is@18.3.1": { 5439 + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" 5440 + }, 5328 5441 "react-lazy-load-image-component@1.6.2_react@18.3.1": { 5329 5442 "integrity": "sha512-dAdH5PsRgvDMlHC7QpZRA9oRzEZl1kPFwowmR9Mt0IUUhxk2wwq43PB6Ffwv84HFYuPmsxDUCka0E9KVXi8roQ==", 5330 5443 "dependencies": [ ··· 5375 5488 "react@18.3.1" 5376 5489 ] 5377 5490 }, 5491 + "react-transition-group@4.4.5_react@18.3.1_react-dom@18.3.1__react@18.3.1": { 5492 + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", 5493 + "dependencies": [ 5494 + "@babel/runtime", 5495 + "dom-helpers", 5496 + "loose-envify", 5497 + "prop-types", 5498 + "react@18.3.1", 5499 + "react-dom@18.3.1_react@18.3.1" 5500 + ] 5501 + }, 5378 5502 "react-uid@2.3.3_@types+react@18.3.11_react@18.3.1": { 5379 5503 "integrity": "sha512-iNpDovcb9qBpBTo8iUgqRSQOS8GV3bWoNaTaUptHkXtAooXSo0OWe7vN6TqqB8x3x0bNBbQx96kkmSltQ5h9kQ==", 5380 5504 "dependencies": [ ··· 5394 5518 "integrity": "sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ==", 5395 5519 "dependencies": [ 5396 5520 "@babel/runtime", 5397 - "clsx", 5521 + "clsx@1.2.1", 5398 5522 "dom-helpers", 5399 5523 "loose-envify", 5400 5524 "prop-types", ··· 6528 6652 "npm:@types/react-dom@^18.3.0", 6529 6653 "npm:@types/react-lazy-load-image-component@^1.6.4", 6530 6654 "npm:@types/react@^18.3.11", 6655 + "npm:@types/wicg-file-system-access@^2023.10.5", 6531 6656 "npm:@vitejs/plugin-react@^4.3.1", 6532 6657 "npm:@vitest/ui@^2.1.2", 6533 6658 "npm:baseui@12.2.0", 6659 + "npm:bignumber.js@^9.1.2", 6534 6660 "npm:cross-env@^7.0.3", 6535 6661 "npm:dayjs@^1.11.13", 6536 6662 "npm:eslint-plugin-react-hooks@^5.1.0-rc.0",
+510
webui/rockbox/graphql.schema.json
··· 563 563 "possibleTypes": null 564 564 }, 565 565 { 566 + "kind": "INPUT_OBJECT", 567 + "name": "EqBandSettingInput", 568 + "description": null, 569 + "fields": null, 570 + "inputFields": [ 571 + { 572 + "name": "cutoff", 573 + "description": null, 574 + "type": { 575 + "kind": "NON_NULL", 576 + "name": null, 577 + "ofType": { 578 + "kind": "SCALAR", 579 + "name": "Int", 580 + "ofType": null 581 + } 582 + }, 583 + "defaultValue": null, 584 + "isDeprecated": false, 585 + "deprecationReason": null 586 + }, 587 + { 588 + "name": "gain", 589 + "description": null, 590 + "type": { 591 + "kind": "NON_NULL", 592 + "name": null, 593 + "ofType": { 594 + "kind": "SCALAR", 595 + "name": "Int", 596 + "ofType": null 597 + } 598 + }, 599 + "defaultValue": null, 600 + "isDeprecated": false, 601 + "deprecationReason": null 602 + }, 603 + { 604 + "name": "q", 605 + "description": null, 606 + "type": { 607 + "kind": "NON_NULL", 608 + "name": null, 609 + "ofType": { 610 + "kind": "SCALAR", 611 + "name": "Int", 612 + "ofType": null 613 + } 614 + }, 615 + "defaultValue": null, 616 + "isDeprecated": false, 617 + "deprecationReason": null 618 + } 619 + ], 620 + "interfaces": null, 621 + "enumValues": null, 622 + "possibleTypes": null 623 + }, 624 + { 566 625 "kind": "SCALAR", 567 626 "name": "Float", 568 627 "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).", ··· 1785 1844 "deprecationReason": null 1786 1845 }, 1787 1846 { 1847 + "name": "saveSettings", 1848 + "description": null, 1849 + "args": [ 1850 + { 1851 + "name": "settings", 1852 + "description": null, 1853 + "type": { 1854 + "kind": "NON_NULL", 1855 + "name": null, 1856 + "ofType": { 1857 + "kind": "INPUT_OBJECT", 1858 + "name": "NewGlobalSettings", 1859 + "ofType": null 1860 + } 1861 + }, 1862 + "defaultValue": null, 1863 + "isDeprecated": false, 1864 + "deprecationReason": null 1865 + } 1866 + ], 1867 + "type": { 1868 + "kind": "NON_NULL", 1869 + "name": null, 1870 + "ofType": { 1871 + "kind": "SCALAR", 1872 + "name": "Boolean", 1873 + "ofType": null 1874 + } 1875 + }, 1876 + "isDeprecated": false, 1877 + "deprecationReason": null 1878 + }, 1879 + { 1788 1880 "name": "scanLibrary", 1789 1881 "description": null, 1790 1882 "args": [], ··· 1985 2077 "possibleTypes": null 1986 2078 }, 1987 2079 { 2080 + "kind": "INPUT_OBJECT", 2081 + "name": "NewGlobalSettings", 2082 + "description": null, 2083 + "fields": null, 2084 + "inputFields": [ 2085 + { 2086 + "name": "balance", 2087 + "description": null, 2088 + "type": { 2089 + "kind": "SCALAR", 2090 + "name": "Int", 2091 + "ofType": null 2092 + }, 2093 + "defaultValue": null, 2094 + "isDeprecated": false, 2095 + "deprecationReason": null 2096 + }, 2097 + { 2098 + "name": "bass", 2099 + "description": null, 2100 + "type": { 2101 + "kind": "SCALAR", 2102 + "name": "Int", 2103 + "ofType": null 2104 + }, 2105 + "defaultValue": null, 2106 + "isDeprecated": false, 2107 + "deprecationReason": null 2108 + }, 2109 + { 2110 + "name": "bassCutoff", 2111 + "description": null, 2112 + "type": { 2113 + "kind": "SCALAR", 2114 + "name": "Int", 2115 + "ofType": null 2116 + }, 2117 + "defaultValue": null, 2118 + "isDeprecated": false, 2119 + "deprecationReason": null 2120 + }, 2121 + { 2122 + "name": "channelConfig", 2123 + "description": null, 2124 + "type": { 2125 + "kind": "SCALAR", 2126 + "name": "Int", 2127 + "ofType": null 2128 + }, 2129 + "defaultValue": null, 2130 + "isDeprecated": false, 2131 + "deprecationReason": null 2132 + }, 2133 + { 2134 + "name": "crossfade", 2135 + "description": null, 2136 + "type": { 2137 + "kind": "SCALAR", 2138 + "name": "Int", 2139 + "ofType": null 2140 + }, 2141 + "defaultValue": null, 2142 + "isDeprecated": false, 2143 + "deprecationReason": null 2144 + }, 2145 + { 2146 + "name": "eqBandSettings", 2147 + "description": null, 2148 + "type": { 2149 + "kind": "LIST", 2150 + "name": null, 2151 + "ofType": { 2152 + "kind": "NON_NULL", 2153 + "name": null, 2154 + "ofType": { 2155 + "kind": "INPUT_OBJECT", 2156 + "name": "EqBandSettingInput", 2157 + "ofType": null 2158 + } 2159 + } 2160 + }, 2161 + "defaultValue": null, 2162 + "isDeprecated": false, 2163 + "deprecationReason": null 2164 + }, 2165 + { 2166 + "name": "eqEnabled", 2167 + "description": null, 2168 + "type": { 2169 + "kind": "SCALAR", 2170 + "name": "Boolean", 2171 + "ofType": null 2172 + }, 2173 + "defaultValue": null, 2174 + "isDeprecated": false, 2175 + "deprecationReason": null 2176 + }, 2177 + { 2178 + "name": "fadeInDelay", 2179 + "description": null, 2180 + "type": { 2181 + "kind": "SCALAR", 2182 + "name": "Int", 2183 + "ofType": null 2184 + }, 2185 + "defaultValue": null, 2186 + "isDeprecated": false, 2187 + "deprecationReason": null 2188 + }, 2189 + { 2190 + "name": "fadeInDuration", 2191 + "description": null, 2192 + "type": { 2193 + "kind": "SCALAR", 2194 + "name": "Int", 2195 + "ofType": null 2196 + }, 2197 + "defaultValue": null, 2198 + "isDeprecated": false, 2199 + "deprecationReason": null 2200 + }, 2201 + { 2202 + "name": "fadeOnStop", 2203 + "description": null, 2204 + "type": { 2205 + "kind": "SCALAR", 2206 + "name": "Boolean", 2207 + "ofType": null 2208 + }, 2209 + "defaultValue": null, 2210 + "isDeprecated": false, 2211 + "deprecationReason": null 2212 + }, 2213 + { 2214 + "name": "fadeOutDelay", 2215 + "description": null, 2216 + "type": { 2217 + "kind": "SCALAR", 2218 + "name": "Int", 2219 + "ofType": null 2220 + }, 2221 + "defaultValue": null, 2222 + "isDeprecated": false, 2223 + "deprecationReason": null 2224 + }, 2225 + { 2226 + "name": "fadeOutDuration", 2227 + "description": null, 2228 + "type": { 2229 + "kind": "SCALAR", 2230 + "name": "Int", 2231 + "ofType": null 2232 + }, 2233 + "defaultValue": null, 2234 + "isDeprecated": false, 2235 + "deprecationReason": null 2236 + }, 2237 + { 2238 + "name": "fadeOutMixmode", 2239 + "description": null, 2240 + "type": { 2241 + "kind": "SCALAR", 2242 + "name": "Int", 2243 + "ofType": null 2244 + }, 2245 + "defaultValue": null, 2246 + "isDeprecated": false, 2247 + "deprecationReason": null 2248 + }, 2249 + { 2250 + "name": "musicDir", 2251 + "description": null, 2252 + "type": { 2253 + "kind": "SCALAR", 2254 + "name": "String", 2255 + "ofType": null 2256 + }, 2257 + "defaultValue": null, 2258 + "isDeprecated": false, 2259 + "deprecationReason": null 2260 + }, 2261 + { 2262 + "name": "partyMode", 2263 + "description": null, 2264 + "type": { 2265 + "kind": "SCALAR", 2266 + "name": "Boolean", 2267 + "ofType": null 2268 + }, 2269 + "defaultValue": null, 2270 + "isDeprecated": false, 2271 + "deprecationReason": null 2272 + }, 2273 + { 2274 + "name": "playerName", 2275 + "description": null, 2276 + "type": { 2277 + "kind": "SCALAR", 2278 + "name": "String", 2279 + "ofType": null 2280 + }, 2281 + "defaultValue": null, 2282 + "isDeprecated": false, 2283 + "deprecationReason": null 2284 + }, 2285 + { 2286 + "name": "playlistShuffle", 2287 + "description": null, 2288 + "type": { 2289 + "kind": "SCALAR", 2290 + "name": "Boolean", 2291 + "ofType": null 2292 + }, 2293 + "defaultValue": null, 2294 + "isDeprecated": false, 2295 + "deprecationReason": null 2296 + }, 2297 + { 2298 + "name": "repeatMode", 2299 + "description": null, 2300 + "type": { 2301 + "kind": "SCALAR", 2302 + "name": "Int", 2303 + "ofType": null 2304 + }, 2305 + "defaultValue": null, 2306 + "isDeprecated": false, 2307 + "deprecationReason": null 2308 + }, 2309 + { 2310 + "name": "replaygainSettings", 2311 + "description": null, 2312 + "type": { 2313 + "kind": "INPUT_OBJECT", 2314 + "name": "ReplaygainSettingsInput", 2315 + "ofType": null 2316 + }, 2317 + "defaultValue": null, 2318 + "isDeprecated": false, 2319 + "deprecationReason": null 2320 + }, 2321 + { 2322 + "name": "stereoWidth", 2323 + "description": null, 2324 + "type": { 2325 + "kind": "SCALAR", 2326 + "name": "Int", 2327 + "ofType": null 2328 + }, 2329 + "defaultValue": null, 2330 + "isDeprecated": false, 2331 + "deprecationReason": null 2332 + }, 2333 + { 2334 + "name": "stereoswMode", 2335 + "description": null, 2336 + "type": { 2337 + "kind": "SCALAR", 2338 + "name": "Int", 2339 + "ofType": null 2340 + }, 2341 + "defaultValue": null, 2342 + "isDeprecated": false, 2343 + "deprecationReason": null 2344 + }, 2345 + { 2346 + "name": "surroundBalance", 2347 + "description": null, 2348 + "type": { 2349 + "kind": "SCALAR", 2350 + "name": "Int", 2351 + "ofType": null 2352 + }, 2353 + "defaultValue": null, 2354 + "isDeprecated": false, 2355 + "deprecationReason": null 2356 + }, 2357 + { 2358 + "name": "surroundEnabled", 2359 + "description": null, 2360 + "type": { 2361 + "kind": "SCALAR", 2362 + "name": "Boolean", 2363 + "ofType": null 2364 + }, 2365 + "defaultValue": null, 2366 + "isDeprecated": false, 2367 + "deprecationReason": null 2368 + }, 2369 + { 2370 + "name": "surroundFx1", 2371 + "description": null, 2372 + "type": { 2373 + "kind": "SCALAR", 2374 + "name": "Int", 2375 + "ofType": null 2376 + }, 2377 + "defaultValue": null, 2378 + "isDeprecated": false, 2379 + "deprecationReason": null 2380 + }, 2381 + { 2382 + "name": "surroundFx2", 2383 + "description": null, 2384 + "type": { 2385 + "kind": "SCALAR", 2386 + "name": "Boolean", 2387 + "ofType": null 2388 + }, 2389 + "defaultValue": null, 2390 + "isDeprecated": false, 2391 + "deprecationReason": null 2392 + }, 2393 + { 2394 + "name": "treble", 2395 + "description": null, 2396 + "type": { 2397 + "kind": "SCALAR", 2398 + "name": "Int", 2399 + "ofType": null 2400 + }, 2401 + "defaultValue": null, 2402 + "isDeprecated": false, 2403 + "deprecationReason": null 2404 + }, 2405 + { 2406 + "name": "trebleCutoff", 2407 + "description": null, 2408 + "type": { 2409 + "kind": "SCALAR", 2410 + "name": "Int", 2411 + "ofType": null 2412 + }, 2413 + "defaultValue": null, 2414 + "isDeprecated": false, 2415 + "deprecationReason": null 2416 + } 2417 + ], 2418 + "interfaces": null, 2419 + "enumValues": null, 2420 + "possibleTypes": null 2421 + }, 2422 + { 1988 2423 "kind": "OBJECT", 1989 2424 "name": "Playlist", 1990 2425 "description": null, ··· 2739 3174 ], 2740 3175 "inputFields": null, 2741 3176 "interfaces": [], 3177 + "enumValues": null, 3178 + "possibleTypes": null 3179 + }, 3180 + { 3181 + "kind": "INPUT_OBJECT", 3182 + "name": "ReplaygainSettingsInput", 3183 + "description": null, 3184 + "fields": null, 3185 + "inputFields": [ 3186 + { 3187 + "name": "noclip", 3188 + "description": null, 3189 + "type": { 3190 + "kind": "NON_NULL", 3191 + "name": null, 3192 + "ofType": { 3193 + "kind": "SCALAR", 3194 + "name": "Boolean", 3195 + "ofType": null 3196 + } 3197 + }, 3198 + "defaultValue": null, 3199 + "isDeprecated": false, 3200 + "deprecationReason": null 3201 + }, 3202 + { 3203 + "name": "preamp", 3204 + "description": null, 3205 + "type": { 3206 + "kind": "NON_NULL", 3207 + "name": null, 3208 + "ofType": { 3209 + "kind": "SCALAR", 3210 + "name": "Int", 3211 + "ofType": null 3212 + } 3213 + }, 3214 + "defaultValue": null, 3215 + "isDeprecated": false, 3216 + "deprecationReason": null 3217 + }, 3218 + { 3219 + "name": "type", 3220 + "description": null, 3221 + "type": { 3222 + "kind": "NON_NULL", 3223 + "name": null, 3224 + "ofType": { 3225 + "kind": "SCALAR", 3226 + "name": "Int", 3227 + "ofType": null 3228 + } 3229 + }, 3230 + "defaultValue": null, 3231 + "isDeprecated": false, 3232 + "deprecationReason": null 3233 + } 3234 + ], 3235 + "interfaces": null, 2742 3236 "enumValues": null, 2743 3237 "possibleTypes": null 2744 3238 }, ··· 5047 5541 "ofType": { 5048 5542 "kind": "SCALAR", 5049 5543 "name": "Int", 5544 + "ofType": null 5545 + } 5546 + }, 5547 + "isDeprecated": false, 5548 + "deprecationReason": null 5549 + }, 5550 + { 5551 + "name": "musicDir", 5552 + "description": null, 5553 + "args": [], 5554 + "type": { 5555 + "kind": "NON_NULL", 5556 + "name": null, 5557 + "ofType": { 5558 + "kind": "SCALAR", 5559 + "name": "String", 5050 5560 "ofType": null 5051 5561 } 5052 5562 },
+2
webui/rockbox/package.json
··· 47 47 "@types/react-lazy-load-image-component": "^1.6.4", 48 48 "@vitest/ui": "^2.1.2", 49 49 "baseui": "12.2.0", 50 + "bignumber.js": "^9.1.2", 50 51 "dayjs": "^1.11.13", 51 52 "graphql": "15.7.2", 52 53 "jsdom": "^25.0.1", ··· 80 81 "@storybook/test": "^8.3.4", 81 82 "@types/react": "^18.3.11", 82 83 "@types/react-dom": "^18.3.0", 84 + "@types/wicg-file-system-access": "^2023.10.5", 83 85 "@vitejs/plugin-react": "^4.3.1", 84 86 "cross-env": "^7.0.3", 85 87 "eslint": "^9.9.0",
+2
webui/rockbox/src/App.tsx
··· 6 6 import ArtistDetails from "./Components/ArtistDetails"; 7 7 import FilesPage from "./Containers/Files"; 8 8 import LikesPage from "./Containers/Likes"; 9 + import SettingsPage from "./Containers/Settings"; 9 10 10 11 function App() { 11 12 return ( ··· 19 20 <Route path="/tracks" element={<TracksPage />} /> 20 21 <Route path="/files" element={<FilesPage />} /> 21 22 <Route path="/likes" element={<LikesPage />} /> 23 + <Route path="/settings" element={<SettingsPage />} /> 22 24 </Routes> 23 25 </BrowserRouter> 24 26 );
+36 -9
webui/rockbox/src/Components/AlbumDetails/__snapshots__/AlbumDetails.test.tsx.snap
··· 11 11 <div 12 12 class="css-1tlxqlf" 13 13 > 14 - <a 15 - href="/" 16 - style="text-decoration: none;" 14 + <div 15 + class="css-1osgnyu" 17 16 > 18 - <img 19 - alt="Rockbox" 20 - src="/src/Assets/rockbox-icon.svg" 21 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 22 - /> 23 - </a> 17 + <a 18 + href="/" 19 + style="text-decoration: none;" 20 + > 21 + <img 22 + alt="Rockbox" 23 + src="/src/Assets/rockbox-icon.svg" 24 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 25 + /> 26 + </a> 27 + <a 28 + href="/settings" 29 + > 30 + <button 31 + class="css-fduwnf" 32 + > 33 + <svg 34 + aria-hidden="true" 35 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 36 + color="#000" 37 + fill="currentColor" 38 + focusable="false" 39 + height="24" 40 + viewBox="0 0 48 48" 41 + width="24" 42 + xmlns="http://www.w3.org/2000/svg" 43 + > 44 + <path 45 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 46 + /> 47 + </svg> 48 + </button> 49 + </a> 50 + </div> 24 51 <a 25 52 class="css-8v05qj" 26 53 color="#fe099c"
+36 -9
webui/rockbox/src/Components/Albums/__snapshots__/Albums.test.tsx.snap
··· 11 11 <div 12 12 class="css-1tlxqlf" 13 13 > 14 - <a 15 - href="/" 16 - style="text-decoration: none;" 14 + <div 15 + class="css-1osgnyu" 17 16 > 18 - <img 19 - alt="Rockbox" 20 - src="/src/Assets/rockbox-icon.svg" 21 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 22 - /> 23 - </a> 17 + <a 18 + href="/" 19 + style="text-decoration: none;" 20 + > 21 + <img 22 + alt="Rockbox" 23 + src="/src/Assets/rockbox-icon.svg" 24 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 25 + /> 26 + </a> 27 + <a 28 + href="/settings" 29 + > 30 + <button 31 + class="css-fduwnf" 32 + > 33 + <svg 34 + aria-hidden="true" 35 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 36 + color="#000" 37 + fill="currentColor" 38 + focusable="false" 39 + height="24" 40 + viewBox="0 0 48 48" 41 + width="24" 42 + xmlns="http://www.w3.org/2000/svg" 43 + > 44 + <path 45 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 46 + /> 47 + </svg> 48 + </button> 49 + </a> 50 + </div> 24 51 <a 25 52 class="css-8v05qj" 26 53 color="#fe099c"
+36 -9
webui/rockbox/src/Components/ArtistDetails/__snapshots__/ArtistDetails.test.tsx.snap
··· 11 11 <div 12 12 class="css-1tlxqlf" 13 13 > 14 - <a 15 - href="/" 16 - style="text-decoration: none;" 14 + <div 15 + class="css-1osgnyu" 17 16 > 18 - <img 19 - alt="Rockbox" 20 - src="/src/Assets/rockbox-icon.svg" 21 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 22 - /> 23 - </a> 17 + <a 18 + href="/" 19 + style="text-decoration: none;" 20 + > 21 + <img 22 + alt="Rockbox" 23 + src="/src/Assets/rockbox-icon.svg" 24 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 25 + /> 26 + </a> 27 + <a 28 + href="/settings" 29 + > 30 + <button 31 + class="css-fduwnf" 32 + > 33 + <svg 34 + aria-hidden="true" 35 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 36 + color="#000" 37 + fill="currentColor" 38 + focusable="false" 39 + height="24" 40 + viewBox="0 0 48 48" 41 + width="24" 42 + xmlns="http://www.w3.org/2000/svg" 43 + > 44 + <path 45 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 46 + /> 47 + </svg> 48 + </button> 49 + </a> 50 + </div> 24 51 <a 25 52 class="css-tujm8o" 26 53 color="initial"
+36 -9
webui/rockbox/src/Components/Artists/__snapshots__/Artists.test.tsx.snap
··· 11 11 <div 12 12 class="css-1tlxqlf" 13 13 > 14 - <a 15 - href="/" 16 - style="text-decoration: none;" 14 + <div 15 + class="css-1osgnyu" 17 16 > 18 - <img 19 - alt="Rockbox" 20 - src="/src/Assets/rockbox-icon.svg" 21 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 22 - /> 23 - </a> 17 + <a 18 + href="/" 19 + style="text-decoration: none;" 20 + > 21 + <img 22 + alt="Rockbox" 23 + src="/src/Assets/rockbox-icon.svg" 24 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 25 + /> 26 + </a> 27 + <a 28 + href="/settings" 29 + > 30 + <button 31 + class="css-fduwnf" 32 + > 33 + <svg 34 + aria-hidden="true" 35 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 36 + color="#000" 37 + fill="currentColor" 38 + focusable="false" 39 + height="24" 40 + viewBox="0 0 48 48" 41 + width="24" 42 + xmlns="http://www.w3.org/2000/svg" 43 + > 44 + <path 45 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 46 + /> 47 + </svg> 48 + </button> 49 + </a> 50 + </div> 24 51 <a 25 52 class="css-tujm8o" 26 53 color="initial"
+1 -1
webui/rockbox/src/Components/ControlBar/CurrentTrack/styles.ts
··· 118 118 }, 119 119 BarProgress: { 120 120 style: () => ({ 121 - backgroundColor: "#fe099c", 121 + backgroundColor: "rgb(254, 9, 156)", 122 122 }), 123 123 }, 124 124 Bar: {
+36 -9
webui/rockbox/src/Components/Files/__snapshots__/Files.test.tsx.snap
··· 11 11 <div 12 12 class="css-1tlxqlf" 13 13 > 14 - <a 15 - href="/" 16 - style="text-decoration: none;" 14 + <div 15 + class="css-1osgnyu" 17 16 > 18 - <img 19 - alt="Rockbox" 20 - src="/src/Assets/rockbox-icon.svg" 21 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 22 - /> 23 - </a> 17 + <a 18 + href="/" 19 + style="text-decoration: none;" 20 + > 21 + <img 22 + alt="Rockbox" 23 + src="/src/Assets/rockbox-icon.svg" 24 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 25 + /> 26 + </a> 27 + <a 28 + href="/settings" 29 + > 30 + <button 31 + class="css-fduwnf" 32 + > 33 + <svg 34 + aria-hidden="true" 35 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 36 + color="#000" 37 + fill="currentColor" 38 + focusable="false" 39 + height="24" 40 + viewBox="0 0 48 48" 41 + width="24" 42 + xmlns="http://www.w3.org/2000/svg" 43 + > 44 + <path 45 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 46 + /> 47 + </svg> 48 + </button> 49 + </a> 50 + </div> 24 51 <a 25 52 class="css-tujm8o" 26 53 color="initial"
+39
webui/rockbox/src/Components/Settings/Library/Library.tsx
··· 1 + import { FC, useEffect, useState } from "react"; 2 + import { Item, Section, SettingsTitle } from "./styles"; 3 + import { Input } from "baseui/input"; 4 + 5 + export type LibraryProps = { 6 + musicDir: string; 7 + onSaveMusicDirectoryPath: (musicDir: string) => void; 8 + }; 9 + 10 + const Library: FC<LibraryProps> = (props) => { 11 + const [musicFolder, setMusicFolder] = useState<string>(props.musicDir); 12 + 13 + useEffect(() => { 14 + setMusicFolder(props.musicDir); 15 + }, [props.musicDir]); 16 + 17 + return ( 18 + <> 19 + <SettingsTitle>Library</SettingsTitle> 20 + <Section> 21 + <Item style={{ height: 80 }}> 22 + <div>Load music from folder</div> 23 + <div> 24 + <Input 25 + value={musicFolder} 26 + onChange={(e) => { 27 + setMusicFolder(e.target.value); 28 + props.onSaveMusicDirectoryPath(e.target.value); 29 + }} 30 + placeholder="Music folder" 31 + /> 32 + </div> 33 + </Item> 34 + </Section> 35 + </> 36 + ); 37 + }; 38 + 39 + export default Library;
+29
webui/rockbox/src/Components/Settings/Library/LibraryWithData.tsx
··· 1 + import { FC } from "react"; 2 + import Library from "./Library"; 3 + import { 4 + useGetGlobalSettingsQuery, 5 + useSaveSettingsMutation, 6 + } from "../../../Hooks/GraphQL"; 7 + 8 + const LibraryWithData: FC = () => { 9 + const { data } = useGetGlobalSettingsQuery(); 10 + const [saveSettings] = useSaveSettingsMutation(); 11 + 12 + const onSaveMusicDirectoryPath = (musicDir: string) => 13 + saveSettings({ 14 + variables: { 15 + settings: { 16 + musicDir, 17 + }, 18 + }, 19 + }); 20 + 21 + return ( 22 + <Library 23 + musicDir={data?.globalSettings.musicDir || ""} 24 + onSaveMusicDirectoryPath={onSaveMusicDirectoryPath} 25 + /> 26 + ); 27 + }; 28 + 29 + export default LibraryWithData;
+3
webui/rockbox/src/Components/Settings/Library/index.tsx
··· 1 + import Library from "./LibraryWithData"; 2 + 3 + export default Library;
+130
webui/rockbox/src/Components/Settings/Library/styles.tsx
··· 1 + import styled from "@emotion/styled"; 2 + 3 + export const Container = styled.div` 4 + display: flex; 5 + flex-direction: row; 6 + width: 100%; 7 + height: 100%; 8 + `; 9 + 10 + export const Title = styled.div` 11 + font-size: 24px; 12 + font-family: RockfordSansMedium; 13 + margin: auto; 14 + margin-bottom: 40px; 15 + `; 16 + 17 + export const Scrollable = styled.div` 18 + height: calc(100vh - 60px); 19 + overflow-y: auto; 20 + `; 21 + 22 + export const Wrapper = styled.div` 23 + width: 60vw; 24 + margin: 0 auto; 25 + margin-bottom: 100px; 26 + margin-top: 30px; 27 + `; 28 + 29 + export const SettingsTitle = styled.div` 30 + font-size: 16px; 31 + font-weight: 600; 32 + margin-bottom: 16px; 33 + `; 34 + 35 + export const Section = styled.div` 36 + margin-bottom: 50px; 37 + font-size: 15px; 38 + border: 1px solid #8a8a8a65; 39 + border-radius: 10px; 40 + padding-left: 20px; 41 + padding-right: 20px; 42 + padding-top: 5px; 43 + padding-bottom: 5px; 44 + `; 45 + 46 + export const Item = styled.div` 47 + display: flex; 48 + flex-direction: row; 49 + align-items: center; 50 + justify-content: space-between; 51 + min-height: 50px; 52 + `; 53 + 54 + const iOSBoxShadow = 55 + "0 3px 1px rgba(0,0,0,0.1),0 4px 8px rgba(0,0,0,0.13),0 0 0 1px rgba(0,0,0,0.02)"; 56 + 57 + export default { 58 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 59 + slider: (t: any) => ({ 60 + color: "rgba(0, 0, 0, 0.682)", 61 + "& .MuiSlider-track": { 62 + border: "none", 63 + }, 64 + "& .MuiSlider-thumb": { 65 + width: 18, 66 + height: 18, 67 + backgroundColor: "#fff", 68 + "&::before": { 69 + boxShadow: "0 4px 8px rgba(0,0,0,0.18)", 70 + }, 71 + "&:hover, &.Mui-focusVisible, &.Mui-active": { 72 + boxShadow: "none", 73 + }, 74 + }, 75 + ...t.applyStyles("dark", { 76 + color: "#fff", 77 + }), 78 + }), 79 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 80 + sliderIOS: (theme: any) => ({ 81 + color: "#7C00FE", 82 + 83 + "& .MuiSlider-thumb": { 84 + height: 20, 85 + width: 20, 86 + backgroundColor: "#fff", 87 + boxShadow: "0 0 2px 0px rgba(0, 0, 0, 0.1)", 88 + "&:focus, &:hover, &.Mui-active": { 89 + boxShadow: "0px 0px 3px 1px rgba(0, 0, 0, 0.1)", 90 + // Reset on touch devices, it doesn't add specificity 91 + "@media (hover: none)": { 92 + boxShadow: iOSBoxShadow, 93 + }, 94 + }, 95 + "&:before": { 96 + boxShadow: 97 + "0px 0px 1px 0px rgba(0,0,0,0.2), 0px 0px 0px 0px rgba(0,0,0,0.14), 0px 0px 1px 0px rgba(0,0,0,0.12)", 98 + }, 99 + }, 100 + "& .MuiSlider-valueLabel": { 101 + fontSize: 12, 102 + fontWeight: "normal", 103 + top: -6, 104 + backgroundColor: "unset", 105 + color: theme.palette.text.primary, 106 + "&::before": { 107 + display: "none", 108 + }, 109 + "& *": { 110 + background: "transparent", 111 + color: "#000", 112 + ...theme.applyStyles("dark", { 113 + color: "#fff", 114 + }), 115 + }, 116 + }, 117 + "& .MuiSlider-track": { 118 + border: "none", 119 + height: 5, 120 + }, 121 + "& .MuiSlider-rail": { 122 + opacity: 0.5, 123 + boxShadow: "inset 0px 0px 4px -2px #000", 124 + backgroundColor: "#d0d0d0", 125 + }, 126 + ...theme.applyStyles("dark", { 127 + color: "#7C00FE", 128 + }), 129 + }), 130 + };
+289
webui/rockbox/src/Components/Settings/Playback/Playback.tsx
··· 1 + import { FC, useState, useEffect } from "react"; 2 + import styles, { Item, Section, SettingsTitle } from "./styles"; 3 + import Switch from "../../Switch"; 4 + import { Slider } from "@mui/material"; 5 + import { Select } from "baseui/select"; 6 + import { 7 + crossfadeOptions, 8 + crossfadeValues, 9 + fadeOutModeOptions, 10 + fadeOutModeValues, 11 + repeatOptions, 12 + repeatValues, 13 + replaygainOptions, 14 + replaygainValues, 15 + } from "./consts"; 16 + 17 + export type PlaybackProps = { 18 + shuffle: boolean; 19 + repeat: number; 20 + fadeOnStopPause: boolean; 21 + crossfade: number; 22 + replaygain: number; 23 + fadeInDelay: number; 24 + fadeInDuration: number; 25 + fadeOutDelay: number; 26 + fadeOutDuration: number; 27 + fadeOutMode: number; 28 + onShuffleChange: (shuffle: boolean) => void; 29 + onRepeatChange: (repeat: number) => void; 30 + onFadeOnStopPauseChange: (fadeOnStopPause: boolean) => void; 31 + onCrossfadeChange: (crossfade: number) => void; 32 + onReplaygainChange: (replaygain: number) => void; 33 + onFadeInDelayChange: (fadeInDelay: number) => void; 34 + onFadeInDurationChange: (fadeInDuration: number) => void; 35 + onFadeOutDelayChange: (fadeOutDelay: number) => void; 36 + onFadeOutDurationChange: (fadeOutDuration: number) => void; 37 + onFadeOutModeChange: (fadeOutMode: number) => void; 38 + }; 39 + 40 + const Playback: FC<PlaybackProps> = (props) => { 41 + const [shuffle, setShuffle] = useState(props.shuffle); 42 + const [repeat, setRepeat] = useState(repeatValues[props.repeat]); 43 + const [fadeOnStopPause, setFadeOnStopPause] = useState(props.fadeOnStopPause); 44 + const [crossfade, setCrossfade] = useState(crossfadeValues[props.crossfade]); 45 + const [replaygain, setReplaygain] = useState( 46 + replaygainValues[props.replaygain] 47 + ); 48 + const [fadeInDelay, setFadeInDelay] = useState(props.fadeInDelay); 49 + const [fadeInDuration, setFadeInDuration] = useState(props.fadeInDuration); 50 + const [fadeOutDelay, setFadeOutDelay] = useState(props.fadeOutDelay); 51 + const [fadeOutDuration, setFadeOutDuration] = useState(props.fadeOutDuration); 52 + const [fadeOutMode, setFadeOutMode] = useState( 53 + fadeOutModeValues[props.fadeOutMode] 54 + ); 55 + 56 + useEffect(() => { 57 + setShuffle(props.shuffle); 58 + setRepeat(repeatValues[props.repeat]); 59 + setFadeOnStopPause(props.fadeOnStopPause); 60 + setCrossfade(crossfadeValues[props.crossfade]); 61 + setReplaygain(replaygainValues[props.replaygain]); 62 + setFadeInDelay(props.fadeInDelay); // 0 - 7 s 63 + setFadeInDuration(props.fadeInDuration); // 0 - 15 s 64 + setFadeOutDelay(props.fadeOutDelay); // 0 - 7 s 65 + setFadeOutDuration(props.fadeOutDuration); // 0 - 15 s 66 + setFadeOutMode(fadeOutModeValues[props.fadeOutMode]); // crossfade | mix 67 + }, [ 68 + props.shuffle, 69 + props.repeat, 70 + props.fadeOnStopPause, 71 + props.crossfade, 72 + props.replaygain, 73 + props.fadeInDelay, 74 + props.fadeInDuration, 75 + props.fadeOutDelay, 76 + props.fadeOutDuration, 77 + props.fadeOutMode, 78 + ]); 79 + 80 + const onShuffleChange = (shuffle: boolean) => { 81 + setShuffle(shuffle); 82 + props.onShuffleChange(shuffle); 83 + }; 84 + 85 + const onRepeatChange = (repeat: number) => { 86 + setRepeat(repeatValues[props.repeat]); 87 + props.onRepeatChange(repeat); 88 + }; 89 + 90 + const onFadeOnStopPauseChange = (fadeOnStopPause: boolean) => { 91 + setFadeOnStopPause(fadeOnStopPause); 92 + props.onFadeOnStopPauseChange(fadeOnStopPause); 93 + }; 94 + 95 + const onCrossfadeChange = (crossfade: number) => { 96 + setCrossfade(crossfadeValues[crossfade]); 97 + props.onCrossfadeChange(crossfade); 98 + }; 99 + 100 + const onReplaygainChange = (replaygain: number) => { 101 + setReplaygain(replaygainValues[replaygain]); 102 + props.onReplaygainChange(replaygain); 103 + }; 104 + 105 + const onFadeInDelayChange = (fadeInDelay: number) => { 106 + setFadeInDelay(fadeInDelay); 107 + props.onFadeInDelayChange(fadeInDelay); 108 + }; 109 + 110 + const onFadeInDurationChange = (fadeInDuration: number) => { 111 + setFadeInDuration(fadeInDuration); 112 + props.onFadeInDurationChange(fadeInDuration); 113 + }; 114 + 115 + const onFadeOutDelayChange = (fadeOutDelay: number) => { 116 + setFadeOutDelay(fadeOutDelay); 117 + props.onFadeOutDelayChange(fadeOutDelay); 118 + }; 119 + 120 + const onFadeOutDurationChange = (fadeOutDuration: number) => { 121 + setFadeOutDuration(fadeOutDuration); 122 + props.onFadeOutDurationChange(fadeOutDuration); 123 + }; 124 + 125 + const onFadeOutModeChange = (fadeOutMode: number) => { 126 + setFadeOutMode(fadeOutModeValues[fadeOutMode]); 127 + props.onFadeOutModeChange(fadeOutMode); 128 + }; 129 + 130 + return ( 131 + <> 132 + <SettingsTitle>Playback</SettingsTitle> 133 + <Section> 134 + <Item> 135 + <div>Shuffle</div> 136 + <div> 137 + <Switch 138 + checked={shuffle} 139 + onChange={() => onShuffleChange(!shuffle)} 140 + /> 141 + </div> 142 + </Item> 143 + <Item> 144 + <div>Repeat</div> 145 + <div style={{ width: 280 }}> 146 + <Select 147 + options={repeatOptions} 148 + value={repeat} 149 + onChange={(params) => { 150 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 151 + onRepeatChange((params.value as any)[0].value); 152 + }} 153 + clearable={false} 154 + /> 155 + </div> 156 + </Item> 157 + 158 + <Item> 159 + <div>Fade on Stop/Pause</div> 160 + <div> 161 + <Switch 162 + checked={fadeOnStopPause} 163 + onChange={() => onFadeOnStopPauseChange(!fadeOnStopPause)} 164 + /> 165 + </div> 166 + </Item> 167 + 168 + <Item> 169 + <div>Crossfade</div> 170 + <div style={{ width: 280 }}> 171 + <Select 172 + options={crossfadeOptions} 173 + value={crossfade} 174 + onChange={(params) => { 175 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 176 + onCrossfadeChange((params.value as any)[0].value); 177 + }} 178 + clearable={false} 179 + /> 180 + </div> 181 + </Item> 182 + 183 + <Item> 184 + <div>Fade-In Delay</div> 185 + <div style={{ width: 120 }}> 186 + <Slider 187 + value={fadeInDelay} 188 + onChange={(_event, value) => setFadeInDelay(value as number)} 189 + onChangeCommitted={(_event, value) => 190 + onFadeInDelayChange(value as number) 191 + } 192 + sx={styles.slider} 193 + valueLabelDisplay="auto" 194 + min={0} 195 + max={7} 196 + step={1} 197 + valueLabelFormat={(value) => `${value} s`} 198 + /> 199 + </div> 200 + </Item> 201 + 202 + <Item> 203 + <div>Fade-In Duration</div> 204 + <div style={{ width: 120 }}> 205 + <Slider 206 + value={fadeInDuration} 207 + onChange={(_event, value) => setFadeInDuration(value as number)} 208 + onChangeCommitted={(_event, value) => 209 + onFadeInDurationChange(value as number) 210 + } 211 + sx={styles.slider} 212 + valueLabelDisplay="auto" 213 + min={0} 214 + max={15} 215 + valueLabelFormat={(value) => `${value} s`} 216 + /> 217 + </div> 218 + </Item> 219 + 220 + <Item> 221 + <div>Fade-Out Delay</div> 222 + <div style={{ width: 120 }}> 223 + <Slider 224 + value={fadeOutDelay} 225 + onChange={(_event, value) => setFadeOutDelay(value as number)} 226 + onChangeCommitted={(_event, value) => 227 + onFadeOutDelayChange(value as number) 228 + } 229 + sx={styles.slider} 230 + valueLabelDisplay="auto" 231 + min={0} 232 + max={7} 233 + step={1} 234 + valueLabelFormat={(value) => `${value} s`} 235 + /> 236 + </div> 237 + </Item> 238 + <Item> 239 + <div>Fade-Out Duration</div> 240 + <div style={{ width: 120 }}> 241 + <Slider 242 + value={fadeOutDuration} 243 + onChange={(_event, value) => setFadeOutDuration(value as number)} 244 + onChangeCommitted={(_event, value) => 245 + onFadeOutDurationChange(value as number) 246 + } 247 + sx={styles.slider} 248 + valueLabelDisplay="auto" 249 + min={0} 250 + max={15} 251 + valueLabelFormat={(value) => `${value} s`} 252 + /> 253 + </div> 254 + </Item> 255 + <Item> 256 + <div>Fade-Out Mode</div> 257 + <div style={{ width: 280 }}> 258 + <Select 259 + options={fadeOutModeOptions} 260 + value={fadeOutMode} 261 + onChange={(params) => { 262 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 263 + onFadeOutModeChange((params.value as any)[0].value); 264 + }} 265 + clearable={false} 266 + /> 267 + </div> 268 + </Item> 269 + 270 + <Item> 271 + <div>Replaygain</div> 272 + <div style={{ width: 280 }}> 273 + <Select 274 + options={replaygainOptions} 275 + value={replaygain} 276 + onChange={(params) => { 277 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 278 + onReplaygainChange((params.value as any)[0].value); 279 + }} 280 + clearable={false} 281 + /> 282 + </div> 283 + </Item> 284 + </Section> 285 + </> 286 + ); 287 + }; 288 + 289 + export default Playback;
+141
webui/rockbox/src/Components/Settings/Playback/PlaybackWithData.tsx
··· 1 + import { FC } from "react"; 2 + import Playback from "./Playback"; 3 + import { useRecoilState } from "recoil"; 4 + import { settingsState } from "../SettingsState"; 5 + import { useSaveSettingsMutation } from "../../../Hooks/GraphQL"; 6 + 7 + const PlaybackWithData: FC = () => { 8 + const [settings] = useRecoilState(settingsState); 9 + const [saveSettings] = useSaveSettingsMutation(); 10 + 11 + const onShuffleChange = (playlistShuffle: boolean) => { 12 + saveSettings({ 13 + variables: { 14 + settings: { 15 + playlistShuffle, 16 + }, 17 + }, 18 + }); 19 + }; 20 + 21 + const onRepeatChange = (repeatMode: number) => { 22 + saveSettings({ 23 + variables: { 24 + settings: { 25 + repeatMode, 26 + }, 27 + }, 28 + }); 29 + }; 30 + 31 + const onCrossfadeChange = (crossfade: number) => { 32 + saveSettings({ 33 + variables: { 34 + settings: { 35 + crossfade, 36 + }, 37 + }, 38 + }); 39 + }; 40 + 41 + const onFadeOnStopPauseChange = (fadeOnStop: boolean) => { 42 + saveSettings({ 43 + variables: { 44 + settings: { 45 + fadeOnStop, 46 + }, 47 + }, 48 + }); 49 + }; 50 + 51 + const onReplaygainChange = (replaygain: number) => { 52 + saveSettings({ 53 + variables: { 54 + settings: { 55 + replaygainSettings: { 56 + type: replaygain, 57 + preamp: settings.replaygainSettings.preamp, 58 + noclip: settings.replaygainSettings.noclip, 59 + }, 60 + }, 61 + }, 62 + }); 63 + }; 64 + 65 + const onFadeInDelayChange = (fadeInDelay: number) => { 66 + saveSettings({ 67 + variables: { 68 + settings: { 69 + fadeInDelay, 70 + }, 71 + }, 72 + }); 73 + }; 74 + 75 + const onFadeInDurationChange = (fadeInDuration: number) => { 76 + saveSettings({ 77 + variables: { 78 + settings: { 79 + fadeInDuration, 80 + }, 81 + }, 82 + }); 83 + }; 84 + 85 + const onFadeOutDelayChange = (fadeOutDelay: number) => { 86 + saveSettings({ 87 + variables: { 88 + settings: { 89 + fadeOutDelay, 90 + }, 91 + }, 92 + }); 93 + }; 94 + 95 + const onFadeOutDurationChange = (fadeOutDuration: number) => { 96 + saveSettings({ 97 + variables: { 98 + settings: { 99 + fadeOutDuration, 100 + }, 101 + }, 102 + }); 103 + }; 104 + 105 + const onFadeOutModeChange = (fadeOutMixmode: number) => { 106 + saveSettings({ 107 + variables: { 108 + settings: { 109 + fadeOutMixmode, 110 + }, 111 + }, 112 + }); 113 + }; 114 + 115 + return ( 116 + <Playback 117 + shuffle={settings.playlistShuffle} 118 + repeat={settings.repeatMode} 119 + fadeOnStopPause={settings.fadeOnStop} 120 + crossfade={settings.crossfade} 121 + replaygain={settings.replaygainSettings.type} 122 + fadeInDelay={settings.crossfadeFadeInDelay} 123 + fadeInDuration={settings.crossfadeFadeInDuration} 124 + fadeOutDelay={settings.crossfadeFadeOutDelay} 125 + fadeOutDuration={settings.crossfadeFadeOutDuration} 126 + fadeOutMode={settings.crossfadeFadeOutMixmode} 127 + onShuffleChange={onShuffleChange} 128 + onRepeatChange={onRepeatChange} 129 + onCrossfadeChange={onCrossfadeChange} 130 + onFadeOnStopPauseChange={onFadeOnStopPauseChange} 131 + onReplaygainChange={onReplaygainChange} 132 + onFadeInDelayChange={onFadeInDelayChange} 133 + onFadeInDurationChange={onFadeInDurationChange} 134 + onFadeOutDelayChange={onFadeOutDelayChange} 135 + onFadeOutDurationChange={onFadeOutDurationChange} 136 + onFadeOutModeChange={onFadeOutModeChange} 137 + /> 138 + ); 139 + }; 140 + 141 + export default PlaybackWithData;
+187
webui/rockbox/src/Components/Settings/Playback/consts.ts
··· 1 + import { 2 + REPEAT_OFF, 3 + REPEAT_ALL, 4 + REPEAT_ONE, 5 + REPEAT_SHUFFLE, 6 + REPEAT_AB, 7 + CROSSFADE_OFF, 8 + CROSSFADE_ENABLE_AUTOSKIP, 9 + CROSSFADE_ENABLE_MANSKIP, 10 + CROSSFADE_ENABLE_SHUFFLE, 11 + CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP, 12 + CROSSFADE_ENABLE_ALWAYS, 13 + FADE_OUT_CROSSFADE_MODE, 14 + FADE_OUT_MIX_MODE, 15 + REPLAYGAIN_TRACK, 16 + REPLAYGAIN_ALBUM, 17 + REPLAYGAIN_SHUFFLE, 18 + REPLAYGAIN_OFF, 19 + } from "../../../constants"; 20 + 21 + export const repeatOptions = [ 22 + { 23 + label: "Off", 24 + id: "1", 25 + value: REPEAT_OFF, 26 + }, 27 + { 28 + label: "All", 29 + id: "2", 30 + value: REPEAT_ALL, 31 + }, 32 + { 33 + label: "One", 34 + id: "3", 35 + value: REPEAT_ONE, 36 + }, 37 + { 38 + label: "Shuffle", 39 + id: "4", 40 + value: REPEAT_SHUFFLE, 41 + }, 42 + { 43 + label: "A-B", 44 + id: "5", 45 + value: REPEAT_AB, 46 + }, 47 + ]; 48 + 49 + export const repeatValues: Record< 50 + number, 51 + { label: string; id: string; value: number }[] 52 + > = { 53 + [REPEAT_OFF]: [{ label: "Off", id: "1", value: REPEAT_OFF }], 54 + [REPEAT_ALL]: [{ label: "All", id: "2", value: REPEAT_ALL }], 55 + [REPEAT_ONE]: [{ label: "One", id: "3", value: REPEAT_ONE }], 56 + [REPEAT_SHUFFLE]: [{ label: "Shuffle", id: "4", value: REPEAT_SHUFFLE }], 57 + [REPEAT_AB]: [{ label: "A-B", id: "5", value: REPEAT_AB }], 58 + }; 59 + 60 + export const crossfadeOptions = [ 61 + { 62 + label: "Off", 63 + id: "1", 64 + value: CROSSFADE_OFF, 65 + }, 66 + { 67 + label: "Automatic Track Change Only", 68 + id: "2", 69 + value: CROSSFADE_ENABLE_AUTOSKIP, 70 + }, 71 + { 72 + label: "Manual Track Change Only", 73 + id: "3", 74 + value: CROSSFADE_ENABLE_MANSKIP, 75 + }, 76 + { 77 + label: "Shuffle", 78 + id: "4", 79 + value: CROSSFADE_ENABLE_SHUFFLE, 80 + }, 81 + { 82 + label: "Shuffle or Manual Track Skip", 83 + id: "5", 84 + value: CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP, 85 + }, 86 + { 87 + label: "Always", 88 + id: "6", 89 + value: CROSSFADE_ENABLE_ALWAYS, 90 + }, 91 + ]; 92 + 93 + export const crossfadeValues: Record< 94 + number, 95 + { label: string; id: string; value: number }[] 96 + > = { 97 + [CROSSFADE_OFF]: [{ label: "Off", id: "1", value: CROSSFADE_OFF }], 98 + [CROSSFADE_ENABLE_AUTOSKIP]: [ 99 + { 100 + label: "Automatic Track Change Only", 101 + id: "2", 102 + value: CROSSFADE_ENABLE_AUTOSKIP, 103 + }, 104 + ], 105 + [CROSSFADE_ENABLE_MANSKIP]: [ 106 + { 107 + label: "Manual Track Change Only", 108 + id: "3", 109 + value: CROSSFADE_ENABLE_MANSKIP, 110 + }, 111 + ], 112 + [CROSSFADE_ENABLE_SHUFFLE]: [ 113 + { label: "Shuffle", id: "4", value: CROSSFADE_ENABLE_SHUFFLE }, 114 + ], 115 + [CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP]: [ 116 + { 117 + label: "Shuffle or Manual Track Skip", 118 + id: "5", 119 + value: CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP, 120 + }, 121 + ], 122 + [CROSSFADE_ENABLE_ALWAYS]: [ 123 + { label: "Always", id: "6", value: CROSSFADE_ENABLE_ALWAYS }, 124 + ], 125 + }; 126 + 127 + export const fadeOutModeOptions = [ 128 + { 129 + label: "Crossfade", 130 + id: "1", 131 + value: FADE_OUT_CROSSFADE_MODE, 132 + }, 133 + { 134 + label: "Mix", 135 + id: "2", 136 + value: FADE_OUT_MIX_MODE, 137 + }, 138 + ]; 139 + 140 + export const fadeOutModeValues: Record< 141 + number, 142 + { label: string; id: string; value: number }[] 143 + > = { 144 + [FADE_OUT_CROSSFADE_MODE]: [ 145 + { label: "Crossfade", id: "1", value: FADE_OUT_CROSSFADE_MODE }, 146 + ], 147 + [FADE_OUT_MIX_MODE]: [{ label: "Mix", id: "2", value: FADE_OUT_MIX_MODE }], 148 + }; 149 + 150 + export const replaygainOptions = [ 151 + { 152 + label: "Track Gain", 153 + id: "1", 154 + value: REPLAYGAIN_TRACK, 155 + }, 156 + { 157 + label: "Album Gain", 158 + id: "2", 159 + value: REPLAYGAIN_ALBUM, 160 + }, 161 + { 162 + label: "Track Gain if Shuffling", 163 + id: "3", 164 + value: REPLAYGAIN_SHUFFLE, 165 + }, 166 + { 167 + label: "Off", 168 + id: "4", 169 + value: REPLAYGAIN_OFF, 170 + }, 171 + ]; 172 + 173 + export const replaygainValues: Record< 174 + number, 175 + { label: string; id: string; value: number }[] 176 + > = { 177 + [REPLAYGAIN_TRACK]: [ 178 + { label: "Track Gain", id: "1", value: REPLAYGAIN_TRACK }, 179 + ], 180 + [REPLAYGAIN_ALBUM]: [ 181 + { label: "Album Gain", id: "2", value: REPLAYGAIN_ALBUM }, 182 + ], 183 + [REPLAYGAIN_SHUFFLE]: [ 184 + { label: "Track Gain if Shuffling", id: "3", value: REPLAYGAIN_SHUFFLE }, 185 + ], 186 + [REPLAYGAIN_OFF]: [{ label: "Off", id: "4", value: REPLAYGAIN_OFF }], 187 + };
+3
webui/rockbox/src/Components/Settings/Playback/index.tsx
··· 1 + import Playback from "./PlaybackWithData"; 2 + 3 + export default Playback;
+130
webui/rockbox/src/Components/Settings/Playback/styles.tsx
··· 1 + import styled from "@emotion/styled"; 2 + 3 + export const Container = styled.div` 4 + display: flex; 5 + flex-direction: row; 6 + width: 100%; 7 + height: 100%; 8 + `; 9 + 10 + export const Title = styled.div` 11 + font-size: 24px; 12 + font-family: RockfordSansMedium; 13 + margin: auto; 14 + margin-bottom: 40px; 15 + `; 16 + 17 + export const Scrollable = styled.div` 18 + height: calc(100vh - 60px); 19 + overflow-y: auto; 20 + `; 21 + 22 + export const Wrapper = styled.div` 23 + width: 60vw; 24 + margin: 0 auto; 25 + margin-bottom: 100px; 26 + margin-top: 30px; 27 + `; 28 + 29 + export const SettingsTitle = styled.div` 30 + font-size: 16px; 31 + font-weight: 600; 32 + margin-bottom: 16px; 33 + `; 34 + 35 + export const Section = styled.div` 36 + margin-bottom: 50px; 37 + font-size: 15px; 38 + border: 1px solid #8a8a8a65; 39 + border-radius: 10px; 40 + padding-left: 20px; 41 + padding-right: 20px; 42 + padding-top: 5px; 43 + padding-bottom: 5px; 44 + `; 45 + 46 + export const Item = styled.div` 47 + display: flex; 48 + flex-direction: row; 49 + align-items: center; 50 + justify-content: space-between; 51 + min-height: 80px; 52 + `; 53 + 54 + const iOSBoxShadow = 55 + "0 3px 1px rgba(0,0,0,0.1),0 4px 8px rgba(0,0,0,0.13),0 0 0 1px rgba(0,0,0,0.02)"; 56 + 57 + export default { 58 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 59 + slider: (t: any) => ({ 60 + color: "rgba(0, 0, 0, 0.682)", 61 + "& .MuiSlider-track": { 62 + border: "none", 63 + }, 64 + "& .MuiSlider-thumb": { 65 + width: 18, 66 + height: 18, 67 + backgroundColor: "#fff", 68 + "&::before": { 69 + boxShadow: "0 4px 8px rgba(0,0,0,0.18)", 70 + }, 71 + "&:hover, &.Mui-focusVisible, &.Mui-active": { 72 + boxShadow: "none", 73 + }, 74 + }, 75 + ...t.applyStyles("dark", { 76 + color: "#fff", 77 + }), 78 + }), 79 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 80 + sliderIOS: (theme: any) => ({ 81 + color: "#7C00FE", 82 + 83 + "& .MuiSlider-thumb": { 84 + height: 20, 85 + width: 20, 86 + backgroundColor: "#fff", 87 + boxShadow: "0 0 2px 0px rgba(0, 0, 0, 0.1)", 88 + "&:focus, &:hover, &.Mui-active": { 89 + boxShadow: "0px 0px 3px 1px rgba(0, 0, 0, 0.1)", 90 + // Reset on touch devices, it doesn't add specificity 91 + "@media (hover: none)": { 92 + boxShadow: iOSBoxShadow, 93 + }, 94 + }, 95 + "&:before": { 96 + boxShadow: 97 + "0px 0px 1px 0px rgba(0,0,0,0.2), 0px 0px 0px 0px rgba(0,0,0,0.14), 0px 0px 1px 0px rgba(0,0,0,0.12)", 98 + }, 99 + }, 100 + "& .MuiSlider-valueLabel": { 101 + fontSize: 12, 102 + fontWeight: "normal", 103 + top: -6, 104 + backgroundColor: "unset", 105 + color: theme.palette.text.primary, 106 + "&::before": { 107 + display: "none", 108 + }, 109 + "& *": { 110 + background: "transparent", 111 + color: "#000", 112 + ...theme.applyStyles("dark", { 113 + color: "#fff", 114 + }), 115 + }, 116 + }, 117 + "& .MuiSlider-track": { 118 + border: "none", 119 + height: 5, 120 + }, 121 + "& .MuiSlider-rail": { 122 + opacity: 0.5, 123 + boxShadow: "inset 0px 0px 4px -2px #000", 124 + backgroundColor: "#d0d0d0", 125 + }, 126 + ...theme.applyStyles("dark", { 127 + color: "#7C00FE", 128 + }), 129 + }), 130 + };
+30
webui/rockbox/src/Components/Settings/Settings.tsx
··· 1 + /* eslint-disable @typescript-eslint/no-explicit-any */ 2 + import { FC } from "react"; 3 + import Sidebar from "../Sidebar"; 4 + import MainView from "../MainView"; 5 + import ControlBar from "../ControlBar"; 6 + import { Container, Scrollable, Title, Wrapper } from "./styles"; 7 + import Sound from "./Sound"; 8 + import Library from "./Library"; 9 + import Playback from "./Playback"; 10 + 11 + const Settings: FC = () => { 12 + return ( 13 + <Container> 14 + <Sidebar active="settings" /> 15 + <MainView> 16 + <ControlBar /> 17 + <Scrollable> 18 + <Wrapper> 19 + <Title>Settings</Title> 20 + <Library /> 21 + <Sound /> 22 + <Playback /> 23 + </Wrapper> 24 + </Scrollable> 25 + </MainView> 26 + </Container> 27 + ); 28 + }; 29 + 30 + export default Settings;
+68
webui/rockbox/src/Components/Settings/SettingsState.ts
··· 2 2 3 3 export const settingsState = atom<{ 4 4 enableBlur: boolean; 5 + eqBandSettings: { 6 + q: number; 7 + gain: number; 8 + cutoff: number; 9 + }[]; 10 + eqEnabled: boolean; 11 + volume: number; 12 + bass: number; 13 + bassCutoff: number; 14 + treble: number; 15 + trebleCutoff: number; 16 + playlistShuffle: boolean; 17 + repeatMode: number; 18 + replaygainSettings: { 19 + noclip: boolean; 20 + type: number; 21 + preamp: number; 22 + }; 23 + playerName: string; 24 + partyMode: boolean; 25 + ditheringEnabled: boolean; 26 + channelConfig: number; 27 + balance: number; 28 + crossfade: number; 29 + fadeOnStop: boolean; 30 + crossfadeFadeInDelay: number; 31 + crossfadeFadeInDuration: number; 32 + crossfadeFadeOutDelay: number; 33 + crossfadeFadeOutDuration: number; 34 + crossfadeFadeOutMixmode: number; 35 + stereoWidth: number; 36 + stereoswMode: number; 37 + surroundEnabled: number; 38 + surroundBalance: number; 39 + surroundFx1: number; 40 + surroundFx2: boolean; 5 41 }>({ 6 42 key: "settings", 7 43 default: { 8 44 enableBlur: false, 45 + eqBandSettings: [], 46 + eqEnabled: false, 47 + volume: 0, 48 + bass: 0, 49 + bassCutoff: 0, 50 + treble: 0, 51 + trebleCutoff: 0, 52 + playlistShuffle: false, 53 + repeatMode: 0, 54 + replaygainSettings: { 55 + noclip: false, 56 + type: 0, 57 + preamp: 0, 58 + }, 59 + playerName: "", 60 + partyMode: false, 61 + ditheringEnabled: false, 62 + channelConfig: 0, 63 + balance: 0, 64 + stereoswMode: 0, 65 + stereoWidth: 0, 66 + surroundEnabled: 0, 67 + surroundBalance: 0, 68 + surroundFx1: 0, 69 + surroundFx2: false, 70 + crossfade: 0, 71 + fadeOnStop: false, 72 + crossfadeFadeInDelay: 0, 73 + crossfadeFadeInDuration: 0, 74 + crossfadeFadeOutDelay: 0, 75 + crossfadeFadeOutDuration: 0, 76 + crossfadeFadeOutMixmode: 0, 9 77 }, 10 78 });
+52
webui/rockbox/src/Components/Settings/SettingsWithData.tsx
··· 1 + import { FC, useEffect } from "react"; 2 + import Settings from "./Settings"; 3 + import { useGetGlobalSettingsQuery } from "../../Hooks/GraphQL"; 4 + import { useRecoilState } from "recoil"; 5 + import { settingsState } from "./SettingsState"; 6 + 7 + const SettingsWithData: FC = () => { 8 + const [, setSettings] = useRecoilState(settingsState); 9 + const { data, loading } = useGetGlobalSettingsQuery(); 10 + 11 + useEffect(() => { 12 + if (!data || loading) { 13 + return; 14 + } 15 + setSettings((state) => ({ 16 + ...state, 17 + eqEnabled: data.globalSettings.eqEnabled, 18 + eqBandSettings: data.globalSettings.eqBandSettings, 19 + volume: data.globalSettings.volume, 20 + bass: data.globalSettings.bass, 21 + bassCutoff: data.globalSettings.bassCutoff, 22 + treble: data.globalSettings.treble, 23 + trebleCutoff: data.globalSettings.trebleCutoff, 24 + playlistShuffle: data.globalSettings.playlistShuffle, 25 + repeatMode: data.globalSettings.repeatMode, 26 + replaygainSettings: data.globalSettings.replaygainSettings, 27 + playerName: data.globalSettings.playerName, 28 + partyMode: data.globalSettings.partyMode, 29 + ditheringEnabled: data.globalSettings.ditheringEnabled, 30 + channelConfig: data.globalSettings.channelConfig, 31 + balance: data.globalSettings.balance, 32 + fadeOnStop: data.globalSettings.fadeOnStop, 33 + crossfade: data.globalSettings.crossfade, 34 + crossfadeFadeInDelay: data.globalSettings.crossfadeFadeInDelay, 35 + crossfadeFadeInDuration: data.globalSettings.crossfadeFadeInDuration, 36 + crossfadeFadeOutDelay: data.globalSettings.crossfadeFadeOutDelay, 37 + crossfadeFadeOutDuration: data.globalSettings.crossfadeFadeOutDuration, 38 + crossfadeFadeOutMixmode: data.globalSettings.crossfadeFadeOutMixmode, 39 + stereoWidth: data.globalSettings.stereoWidth, 40 + stereoswMode: data.globalSettings.stereoswMode, 41 + surroundEnabled: data.globalSettings.surroundEnabled, 42 + surroundBalance: data.globalSettings.surroundBalance, 43 + surroundFx1: data.globalSettings.surroundFx1, 44 + surroundFx2: data.globalSettings.surroundFx2, 45 + })); 46 + // eslint-disable-next-line react-hooks/exhaustive-deps 47 + }, [data, loading]); 48 + 49 + return <Settings />; 50 + }; 51 + 52 + export default SettingsWithData;
+127
webui/rockbox/src/Components/Settings/Sound/Equalizer/Equalizer.tsx
··· 1 + import { FC, useEffect, useState } from "react"; 2 + import styles, { Container, Item } from "./styles"; 3 + import Switch from "../../../Switch"; 4 + import { Slider } from "@mui/material"; 5 + import BigNumber from "bignumber.js"; 6 + 7 + export type EqualizerProps = { 8 + eqEnabled: boolean; 9 + onEnableEq: (enabled: boolean) => void; 10 + eqBandSettings: { 11 + q: number; 12 + gain: number; 13 + cutoff: number; 14 + }[]; 15 + onEqBandSettingsChange: ( 16 + bandSettings: { 17 + q: number; 18 + gain: number; 19 + cutoff: number; 20 + }[] 21 + ) => void; 22 + }; 23 + 24 + const Equalizer: FC<EqualizerProps> = (props) => { 25 + const [eqEnabled, setEqEnabled] = useState(props.eqEnabled); 26 + const [eqBandSettings, setEqBandSettings] = useState< 27 + { 28 + q: number; 29 + gain: number; 30 + cutoff: number; 31 + }[] 32 + >(props.eqBandSettings); 33 + 34 + useEffect(() => { 35 + setEqEnabled(props.eqEnabled); 36 + setEqBandSettings(props.eqBandSettings); 37 + }, [props.eqEnabled, props.eqBandSettings]); 38 + 39 + const formatLabel = (value: number) => { 40 + const labels: Record<number, string> = { 41 + 64: "1 Hz", 42 + 125: "64 Hz", 43 + 250: "125 Hz", 44 + 500: "250 Hz", 45 + 1000: "500 Hz", 46 + 2000: "1 kHz", 47 + 4000: "2 kHz", 48 + 8000: "4 kHz", 49 + 16000: "8 kHz", 50 + 0: "16 kHz", 51 + }; 52 + return `${labels[value]}`; 53 + }; 54 + 55 + const computeCutOff = (cutoff: number) => { 56 + // -24 dB to 24 dB 57 + return ((cutoff + 24) / 48) * 100; 58 + }; 59 + 60 + const handleChange = (value: number, index: number) => { 61 + const newBandSettings = [...eqBandSettings]; 62 + newBandSettings[index] = { 63 + ...newBandSettings[index], 64 + cutoff: Math.floor(((value / 100) * 48 - 24) * 10), 65 + }; 66 + setEqBandSettings(newBandSettings); 67 + }; 68 + 69 + const onChangeCommitted = (value: number, index: number) => { 70 + const newBandSettings = [...eqBandSettings]; 71 + newBandSettings[index] = { 72 + ...newBandSettings[index], 73 + cutoff: Math.floor(((value / 100) * 48 - 24) * 10), 74 + }; 75 + props.onEqBandSettingsChange(newBandSettings); 76 + }; 77 + 78 + return ( 79 + <> 80 + <Item> 81 + <div>Equalizer</div> 82 + <div> 83 + <Switch 84 + checked={eqEnabled} 85 + onChange={() => { 86 + props.onEnableEq(!eqEnabled); 87 + setEqEnabled(!eqEnabled); 88 + }} 89 + /> 90 + </div> 91 + </Item> 92 + <div> 93 + <Container> 94 + {eqBandSettings.map((band, index) => ( 95 + <div key={index}> 96 + <Slider 97 + value={computeCutOff(band.cutoff * 0.1)} 98 + onChange={(_event, value) => 99 + handleChange(value as number, index) 100 + } 101 + onChangeCommitted={(_event, value) => 102 + onChangeCommitted(value as number, index) 103 + } 104 + sx={styles.sliderIOS} 105 + valueLabelDisplay="auto" 106 + orientation="vertical" 107 + min={0} 108 + max={100} 109 + step={0.1} 110 + valueLabelFormat={(value) => 111 + `${new BigNumber(value) 112 + .dividedBy(100) 113 + .multipliedBy(48) 114 + .minus(24) 115 + .toPrecision(2, 1)} dB` 116 + } 117 + /> 118 + <div>{formatLabel(band.q)}</div> 119 + </div> 120 + ))} 121 + </Container> 122 + </div> 123 + </> 124 + ); 125 + }; 126 + 127 + export default Equalizer;
+47
webui/rockbox/src/Components/Settings/Sound/Equalizer/EqualizerWithData.tsx
··· 1 + import { FC } from "react"; 2 + import Equalizer from "./Equalizer"; 3 + import { useRecoilState } from "recoil"; 4 + import { settingsState } from "../../SettingsState"; 5 + import { useSaveSettingsMutation } from "../../../../Hooks/GraphQL"; 6 + 7 + const EqualizerWithData: FC = () => { 8 + const [settings] = useRecoilState(settingsState); 9 + const [saveSettings] = useSaveSettingsMutation(); 10 + 11 + const onEnableEq = (eqEnabled: boolean) => { 12 + saveSettings({ 13 + variables: { 14 + settings: { 15 + eqEnabled, 16 + }, 17 + }, 18 + }); 19 + }; 20 + 21 + const onEqBandSettingsChange = ( 22 + eqBandSettings: { 23 + q: number; 24 + gain: number; 25 + cutoff: number; 26 + }[] 27 + ) => { 28 + saveSettings({ 29 + variables: { 30 + settings: { 31 + eqBandSettings, 32 + }, 33 + }, 34 + }); 35 + }; 36 + 37 + return ( 38 + <Equalizer 39 + eqEnabled={settings.eqEnabled} 40 + onEnableEq={onEnableEq} 41 + eqBandSettings={settings.eqBandSettings} 42 + onEqBandSettingsChange={onEqBandSettingsChange} 43 + /> 44 + ); 45 + }; 46 + 47 + export default EqualizerWithData;
+3
webui/rockbox/src/Components/Settings/Sound/Equalizer/index.tsx
··· 1 + import Equalizer from "./EqualizerWithData"; 2 + 3 + export default Equalizer;
+112
webui/rockbox/src/Components/Settings/Sound/Equalizer/styles.tsx
··· 1 + import styled from "@emotion/styled"; 2 + 3 + export const Container = styled.div` 4 + height: 250px; 5 + margin: 0 auto; 6 + margin-top: 50px; 7 + margin-bottom: 120px; 8 + display: flex; 9 + flex-direction: row; 10 + justify-content: space-between; 11 + width: 73%; 12 + font-size: 13px; 13 + `; 14 + 15 + export const Title = styled.div` 16 + font-size: 24px; 17 + font-family: RockfordSansMedium; 18 + margin: auto; 19 + margin-bottom: 40px; 20 + `; 21 + 22 + export const Scrollable = styled.div` 23 + height: calc(100vh - 60px); 24 + overflow-y: auto; 25 + `; 26 + 27 + export const Wrapper = styled.div` 28 + width: 60vw; 29 + margin: 0 auto; 30 + margin-bottom: 100px; 31 + margin-top: 30px; 32 + `; 33 + 34 + export const SettingsTitle = styled.div` 35 + font-size: 16px; 36 + font-weight: 600; 37 + margin-bottom: 5px; 38 + `; 39 + 40 + export const Section = styled.div` 41 + margin-bottom: 50px; 42 + font-size: 15px; 43 + `; 44 + 45 + export const Item = styled.div` 46 + display: flex; 47 + flex-direction: row; 48 + align-items: center; 49 + justify-content: space-between; 50 + height: 50px; 51 + `; 52 + 53 + const iOSBoxShadow = 54 + "0 3px 1px rgba(0,0,0,0.1),0 4px 8px rgba(0,0,0,0.13),0 0 0 1px rgba(0,0,0,0.02)"; 55 + 56 + export default { 57 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 58 + slider: (t: any) => ({ 59 + color: "rgba(0, 0, 0, 0.682)", 60 + "& .MuiSlider-track": { 61 + border: "none", 62 + }, 63 + "& .MuiSlider-thumb": { 64 + width: 18, 65 + height: 18, 66 + backgroundColor: "#fff", 67 + "&::before": { 68 + boxShadow: "0 4px 8px rgba(0,0,0,0.18)", 69 + }, 70 + "&:hover, &.Mui-focusVisible, &.Mui-active": { 71 + boxShadow: "none", 72 + }, 73 + }, 74 + ...t.applyStyles("dark", { 75 + color: "#fff", 76 + }), 77 + }), 78 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 79 + sliderIOS: (theme: any) => ({ 80 + color: "rgb(254, 9, 156)", 81 + 82 + "& .MuiSlider-thumb": { 83 + height: 20, 84 + width: 20, 85 + backgroundColor: "#fff", 86 + boxShadow: "0 0 2px 0px rgba(0, 0, 0, 0.1)", 87 + "&:focus, &:hover, &.Mui-active": { 88 + boxShadow: "0px 0px 3px 1px rgba(0, 0, 0, 0.1)", 89 + // Reset on touch devices, it doesn't add specificity 90 + "@media (hover: none)": { 91 + boxShadow: iOSBoxShadow, 92 + }, 93 + }, 94 + "&:before": { 95 + boxShadow: 96 + "0px 0px 1px 0px rgba(0,0,0,0.2), 0px 0px 0px 0px rgba(0,0,0,0.14), 0px 0px 1px 0px rgba(0,0,0,0.12)", 97 + }, 98 + }, 99 + "& .MuiSlider-track": { 100 + border: "none", 101 + height: 5, 102 + }, 103 + "& .MuiSlider-rail": { 104 + opacity: 0.5, 105 + boxShadow: "inset 0px 0px 4px -2px #000", 106 + backgroundColor: "#d0d0d0", 107 + }, 108 + ...theme.applyStyles("dark", { 109 + color: "rgb(254, 9, 156)", 110 + }), 111 + }), 112 + };
+123
webui/rockbox/src/Components/Settings/Sound/Sound.tsx
··· 1 + import { FC, useEffect, useState } from "react"; 2 + import styles, { Item, Section, SettingsTitle } from "./styles"; 3 + import { Slider } from "@mui/material"; 4 + import Equalizer from "./Equalizer"; 5 + 6 + export type SoundProps = { 7 + bass: number; 8 + treble: number; 9 + balance: number; 10 + onBassChange: (bass: number) => void; 11 + onTrebleChange: (treble: number) => void; 12 + onBalanceChange: (balance: number) => void; 13 + }; 14 + 15 + const Sound: FC<SoundProps> = (props) => { 16 + const [bass, setBass] = useState(props.bass); 17 + const [treble, setTreble] = useState(props.treble); 18 + const [balance, setBalance] = useState(props.balance); 19 + 20 + useEffect(() => { 21 + setBass(props.bass); 22 + setTreble(props.treble); 23 + setBalance(props.balance); 24 + }, [props.bass, props.treble, props.balance]); 25 + 26 + const computeCutOff = (cutoff: number) => { 27 + // -24 dB to 24 dB 28 + return ((cutoff + 24) / 48) * 100; 29 + }; 30 + 31 + const handleBassChange = (value: number) => { 32 + setBass(Math.floor((value / 100) * 48 - 24)); 33 + }; 34 + 35 + const handleTrebleChange = (value: number) => { 36 + setTreble(Math.floor((value / 100) * 48 - 24)); 37 + }; 38 + 39 + const handleBalanceChange = (value: number) => { 40 + setBalance(value); 41 + }; 42 + 43 + const onBassChangeCommitted = (value: number) => { 44 + props.onBassChange(Math.floor((value / 100) * 48 - 24)); 45 + }; 46 + 47 + const onTrebleChangeCommitted = (value: number) => { 48 + props.onTrebleChange(Math.floor((value / 100) * 48 - 24)); 49 + }; 50 + 51 + const onBalanceChangeCommitted = (value: number) => { 52 + props.onBalanceChange(value); 53 + }; 54 + 55 + return ( 56 + <> 57 + <SettingsTitle>Sound</SettingsTitle> 58 + <Section> 59 + <Item> 60 + <div>Bass</div> 61 + <div style={{ width: 120 }}> 62 + <Slider 63 + value={computeCutOff(bass)} 64 + onChange={(_event, value) => handleBassChange(value as number)} 65 + onChangeCommitted={(_event, value) => 66 + onBassChangeCommitted(value as number) 67 + } 68 + sx={styles.slider} 69 + valueLabelDisplay="auto" 70 + valueLabelFormat={(value) => 71 + `${Math.floor((value / 100) * 48 - 24)} dB` 72 + } 73 + min={0} 74 + max={100} 75 + step={1} 76 + /> 77 + </div> 78 + </Item> 79 + <Item> 80 + <div>Treble</div> 81 + <div style={{ width: 120 }}> 82 + <Slider 83 + value={computeCutOff(treble)} 84 + onChange={(_event, value) => handleTrebleChange(value as number)} 85 + onChangeCommitted={(_event, value) => 86 + onTrebleChangeCommitted(value as number) 87 + } 88 + sx={styles.slider} 89 + valueLabelDisplay="auto" 90 + valueLabelFormat={(value) => 91 + `${Math.floor((value / 100) * 48 - 24)} dB` 92 + } 93 + min={0} 94 + max={100} 95 + step={1} 96 + /> 97 + </div> 98 + </Item> 99 + <Item> 100 + <div>Balance</div> 101 + <div style={{ width: 120 }}> 102 + <Slider 103 + value={balance} 104 + onChange={(_event, value) => handleBalanceChange(value as number)} 105 + onChangeCommitted={(_event, value) => 106 + onBalanceChangeCommitted(value as number) 107 + } 108 + sx={styles.slider} 109 + valueLabelDisplay="auto" 110 + valueLabelFormat={(value) => `${value} %`} 111 + min={-100} 112 + max={100} 113 + step={1} 114 + /> 115 + </div> 116 + </Item> 117 + <Equalizer /> 118 + </Section> 119 + </> 120 + ); 121 + }; 122 + 123 + export default Sound;
+51
webui/rockbox/src/Components/Settings/Sound/SoundWithData.tsx
··· 1 + import { FC } from "react"; 2 + import Sound from "./Sound"; 3 + import { settingsState } from "../SettingsState"; 4 + import { useRecoilState } from "recoil"; 5 + import { useSaveSettingsMutation } from "../../../Hooks/GraphQL"; 6 + 7 + const SoundWithData: FC = () => { 8 + const [settings] = useRecoilState(settingsState); 9 + const [saveSettings] = useSaveSettingsMutation(); 10 + 11 + const onBalanceChange = (balance: number) => { 12 + saveSettings({ 13 + variables: { 14 + settings: { 15 + balance, 16 + }, 17 + }, 18 + }); 19 + }; 20 + 21 + const onBassChange = (bass: number) => { 22 + saveSettings({ 23 + variables: { 24 + settings: { 25 + bass, 26 + }, 27 + }, 28 + }); 29 + }; 30 + const onTrebleChange = (treble: number) => { 31 + saveSettings({ 32 + variables: { 33 + settings: { 34 + treble, 35 + }, 36 + }, 37 + }); 38 + }; 39 + return ( 40 + <Sound 41 + bass={settings.bass} 42 + treble={settings.treble} 43 + balance={settings.balance} 44 + onBalanceChange={onBalanceChange} 45 + onBassChange={onBassChange} 46 + onTrebleChange={onTrebleChange} 47 + /> 48 + ); 49 + }; 50 + 51 + export default SoundWithData;
+3
webui/rockbox/src/Components/Settings/Sound/index.tsx
··· 1 + import Sound from "./SoundWithData"; 2 + 3 + export default Sound;
+130
webui/rockbox/src/Components/Settings/Sound/styles.tsx
··· 1 + import styled from "@emotion/styled"; 2 + 3 + export const Container = styled.div` 4 + display: flex; 5 + flex-direction: row; 6 + width: 100%; 7 + height: 100%; 8 + `; 9 + 10 + export const Title = styled.div` 11 + font-size: 24px; 12 + font-family: RockfordSansMedium; 13 + margin: auto; 14 + margin-bottom: 40px; 15 + `; 16 + 17 + export const Scrollable = styled.div` 18 + height: calc(100vh - 60px); 19 + overflow-y: auto; 20 + `; 21 + 22 + export const Wrapper = styled.div` 23 + width: 60vw; 24 + margin: 0 auto; 25 + margin-bottom: 100px; 26 + margin-top: 30px; 27 + `; 28 + 29 + export const SettingsTitle = styled.div` 30 + font-size: 16px; 31 + font-weight: 600; 32 + margin-bottom: 16px; 33 + `; 34 + 35 + export const Section = styled.div` 36 + margin-bottom: 50px; 37 + font-size: 15px; 38 + border: 1px solid #8a8a8a65; 39 + border-radius: 10px; 40 + padding-left: 20px; 41 + padding-right: 20px; 42 + padding-top: 5px; 43 + padding-bottom: 5px; 44 + `; 45 + 46 + export const Item = styled.div` 47 + display: flex; 48 + flex-direction: row; 49 + align-items: center; 50 + justify-content: space-between; 51 + height: 50px; 52 + `; 53 + 54 + const iOSBoxShadow = 55 + "0 3px 1px rgba(0,0,0,0.1),0 4px 8px rgba(0,0,0,0.13),0 0 0 1px rgba(0,0,0,0.02)"; 56 + 57 + export default { 58 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 59 + slider: (t: any) => ({ 60 + color: "rgba(0, 0, 0, 0.682)", 61 + "& .MuiSlider-track": { 62 + border: "none", 63 + }, 64 + "& .MuiSlider-thumb": { 65 + width: 18, 66 + height: 18, 67 + backgroundColor: "#fff", 68 + "&::before": { 69 + boxShadow: "0 4px 8px rgba(0,0,0,0.18)", 70 + }, 71 + "&:hover, &.Mui-focusVisible, &.Mui-active": { 72 + boxShadow: "none", 73 + }, 74 + }, 75 + ...t.applyStyles("dark", { 76 + color: "#fff", 77 + }), 78 + }), 79 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 80 + sliderIOS: (theme: any) => ({ 81 + color: "#7C00FE", 82 + 83 + "& .MuiSlider-thumb": { 84 + height: 20, 85 + width: 20, 86 + backgroundColor: "#fff", 87 + boxShadow: "0 0 2px 0px rgba(0, 0, 0, 0.1)", 88 + "&:focus, &:hover, &.Mui-active": { 89 + boxShadow: "0px 0px 3px 1px rgba(0, 0, 0, 0.1)", 90 + // Reset on touch devices, it doesn't add specificity 91 + "@media (hover: none)": { 92 + boxShadow: iOSBoxShadow, 93 + }, 94 + }, 95 + "&:before": { 96 + boxShadow: 97 + "0px 0px 1px 0px rgba(0,0,0,0.2), 0px 0px 0px 0px rgba(0,0,0,0.14), 0px 0px 1px 0px rgba(0,0,0,0.12)", 98 + }, 99 + }, 100 + "& .MuiSlider-valueLabel": { 101 + fontSize: 12, 102 + fontWeight: "normal", 103 + top: -6, 104 + backgroundColor: "unset", 105 + color: theme.palette.text.primary, 106 + "&::before": { 107 + display: "none", 108 + }, 109 + "& *": { 110 + background: "transparent", 111 + color: "#000", 112 + ...theme.applyStyles("dark", { 113 + color: "#fff", 114 + }), 115 + }, 116 + }, 117 + "& .MuiSlider-track": { 118 + border: "none", 119 + height: 5, 120 + }, 121 + "& .MuiSlider-rail": { 122 + opacity: 0.5, 123 + boxShadow: "inset 0px 0px 4px -2px #000", 124 + backgroundColor: "#d0d0d0", 125 + }, 126 + ...theme.applyStyles("dark", { 127 + color: "#7C00FE", 128 + }), 129 + }), 130 + };
+3
webui/rockbox/src/Components/Settings/index.tsx
··· 1 + import Settings from "./SettingsWithData"; 2 + 3 + export default Settings;
+29
webui/rockbox/src/Components/Settings/styles.tsx
··· 1 + import styled from "@emotion/styled"; 2 + 3 + export const Container = styled.div` 4 + display: flex; 5 + flex-direction: row; 6 + width: 100%; 7 + height: 100%; 8 + `; 9 + 10 + export const Title = styled.div` 11 + font-size: 24px; 12 + font-family: RockfordSansMedium; 13 + margin: auto; 14 + margin-bottom: 40px; 15 + `; 16 + 17 + export const Scrollable = styled.div` 18 + height: calc(100vh - 60px); 19 + overflow-y: auto; 20 + `; 21 + 22 + export const Wrapper = styled.div` 23 + width: 60vw; 24 + margin: 0 auto; 25 + margin-bottom: 100px; 26 + margin-top: 30px; 27 + min-width: 435px; 28 + max-width: 800px; 29 + `;
+22 -8
webui/rockbox/src/Components/Sidebar/Sidebar.tsx
··· 1 1 import { FC } from "react"; 2 - import { SidebarContainer, MenuItem } from "./styles"; 2 + import { SidebarContainer, MenuItem, Header, SettingsButton } from "./styles"; 3 3 import { Disc } from "@styled-icons/boxicons-regular"; 4 4 import { HardDrive } from "@styled-icons/feather"; 5 5 import Artist from "../Icons/Artist"; 6 6 import Track from "../Icons/Track"; 7 7 import RockboxLogo from "../../Assets/rockbox-icon.svg"; 8 8 import HeartOutline from "../Icons/HeartOutline"; 9 + import { Options } from "@styled-icons/fluentui-system-regular"; 10 + import { Link } from "react-router-dom"; 9 11 10 12 export type SidebarProps = { 11 13 active: string; ··· 15 17 const Sidebar: FC<SidebarProps> = ({ active, cover }) => { 16 18 return ( 17 19 <SidebarContainer cover={cover}> 18 - <a href="/" style={{ textDecoration: "none" }}> 19 - <img 20 - src={RockboxLogo} 21 - alt="Rockbox" 22 - style={{ width: 40, marginBottom: 20, marginLeft: 12 }} 23 - /> 24 - </a> 20 + <Header> 21 + <a href="/" style={{ textDecoration: "none" }}> 22 + <img 23 + src={RockboxLogo} 24 + alt="Rockbox" 25 + style={{ 26 + width: 40, 27 + marginBottom: 20, 28 + marginLeft: 12, 29 + marginTop: -12, 30 + }} 31 + /> 32 + </a> 33 + <Link to="/settings"> 34 + <SettingsButton> 35 + <Options size={24} color="#000" /> 36 + </SettingsButton> 37 + </Link> 38 + </Header> 25 39 <MenuItem 26 40 color={active === "albums" ? "#fe099c" : "initial"} 27 41 to="/albums"
+36 -9
webui/rockbox/src/Components/Sidebar/__snapshots__/Sidebar.test.tsx.snap
··· 8 8 <div 9 9 class="css-1tlxqlf" 10 10 > 11 - <a 12 - href="/" 13 - style="text-decoration: none;" 11 + <div 12 + class="css-1osgnyu" 14 13 > 15 - <img 16 - alt="Rockbox" 17 - src="/src/Assets/rockbox-icon.svg" 18 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 19 - /> 20 - </a> 14 + <a 15 + href="/" 16 + style="text-decoration: none;" 17 + > 18 + <img 19 + alt="Rockbox" 20 + src="/src/Assets/rockbox-icon.svg" 21 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 22 + /> 23 + </a> 24 + <a 25 + href="/settings" 26 + > 27 + <button 28 + class="css-fduwnf" 29 + > 30 + <svg 31 + aria-hidden="true" 32 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 33 + color="#000" 34 + fill="currentColor" 35 + focusable="false" 36 + height="24" 37 + viewBox="0 0 48 48" 38 + width="24" 39 + xmlns="http://www.w3.org/2000/svg" 40 + > 41 + <path 42 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 43 + /> 44 + </svg> 45 + </button> 46 + </a> 47 + </div> 21 48 <a 22 49 class="css-8v05qj" 23 50 color="#fe099c"
+16
webui/rockbox/src/Components/Sidebar/styles.tsx
··· 2 2 import { css } from "@emotion/react"; 3 3 import { Link } from "react-router-dom"; 4 4 5 + export const SettingsButton = styled.button` 6 + display: flex; 7 + background-color: #f6f9fc; 8 + border: none; 9 + cursor: pointer; 10 + margin-top: 3px; 11 + height: 64px; 12 + `; 13 + 14 + export const Header = styled.div` 15 + display: flex; 16 + flex-direction: row; 17 + align-items: center; 18 + justify-content: space-between; 19 + `; 20 + 5 21 export const SidebarContainer = styled.div<{ cover?: string }>` 6 22 display: flex; 7 23 flex-direction: column;
+64
webui/rockbox/src/Components/Switch/Switch.tsx
··· 1 + import Switch, { SwitchProps } from "@mui/material/Switch"; 2 + import { styled } from "@mui/material/styles"; 3 + 4 + const IOSSwitch = styled((props: SwitchProps) => ( 5 + <Switch focusVisibleClassName=".Mui-focusVisible" disableRipple {...props} /> 6 + ))(({ theme }) => ({ 7 + width: 42, 8 + height: 26, 9 + padding: 0, 10 + "& .MuiSwitch-switchBase": { 11 + padding: 0, 12 + margin: 2, 13 + transitionDuration: "300ms", 14 + "&.Mui-checked": { 15 + transform: "translateX(16px)", 16 + color: "#fff", 17 + "& + .MuiSwitch-track": { 18 + backgroundColor: "rgb(254, 9, 156)", 19 + opacity: 1, 20 + border: 0, 21 + ...theme.applyStyles("dark", { 22 + backgroundColor: "rgb(254, 9, 156)", 23 + }), 24 + }, 25 + "&.Mui-disabled + .MuiSwitch-track": { 26 + opacity: 0.5, 27 + }, 28 + }, 29 + "&.Mui-focusVisible .MuiSwitch-thumb": { 30 + color: "rgb(254, 9, 156)", 31 + border: "6px solid #fff", 32 + }, 33 + "&.Mui-disabled .MuiSwitch-thumb": { 34 + color: theme.palette.grey[100], 35 + ...theme.applyStyles("dark", { 36 + color: theme.palette.grey[600], 37 + }), 38 + }, 39 + "&.Mui-disabled + .MuiSwitch-track": { 40 + opacity: 0.7, 41 + ...theme.applyStyles("dark", { 42 + opacity: 0.3, 43 + }), 44 + }, 45 + }, 46 + "& .MuiSwitch-thumb": { 47 + boxSizing: "border-box", 48 + width: 22, 49 + height: 22, 50 + }, 51 + "& .MuiSwitch-track": { 52 + borderRadius: 26 / 2, 53 + backgroundColor: "#E9E9EA", 54 + opacity: 1, 55 + transition: theme.transitions.create(["background-color"], { 56 + duration: 500, 57 + }), 58 + ...theme.applyStyles("dark", { 59 + backgroundColor: "#39393D", 60 + }), 61 + }, 62 + })); 63 + 64 + export default IOSSwitch;
+3
webui/rockbox/src/Components/Switch/index.tsx
··· 1 + import Switch from "./Switch"; 2 + 3 + export default Switch;
+36 -9
webui/rockbox/src/Components/Tracks/__snapshots__/Tracks.test.tsx.snap
··· 11 11 <div 12 12 class="css-1tlxqlf" 13 13 > 14 - <a 15 - href="/" 16 - style="text-decoration: none;" 14 + <div 15 + class="css-1osgnyu" 17 16 > 18 - <img 19 - alt="Rockbox" 20 - src="/src/Assets/rockbox-icon.svg" 21 - style="width: 40px; margin-bottom: 20px; margin-left: 12px;" 22 - /> 23 - </a> 17 + <a 18 + href="/" 19 + style="text-decoration: none;" 20 + > 21 + <img 22 + alt="Rockbox" 23 + src="/src/Assets/rockbox-icon.svg" 24 + style="width: 40px; margin-bottom: 20px; margin-left: 12px; margin-top: -12px;" 25 + /> 26 + </a> 27 + <a 28 + href="/settings" 29 + > 30 + <button 31 + class="css-fduwnf" 32 + > 33 + <svg 34 + aria-hidden="true" 35 + class="StyledIconBase-sc-ea9ulj-0 dmvaRK" 36 + color="#000" 37 + fill="currentColor" 38 + focusable="false" 39 + height="24" 40 + viewBox="0 0 48 48" 41 + width="24" 42 + xmlns="http://www.w3.org/2000/svg" 43 + > 44 + <path 45 + d="M5.25 15.5h19.88a6.25 6.25 0 0 0 12.25 0h5.37a1.25 1.25 0 1 0 0-2.5h-5.38a6.25 6.25 0 0 0-12.24 0H5.25a1.25 1.25 0 1 0 0 2.5Zm26-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm-26 24h5.63a6.25 6.25 0 0 0 12.24 0h19.63a1.25 1.25 0 1 0 0-2.5H23.12a6.25 6.25 0 0 0-12.25 0H5.25a1.25 1.25 0 1 0 0 2.5Zm11.75-5a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Z" 46 + /> 47 + </svg> 48 + </button> 49 + </a> 50 + </div> 24 51 <a 25 52 class="css-tujm8o" 26 53 color="initial"
+8
webui/rockbox/src/Containers/Settings/SettingsPage.tsx
··· 1 + import { FC } from "react"; 2 + import Settings from "../../Components/Settings"; 3 + 4 + const SettingsPage: FC = () => { 5 + return <Settings />; 6 + }; 7 + 8 + export default SettingsPage;
+3
webui/rockbox/src/Containers/Settings/index.tsx
··· 1 + import SettingsPage from "./SettingsPage"; 2 + 3 + export default SettingsPage;
+7
webui/rockbox/src/GraphQL/Settings/Mutation.ts
··· 1 + import { gql } from "@apollo/client"; 2 + 3 + export const SAVE_SETTINGS = gql` 4 + mutation SaveSettings($settings: NewGlobalSettings!) { 5 + saveSettings(settings: $settings) 6 + } 7 + `;
+45
webui/rockbox/src/GraphQL/Settings/Query.ts
··· 1 + import { gql } from "@apollo/client"; 2 + 3 + export const GET_GLOBAL_SETTINGS = gql` 4 + query GetGlobalSettings { 5 + globalSettings { 6 + musicDir 7 + volume 8 + playlistShuffle 9 + repeatMode 10 + bass 11 + bassCutoff 12 + treble 13 + trebleCutoff 14 + crossfade 15 + fadeOnStop 16 + crossfadeFadeInDelay 17 + crossfadeFadeInDuration 18 + crossfadeFadeOutDelay 19 + crossfadeFadeOutDuration 20 + crossfadeFadeOutMixmode 21 + balance 22 + stereoWidth 23 + stereoswMode 24 + surroundEnabled 25 + surroundBalance 26 + surroundFx1 27 + surroundFx2 28 + partyMode 29 + ditheringEnabled 30 + channelConfig 31 + playerName 32 + eqEnabled 33 + eqBandSettings { 34 + q 35 + cutoff 36 + gain 37 + } 38 + replaygainSettings { 39 + noclip 40 + type 41 + preamp 42 + } 43 + } 44 + } 45 + `;
-15
webui/rockbox/src/GraphQL/Settings/Query.tsx
··· 1 - import { gql } from "@apollo/client"; 2 - 3 - export const GET_GLOBAL_SETTINGS = gql` 4 - query GetGlobalSettings { 5 - globalSettings { 6 - volume 7 - eqEnabled 8 - eqBandSettings { 9 - q 10 - cutoff 11 - gain 12 - } 13 - } 14 - } 15 - `;
+118 -1
webui/rockbox/src/Hooks/GraphQL.tsx
··· 70 70 q: Scalars['Int']['output']; 71 71 }; 72 72 73 + export type EqBandSettingInput = { 74 + cutoff: Scalars['Int']['input']; 75 + gain: Scalars['Int']['input']; 76 + q: Scalars['Int']['input']; 77 + }; 78 + 73 79 export type Mutation = { 74 80 __typename?: 'Mutation'; 75 81 adjustVolume: Scalars['Int']['output']; ··· 107 113 previous: Scalars['Int']['output']; 108 114 resume: Scalars['Int']['output']; 109 115 resumeTrack: Scalars['String']['output']; 116 + saveSettings: Scalars['Boolean']['output']; 110 117 scanLibrary: Scalars['Int']['output']; 111 118 setPitch: Scalars['String']['output']; 112 119 shufflePlaylist: Scalars['Int']['output']; ··· 238 245 }; 239 246 240 247 248 + export type MutationSaveSettingsArgs = { 249 + settings: NewGlobalSettings; 250 + }; 251 + 252 + 241 253 export type MutationUnlikeAlbumArgs = { 242 254 id: Scalars['String']['input']; 243 255 }; ··· 247 259 id: Scalars['String']['input']; 248 260 }; 249 261 262 + export type NewGlobalSettings = { 263 + balance?: InputMaybe<Scalars['Int']['input']>; 264 + bass?: InputMaybe<Scalars['Int']['input']>; 265 + bassCutoff?: InputMaybe<Scalars['Int']['input']>; 266 + channelConfig?: InputMaybe<Scalars['Int']['input']>; 267 + crossfade?: InputMaybe<Scalars['Int']['input']>; 268 + eqBandSettings?: InputMaybe<Array<EqBandSettingInput>>; 269 + eqEnabled?: InputMaybe<Scalars['Boolean']['input']>; 270 + fadeInDelay?: InputMaybe<Scalars['Int']['input']>; 271 + fadeInDuration?: InputMaybe<Scalars['Int']['input']>; 272 + fadeOnStop?: InputMaybe<Scalars['Boolean']['input']>; 273 + fadeOutDelay?: InputMaybe<Scalars['Int']['input']>; 274 + fadeOutDuration?: InputMaybe<Scalars['Int']['input']>; 275 + fadeOutMixmode?: InputMaybe<Scalars['Int']['input']>; 276 + musicDir?: InputMaybe<Scalars['String']['input']>; 277 + partyMode?: InputMaybe<Scalars['Boolean']['input']>; 278 + playerName?: InputMaybe<Scalars['String']['input']>; 279 + playlistShuffle?: InputMaybe<Scalars['Boolean']['input']>; 280 + repeatMode?: InputMaybe<Scalars['Int']['input']>; 281 + replaygainSettings?: InputMaybe<ReplaygainSettingsInput>; 282 + stereoWidth?: InputMaybe<Scalars['Int']['input']>; 283 + stereoswMode?: InputMaybe<Scalars['Int']['input']>; 284 + surroundBalance?: InputMaybe<Scalars['Int']['input']>; 285 + surroundEnabled?: InputMaybe<Scalars['Boolean']['input']>; 286 + surroundFx1?: InputMaybe<Scalars['Int']['input']>; 287 + surroundFx2?: InputMaybe<Scalars['Boolean']['input']>; 288 + treble?: InputMaybe<Scalars['Int']['input']>; 289 + trebleCutoff?: InputMaybe<Scalars['Int']['input']>; 290 + }; 291 + 250 292 export type Playlist = { 251 293 __typename?: 'Playlist'; 252 294 amount: Scalars['Int']['output']; ··· 320 362 noclip: Scalars['Boolean']['output']; 321 363 preamp: Scalars['Int']['output']; 322 364 type: Scalars['Int']['output']; 365 + }; 366 + 367 + export type ReplaygainSettingsInput = { 368 + noclip: Scalars['Boolean']['input']; 369 + preamp: Scalars['Int']['input']; 370 + type: Scalars['Int']['input']; 323 371 }; 324 372 325 373 export type SearchResults = { ··· 479 527 lstColor: Scalars['Int']['output']; 480 528 maxFilesInDir: Scalars['Int']['output']; 481 529 maxFilesInPlaylist: Scalars['Int']['output']; 530 + musicDir: Scalars['String']['output']; 482 531 nextFolder: Scalars['Int']['output']; 483 532 offsetOutOfView: Scalars['Boolean']['output']; 484 533 partyMode: Scalars['Boolean']['output']; ··· 838 887 839 888 export type PlaylistChangedSubscription = { __typename?: 'Subscription', playlistChanged: { __typename?: 'Playlist', index: number, amount: number, maxPlaylistSize: number, tracks: Array<{ __typename?: 'Track', id?: string | null, title: string, artist: string, albumArt?: string | null, artistId?: string | null, albumId?: string | null, path: string }> } }; 840 889 890 + export type SaveSettingsMutationVariables = Exact<{ 891 + settings: NewGlobalSettings; 892 + }>; 893 + 894 + 895 + export type SaveSettingsMutation = { __typename?: 'Mutation', saveSettings: boolean }; 896 + 841 897 export type GetGlobalSettingsQueryVariables = Exact<{ [key: string]: never; }>; 842 898 843 899 844 - export type GetGlobalSettingsQuery = { __typename?: 'Query', globalSettings: { __typename?: 'UserSettings', volume: number, eqEnabled: boolean, eqBandSettings: Array<{ __typename?: 'EqBandSetting', q: number, cutoff: number, gain: number }> } }; 900 + export type GetGlobalSettingsQuery = { __typename?: 'Query', globalSettings: { __typename?: 'UserSettings', musicDir: string, volume: number, playlistShuffle: boolean, repeatMode: number, bass: number, bassCutoff: number, treble: number, trebleCutoff: number, crossfade: number, fadeOnStop: boolean, crossfadeFadeInDelay: number, crossfadeFadeInDuration: number, crossfadeFadeOutDelay: number, crossfadeFadeOutDuration: number, crossfadeFadeOutMixmode: number, balance: number, stereoWidth: number, stereoswMode: number, surroundEnabled: number, surroundBalance: number, surroundFx1: number, surroundFx2: boolean, partyMode: boolean, ditheringEnabled: boolean, channelConfig: number, playerName: string, eqEnabled: boolean, eqBandSettings: Array<{ __typename?: 'EqBandSetting', q: number, cutoff: number, gain: number }>, replaygainSettings: { __typename?: 'ReplaygainSettings', noclip: boolean, type: number, preamp: number } } }; 845 901 846 902 export type AdjustVolumeMutationVariables = Exact<{ 847 903 steps: Scalars['Int']['input']; ··· 2397 2453 } 2398 2454 export type PlaylistChangedSubscriptionHookResult = ReturnType<typeof usePlaylistChangedSubscription>; 2399 2455 export type PlaylistChangedSubscriptionResult = Apollo.SubscriptionResult<PlaylistChangedSubscription>; 2456 + export const SaveSettingsDocument = gql` 2457 + mutation SaveSettings($settings: NewGlobalSettings!) { 2458 + saveSettings(settings: $settings) 2459 + } 2460 + `; 2461 + export type SaveSettingsMutationFn = Apollo.MutationFunction<SaveSettingsMutation, SaveSettingsMutationVariables>; 2462 + 2463 + /** 2464 + * __useSaveSettingsMutation__ 2465 + * 2466 + * To run a mutation, you first call `useSaveSettingsMutation` within a React component and pass it any options that fit your needs. 2467 + * When your component renders, `useSaveSettingsMutation` returns a tuple that includes: 2468 + * - A mutate function that you can call at any time to execute the mutation 2469 + * - An object with fields that represent the current status of the mutation's execution 2470 + * 2471 + * @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; 2472 + * 2473 + * @example 2474 + * const [saveSettingsMutation, { data, loading, error }] = useSaveSettingsMutation({ 2475 + * variables: { 2476 + * settings: // value for 'settings' 2477 + * }, 2478 + * }); 2479 + */ 2480 + export function useSaveSettingsMutation(baseOptions?: Apollo.MutationHookOptions<SaveSettingsMutation, SaveSettingsMutationVariables>) { 2481 + const options = {...defaultOptions, ...baseOptions} 2482 + return Apollo.useMutation<SaveSettingsMutation, SaveSettingsMutationVariables>(SaveSettingsDocument, options); 2483 + } 2484 + export type SaveSettingsMutationHookResult = ReturnType<typeof useSaveSettingsMutation>; 2485 + export type SaveSettingsMutationResult = Apollo.MutationResult<SaveSettingsMutation>; 2486 + export type SaveSettingsMutationOptions = Apollo.BaseMutationOptions<SaveSettingsMutation, SaveSettingsMutationVariables>; 2400 2487 export const GetGlobalSettingsDocument = gql` 2401 2488 query GetGlobalSettings { 2402 2489 globalSettings { 2490 + musicDir 2403 2491 volume 2492 + playlistShuffle 2493 + repeatMode 2494 + bass 2495 + bassCutoff 2496 + treble 2497 + trebleCutoff 2498 + crossfade 2499 + fadeOnStop 2500 + crossfadeFadeInDelay 2501 + crossfadeFadeInDuration 2502 + crossfadeFadeOutDelay 2503 + crossfadeFadeOutDuration 2504 + crossfadeFadeOutMixmode 2505 + balance 2506 + stereoWidth 2507 + stereoswMode 2508 + surroundEnabled 2509 + surroundBalance 2510 + surroundFx1 2511 + surroundFx2 2512 + partyMode 2513 + ditheringEnabled 2514 + channelConfig 2515 + playerName 2404 2516 eqEnabled 2405 2517 eqBandSettings { 2406 2518 q 2407 2519 cutoff 2408 2520 gain 2521 + } 2522 + replaygainSettings { 2523 + noclip 2524 + type 2525 + preamp 2409 2526 } 2410 2527 } 2411 2528 }
+6 -3
webui/rockbox/src/Providers/GraphQLProvider.tsx
··· 4 4 InMemoryCache, 5 5 ApolloProvider, 6 6 split, 7 + from, 7 8 } from "@apollo/client"; 9 + import { removeTypenameFromVariables } from "@apollo/client/link/remove-typename"; 8 10 import { WebSocketLink } from "@apollo/client/link/ws"; 9 11 import { getMainDefinition } from "@apollo/client/utilities"; 10 12 import { SubscriptionClient } from "subscriptions-transport-ws"; ··· 13 15 const uri = 14 16 process.env.NODE_ENV === "development" 15 17 ? import.meta.env.VITE_APP_API_URL || "http://localhost:6062/graphql" 16 - : // eslint-disable-next-line no-restricted-globals 17 - `${origin}/graphql`; 18 + : `${origin}/graphql`; 19 + 20 + const removeTypenameLink = removeTypenameFromVariables(); 18 21 19 22 const httpLink = createHttpLink({ 20 23 uri, ··· 33 36 ); 34 37 }, 35 38 wsLink, 36 - httpLink 39 + from([removeTypenameLink, httpLink]) 37 40 ); 38 41 const link = splitLink; 39 42
+20
webui/rockbox/src/constants.ts
··· 1 + export const REPLAYGAIN_TRACK = 0; 2 + export const REPLAYGAIN_ALBUM = 1; 3 + export const REPLAYGAIN_SHUFFLE = 2; 4 + export const REPLAYGAIN_OFF = 3; 5 + 6 + export const REPEAT_OFF = 0; 7 + export const REPEAT_ALL = 1; 8 + export const REPEAT_ONE = 2; 9 + export const REPEAT_SHUFFLE = 3; 10 + export const REPEAT_AB = 4; 11 + 12 + export const CROSSFADE_OFF = 0; 13 + export const CROSSFADE_ENABLE_AUTOSKIP = 1; 14 + export const CROSSFADE_ENABLE_MANSKIP = 2; 15 + export const CROSSFADE_ENABLE_SHUFFLE = 3; 16 + export const CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP = 4; 17 + export const CROSSFADE_ENABLE_ALWAYS = 5; 18 + 19 + export const FADE_OUT_CROSSFADE_MODE = 0; 20 + export const FADE_OUT_MIX_MODE = 2;
+3 -1
webui/rockbox/tsconfig.app.json
··· 18 18 "strict": true, 19 19 "noUnusedLocals": true, 20 20 "noUnusedParameters": true, 21 - "noFallthroughCasesInSwitch": true 21 + "noFallthroughCasesInSwitch": true, 22 + 23 + "types": [ "@types/wicg-file-system-access"] 22 24 }, 23 25 "include": ["src"] 24 26 }