use std::collections::HashMap; use gloo_net::http::Request; use gloo_storage::{LocalStorage, Storage}; use leptos::*; use leptos_router::*; use api_boundary::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::Url; mod api; mod components; mod gallery; mod pages; pub(crate) mod web_sys_ext; use self::{components::*, pages::*}; const DEFAULT_API_URL: &str = "/api"; const API_TOKEN_STORAGE_KEY: &str = "photos-super-fancy-token"; #[component] pub fn App(cx: Scope) -> impl IntoView { // -- signals -- // let authorized_api = create_rw_signal(cx, None::); let user_info = create_rw_signal(cx, None::); let photos = create_rw_signal(cx, None::>); let urls = create_rw_signal(cx, None::>); let logged_in = Signal::derive(cx, move || authorized_api.get().is_some()); // -- actions -- // let fetch_user_info = create_action(cx, move |_| async move { match authorized_api.get() { Some(api) => match api.user_info().await { Ok(info) => { log::info!("{:?}", info); user_info.update(|i| *i = Some(info.user)); } Err(err) => { log::error!("Unable to fetch user info: {err}") } }, None => { log::error!("Unable to fetch user info: not logged in") } } }); let get_url = create_action(cx, move |thumb: &String| { let thumb = thumb.clone(); async move { let resp = Request::get(&thumb).send().await.unwrap(); if resp.ok() { let blob = JsFuture::from(resp.as_raw().blob().unwrap()).await.unwrap(); let url = Url::create_object_url_with_blob(&blob.dyn_into().unwrap()).unwrap(); urls.update(|u| match u { Some(_u) => { let mut n = _u.clone(); n.insert(thumb, url); *u = Some(n); } None => { let mut n = HashMap::new(); n.insert(thumb, url); *u = Some(n); } }); } } }); let fetch_photos = create_action(cx, move |_| async move { match authorized_api.get() { Some(api) => match api.get_photos().await { Ok(info) => { log::info!("{:?}", info); info.iter().for_each(|p| { if let Some(thumb) = p.thumbnail.clone() { get_url.dispatch(thumb); } }); photos.update(|i| *i = Some(info)); } Err(err) => { log::error!("Unable to fetch photos: {err}") } }, None => { log::error!("Unable to fetch photos: not logged in") } } }); let logout = create_action(cx, move |_| async move { match authorized_api.get() { Some(api) => match api.logout().await { Ok(_) => { authorized_api.update(|a| *a = None); user_info.update(|i| *i = None); log::debug!("logout: delete token from LocalStorage"); LocalStorage::delete(API_TOKEN_STORAGE_KEY); } Err(err) => { log::error!("Unable to logout: {err}") } }, None => { log::error!("Unable to logout user: not logged in") } } }); // -- callbacks -- // let on_logout = move || { logout.dispatch(()); }; // -- init API -- // let unauthorized_api = api::UnauthorizedApi::new(DEFAULT_API_URL); if let Ok(token) = LocalStorage::get(API_TOKEN_STORAGE_KEY) { let api = api::AuthorizedApi::new(DEFAULT_API_URL, token); authorized_api.update(|a| *a = Some(api)); fetch_user_info.dispatch(()); fetch_photos.dispatch(()); } log::debug!("User is logged in: {}", logged_in.get()); // -- effects -- // create_effect(cx, move |_| { log::debug!("API authorization state changed"); match authorized_api.get() { Some(api) => { log::debug!("API is now authorized: save token in LocalStorage"); LocalStorage::set(API_TOKEN_STORAGE_KEY, api.token()).expect("LocalStorage::set"); } None => { log::debug!("API is no longer authorized: delete token from LocalStorage"); LocalStorage::delete(API_TOKEN_STORAGE_KEY); } } }); view! { cx,
} /> } /> } />
} }