Start to make dynamic interfaces work

This commit is contained in:
Michael Mikovsky
2025-12-21 00:35:28 -07:00
parent 1ea26641d6
commit c7d66c5560
26 changed files with 720 additions and 296 deletions
+38 -5
View File
@@ -1,9 +1,11 @@
use egui::{Align2, Area, Color32, Frame, Order, Sense, UiKind, Vec2, mutex::Mutex};
use serde::de::DeserializeOwned;
use serde::{Serialize, de::DeserializeOwned};
use serde_json::json;
use std::sync::Arc;
use wasm_bindgen::prelude::Closure;
use unshell_lib::Result;
#[derive(serde::Deserialize, serde::Serialize)]
pub struct Auth {
// Auth Stuff
@@ -150,10 +152,10 @@ impl Auth {
// });
}
pub fn get<T, F>(&self, path: &str, ret: F)
pub fn get<R, F>(&self, path: &str, ret: F) -> Result<()>
where
F: FnOnce(T) + 'static,
T: DeserializeOwned,
F: FnOnce(R) + 'static,
R: DeserializeOwned,
{
if let Some(ref token) = self.token {
let state = self.auth_state.clone();
@@ -162,7 +164,7 @@ impl Auth {
format!("Bearer {}", token.token),
Closure::once_into_js(move |ok: bool, response: String| {
if ok {
if let Ok(value) = serde_json::from_str::<T>(&response) {
if let Ok(value) = serde_json::from_str::<R>(&response) {
ret(value)
} else {
*(state.lock()) = AuthState::Error("Malformed Response".into());
@@ -173,6 +175,37 @@ impl Auth {
}),
);
}
Ok(())
}
pub fn post<R, T, F>(&self, path: &str, data: &T, ret: F) -> Result<()>
where
R: DeserializeOwned,
T: Serialize,
F: FnOnce(R) + 'static,
{
if let Some(ref token) = self.token {
let state = self.auth_state.clone();
crate::httpPostAuth(
path,
format!("Bearer {}", token.token),
&serde_json::to_string(data)?,
Closure::once_into_js(move |ok: bool, response: String| {
if ok {
if let Ok(value) = serde_json::from_str::<R>(&response) {
ret(value)
} else {
*(state.lock()) = AuthState::Error("Malformed Response".into());
}
} else {
*(state.lock()) = AuthState::Error(response);
}
}),
);
}
Ok(())
}
}
-42
View File
@@ -1,42 +0,0 @@
use std::collections::HashMap;
use serde_json::Value;
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub enum Tree2Repr {
File(String),
Folder(Vec<String>),
}
// #[derive(Clone, serde::Deserialize, serde::Serialize)]
// pub struct InterfaceWrapper {
// pub name: String,
// pub interface: Interface,
// }
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub enum ConfigStructField {
Header(String),
Text(String),
String {
// Default value of string edit in struct
#[serde(default)]
default: String,
max_length: Option<usize>,
// Display string edit as password
#[serde(default)]
protected: Option<bool>,
},
Integer {
// Default value of integer in struct
#[serde(default)]
default: i32,
min: Option<i32>,
max: Option<i32>,
},
// Checkbox
// Dropdown
// Collapsing header
// Slider
// ...
}
+13 -9
View File
@@ -1,11 +1,14 @@
mod config;
mod render_interface;
use std::{
path::PathBuf,
sync::{Arc, Mutex},
};
use crate::{auth::Auth, interface::config::Tree2Repr};
use unshell_lib::Result;
use unshell_lib::config::TreeMessage;
use crate::auth::Auth;
#[derive(serde::Deserialize, serde::Serialize)]
pub struct InterfaceWindow {
@@ -18,7 +21,7 @@ pub struct InterfaceWindow {
pub struct InterfaceWindowState {
is_request: bool,
is_error: bool,
branch: Option<Tree2Repr>,
branch: Option<TreeMessage>,
}
impl InterfaceWindow {
@@ -63,7 +66,7 @@ impl InterfaceWindow {
let state_clone = self.state.clone();
auth.get(
&format!("/api/interface{}", self.path.display()),
move |response: Result<Tree2Repr, String>| {
move |response: Result<TreeMessage>| {
let mut state_lock = state_clone.lock().unwrap();
match response {
@@ -79,20 +82,21 @@ impl InterfaceWindow {
drop(state_lock);
},
);
)
.unwrap();
} else {
let state_clone = self.state.clone();
let mut state_lock = state_clone.lock().unwrap();
let branch = (state_lock.branch).as_ref().unwrap();
let mut branch = (state_lock.branch).as_mut().unwrap();
let clear = match branch {
Tree2Repr::File(file) => {
ui.label(&format!("File {}", file));
TreeMessage::InterfaceAndValue(interface_struct, interface_data) => {
render_interface::render(ui, interface_struct, interface_data);
false
}
Tree2Repr::Folder(items) => {
TreeMessage::Folder(items) => {
let mut clear = false;
for item in items {
if ui.button(&format!("Item {}", item)).clicked() {
@@ -0,0 +1,64 @@
use egui::{Color32, TextEdit};
use unshell_lib::config::{InterfaceData, InterfaceStruct, config_struct};
pub fn render(
ui: &mut egui::Ui,
interface_struct: &InterfaceStruct,
interface_data: &mut InterfaceData,
) {
match (interface_struct, interface_data) {
(InterfaceStruct::ConfigStruct(interface), InterfaceData::ConfigStruct(data)) => {
render_config_struct(ui, interface, data);
}
}
}
fn render_config_struct(
ui: &mut egui::Ui,
interface: &config_struct::ConfigStructKeys,
data: &mut config_struct::ConfigStructValues,
) {
for (interface, data) in interface.iter().zip(data) {
match (interface, data) {
(config_struct::ConfigStructField::Header(text), serde_json::Value::Null) => {
ui.heading(text);
}
(config_struct::ConfigStructField::Text(text), serde_json::Value::Null) => {
ui.label(text);
}
(
config_struct::ConfigStructField::String {
default: _,
max_length,
protected,
},
serde_json::Value::String(value),
) => {
let mut widget = TextEdit::singleline(value);
if let Some(limit) = &max_length {
widget = widget.char_limit(*limit);
}
if let Some(protected) = &protected {
widget = widget.password(*protected);
}
ui.add(widget);
}
(
config_struct::ConfigStructField::Integer { default, min, max },
serde_json::Value::Number(number),
) => todo!(),
(interface, data) => {
ui.colored_label(
Color32::RED,
&format!("Incorrect type and value! {interface:?} and {data:?}"),
);
}
}
}
}
+2 -1
View File
@@ -192,7 +192,8 @@ impl LogViewer {
state_lock.requested_data = true;
// crate::log(&format!("{e:?}"));
});
})
.unwrap();
}
}