first commit
This commit is contained in:
commit
2a1252cc7a
69 changed files with 7559 additions and 0 deletions
262
src/app/root.rs
Normal file
262
src/app/root.rs
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
let devices = RwSignal::new(Vec::<DeviceSummary>::new());
|
||||
let selected_path = RwSignal::new(String::new());
|
||||
let selected_profile = RwSignal::new("direct".to_string());
|
||||
let device_state = RwSignal::new(None::<DeviceState>);
|
||||
let active_tab = RwSignal::new("basic".to_string());
|
||||
let status = RwSignal::new("Scanning for a supported Basilisk mouse.".to_string());
|
||||
let busy = RwSignal::new(false);
|
||||
|
||||
let connect_path = move |path: String| {
|
||||
if path.is_empty() {
|
||||
status.set("Select a device path before connecting.".to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
busy.set(true);
|
||||
status.set(format!("Connecting to {path}..."));
|
||||
spawn_local(async move {
|
||||
let args = ConnectArgs { path };
|
||||
match invoke::<DeviceState, _>("connect_device", &args).await {
|
||||
Ok(snapshot) => {
|
||||
selected_profile.set(snapshot.basic.profile.clone());
|
||||
status.set(format!("Connected to {}.", snapshot.device.supported_name));
|
||||
device_state.set(Some(snapshot));
|
||||
}
|
||||
Err(error) => status.set(error),
|
||||
}
|
||||
busy.set(false);
|
||||
});
|
||||
};
|
||||
|
||||
let scan = move || {
|
||||
busy.set(true);
|
||||
status.set("Scanning /sys/class/hidraw for Basilisk V3 devices...".to_string());
|
||||
spawn_local(async move {
|
||||
match invoke_no_args::<Vec<DeviceSummary>>("list_supported_devices").await {
|
||||
Ok(found) => {
|
||||
if let Some(first) = found.first() {
|
||||
let path = first.path.clone();
|
||||
selected_path.set(path.clone());
|
||||
devices.set(found);
|
||||
status.set(format!("Found supported device. Connecting to {path}..."));
|
||||
match invoke::<DeviceState, _>("connect_device", &ConnectArgs { path }).await {
|
||||
Ok(snapshot) => {
|
||||
selected_profile.set(snapshot.basic.profile.clone());
|
||||
status.set(format!("Connected to {}.", snapshot.device.supported_name));
|
||||
device_state.set(Some(snapshot));
|
||||
}
|
||||
Err(error) => status.set(error),
|
||||
}
|
||||
} else {
|
||||
selected_path.set(String::new());
|
||||
devices.set(found);
|
||||
status.set("No supported Razer hidraw devices found.".to_string());
|
||||
}
|
||||
}
|
||||
Err(error) => status.set(error),
|
||||
}
|
||||
busy.set(false);
|
||||
});
|
||||
};
|
||||
|
||||
Effect::new(move |_| scan());
|
||||
|
||||
let connect = move |_| {
|
||||
let path = selected_path.get_untracked();
|
||||
connect_path(path);
|
||||
};
|
||||
|
||||
let refresh_profile = move |profile: String| {
|
||||
selected_profile.set(profile.clone());
|
||||
busy.set(true);
|
||||
status.set(format!("Loading {profile} profile..."));
|
||||
spawn_local(async move {
|
||||
let args = ProfileArgs { profile };
|
||||
match invoke::<DeviceState, _>("refresh_device_state", &args).await {
|
||||
Ok(snapshot) => {
|
||||
status.set("Profile settings loaded.".to_string());
|
||||
device_state.set(Some(snapshot));
|
||||
}
|
||||
Err(error) => status.set(error),
|
||||
}
|
||||
busy.set(false);
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<main class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<img src="public/snakemouse.svg" alt="" />
|
||||
<div>
|
||||
<h1>"Razer Basilisk V3"</h1>
|
||||
<p>"Onboard memory tools"</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="primary-action" type="button" on:click=move |_| scan() disabled=move || busy.get()>
|
||||
"Scan"
|
||||
</button>
|
||||
|
||||
<label class="field">
|
||||
<span>"Device"</span>
|
||||
<select
|
||||
prop:value=move || selected_path.get()
|
||||
on:change=move |ev| selected_path.set(event_target_value(&ev))
|
||||
disabled=move || busy.get()
|
||||
>
|
||||
<option value="">"No supported device selected"</option>
|
||||
{move || devices.get().into_iter().map(|device| {
|
||||
let label = format!(
|
||||
"{} - {} ({:04x}:{:04x}){}",
|
||||
device.path,
|
||||
device.supported_name,
|
||||
0x1532,
|
||||
device.product_id,
|
||||
device
|
||||
.serial
|
||||
.as_ref()
|
||||
.map(|serial| format!(" - {serial}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
view! {
|
||||
<option value=device.path.clone()>{label}</option>
|
||||
}
|
||||
}).collect_view()}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button class="primary-action" type="button" on:click=connect disabled=move || busy.get() || selected_path.get().is_empty()>
|
||||
"Connect"
|
||||
</button>
|
||||
<nav>
|
||||
<button class:active=move || active_tab.get() == "basic" on:click=move |_| active_tab.set("basic".to_string())>"Basic"</button>
|
||||
<button class:active=move || active_tab.get() == "led" on:click=move |_| active_tab.set("led".to_string())>"LED"</button>
|
||||
<button class:active=move || active_tab.get() == "button" on:click=move |_| active_tab.set("button".to_string())>"Button"</button>
|
||||
<button class:active=move || active_tab.get() == "profile" on:click=move |_| active_tab.set("profile".to_string())>"Profiles"</button>
|
||||
<button class:active=move || active_tab.get() == "macro" on:click=move |_| active_tab.set("macro".to_string())>"Macros"</button>
|
||||
<button class:active=move || active_tab.get() == "sensor" on:click=move |_| active_tab.set("sensor".to_string())>"Sensor"</button>
|
||||
<button class:active=move || active_tab.get() == "info" on:click=move |_| active_tab.set("info".to_string())>"Info"</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<section class="workspace">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">"Linux desktop configuration"</p>
|
||||
<h2>{move || device_state.get().map(|state| state.device.supported_name).unwrap_or_else(|| "No device connected".to_string())}</h2>
|
||||
</div>
|
||||
<div class="status" class:busy=move || busy.get()>{move || status.get()}</div>
|
||||
</header>
|
||||
|
||||
{move || match device_state.get() {
|
||||
Some(snapshot) => view! {
|
||||
<ConnectedView
|
||||
snapshot=snapshot
|
||||
active_tab=active_tab
|
||||
selected_profile=selected_profile
|
||||
refresh_profile=refresh_profile
|
||||
set_snapshot=device_state.write_only()
|
||||
set_status=status.write_only()
|
||||
set_busy=busy.write_only()
|
||||
/>
|
||||
}.into_any(),
|
||||
None => view! {
|
||||
<section class="empty-state">
|
||||
<h3>"Connect to a Basilisk V3 or V3 Pro"</h3>
|
||||
<p>"This app uses Linux hidraw feature reports through the Tauri backend instead of WebHID and Pyodide."</p>
|
||||
<p>"If your mouse is connected but does not appear, check that the current user can read and write the matching /dev/hidraw node."</p>
|
||||
</section>
|
||||
}.into_any()
|
||||
}}
|
||||
</section>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ConnectedView(
|
||||
snapshot: DeviceState,
|
||||
active_tab: RwSignal<String>,
|
||||
selected_profile: RwSignal<String>,
|
||||
refresh_profile: impl Fn(String) + Copy + 'static,
|
||||
set_snapshot: WriteSignal<Option<DeviceState>>,
|
||||
set_status: WriteSignal<String>,
|
||||
set_busy: WriteSignal<bool>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<div class="profile-row">
|
||||
<span>"Profile"</span>
|
||||
<div class="segments">
|
||||
{snapshot.profiles.iter().map(|profile| {
|
||||
let profile_name = profile.clone();
|
||||
let profile_for_click = profile.clone();
|
||||
view! {
|
||||
<button
|
||||
type="button"
|
||||
class:active=move || selected_profile.get() == profile_name
|
||||
on:click=move |_| refresh_profile(profile_for_click.clone())
|
||||
>
|
||||
{profile.clone()}
|
||||
</button>
|
||||
}
|
||||
}).collect_view()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{move || match active_tab.get().as_str() {
|
||||
"basic" => view! {
|
||||
<BasicPanel
|
||||
snapshot=snapshot.clone()
|
||||
selected_profile=selected_profile
|
||||
set_snapshot=set_snapshot
|
||||
set_status=set_status
|
||||
set_busy=set_busy
|
||||
/>
|
||||
}.into_any(),
|
||||
"info" => view! { <InfoPanel snapshot=snapshot.clone() /> }.into_any(),
|
||||
"profile" => view! {
|
||||
<ProfilePanel
|
||||
snapshot=snapshot.clone()
|
||||
selected_profile=selected_profile
|
||||
set_snapshot=set_snapshot
|
||||
set_status=set_status
|
||||
set_busy=set_busy
|
||||
/>
|
||||
}.into_any(),
|
||||
"led" => view! {
|
||||
<LedPanel
|
||||
snapshot=snapshot.clone()
|
||||
selected_profile=selected_profile
|
||||
set_status=set_status
|
||||
set_busy=set_busy
|
||||
/>
|
||||
}.into_any(),
|
||||
"button" => view! {
|
||||
<ButtonPanel
|
||||
snapshot=snapshot.clone()
|
||||
selected_profile=selected_profile
|
||||
set_status=set_status
|
||||
set_busy=set_busy
|
||||
/>
|
||||
}.into_any(),
|
||||
"macro" => view! {
|
||||
<MacroPanel
|
||||
snapshot=snapshot.clone()
|
||||
set_status=set_status
|
||||
set_busy=set_busy
|
||||
/>
|
||||
}.into_any(),
|
||||
"sensor" => view! {
|
||||
<SensorPanel
|
||||
snapshot=snapshot.clone()
|
||||
set_status=set_status
|
||||
set_busy=set_busy
|
||||
/>
|
||||
}.into_any(),
|
||||
_ => view! { <BacklogPanel title="Unknown Section" body="Select a section from the sidebar." /> }.into_any(),
|
||||
}}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue