lento 0.1.1

Cross platform ui framework
use std::collections::HashMap;
use std::io::{Error, ErrorKind};
use anyhow::anyhow;
use quick_js::loader::{FsJsModuleLoader, JsModuleLoader};

pub struct RemoteModuleLoader {
    base: Option<String>,
}

impl RemoteModuleLoader {
    pub fn new(base: Option<String>) -> Self {
        Self {
            base,
        }
    }
}

impl JsModuleLoader for RemoteModuleLoader {
    fn load(&mut self, module_name: &str) -> Result<String, Error> {
        let url = if module_name.starts_with("http://") || module_name.starts_with("https://") {
            module_name.to_string()
        } else if let Some(base) = &self.base {
            format!("{}/{}", base.trim_end_matches("/"), module_name)
        } else {
            return Err(Error::new(ErrorKind::AddrNotAvailable, anyhow!("Failed to resolve module: {}", module_name)));
        };
        let body = reqwest::blocking::get(&url).map_err(|e| Error::new(ErrorKind::Other, e))?
            .text().map_err(|e| Error::new(ErrorKind::Other, e))?;
        Ok(body)
    }
}

pub struct DevModuleLoader {
    is_first_load: bool,
    remote_module_loader: RemoteModuleLoader,
}

impl DevModuleLoader {
    pub fn new(base: Option<&str>) -> Self {
        let base = base.map(String::from);
        Self {
            is_first_load: true,
            remote_module_loader: RemoteModuleLoader::new(base),
        }
    }
}

impl JsModuleLoader for DevModuleLoader {
    fn load(&mut self, module_name: &str) -> Result<String, Error> {
        let is_first_load = self.is_first_load;
        self.is_first_load = false;
        let start_time = std::time::Instant::now();
        loop {
            let result = self.remote_module_loader.load(module_name);
            match result {
                Ok(source) => {
                    return Ok(source);
                }
                Err(err) => {
                    if is_first_load || start_time.elapsed() >= std::time::Duration::from_secs(60) {
                        return Err(err);
                    }
                    std::thread::sleep(std::time::Duration::from_millis(1000));
                }
            }
        }
    }
}

pub struct StaticModuleLoader {
    sources: HashMap<String, String>,
}

impl StaticModuleLoader {
    pub fn new() -> Self {
        StaticModuleLoader {
            sources: HashMap::new(),
        }
    }
    pub fn add_module(&mut self, module_name: String, source: String) {
        self.sources.insert(module_name, source);
    }
}

impl JsModuleLoader for StaticModuleLoader {
    fn load(&mut self, module_name: &str) -> Result<String, Error> {
        match self.sources.get(module_name) {
            None => Err(Error::new(ErrorKind::NotFound, anyhow!("Not found"))),
            Some(s) => Ok(s.to_string())
        }
    }
}

pub struct DefaultModuleLoader {
    remote_module_loader: Option<RemoteModuleLoader>,
    fs_module_loader: Option<FsJsModuleLoader>,
}

impl DefaultModuleLoader {

    pub fn new(allow_remote: bool) -> Self {
        let remote_module_loader = if allow_remote {
            Some(RemoteModuleLoader::new(None))
        } else {
            None
        };
        Self {
            remote_module_loader,
            fs_module_loader: None,
        }
    }

    pub fn set_fs_base(&mut self, dir: &str) {
        self.fs_module_loader = Some(FsJsModuleLoader::new(dir))
    }

}

impl JsModuleLoader for DefaultModuleLoader {
    fn load(&mut self, module_name: &str) -> Result<String, Error> {
        if let Some(fs_loader) = &mut self.fs_module_loader {
            if let Ok(module) = fs_loader.load(module_name) {
               return Ok(module)
            }
        }
        if let Some(remote_loader) = &mut self.remote_module_loader {
            return remote_loader.load(module_name);
        }
        Err(Error::new(ErrorKind::NotFound, "failed to load module"))
    }
}