//! Public share page (milestone 6): renders a shared item without login. use leptos::prelude::*; use wasm_bindgen_futures::spawn_local; use crate::api::{self, Me, Mode, RootInfo, ShareInfo, UserInfo}; use crate::components::icon::Icon; use crate::components::logo::Logo; use crate::components::toast::{ToastView, provide_toast}; use crate::icons::IconName; use crate::router::Location; use crate::views::browser::Browser; use crate::views::file_view::{FileView, FileViewActions, FileViewContent, FileViewTitle}; #[derive(Clone, PartialEq)] enum SharePageState { Loading, NotFound, Expired, Active, } #[component] pub fn ShareView(token: String, loc: ReadSignal) -> impl IntoView { let toast = provide_toast(); let (me, set_me) = signal(Option::::None); let (info, set_info) = signal(Option::::None); let (state, set_state) = signal(SharePageState::Loading); // Resolve the share once per token and scope all file API calls to it. { let tok = token.clone(); let set_me2 = set_me; let set_info2 = set_info; let set_state2 = set_state; spawn_local(async move { api::set_share_token(Some(&tok)); match api::resolve_share(&tok).await { Ok(i) => { let mode = if i.writable { Mode::Rw } else { Mode::Ro }; let me_val = Me { first_boot: false, user: Some(UserInfo { id: i.id, name: "shared".to_string(), is_admin: false, }), roots: vec![RootInfo { id: i.root_id, name: i.name.clone(), path: "/".to_string(), mode, }], allow_writable_shares: false, }; set_me2.set(Some(me_val)); set_info2.set(Some(i)); set_state2.set(SharePageState::Active); } Err(e) => { let expired = e.status() == Some(410); set_state2.set(if expired { SharePageState::Expired } else { SharePageState::NotFound }); } } }); on_cleanup(move || api::set_share_token(None)); } // A file opened in the full-page view; the top bar doubles as its header. let (file_view, set_file_view) = signal(Option::::None); // The open editor stores its save callback here for the top-bar button. let (save_cb, set_save_cb) = signal(Option::>::None); // The open editor's guarded close request (unsaved-changes prompt). let (close_cb, set_close_cb) = signal(Option::>::None); let (dirty, set_dirty) = signal(false); let (saving, set_saving) = signal(false); // The browser's fetch, exposed so the editor can re-list after a save. let (browser_refresh, set_browser_refresh) = signal(Option::>::None); let close_file = Callback::new(move |_| { set_file_view.set(None); set_save_cb.set(None); set_close_cb.set(None); set_dirty.set(false); set_saving.set(false); }); // Escape closes previews and the "no preview" view. The editor handles // Escape itself (it asks first when there are unsaved changes). { let fv = file_view; let cf = close_file; crate::util::owned_window_listener( leptos::ev::keydown, move |ev: web_sys::KeyboardEvent| { if ev.key() != "Escape" { return; } if fv .get() .is_some_and(|v| matches!(v, FileView::Preview(..) | FileView::Unsupported(_))) { cf.run(()); } }, ); } view! {
{move || match file_view.get() { Some(ref v) => view! { } .into_view() .into_any(), None => view! {
"filebrowser-ng"
} .into_view() .into_any(), }}
{move || match file_view.get() { Some(_) => view! { } .into_view() .into_any(), None => { let name = info.get().map(|i| i.name.clone()); match name { Some(n) => view! { } .into_view() .into_any(), None => { view! {}.into_any() }, } } }}
{move || match state.get() { SharePageState::Loading => view! {

"Loading share…"

} .into_view() .into_any(), SharePageState::NotFound => view! {

"Share not found"

"This link is invalid or the share was removed."

} .into_view() .into_any(), SharePageState::Expired => view! {

"Share expired"

"This share link is no longer active."

} .into_view() .into_any(), SharePageState::Active => { let Some(i) = info.get() else { return { view! {}.into_any() }; }; if i.is_file { let name = i.name.clone(); let rid = i.root_id; view! { } .into_view() .into_any() } else { if me.get().is_none() { return view! {

"Loading…"

} .into_view() .into_any(); } view! { } .into_view() .into_any() } } }} {move || match file_view.get() { Some(v) => { let refresh = browser_refresh .get() .unwrap_or_else(|| Callback::new(move |_| {})); view! { } .into_view() .into_any() } None => view! {}.into_any(), }}
"Powered by filebrowser-ng"
} }