mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-09 06:47:59 -06:00
Fix latency issue
This commit is contained in:
@@ -48,6 +48,8 @@ impl eframe::App for TemplateApp {
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
ctx.plugin_or_default::<egui_async::EguiAsyncPlugin>();
|
||||
|
||||
if !self.state.auth.logged_in() {
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::central_panel(&ctx.style()).inner_margin(0))
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::js_sys::Promise;
|
||||
|
||||
pub struct PromiseWrapper<T> {
|
||||
state: Rc<RefCell<PromiseState<T>>>,
|
||||
}
|
||||
|
||||
enum PromiseState<T> {
|
||||
Pending,
|
||||
Resolved(T),
|
||||
Rejected(String),
|
||||
}
|
||||
|
||||
impl<T: 'static> PromiseWrapper<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
/// Create a new PromiseWrapper from a JavaScript Promise
|
||||
/// The promise should resolve to a Response object (from fetch)
|
||||
pub fn new(promise: Promise) -> Self {
|
||||
let state = Rc::new(RefCell::new(PromiseState::Pending));
|
||||
let state_clone = state.clone();
|
||||
let state_clone2 = state.clone();
|
||||
|
||||
// Success callback
|
||||
let success = Closure::once(move |value: JsValue| {
|
||||
if let Ok(response) = value.dyn_into::<web_sys::Response>() {
|
||||
if let Ok(json_promise) = response.json() {
|
||||
let state_inner = state_clone.clone();
|
||||
|
||||
let json_success = Closure::once(move |json_value: JsValue| {
|
||||
*state_inner.borrow_mut() = if let Some(body) = json_value.as_string() {
|
||||
match serde_json::from_str(&body) {
|
||||
Ok(data) => PromiseState::Resolved(data),
|
||||
Err(e) => PromiseState::Rejected(format!("{:?}", e)),
|
||||
}
|
||||
} else {
|
||||
PromiseState::Rejected(format!("Response was not a string"))
|
||||
};
|
||||
|
||||
match serde_wasm_bindgen::from_value::<T>(json_value) {
|
||||
Ok(data) => *state_inner.borrow_mut() = PromiseState::Resolved(data),
|
||||
Err(e) => {
|
||||
*state_inner.borrow_mut() =
|
||||
PromiseState::Rejected(format!("{:?}", e))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = json_promise.then(&json_success);
|
||||
json_success.forget();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Error callback
|
||||
let error = Closure::once(move |err: JsValue| {
|
||||
*state_clone2.borrow_mut() = PromiseState::Rejected(
|
||||
err.as_string()
|
||||
.unwrap_or_else(|| "Unknown error".to_string()),
|
||||
);
|
||||
});
|
||||
|
||||
let _ = promise.then2(&success, &error);
|
||||
success.forget();
|
||||
error.forget();
|
||||
|
||||
Self { state }
|
||||
}
|
||||
|
||||
/// Poll the promise to check if it has completed
|
||||
/// Returns Some(T) if resolved successfully, None if still pending or rejected
|
||||
/// This is a lightweight check that's safe to call every frame
|
||||
#[inline]
|
||||
pub fn poll(&self) -> Option<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
match &*self.state.borrow() {
|
||||
PromiseState::Resolved(value) => Some(value.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the promise is still pending (lightweight)
|
||||
#[inline]
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(&*self.state.borrow(), PromiseState::Pending)
|
||||
}
|
||||
|
||||
/// Check if the promise was rejected (lightweight)
|
||||
#[inline]
|
||||
pub fn is_rejected(&self) -> bool {
|
||||
matches!(&*self.state.borrow(), PromiseState::Rejected(_))
|
||||
}
|
||||
|
||||
/// Get the error message if rejected
|
||||
pub fn error(&self) -> Option<String> {
|
||||
match &*self.state.borrow() {
|
||||
PromiseState::Rejected(err) => Some(err.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
impl Auth {
|
||||
// pub fn get_test(url: String) -> Promise {
|
||||
// let fut = Self::get_async(&url);
|
||||
|
||||
// // wasm_bindgen_futures::(fut)
|
||||
// }
|
||||
|
||||
// pub fn get_async<R>(&self, url: &str) -> PromiseWrapper<R>
|
||||
// where
|
||||
// // F: FnOnce(R) + 'static,
|
||||
// R: DeserializeOwned + 'static,
|
||||
// {
|
||||
// let token = self.token.as_ref().unwrap();
|
||||
|
||||
// let opts = RequestInit::new();
|
||||
// opts.set_method("GET");
|
||||
|
||||
// let request = Request::new_with_str_and_init(url, &opts).unwrap();
|
||||
|
||||
// let headers = request.headers();
|
||||
|
||||
// headers.set("content-type", "application/json").unwrap();
|
||||
|
||||
// headers
|
||||
// .set("Authorization", &format!("Bearer {}", token.token))
|
||||
// .unwrap();
|
||||
|
||||
// let window = web_sys::window().unwrap();
|
||||
// let promise = window.fetch_with_request(&request);
|
||||
|
||||
// // wasm_bindgen_futures::spawn_local(async move {
|
||||
// // let resp_value = JsFuture::from(window.fetch_with_request(&request))
|
||||
// // .await
|
||||
// // .unwrap();
|
||||
|
||||
// // // `resp_value` is a `Response` object.
|
||||
// // assert!(resp_value.is_instance_of::<Response>());
|
||||
// // let resp: Response = resp_value.dyn_into().unwrap();
|
||||
|
||||
// // // Convert this other `Promise` into a rust `Future`.
|
||||
// // if let Ok(json) = resp.json() {
|
||||
// // if let Ok(json) = JsFuture::from(json).await {
|
||||
// // crate::log(&format!("{json:?}"));
|
||||
// // // let json = ;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // // crate::log(text);
|
||||
// // // Any follow-up work here
|
||||
// // });
|
||||
|
||||
// PromiseWrapper::new(promise)
|
||||
|
||||
// // Ok(())
|
||||
// }
|
||||
|
||||
// pub fn get_async_callback<R, F>(&self, url: &str, callback: F) -> Result<()>
|
||||
// where
|
||||
// F: FnOnce(R) + 'static,
|
||||
// R: DeserializeOwned + 'static,
|
||||
// {
|
||||
// if self.token.is_none() {
|
||||
// return Err(ModuleError::Error("Not authenticated".into()));
|
||||
// }
|
||||
|
||||
// let token = self.token.clone().unwrap();
|
||||
|
||||
// let url = url.to_string();
|
||||
|
||||
// let state_clone = self.auth_state.clone();
|
||||
|
||||
// wasm_bindgen_futures::future_to_promise(async move {
|
||||
// let result = Self::get_async(&url, &token).await;
|
||||
|
||||
// match result {
|
||||
// Ok(result) => callback(result),
|
||||
// Err(err) => (*state_clone.lock()) = AuthState::Error(err.into()),
|
||||
// }
|
||||
|
||||
// Ok(JsValue::NULL)
|
||||
// });
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// async fn get_async<R>(url: &str, token: &Token) -> Result<R>
|
||||
// where
|
||||
// R: DeserializeOwned + 'static,
|
||||
// {
|
||||
// let res = reqwest::Client::new()
|
||||
// .get(format!("http://localhost:8080{url}"))
|
||||
// .bearer_auth(&token.token)
|
||||
// .send()
|
||||
// .await
|
||||
// .map_err(|e| ModuleError::Error(e.to_string()))?;
|
||||
|
||||
// match res.error_for_status() {
|
||||
// Ok(res) => res
|
||||
// .json()
|
||||
// .await
|
||||
// .map_err(|e| ModuleError::Error(e.to_string())),
|
||||
// Err(err) => Err(ModuleError::Error(format!(
|
||||
// "Server returned error: {err:?}"
|
||||
// ))),
|
||||
// }
|
||||
|
||||
// // .json::<R>()
|
||||
// // .await
|
||||
// // .map_err(|e| ModuleError::Error(e.to_string()))?;
|
||||
|
||||
// // serde_json::from_str(&res).map_err(|e| ModuleError::SerdeJsonError(e.to_string()))
|
||||
// }
|
||||
}
|
||||
@@ -1,21 +1,26 @@
|
||||
mod render_interface;
|
||||
mod interface;
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use unshell_lib::Result;
|
||||
use egui_async::Bind;
|
||||
use log::debug;
|
||||
use unshell_lib::config::TreeMessage;
|
||||
use unshell_lib::{ModuleError, Result};
|
||||
|
||||
use crate::auth::Auth;
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct InterfaceWindow {
|
||||
path: PathBuf,
|
||||
|
||||
// #[serde(skip)]
|
||||
// data_bind: Bind<String, ModuleError>,
|
||||
#[serde(skip)]
|
||||
state: Arc<Mutex<InterfaceWindowState>>,
|
||||
// #[serde(skip)]
|
||||
// promise: Option<PromiseWrapper<Result<TreeMessage>>>,
|
||||
}
|
||||
|
||||
pub struct InterfaceWindowState {
|
||||
@@ -26,6 +31,8 @@ pub struct InterfaceWindowState {
|
||||
|
||||
impl InterfaceWindow {
|
||||
pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) {
|
||||
// let data_bind = Bind::<InterfaceWindowState, ModuleError>::new(false);
|
||||
|
||||
ui.heading("Interface");
|
||||
ui.label(self.path.to_string_lossy());
|
||||
|
||||
@@ -73,7 +80,8 @@ impl InterfaceWindow {
|
||||
Ok(item) => {
|
||||
state_lock.branch = Some(item);
|
||||
}
|
||||
Err(_) => {
|
||||
Err(err) => {
|
||||
crate::log(&format!("Got error {err:?}"));
|
||||
state_lock.is_error = true;
|
||||
}
|
||||
}
|
||||
@@ -93,14 +101,14 @@ impl InterfaceWindow {
|
||||
|
||||
let clear = match branch {
|
||||
TreeMessage::InterfaceAndValue(interface_struct, interface_data) => {
|
||||
render_interface::render(ui, interface_struct, interface_data);
|
||||
interface::render(ui, interface_struct, interface_data);
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
auth.post(
|
||||
&format!("/api/interface{}", self.path.display()),
|
||||
&TreeMessage::State(interface_data.clone()),
|
||||
move |response: Result<TreeMessage>| {
|
||||
crate::log(&format!("{response:?}"));
|
||||
debug!("{response:?}");
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -129,6 +137,56 @@ impl InterfaceWindow {
|
||||
drop(state_lock)
|
||||
}
|
||||
}
|
||||
|
||||
// if ui.button("Go Up").clicked() {
|
||||
// self.go_up();
|
||||
// // self.data_bind.clear();
|
||||
// }
|
||||
|
||||
// match self.promise.as_mut() {
|
||||
// Some(promise) => {
|
||||
// if let Some(result) = promise.poll() {
|
||||
// crate::log(&format!("{result:?}"));
|
||||
// } else {
|
||||
// crate::log("Poll failed");
|
||||
// }
|
||||
// }
|
||||
// None => {
|
||||
// self.promise =
|
||||
// Some(auth.get_async(&format!("/api/interface{}", self.path.to_str().unwrap())));
|
||||
// }
|
||||
// }
|
||||
|
||||
// let future = Auth::get_async::<String>("/api/test").await;
|
||||
// future.;
|
||||
|
||||
// if let Some(res) = self.data_bind.read_or_request(async || {
|
||||
// // self.go_up();
|
||||
// // auth.get("", |e: String| {});
|
||||
|
||||
// reqwest::get("https://icanhazip.com/")
|
||||
// .await
|
||||
// .map_err(|e| ModuleError::Error(e.to_string()))?
|
||||
// .text()
|
||||
// .await
|
||||
// .map_err(|e| ModuleError::Error(e.to_string()))
|
||||
// }) {
|
||||
// match res {
|
||||
// Ok(ip) => {
|
||||
// ui.label(format!("OK {ip}"));
|
||||
// }
|
||||
// Err(err) => {
|
||||
// ui.label(format!("ERR {err}"));
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// ui.spinner();
|
||||
// };
|
||||
|
||||
// if ui.button("Refresh").clicked() {
|
||||
// Auth::get_async_callback::<String>("/api/interface");
|
||||
// // self.data_bind.clear();
|
||||
// }
|
||||
}
|
||||
|
||||
fn reset_path(&mut self) {
|
||||
@@ -136,19 +194,25 @@ impl InterfaceWindow {
|
||||
}
|
||||
|
||||
fn go_up(&mut self) {
|
||||
self.path = PathBuf::from(self.path.parent().unwrap());
|
||||
if let Some(parent) = self.path.parent() {
|
||||
self.path = PathBuf::from(parent);
|
||||
}
|
||||
}
|
||||
|
||||
fn go_down(&mut self, path: String) {
|
||||
self.path = self.path.join(path);
|
||||
}
|
||||
|
||||
async fn get() {}
|
||||
}
|
||||
|
||||
impl Default for InterfaceWindow {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: "/".into(),
|
||||
// promise: None,
|
||||
state: Arc::new(Mutex::new(InterfaceWindowState::default())),
|
||||
// data_bind: Bind::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,54 @@ extern "C" {
|
||||
pub fn httpPostAuth(url: &str, auth: String, data: &str, ok_callback: JsValue);
|
||||
}
|
||||
|
||||
// When compiling to web using trunk:
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen(start)]
|
||||
pub async fn run() {
|
||||
use wasm_bindgen::JsCast as _;
|
||||
|
||||
use app::TemplateApp;
|
||||
|
||||
// Redirect `log` message to `console.log` and friends:
|
||||
eframe::WebLogger::init(log::LevelFilter::Debug).ok();
|
||||
|
||||
let web_options = eframe::WebOptions::default();
|
||||
|
||||
let document = web_sys::window()
|
||||
.expect("No window")
|
||||
.document()
|
||||
.expect("No document");
|
||||
|
||||
let canvas = document
|
||||
.get_element_by_id("the_canvas_id")
|
||||
.expect("Failed to find the_canvas_id")
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.expect("the_canvas_id was not a HtmlCanvasElement");
|
||||
|
||||
let start_result = eframe::WebRunner::new()
|
||||
.start(
|
||||
canvas,
|
||||
web_options,
|
||||
Box::new(|cc| Ok(Box::new(TemplateApp::new(cc)))),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Remove the loading text and spinner:
|
||||
if let Some(loading_text) = document.get_element_by_id("loading_text") {
|
||||
match start_result {
|
||||
Ok(_) => {
|
||||
loading_text.remove();
|
||||
}
|
||||
Err(e) => {
|
||||
loading_text.set_inner_html(
|
||||
"<p> The app has crashed. See the developer console for details. </p>",
|
||||
);
|
||||
panic!("Failed to start eframe: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// mod JsFunc {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// When compiling natively:
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() -> eframe::Result {
|
||||
// pretty_env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||
|
||||
// let native_options = eframe::NativeOptions {
|
||||
// viewport: egui::ViewportBuilder::default()
|
||||
// .with_inner_size([400.0, 300.0])
|
||||
// .with_min_inner_size([300.0, 220.0]),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// eframe::run_native(
|
||||
// "eframe template",
|
||||
// native_options,
|
||||
// Box::new(|cc| Ok(Box::new(TemplateApp::new(cc)))),
|
||||
// )
|
||||
todo!()
|
||||
}
|
||||
|
||||
// When compiling to web using trunk:
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
use eframe::wasm_bindgen::JsCast as _;
|
||||
|
||||
use unshell_gui::app::TemplateApp;
|
||||
|
||||
// Redirect `log` message to `console.log` and friends:
|
||||
eframe::WebLogger::init(log::LevelFilter::Debug).ok();
|
||||
|
||||
let web_options = eframe::WebOptions::default();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
let document = web_sys::window()
|
||||
.expect("No window")
|
||||
.document()
|
||||
.expect("No document");
|
||||
|
||||
let canvas = document
|
||||
.get_element_by_id("the_canvas_id")
|
||||
.expect("Failed to find the_canvas_id")
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.expect("the_canvas_id was not a HtmlCanvasElement");
|
||||
|
||||
let start_result = eframe::WebRunner::new()
|
||||
.start(
|
||||
canvas,
|
||||
web_options,
|
||||
Box::new(|cc| Ok(Box::new(TemplateApp::new(cc)))),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Remove the loading text and spinner:
|
||||
if let Some(loading_text) = document.get_element_by_id("loading_text") {
|
||||
match start_result {
|
||||
Ok(_) => {
|
||||
loading_text.remove();
|
||||
}
|
||||
Err(e) => {
|
||||
loading_text.set_inner_html(
|
||||
"<p> The app has crashed. See the developer console for details. </p>",
|
||||
);
|
||||
panic!("Failed to start eframe: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// When compiling natively:
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() -> eframe::Result {
|
||||
// pretty_env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||
|
||||
// let native_options = eframe::NativeOptions {
|
||||
// viewport: egui::ViewportBuilder::default()
|
||||
// .with_inner_size([400.0, 300.0])
|
||||
// .with_min_inner_size([300.0, 220.0]),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// eframe::run_native(
|
||||
// "eframe template",
|
||||
// native_options,
|
||||
// Box::new(|cc| Ok(Box::new(TemplateApp::new(cc)))),
|
||||
// )
|
||||
todo!()
|
||||
}
|
||||
Reference in New Issue
Block a user